Rails ActiveRecord: Multiple conditions in find - ruby-on-rails

This might be more of a Ruby syntax thing than anything else. I'm having difficulty getting two limiting conditions on a SomeObject.find going.
Separated, the conditions seem to work:
if search != ''
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all, :conditions => ['active', 1]).shuffle
end
What I'm going for for the first case is this:
find(:all, :conditions => ['name LIKE ?', "%#{search}%"], ['active', 1])
But the line throws syntax error, unexpected ')', expecting tASSOC.

Rails 2
find(:all, :conditions => ['name LIKE ?', "%#{search}%"], ['active', 1]) isn't the proper syntax for passing hashes to a method. You can leave the curly braces off of a hash if it is the last argument to a method, but in this case you are passing an array as the last argument.
Use the following instead:
find(:all, :conditions => ["name LIKE ? AND active = ?", "%#{search}%", 1])
or
params = {:search => "%#{search}%", :active => 1}
find(:all, :conditions => ["name LIKE :search AND active = :active", params])
Rails 3 and 4
You would probably want to do something more like the following for recent Rails versions:
scope :active, -> { where(active: true) }
scope :name_like, ->(search) { where("name LIKE ?", "%#{search}%") }
And then you'd call it like this:
YourModel.active.name_like("Bunnies")
That allows you to reuse those specific queries in different combinations throughout the application. It also makes the code retrieving the data super easy to read.
If you don't like the scope syntax, you can also define these as class methods:
def self.active
where(active: true)
end
def self.name_like(search)
where("name LIKE ?", "%#{search}%")
end
You can also chain scopes on the fly. That will allow you to start building a chain of relation objects and then choose to include others based on conditions. Here's what it could look like when applied to the original question to achieve the same results:
results = active
results = results.name_like(search) if search.present?

Instead of using if-else which would involve a redundancy for checking on active = 1, a simpler syntax would be something like this:
result = where(:active => 1)
result = result.where('name like ?', "%#{search}%") unless search.blank?
As far as the error your seeing, it wouldn't appear to be caused by the code that you are posting. A stack trace may help narrow it down further...

i think you are using rails 2
try this.
find(:all, :conditions => ['name LIKE ? and active = 1', "%#{search}%"])
rails 3 syntax
where('name LIKE ? and active = 1', "%#{search}%")

For Rails 4, In rails new find_by and find_by! methods are introduced
Read answer

Related

Rails 3 update scope with conditions, include, and args to Rails 4

I'm upgrading a Rails 3 code base to Rails 4 and have been updating to use the new scope style and querying interface.
I'm unsure how to switch over this scope that is using include as well as conditions in the scope's lambda.
scope :search_stuff, lambda { |search|
search_conditions = Array.new(4, "%#{search}%")
{
:include => {:sponsor => [], :routing_form_pi_users => [:user], :item => []},
:conditions => ["id like ? or title like ? or users.name like ? or sponsors.name like ?", *search_conditions]
}
}
When you compare the documentation about the query interface in different versions of the Rails Guide then you can see that the interface for eager loading of multiple associations didn't change that much.
The example in Rails 2.3.8 syntax:
Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]}
The example for Rails 4.2 syntax:
Category.includes(articles: [{ comments: :guest }, :tags]).find(1)
Therefore it should be possible to just copy the old nested hash into the new syntax:
scope :search_stuff, lambda { |search|
includes(item: [], routing_form_pi_users: [:user], sponsor: []).
where('id like ? or title like ? or users.name like ? or sponsors.name like ?', *Array.new(4, "%#{search}%"))
}

rails search multiple attributes of multiple modules

I'm trying to search all columns in my Ticket model. Most columns are assocaited with a belongs_to association so I have to search the attributes of the info in my columns because people won't know the IDs.
These articles helped:
Search multiple db columns in Rails 3.0
Rails: Search in has_one association
So far I have the below code, but it only searches the top param. How can I get it so that if it doesn't find anything in the top search, it tries the next. I'll be adding more to this.
Thanks
def self.search(search)
if search
Ticket.joins(:submitter).where('first_name LIKE ?', "%#{search}%")
elsif
Ticket.joins(:issue).where('name LIKE ?', "%#{search}%")
else
all
end
end
I figured it out, updated correct code here:
def self.search(search)
case search
when /^[-+]?[0-9]*\.?[0-9]+$/
Ticket.find(:all, :conditions => ['id LIKE :search', {:search => "%#{search}%"}])
else
Ticket.joins(:submitter,:issue).find(:all, :conditions => ['name LIKE :search OR first_name LIKE :search', {:search => "%#{search}%"}])
end
end
Updated code above - answer found

Is there such a thing as a 'nested find' in Rails?

I've got the hang of nested includes—they're great for performance—but what I'd really like is a 'nested find'. What's the best way to achieve something like:
#matchingProducts = Batch.find(:all,
:conditions => 'product.name LIKE ?', "%#{search}%",
:include => :product)
As you can see, Product is a nested attribute of Batch, but I want to find a batch based on Product.name.
Rails 3 I would use the AREL syntax:
#matches = Batch.where('product.name LIKE ?', "search").includes(:product)
Your idea was right, you can do matchingProducts = Batch.find(:all, :include => 'products', :conditions => ["products.name LIKE ?", whatever_you_want], :order => order_as_you_want)

Conditions with Bind Variables and Optional Parameters

Let's say I have a form where users can search for people whose name starts with a particular name string, for example, "Mi" would find "Mike" and "Miguel". I would probably create a find statement like so:
find(:all, :conditions => ['name LIKE ?', "#{name}%"])
Let's say the form also has two optional fields, hair_color and eye_color that can be used to further filter the results. Ignoring the name portion of the query, a find statement for people that can take in an arbitrary number of optional parameters might look like this:
find(:all, :conditions => { params[:person] })
Which for my two optional parameters would behave as the equivalent of this:
find(:all, :conditions => { :hair_color => hair_color, :eye_color => eye_color })
What I can't figure out is how to merge these two kinds of queries where the required field, "name" is applied to the "like" condition above, and the optional hair_color and eye_color parameters (and perhaps others) can be added to further filter the results.
I certainly can build up a query string to do this, but I feel there must be a "rails way" that is more elegant. How can I merge mandatory bind parameters with optional parameters?
This is the perfect use of a named scope.
create a named scope in the model:
named_scope :with_name_like, lambda {|name|
{:conditions => ['name LIKE ?', "#{name}%"]}
}
At this point you can call
Model.with_name_like("Mi").find(:all, :conditions => params[:person])
And Rails will merge the queries for you.
Edit: Code for Waseem:
If the name is optional you could either omit the named scope from your method chain with an if condition:
unless name.blank?
Model.with_name_like("Mi").find(:all, :conditions => params[:person])
else
Model.find(:all, :conditions => params[:person])
end
Or you could redefine the named scope to do the same thing.
named_scope :with_name_like, lambda {|name|
if name.blank?
{}
else
{:conditions => ['name LIKE ?', "#{name}%"]}
end
}
Update
Here is the Rails 3 version of the last code snippet:
scope :with_name_like, lambda {|name|
if not name.blank?
where('name LIKE ?', "#{name}%")
end
}
To comply also with Waseem request, but allowing nil instead blank? (which is udeful in case you want to use "#things = Thing.named_like(params[:name])" directly)
named_scope :named_like, lambda do |*args|
if (name=args.first)
{:conditions => ["name like ?",name]}
else
{}
end
end
# or oneliner version:
named_scope :named_like, lambda{|*args| (name=args.first ? {:conditions => ["name like ?",name]} : {}) } }
I hope it helps

How to find a record created on the previous month?

I have a table where new records are added daily. How would I go about finding records created in the previous month?
Set up a named scope:
named_scope :in_last_month, :conditions => [ "records.created_at > ?", 1.month.ago ]
To call it (in your controller):
Record.in_last_month
The named_scope is a rather elegant way to go, I think, but if you take that route you will want to use it with a lambda method so that the time doesn't get scoped to when the application is initially loaded.
For example, this:
named_scope :last_month, :conditions =>
['created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month, Date.today.beginning_of_month]
will work properly the first month your application is up, but improperly the next month, unless the app gets restarted.
But this:
named_scope :last_month, lambda {
{:conditions => ['created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month, Date.today.beginning_of_month]}}
will work every time, because the lambda method gets executed on every call, reevaluating the Date.todays.
The accepted answer and lambda improvement do not work in Rails 4.
Update for Rails 4:
scope :last_month, -> {
where( 'created_at > ? AND created_at < ?',
Date.today.last_month.beginning_of_month,
Date.today.beginning_of_month )}
Assuming your records are timestamped, you can just do something like this:
Thing.find(:all, :conditions => ["created_at > ?", Time.now - 1.month])
If they're not timestamped, you should start storing the information since it's something you'll want to look up later.
Thanks everyone, I ended up going with this:
find(:all, :conditions => ['created_at > ? AND created_at < ?', Date.today.last_month.beginning_of_month, Date.today.beginning_of_month])
In one of my projects i used this way:
Thing.where('created_at BETWEEN ? AND ? ', DateTime.now.beginning_of_month - 1.month, DateTime.now.beginning_of_month)
in Rails 3 using last_month throws an error: Date.today.last_month.beginning_of_month
NoMethodError: undefined method `last_month'
Try this for Rails 4+, note that Date.current will use your application's timezone (specified in application.rb):
scope :created_last_month, lambda {
where(created_at: Date.current.last_month.beginning_of_month..Date.current.last_month.end_of_month)
}
Do you have the "usual" fields on your table? See the RoR wiki for a list of them. That way, you can express special queries to find an answer.
Thing.find(:all, :conditions => ["created_at > ?", 1.month.ago.at_beginning_of_month])
this is a good candidate for SQL's BETWEEN syntax
named_scope :last_month, :conditions => ['created_at BETWEEN ? AND ?', Date.today.last_month.beginning_of_month, Date.today.beginning_of_month])

Resources