ROR inner join gives duplicate value - sql

Hello I get duplicate rows of data by inner join.and i write inner join with OR condition.
Controller userscontroller.rb
#users = User.search(params[:search])
#users = User.joins([:requests]).where("name LIKE ? OR destination LIKE ?","#{params[:search]}%", "#{params[:search]}%")
View searches/index.html.haml
%aside.span6
= form_tag(:users, method: "get") do
= text_field_tag "search", params[:search], placeholder: "Enter Name"
%br/
= submit_tag "Search", name: nil, class: "btn-custom-darken"
%br/
%br/
- #users.each do |user|
= render user
= will_paginate #users
View users/index.html.haml
%div{align: "center"}
%b List of Users
- if current_user != (#user)
%ol.microposts
- #users.each do |user|
= render user
The problem is duplicate data printed as a output after search. If user posted 3 request then user will be printed as output 3 times. So image of user is printed 3 times with duplicate request data.
Thanks in advance.

To get unique values from a join just add the .uniq function to the call. And also when reusing one single input for multiple queries you can use the ruby key:value pair syntax.
#users = User.joins([:requests])
.where("name LIKE :search OR
destination LIKE :search",
{search: params[:search]})
.uniq

Related

Rails active record query to retrive all records that contains ALL IDS in the array of IDS

A video can have multiple categories.
video.rb
has_many :video_categories
has_many :categories, through: :video_categories
category.rb
has_many :video_categories
has_many :videos, through: :video_categories
I have this simple form that allows the user to select the categories he wants to combine in order to find specific videos. For example, there's a "python" category and "advanced" category. If he selected these two categories, it should show videos that has both categories.
Video A - Categories [1,4,7] (this is the categories ids)
Video B - Categories [1,2,7,9]
Video C - Categories [7,9]
If the user select the categories [1,7], the output should be video A and B. The current scope that I have its returning ALL videos that has category 1, ALL videos that has category 7 and videos that has BOTH of them.
I want just the videos that has BOTH of them. How do I do that ?
Current scope:
video.rb
scope :for_categories, -> (category_ids) {
joins(:video_categories).where(
video_categories: { category_id: category_ids }
)
}
pages_controller.rb
def search
#results = Video.for_categories(params[:category_ids])
end
My form
<%= form_with url: "/search", method: :get do |form| %>
<%= form.collection_check_boxes(:category_ids, Category.all,
:id, :title, { prompt: 'None'}, { multiple: true} ) do |cat| %>
<label class="text-capitalize checkbox-inline mr-2">
<%= cat.check_box %>
<%= cat.label %>
</label>
<% end %>
<%= form.submit "Search" %>
<% end %>
I was able to find the solution with the following code
video.rb
scope :by_categories, -> (category_ids) {
joins(:categories).where(categories: [category_ids] )
}
scope :by_all_categories, -> (category_ids) {
by_categories(category_ids).
group("videos.id").
having('count(videos.id) >= ?', category_ids.size)
}
pages_controller.rb
def search
#results = Video.by_all_categories(params[:category_ids].reject(&:empty?))
end
You want apply a GROUP and use HAVING to set a condition on the group to ensure that the number of categories joined matches the number of ids:
class Video
def self.for_categories(*category_ids)
ids = category_ids.flatten # handles a single array as input
group(:id)
.joins(:categories)
.where(categories: { id: ids })
.having(
Category.arel_table[:id]
.count
.gteq(ids.length)
)
end
end
In terms of SQL this will give you something like:
SELECT videos.*
FROM videos
INNER JOIN video_categories ON video_categories.video_id = videos.id
INNER JOIN categories ON video_categories.category_id = categories.id
WHERE categories.id IN (1, 2, 3)
GROUP BY videos.id
HAVING COUNT(categories.id) >= 3
categories_to_search = [1, 7]
conditions = categories_to_search.map do |c|
{ viedo_categories: { category_id: c } }
do
Video.where conditions
# this is the same as: Video.where viedo_categories: { category_id: 1 }, viedo_categories: { category_id: 7 }

Get users of search products search results

I would know the best way to get all users who have products of the search result
My controller
#users = User.all // i must change here ?
if params[:product_type].present? && params[:location].present? && params[:discount].present?
#products = Product.near(params[:location], 1, units: :km).where(product_type: params[:product_type], discount: params[:discount])
end
My view
<% #products.each do |p| %>
<%= p.user.company_name %>
<% end %>
My method work but if a company have 2 products its display it 2 times
Assuming the following:
User has_many :products
Product belongs_to :user
You can do:
#users_from_product_results = User.where(id: #products.pluck(:user_id))
If several products refer to the same user, this User will be listed only once.
Rather than making 2 separate AR calls, use includes(eager_loading) which gives both products and users in a single query.
if params[:product_type].present? && params[:location].present? && params[:discount].present?
#products = Product.near(params[:location], 1, units: :km).where(product_type: params[:product_type], discount: params[:discount])
.includes(:user)
end
And in the view you could use #products.map(:users).uniq which gives all the users associated with the products.

Advanced Rails ActiveRecord query using scope

I've been trying for a while to find the best way of querying for the desired result, but I always end up failing at some poing in the query.
Simplified database structure:
User:
id (integer)
first_name (string)
last_name (string)
CourseType:
title (string)
slug (string)
Course:
belongs_to :user
belongs_to :course_type
week (integer)
sold (float)
My controller is calling a scope:
#users = User.sales_results(week)
And here's the scope in my model:
scope :sales_results, lambda { |week|
joins(:courses => [:course_type])
.select("
users.id, users.first_name, users.last_name,
SUM(courses.sold) as total_sold,
COUNT(courses) as num_classes
")
.where("courses.week = ?", week)
.group('users.id')
}
This works fine, and I can use it in my template to show the total amount sold. Although I also want to show a second column where the value sold for some specific types of courses are summed up in. Something like this:
<% #users.each do |user| %>
<%= user.total_sold %>
<%= user.total_sold.where("course_types.slug IN ('H', 'S')") # not possible, but similar to what I desire %>
<% end %>
Update
I ended up adding another scope
scope :sales_results_for_types, lambda { |week, types|
sales_results(week).except(:group).where("course_types.slug IN (?)", types)
.group('users.id')
}
Then calling both scopes in my controller
#users = User.sales_results(...)
#users_filtered = User.sales_results_for_types(...)
Lastly iterating both results at the same time
<% #users.zip(#users_filtered).each do |user, filtered| %>
<%= filtered.total_sold %>
<%= user.total_sold %>
Until I figure out something better. Thanks guys for leading me on the right track.
If you are ok with having another query, you can use this
user.joins(courses: :course_type).sum(:sold, group: 'course_types.slug')
which will give you a hash where the keys are the slugs, and the values are the sums.

rails OR query based on multiple checkbox selections

This seems like it should be a common problem but I'm having trouble finding an answer. Basically I want to have a form with 10 or so checkboxes which I'm creating with check_box_tag. When the form is submitted I want to generate a query that return all records that match ANY of the checked selections. So, the number of checked selections will vary.
So, for example, if I have
class Book < ActiveRecord::Base
belongs_to :author
end
I want to generate something like
Book.where("author_id = ? or author_id = ?", params[authors[0]], params[authors[1]]) if there are two boxes checked, etc.
Thanks for any insight.
Will this work for you?
Book.where(author_id: [array_of_author_ids])
You need to collect author_ids from params first
I recently had to do something similar, this is how I achieved this. It's pretty clever (at least I think so. :))
I created a query model that serializes the query column (text field) in JSON. I use a form to get the query data from the user with selection fields.
class BookQuery < ActiveRecord::Base
has_many :books
# loop through each foreign key of the Book table and create a hash with empty selection
def self.empty_query
q = {}
Book.column_names.each do |column_name|
next unless column_name.ends_with?("_id")
q.merge column_name => []
end
end
end
I'm using Author as an example below:
<%= form_for #book_query do |f| %>
<% for author in Author.all %>
<%= check_box_tag "book_query[query][author_ids][]", author.id, false%>
<%= author.name %>
<% end %>
<%= f.submit "Save Query" %>
<% end %>
When this form is submitted you ended up with parameters like this:
When the form is submitted it generates this parameter:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXXXXXXXXXX", "book_query"=>{"query"=>{"author_ids"=>["2", "3"]}}, "commit"=>"Save Query"}
Now in the BookQuery controller's create action you can just do what create function always does:
def create
#book_query = BookQuery.build(params[:book_query])
if #book_query.save
flash[:success] = "Book query successfully saved."
redirect_to ...
else
flash[:error] = "Failed to save book query."
render :new
end
end
But by default rails serializes the data in hash type:
1.9.3p194 :015 > pp BookQuery.find(9).query
BookQuery Load (0.7ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 9]]
"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
=> "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nauthor_ids:\n- '2'\n- '3'\n"
In BookQuery model, add the following:
serialize :query, JSON
But rail would change the IDs to string:
1.9.3p194 :018 > query = JSON.parse(BookQuery.find(10).query)
BookQuery Load (0.5ms) SELECT "book_queries".* FROM "book_queries" WHERE "book_queries"."id" = $1 LIMIT 1 [["id", 10]]
=> {"author_ids"=>["2", "3"]}
1.9.3p194 :019 > query["author_ids"]
=> ["2", "3"]
What I did then is override the attribute accessors in BookQuery model:
The below has to be done because the hash returns strings, not ids in integer.
def query=(query)
query.each_pair do |k, v|
if query[k].first.present?
query[k].map!(&:to_i)
else
query.except!(k)
end
end
write_attribute(:query, query)
end
# just want to avoid getting nil query's
def query
read_attribute(:query) || {}
end
To find book with this query, you can simply add this function to your Book model:
def self.find_by_book_query(book_query, options = {})
options[:conditions] = book_query.query
find(:all, options)
end
Now you get a customizable query string based on the model definition Book and everything works like the Rails way. :)

Rails 3 noob - difficulty populating an array

UPDATED WITH SOLUTION FROM BELOW PRODUCES NEW ERROR.
Users invite others to review their work. To track this, the invitations table has reviewer_id and reviewee_id fields. The model contains:
belongs_to :user
has_many :users
To display all invitations for a user we first get all the invitations:
#invitations = Invitation.where("reviewee_id = ?", current_user.id ).select(:reviewer_id).order("updated_at")
Then we get all the users who were invited: (this part is wrong)
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
This current solution produces the following:
undefined method `first_name' for []:ActiveRecord::Relation
I did a test with the following code to see what is in #reviewers:
<% #reviewers.each do |r| %>
<%= r.id %><br>
<% end %>
Instead of returning the ids it returned:
2173491700
2173491200
2173490540
So the array is not getting populated appropriately.
I am grateful for your help and most grateful for specific code.
You want to gather up all the IDs then pass them to where.
reviewer_ids = #invitations.collect { |i| i.reviewer_id }
#reviewers = User.where(:id => reviewer_ids)
This grabs all the reviewers in a single database call.
Do the following to get the reviewers:
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
The << adds elements to the array. And, before the iteration we create an array.