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.
Related
I got an array with hashes. Inside those hashes has a unique user id. I need to return all of that user's likes:
p = Post.first
all_likes = p.likes
# This returns: [0][1][2] etc
# inside a hash looks like:
[0] #<Like:0x007f81d3dfb310> {
id: => 4,
user_id: => 1,
like_sent: => 1 # this will always be one
}
Let's assume that all_likes has many users and I need user_id: 1's total like_sent. How to get that?
all_likes.find {|f| f["user_id"] = current_user.id } # this returns one hash. I need to return all if more is found.
So if "Jason" likes a post 10 times, I need to grab his total likes. How to return all matching hashes. That's the perfect question.
Try
all_likes.where 'user_id = ?', current_user.id
And find method has some different syntax, something like this:
all_likes.find :all, :conditions => ['user_id = ?', current_user.id]
Look docs
I want to extract the id number of a unique record, that resides in a different controller as an integer, so I can save it as part of a new record in a new controller.
I can't seem to get the id to shed it's 'Array' attribute.
I've been using this:
class MessagesController < ApplicationController
def incoming
a = Group.where("name = ?", name).map { |n| n.id }
group_number = a.id
puts "#{group_number} is the number!"
end
output is always [2] or [3] or whatever.
adding this doesn't seem to cure it
group_as_int = group_number.to_i
Lastly, the reason I'm doing all this is to save that group number as a new record in a third controller, like this:
Subscriber.create(:group_id => group_number, :active => "1")
or
Subscriber.create(:group_id => group_as_int, :active => "1")
Of course, the create balks when I try to pass an array into the create function.
Thoughts?
You are trying to put business logic into the controller.
Try to refactor your methods and put them into your models instead.
Beside that you get the number in the following way:
group = Group.where("name = ?", name).first
group_number = group.id if group.present?
You might want to try .first to get the integer out of the array.
I will try to explain from your code what you did wrong.
The first line:
matching_group_ids = Group.where("name = ?", name).map { |n| n.id }
You called it a, but i prefer more verbose names. matching_group_ids now holds an array of id's. To get the first value of this array, the easiest solution is to just write
group_number = matching_group_ids[0]
or, more readable:
group_number = matching_group_ids.first
Mind you: you should test that the returned array is not empty.
Hope this helps.
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)
I am doing an ActiveRecord find on a model as such
#foo = MyModel.find(:all, :select => 'year')
As you can see, I only need the year column from this, so my ideal output would be
["2008", "2009", "2010"]
Instead, though, I get an a hash of the models, with each one containing the year, as such:
[#<MyModel year: "2008">, #<MyModel year: "2009">, #<MyModel year: "2010">]
I can loop through it as such to convert it to my ideal output:
#years = []
for bar in #foo
#years.push(bar.year)
end
but is there a way to retrieve this result to begin with? (i.e. without going through the extra processing?). If there is not, what is a more concise way to do this processing?
Thank you.
try:
#foo = MyModel.find(:all, :select => 'year').map(&:year)
You also can call .uniq() on the result to remove duplicates.
#foo = MyModel.find(:all, :select => 'year').map(&:year).uniq
This is the short form for
#foo = MyModel.find(:all, :select => 'year').map{ |model| model.year }
This loops over the MyModel Array and crates a new array of the return values with in the block (model.year).
or even shorter code, but first calling everything from db:
#foo = MyModel.all.map(&:year)
If you don't want to instantiate models, you'd do it this way:
MyModel.connection.select_values("SELECT year FROM my_models")
This will return an Array of String objects. Then you can transform using Ruby tools as you see fit.
You can use map to change every value in an array. Example:
#foo = MyModel.find(:all, :select => 'year').map { |model_object| model_object.year }
Say I have model 'Car' and controller 'cars', and a method 'display'.
I have multiple attributes like:
in_production, year, make
I can easily do something like this to find cars that match all the parameters passed:
def display
#cars = Car.find(:all, :conditions => { :in_production => #{params[:in_production]}, :year => #{params[:year]}, :make => #{params[:make]} })`
end
So what I'm doing is coding hard links in the menu, so if I wanted to find all Nissan cars from 2009 that were in production, I would pass those values as parameters in my link.
On another page I want to show every car from 2009 that is in_production, only two params instead of three. What's the best way to dynamically alter the conditions so it will work with one, two, or three params, whilst using the same action?
Any ideas?
First of all, using
:conditions => "in_production = '#{params[:in_production]}' AND year = '#{params[:year]}' AND make = '#{params[:make]}'"
is vulnerable to SQL injection. You need to escape the user provided parameters before using them in database conditions.
Something like this should let you add conditions more dynamically depending on whether or not the parameters exist. I did not test it, so I may edit it shortly...
def display
conditions = []
conditions << [ "in_production = ?", params[:in_production] ] if params[:in_production].present?
conditions << [ "year = ?", params[:year] ] if params[:year].present?
conditions << [ "make = ?", params[:make] ] if params[:make].present?
#cars = Car.all(:conditions => conditions )
end
Certainly escape the params and ensure that you only query against fields you want to be exposed. Beyond that, you could use what is built into Rails:
Car.find_all_by_in_production_and_year_and_make(in_production, year, make)
Hand-rolling the conditions may allow for additional logic to be applied (search by year only if the year is between x and y, etc). Using the rails finders (which in turn use method_missing) keeps the API clean and flexible without having to stare at direct SQL conditions.
You could construct a Car#search method that takes the entire params hash as input, where the params are sanitized and stripped of non-exposed fields, and construct the Car#find_all_by* method call using the param names themselves. Adding new conditions to search by is then as simple as passing them in the params.
You might check out searchlogic. It uses some method missing magic to construct named_scopes that would do what you want.
http://github.com/binarylogic/searchlogic
I use SmartTuple for stuff like this. Simple, powerful, designed specifically for the task.
#cars = Car.all(:conditions => (SmartTuple.new(" AND ") +
({:in_production => params[:in_production]} if params[:in_production].present?) +
({:year => params[:year]} if params[:year].present?) +
({:make => params[:make]} if params[:make].present?)
).compile)
or
#cars = Car.all(:conditions => [SmartTuple.new(" AND "),
({:in_production => params[:in_production]} if params[:in_production].present?),
({:year => params[:year]} if params[:year].present?),
({:make => params[:make]} if params[:make].present?),
].sum.compile)
or
keys = [:in_production, :year, :make]
#cars = Car.all(:conditions => (SmartTuple.new(" AND ").add_each(keys) do |k|
{k => params[k]} if params[k].present?
end).compile)
Pick the one you like the most. :)