ActiveRecord multiple where date range - ruby-on-rails

Is there a more ActiveRecord idiomatic way to find which records have a start_at or end_at within a certain date_range? (Basically, need to find the records that start or end in a given time frame). Here's what I'm currently doing:
Project.where('(start_at >= ? AND start_at <= ?) OR (end_at >= ? AND end_at <= ?)', start_at, end_at, start_at, end_at)

You can pass ranges to where and use or:
time_range = (start_at..end_at)
Project.where(start_at: time_range).or(Project.where(end_at: time_range))

For Rails 4 you could use BETWEEN and hash params:
Project.where(
"(start_at BETWEEN :start_at AND :end_at) OR (end_at BETWEEN :start_at AND :end_at)",
start_at: start_at.beginning_of_day, end_at: end_at.end_of_day)

Related

What is the difference theses two Ruby on Rails logical sentences?

I have an rails application and there is a filtering function. One of the filtering criteria is time value and when I try to filter by time criterion it keeps showing wrong results.
**There was an mistake in variable name so I edited
start_time = DateTime.new(2000, 1, 1, 6, 0, 0)
end_time = DateTime.new(2000, 1, 1, 9, 0, 0)
When I execute the filtering function, the result is:
Item.where("(start_at >= ? AND start_at < ?) OR (end_at > ? AND end_at <= ?) OR (start_at < ? AND end_at > ?)", start_time, end_time, start_time, end_time, start_time, end_time)
=> Item id: 5931
But with this result I execute the same sentence the result is:
(Item.find(5931).start_at >= start_time AND Item.find(5931).start_at < end_time)
=> false
(Item.find(5931).end_at > start_time AND Item.find(5931).end_at <= end_time)
=> false
(Item.find(5931).start_at < start_time AND Item.find(5931).end_at > end_time)
=> false
(Item.find(5931).start_at >= start_time AND Item.find(5931).start_at < end_time) or (Item.find(5931).end_at > start_time AND Item.find(5931).end_at <= end_time) or (Item.find(5931).start_at < start_time AND Item.find(5931).end_at > end_time)
=> false
Is there anyone to find the point?
If I missed any information needed please tell me.
To query for data between two times use the SQL BETWEEN keyword:
Item.where(start_at: start_time..end_time)
# => SELECT "items".* FROM "items" WHERE ("items"."start_at" BETWEEN $1 AND $2) [["start_at", 2000-01-01 06:00:00 UTC], ["start_at", 2000-01-01 09:00:00 UTC]]
.. in start_time..end_time is a special syntax that creates a Range object.
To add an OR clause you can use the .or method in Rails 5:
Item.where(start_at: start_time..end_time)
.or(Item.where(end_at: start_time..end_time))
In Rails 4 you need to write the SQL string:
Item.where(
'(items.start_at BETWEEN :s AND :e) OR (items.end_at BETWEEN :s AND :e)',
s: start_time, e: end_time
)

How to refer a table column as clause condition with Arel?

I have this scope:
scope :in_range, lambda { |begin_time, end_time, excluded_sales = nil|
where(
'start_at BETWEEN ? AND ? OR finish_at BETWEEN ? AND ? OR ? BETWEEN start_at AND finish_at OR ? BETWEEN start_at AND finish_at',
begin_time, end_time, begin_time, end_time, begin_time, end_time)
.where.not(id: excluded_sales)
}
This produces
> Sale.in_range(Time.now, Time.now + 1)
Sale Load (0.6ms) SELECT "sales".* FROM "sales" WHERE (start_at BETWEEN '2014-12-12 10:45:47.065712' AND '2014-12-12 10:45:48.065714' OR finish_at BETWEEN '2014-12-12 10:45:47.065712' AND '2014-12-12 10:45:48.065714' OR '2014-12-12 10:45:47.065712' BETWEEN start_at AND finish_at OR '2014-12-12 10:45:48.065714' BETWEEN start_at AND finish_at) AND ("sales"."id" IS NOT NULL)
I need to rewrite this scope using Arel because I believe I should not use raw SQL syntax here.
But I do not know how to use in method with table column reference. I've tried this but got exception:
> s = Sale.arel_table
> begin_time = Time.now
> Sale.where(begin_time.in(s[:start_at]..s[:finish_at]))
--> ArgumentError: bad value for range
Also tried this, but had no luck:
> s = Sale.arel_table
> begin_time = Time.now
> Sale.where(begin_time.in('sales.start_time'..'sales.end_time')
--> NoMethodError: undefined method `round' for "sales.end_time".."sales.start_time":Range
Please advise how to refer a table column as clause condition with Arel?
Thank you!
UPDATED:
There are four conditions connected with OR statement:
start_at in range of begin_time and end_time
finish_at in range of begin_time and end_time
begin_time in range of start_time and finish_time
end_time in range of start_time and finish_time
Basically it is just a date ranges overlap.
I rewrote it to
scope :in_range, lambda { |date_range, excluded_sales = nil|
s = Sale.arel_table
where(
s.grouping(s[:starts_at].gteq(date_range.first).and(s[:starts_at].lteq(date_range.last)))
.or(s.grouping(s[:ends_at].gteq(date_range.first).and(s[:ends_at].lteq(date_range.last))))
.or(s.grouping(s[:starts_at].lteq(date_range.first).and(s[:ends_at].gteq(date_range.last))))
.or(s.grouping(s[:starts_at].gteq(date_range.first).and(s[:ends_at].lteq(date_range.last))))
).where.not(id: excluded_sales)
}
Maybe there is a simplier way to achieve my goal?
well as far as I understood, you wish to select all record, which range have an intersection to the specified one? If so, just select records, which have start_at field values that are before the end of range, and have finish_at field values that are after the begin of range. So with arel it should be:
scope :in_range, proc do |begin_time, end_time, excluded_sales|
where(arel_table[:start_at].lteq(end_time)
.and(arel_table[:finish_at].gteq(begin_time))
.and(arel_table[:id].not_eq(excluded_sales)))
end

Issue counting using conditional datetime

I'm trying to count all my policies that are'n expired >= that today
date_end >= TODAY
Here is my table
|policies|
|id| |num| |date_ini| |date_end|
1 12484 2013-04-01 2014-05-01
2 41511 2012-04-01 2013-05-01
3 14441 2013-05-01 2014-06-01
There are 2 values that aren't expired
Here is my controller
#policies =Policy.count(:conditions =>['date_end >= Date.now'])
I tried
#policies =Policy.count(:conditions =>['date_end >= Date.today'])
And also
#policies =Policy.count(:conditions =>['date_end >= curtime()'])
Please somebody can help me?
I will really appreciate help
It's not working because Date.today is inside a String, so it isn't being evaluated as an actual Date. You probably also want to use Date.current instead, to take the configured time zone into account.
Try this instead:
#policies = Policy.count( :conditions => ['date_end >= ?', Date.current] )
If date_end is stored as a DATE type of attribute, you can do this:
Policy.where("date_end >= ?", Date.today).count
or if it's stored as a string, just convert the Date.today to a string:
Policy.where("date_end >= ?", Date.today.to_s).count
The problem is that when you call out Date.today, you are returning the format "Fri, 15 Nov 2013" which will not compare to "2014-04-02". To get them in the same format, use
Date.today.to_s

Rails 3 ActiveRecord conditional includes?

I know this can be done:
Article.where("published_at <= ?", Time.now).includes(:comments)
But what if I'd like to only get comments posted in the past month?
Does the .includes operator allow conditions?
Article.includes(:comments).where("articles.published_at <= ? and comments.created_at >= ?", Time.now, Time.now - 1.month)
EDIT:
Article.joins(:comments).where("articles.published_at <= ? and comments.created_at >= ?", Time.now, Time.now - 1.month)
In Rails4, it should be:
Article.includes(:comments).where("articles.published_at <= ? and comments.created_at >= ?", Time.now, Time.now - 1.month).references(:comments)
Source

Use the same parameters many times in a find conditions: hash

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)]} }

Resources