Use the same parameters many times in a find conditions: hash - ruby-on-rails

I have a model who holds 2 properties: valid_from and valid_to.
I need to select all instances that are currently valid, i.e. valid_from <= today and valid_to >= today.
i have the following find :
Mymodel.find(:all, :conditions => ["valid_from <= ? and valid_to >= ?", Date.today, Date.today])
I already thought about storing Date.today in a variable and calling that variable, but i still need to call it twice.
my_date = Date.today
Mymodel.find(:all, :conditions => ["valid_from <= ? and valid_to >= ?", my_date, my_date])
Is there a way to improve and do only one call to the variable to match all the "?" in the :conditions ?
thanks,
P.

I would use named_scope. In model add:
named_scope :valid,
:conditions =>
["valid_from <= ? and valid_to >= ?", Date.today, Date.today]
And then in your controller you can call:
#mymodels = Mymodel.valid
I think that focusing on reducing two calls to Date.today to only one call is wasting of time. It won't make your application faster or using less memory.

I'm not aware of a way to do what you're asking, but even if you could I don't think it would buy you much. I would create a named scope within your model class.
In this example, you can pass the date to the named scope, or it will default to today's date if no date is specified:
named_scope :by_valid_date, lambda { |*args|
{ :conditions => ["valid_from <= ? and valid_to >= ?",
(args.first || Date.today), (args.first || Date.today)]} }

Related

Dates and scopes are inconsistent

I have the following scope:
scope :this_month, :conditions => ["created_at >= ?", Date.today.beginning_of_month]
Which makes the SQL output of a.response_sets.this_month.to_sql:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-05-01'
But since today is actually June 1, that date seems wrong. So, I tried bypassing the scope and just doing a condition directly, like so:
a.response_sets.where(["created_at >= ?", Date.today.beginning_of_month]).to_sql
Which then, outputs:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-06-01'
Which is correct. So why is there a difference between doing Date.today.beginning_of_month in a scope and doing it directly in where?
When working with dates in scopes you should use a lambda so the scope gets evaluated every time it is called:
scope :this_month, -> { where("created_at >= ?", Date.today.beginning_of_month) }

Rails 3 Query - IN (?) can be blank

This query won't return any records, when hidden_episodes_ids is empty.
:conditions => ["episodes.show_id in (?) AND air_date >= ? AND air_date <= ? AND episodes.id NOT IN (?)", #show_ids, #start_day, #end_day, hidden_episodes_ids]
If it's empty, the SQL will look like NOT IN (null)
So my solution is:
if hidden_episodes_ids.any?
*mode code*:conditions => ["episodes.show_id in (?) AND air_date >= ? AND air_date <= ? AND episodes.id NOT IN (?)", #show_ids, #start_day, #end_day, hidden_episodes_ids]
else
*mode code*:conditions => ["episodes.show_id in (?) AND air_date >= ? AND air_date <= ?", #show_ids, #start_day, #end_day]
end
But it is rather ugly (My real query is actually 5 lines, with joins and selects etc..)
Is there a way to use a single query and avoid the NOT IN (null)?
PS: These are old queries migrated into Rails 3, hence the :conditions
You should just use the where method instead as that'll help clean all of this up. You just chain it together:
scope = Thing.where(:episodes => { :show_id => #show_ids })
scope = scope.where('air_date BETWEEN ? AND ?', #start_day, #end_day)
if (hidden_episode_ids.any?)
scope = scope.where('episodes.id NOT IN (?)', hidden_episode_ids)
end
Being able to conditionally modify the scope avoids a lot of duplication.

Rails 3: How to merge queries or scopes for complex query?

I'm building an events app that is very simple, it has a title and start_date and end_date. I would like to filter my query by mixing some of the values, like: if the start_date has passed but the end_date has not, the event is active and should be displayed. If both dates have passed, it should be omitted, too. I think that scopes is the aswer, but I only was able to filter the records within the view using some methods shown below.
I really would like to filter the query that is passed to the controller (#events). I want to show all events that are active, have a future start_date, or a past start_date but are still in progress (Today's date is in range between start_date and end_date)
EDITED
I have made some scopes which return each part of the query. Chaining them actually substracts the results instead of merging them. So i have used this code and actually works do I do not know how solid or DRY this is. Looks kind of ugly to me... is this a decent way to merge queries in rails 3?
scope :active, where("active = ?", true)
scope :not_over_or_in_progress, lambda { where("start_date < ? AND end_date > ? OR end_date IS NULL AND start_date > ? OR end_date IS NOT NULL AND start_date > ?", Date.today, Date.today, Date.today, Date.today) }
scope :valid, not_over_or_in_progress.active.order("start_date DESC")
Try using scopes:
class Event < AR::Base
scope :active, lambda { |date| where("start_date < ? AND end_date > ?", date) }
scope :future, lambda { |date| where("end_date < ?", date }
...
end
# Console
> #active_events = Event.active(Date.today)
> #future_events = Event.future(Date.today)
See http://guides.rubyonrails.org/active_record_querying.html

Combine arrays of conditions in Rails

I'm using the Rails3 beta, will_paginate gem, and the geokit gem & plugin.
As the geokit-rails plugin, doesn't seem to support Rails3 scopes (including the :origin symbol is the issue), I need to use the .find syntax.
In lieu of scopes, I need to combine two sets of criteria in array format:
I have a default condition:
conditions = ["invoices.cancelled = ? AND invoices.paid = ?", false, false]
I may need to add one of the following conditions to the default condition, depending on a UI selection:
#aged 0
lambda {["created_at IS NULL OR created_at < ?", Date.today + 30.days]}
#aged 30
lambda {["created_at >= ? AND created_at < ?", Date.today + 30.days, Date.today + 60.days]}
#aged > 90
lamdba {["created_at >= ?", Date.today + 90.days]}
The resulting query resembles:
#invoices = Invoice.find(
:all,
:conditions => conditions,
:origin => ll #current_user's lat/lng pair
).paginate(:per_page => #per_page, :page => params[:page])
Questions:
Is there an easy way to combine these two arrays of conditions (if I've worded that correctly)
While it isn't contributing to the problem, is there a DRYer way to create these aging buckets?
Is there a way to use Rails3 scopes with the geokit-rails plugin that will work?
Thanks for your time.
Try this:
ca = [["invoices.cancelled = ? AND invoices.paid = ?", false, false]]
ca << ["created_at IS NULL OR created_at < ?",
Date.today + 30.days] if aged == 0
ca << ["created_at >= ? AND created_at < ?",
Date.today + 30.days, Date.today + 60.days] if aged == 30
ca << ["created_at >= ?", Date.today + 90.days] if aged > 30
condition = [ca.map{|c| c[0] }.join(" AND "), *ca.map{|c| c[1..-1] }.flatten]
Edit Approach 2
Monkey patch the Array class. Create a file called monkey_patch.rb in config/initializers directory.
class Array
def where(*args)
sql = args[0]
unless (sql.is_a?(String) and sql.present?)
return self
end
self[0] = self[0].present? ? " #{self[0]} AND #{sql} " : sql
self.concat(args[1..-1])
end
end
Now you can do this:
cond = []
cond.where("id = ?", params[id]) if params[id].present?
cond.where("state IN (?)", states) unless states.empty?
User.all(:conditions => cond)
I think a better way is to use Anonymous scopes.
Check it out here:
http://railscasts.com/episodes/112-anonymous-scopes

How do I use .find for the last five days in ruby on rails?

Have a model called contact_email.date_sent
I want to be able to run a report which displays all those where the date_sent range is between date.today and date.today 5 days ago.
I assume I use something like
#send_emails = Contact_Email.find(:conditions=> ???)
But not clear what exactly is the best way. Thanks!
Try this:
ContactEmail.all(:conditions => ["date_sent >= ?", 5.days.ago.to_date])
This approach is faster than using BETWEEN clause( assuming date_sent is indexed)
Caveat:
Value of date_sent column should be less than current date.
Edit 1
To add an index in migration:
add_index :contact_emails, :date_sent
ContactEmail.find(:conditions => ['date_sent BETWEEN ? AND ?', Date.today, 5.day.ago.to_date])
If it's something you will use regularly, why not put a named_scope in the model:
named_scope :recent, lambda { |*args| {:conditions => ["date_sent > ?", (args.first || 5.days.ago)]} }
which will let you write:
ContactEmail.recent
for the last 5 days worth, or use the arg to specify your own time frame e.g. the last two weeks:
ContactEmail.recent(2.weeks.ago)

Resources