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

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
)

Related

Comparison of datetime in where condition

I need to add a date comparison in a query.
The field is column delivery_date timestamp without time zone
The condition should be delivery_date <= today.
I have tried :
"delivery_date < ?", Date.today
delivery_date.lt(Date.today)
"delivery_date" <= time.now [Error : NameError - undefined local variable or method `time' for ]
"delivery_date" <= Time.now [Error : ArgumentError - comparison of String with DateTime failed: ]
but I am getting different errors with all.
Here is the query where I need to add my condition :
datas: tab.project
.active
.where("delivery_date" <= date.today, step_id: Step::OPENED, test: {typess: type})
.joins(:test, :account)
.group(:'account.name')
.order('count(tab.id) DESC')
.count(:id)
Any idea how it should be done?
Try:
tab.project.active.where("delivery_date <= ?", Date.today)
or
tab.project.active.where("delivery_date <= :date", date: Date.today)
In both cases we use Date.today that returns (suprisingly :-) ) the date of today. We use two ways of adding param to a query -
array condition where ? is replaced by next where arguments (in this case the Date.today)
placeholder condition where a symbol is replaced by a hash value for this symbol (date: Date.today)

How to check if params present in scope rails

scope :created_after, ->(start_time,end_time) { where("start_time > ? and end_time <?", start_time,end_time) }
I want to add if condition if start time present then only start_time query run if end_time then its query run
I think created_between is better name for what you want to do with your created_after scope.
You can create two helper scopes to filter results using start_time and end_time column.
scope :created_after, ->(start_time) do
where("start_time > ?", start_time)
end
scope :created_before, ->(end_time) do
where("end_time < ?", end_time)
end
Then you can combine them in a way you wanted:
scope :created_between, ->(start_time, end_time) do
query = self.all
query = query.created_after(start_time) if start_time
query = query.created_before(end_time) if end_time
query
end
If you execute query giving nil for any scope argument it will ignore them. For example: created_between(nil, nil) will result in no additional query.
You can just chain it in the condition
scope :created_after, ->(start_time, end_time) { start_time && end_time && where("start_time > ? and end_time <?", start_time, end_time) }
This will add the where condition only if start_time and end_time is present

ActiveRecord multiple where date range

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)

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

Finding records between date range

I'm trying to find records whose start and end date range over a particular date. Date is random and :start_date and :end_date are attributes of the prices entity.
date = Time.now
record_i_want = Price.where(date => :start_date .. :end_date)
Thank you.
You can simply do
Price.where(:date => start_date..end_date)
This will result in the following SQL( for start and end dates - '2014-03-27', '2014-03-28')
SELECT `prices`.* FROM `prices` WHERE (`prices`.`date` BETWEEN '2014-03-27' AND '2014-03-28')
EDIT:
Realized that this is the query you are looking for. Thanks, Coenwulf for pointing it out
Price.where(['start_date < ? AND end_date > ?', date, date])
You want to select rows where your date is greater than the start_date and less than the end_date. You can specify the appropriate SQL where clause parameterized in your call to where like so:
Price.where([":date >= start_date AND :date <= end_date", {date: Date.today})
That will give you all the prices that match. If you know you'll get only one you can get it by calling first.
Price.where([":date >= start_date AND :date <= end_date", {date: Date.today}).first
Make any appropriate adjustment to the >= and <= if you want to exclude the start_date and/or the end_date from the results. If for example the Price is valid starting on the start_date but isn't valid through the end_date you can change the clause to:
":date >= start_date AND :date < end_date"
This should work:
def get_record_by_date(date)
Price.where([start_date.to_i < date.to_i] && [end_date.to_i > date.to_i])
end

Resources