I am using Rails 3 and I need to do a select where the primary key of the records is IN a resulting previous select. You can do this easily using straight SQL using an IN. Here is the obviously incorrect way I have done what I need. What's the Rails way to do this well:
#task = Link.find(params[:id])
clients = Client.where('task_id = ?',#task.id).select('DISTINCT(company_id)')
company_list = []
clients.each do |client|
company_ids << client.company_id
end
#companies = Company.where(:id => company_ids)
As others have mentioned I'd use join in this case. The syntax for using "in" is also very simple though e.g.
company_ids = [1,2,3,4]
#companies = Company.where("id in (?)", company_ids)
Update
Actually it's even simpler than that now (I think rails 3+), you can do
company_ids = [1,2,3,4]
#companies = Company.where(id: company_ids)
This does not answer your question about "select IN using where clauses", but I think your whole script can be rewritten in one line using joins. This should give the same result as your snippet above:
#companies = Company.joins(:clients).where(:clients => {:task_id => params[:id]})
I believe this will do what you are asking for:
#task = Link.find(params[:id])
#companies = Company.where(:id => Client.where(:task_id => #task.id).select('distinct company_id').map(&:company_id))
You can view the sql by tacking .to_sql on the end in the console.
The join syntax in mischa's answer is probably more readable though.
I think the issue might be that you have company_list and company_ids. I would expect company_ids in the iterator to return something like:
NameError: undefined local variable or method `company_ids'
I think I might write this like:
#task = Link.find(params[:id])
clients = Client.where(:task_id => #task.id).select('distinct company_id')
#companies = Company.where(:id => clients.map(&:company_id))
You can simply use find, as find by id accepts a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]), see: http://apidock.com/rails/ActiveRecord/Base/find/class
#companies = Company.find(company_ids)
Related
Thanks in advance for your help. I'm following the example I found here (Rails Find when some params will be blank) and trying to put together a bunch of conditions for a search form. This is for a Rails 2.3 legacy application. The below works for me, but I'm not sure how to do anything other than "=". For example, how can I make the programs_offered_category condition be a LIKE statement? I tried doing
majorcategories = params[:majorcategories]
Above the conditions statement and adding
conditions['programs_offered_category LIKE ?', "%#{majorcategories}%"]
but I get "wrong number of arguments (2 for 1)". Also, how can I do greater than and less than signs in this setup? Thanks!
search_controller.rb
conditions = {}
conditions[:city] = params[:city] unless params[:city].blank?
conditions[:state] = params[:state] unless params[:state].blank?
conditions[:geo_region] = params[:geo_region] unless params[:geo_region].blank?
conditions[:size_category] = params[:size_category] unless params[:size_category].blank?
conditions[:programs_offered_category] = params[:majorcategories]
#location_matches = Masterlocation.find(:all, :conditions => conditions, :order => 'nickname ASC').paginate(:page => params[:page], :per_page => 20)
I would suggest to use regular expression as follow
conditions['programs_offered_category'].map {|k,v| (k =~ /majorcategories/) ? v : nil}
It will return array of results if there is more than one matches otherwise single value
Here is the query I am trying in my controller
query = []
if id
query = "category_id: #{id}"
end
#posts = Post.where(query)
But throwing error as ERROR: syntax error at or near ":"
Why this is not working any other way to do it
if id
query << {sub_category_id: id}
end
if test
query << {test_id: test}
end
#posts = Post.where(query)
Is there any way of doing like this
Change query to a hash instead of string:
if id
query = { category_id: id }
end
#posts = Post.where(query)
The reason query = "category_id: #{id}" did not work is because the supplied string is literally used in the query generated by ActiveRecord, i.e. your select query will have category_id: 1 (assuming id is 1) in the where clause. And this is not a valid SQL syntax.
Please read on how you can use strings in conditions following this link. Thanks to #RustyToms for suggesting the link.
Update: ( Add extra conditions to the query hash )
if id
query[:sub_category_id] = id
end
if test
query[:test_id] = test
end
#posts = Post.where(query)
Another way to do this:
#posts = Post.scoped
#posts = #posts.where(category_id: id) if id
(in case you're playing codegolf)
Edit: (this is definitely a side note that isn't at all relevant)
Your original solution relies on one of my least favorite features of Ruby. Consider the following code:
if false
a = 4
end
puts a
I would expect the puts a to fail with a NameError (undefined local variable "a"), but no! The Ruby parser hits a = and then initalizes its value to nil. So, despite the fact that there is no way for the innards of that if statement to run, it still impacts the other code.
After reading this (the #thomasfedb answer), I though this would work:
#categories = Category.joins(:category_users).where("category_users.user_id = ? AND category_users.interested_learning IS TRUE", current_user.id)
#search = Resource.joins(:categories).where(category_id: #categories.subtree_ids).search_filter(params[:search_filter]).paginate(:per_page => 20, :page => params[:page]).search(params[:q])
But instead, I receive this error
undefined method `subtree_ids' for #<ActiveRecord::Relation:0x007fce832b1070>
I also tried #Rahul answer from here
Without the Ancestry descendants, it is working like this
#search = Resource.joins(:category_users).where("category_users.user_id = ? AND category_users.interested_learning IS TRUE", current_user.id).search_filter(params[:search_filter]).paginate(:per_page => 20, :page => params[:page]).search(params[:q])
Although I want to do the Ransack search after finding the descendants, the error also appears without Ransack. For this reason, I haven't included it in the title. Without Ransack, it would be like this:
#categories = Category.joins(:category_users).where("category_users.user_id = ? AND category_users.interested_learning IS TRUE", current_user.id)
#resources = Resource.joins(:categories).where(category_id: #categories.subtree_ids)
I would appreciate any advices on this could work
#categories.subtree_ids will not work because subtree_ids is an instance method, whereas you're calling on the ActiveRecord::Relation. Try this instead:
#categories.map(&:subtree_ids).flatten.uniq
This might not be particularly performant, but ancestry stores and parses a column similar to "10/1/3" to manage hierarchy. The above will likely trigger an N+1 query. Another option would be to parse the columns yourself. I'm not sure if ancestry provides a better way to do this, but it's fairly simple:
arr = #categories.pluck(:ancestry)
#> ["10/1", "5/6/1", "2", nil, nil]
arr.compact
#> ["10/1", "5/6/1", "2"]
arr.map { |ids| ids.split('/') }
#> [["10","1"],["5","6","1"],["2"]]
arr.flatten
#> ["10","1","5","6","1","2"]
arr.uniq
#> ["10","1","5","6","2"]
arr.map(&:to_i)
#> [10,1,5,6,2]
Put it all together (I'd suggest multi-line within a method):
#categories.pluck(:ancestry).compact.map { |ids| ids.split('/') }.flatten.uniq.map(&:to_i)
#> [10,1,5,6,2]
Is there a way to conditionally append query methods to a ActiveRecord::Relation?
For instance, I'm searching for Users, but I only was the last_name included in the search under certain conditions. Can you append queries to an ActiveRecord::Relation object?
i_want_to_search_for_last_names = true
pending_relation = User.where(:first_name => "John")
pending_relation << where(:last_name => "Doe") if i_want_to_search_for_last_names
#users = pending_relation.all
You code is almost right, except some things. Here what you can do (don't forget: you deals with ActiveRelation):
i_want_to_search_for_last_names = true
#users = User.where(:first_name => "John")
#users = #users.where(:last_name => "Doe") if i_want_to_search_for_last_names
As for me - I used this technique in my projects. Hope it helps you.
To get all posts with publisher_id equals to 10, 16, or 17, I do:
Post.where(:publisher_id => [10, 16, 17])
How would I get all posts with publisher_id not equals to 10, 16, or 17 (i.e. all possible ids besides those three) ?
Just perform a :
Post.where(["publisher_id NOT IN (?)", [10, 16, 17]])
in rails 4 we can do like below
Post.where.not(:publisher_id => [10, 16, 17])
it will generate SQL like below
SELECT "posts".* FROM "posts" WHERE ("posts"."publisher_id" NOT IN (10, 16, 17))
Untested, but should be like (using metawhere gem):
Post.where( :id.not_eq => [10,16,17] )
Using "pure" ActiveRecord syntax sprinkled with Arel using Rails 3 you can do something like this:
Post.where( Post.arel_table[:publisher_id].not_in([10, 16, 17]) )
Every single answer on this page is wrong because none of these answers take care of ALL array cases, Especially arrays that have only one element.
Here is an example that will FAIL using any of the 'so called' solutions on this page:
#ids = [1]
Post.where("publisher_id NOT IN (?)", #ids)
#ERROR
Post.where("publisher_id NOT IN (?)", [4])
#ERROR
#...etc
#ALSO
#ids = []
Post.where("publisher_id NOT IN (?)", #ids)
#ERROR
Post.where("publisher_id NOT IN (?)", [])
#ERROR
#...etc
#The problem here is that when the array only has one item, only that element is
#returned, NOT an array, like we had specified
#Part of the sql that is generated looks like:
#...WHERE (publisher_id NOT IN 166)
#It should be:
#...WHERE (publisher_id NOT IN (166))
The only answer on this page that is actually on the right track and takes care of this very important case is #Tudor Constantin's. But the problem is he didn't actually show a 'way' of using his methodology to solve the real abstract example question the OP posted (not just using the hard-coded numbers).
here is my solution to dynamically find the ids not in an Activerecord association given an array of ids to exclude, that will work with an array of n elements (...including n=1 and n=0)
#ids = [166]
#attribute = "publisher_id"
#predicate = "NOT IN"
#ids = "(" + #ids.join(",") + ")"
if #ids == "()"
#Empty array, just set #ids, #attribute, and #predicate to nil
#ids = #attribute = #predicate = nil
end
#Finally, make the query
Post.where( [#attribute, #predicate, #ids].join(" ") )
#Part of the sql that is generated looks like:
#...WHERE (publisher_id NOT IN (166))
#CORRECT!
#If we had set #ids = [] (empty array)
#Then the if statement sets everything to nil, and then
#rails removes the blank " " space in the where clause automatically and does
#the query as if all records should be returned, which
#logically makes sense!
If this helped you in anyway, please up vote! If you are confused or don't understand one of my comments, please let me know.
Neat solution I've used:
ids = #however you get the IDS
Post.where(["id not in (?)", [0,*ids])
The presence of the 0 means it always has one element in (assuming nothing has an ID of 0)
ID becoming a splat means it'll always be an array.
Post.where(" id NOT IN ( 10, 16, 17) ")