Dates and scopes are inconsistent - ruby-on-rails

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

Related

Rails 4 ActiveRecord::first not working

I'm debugging some legacy code in a Rails 4.1 app and I'm seeing some confusing results from this:
#order.rb
# get the most recent order from the same year we are in
scope :last_from_this_year, -> { where("created_at >= ?", Time.mktime(Time.now.year, 1)).where("created_at < ?", Time.mktime(Time.now.year, 12, 31)).order('payment_id DESC').first }
# orders_controller.rb
prev_payment = Order.last_from_this_year
For reasons I cannot explain, this scope is returning an ActiveRecord_Relation containing ALL the order records, despite the fact that it's calling the .first method. I'm expecting first to return a single ActiveRecord object.
Why is this happening?
Update
When I run this in the console, it works as expected:
o = Order.where("created_at >= ?", Time.mktime(Time.now.year, 1)).where("created_at < ?", Time.mktime(Time.now.year, 12, 31)).order('payment_id DESC').first
I literally copy/pasted exactly what's in the scope and it works. So it's confusing why it doesn't behave as expected as a scope.
$ Order.last_from_this_year.to_sql
=> SELECT "orders".* FROM "orders" WHERE (created_at >= '2017-01-01 08:00:00.000000') AND (created_at < '2017-12-31 08:00:00.000000') ORDER BY payment_id DESC LIMIT 1
Seems right... very strange.
The concept behind that scope is wrong. The idea of a scope is to always return an instance of ActiveRecord::Relation (a collection of objects rather than one) to allow further chaining with other methods such as where, includes, joins, etc.
If you need to retrieve just one object from that scope, you need to remove .first and use it as: Order.last_from_this_year.first.
Other solution is to move that code to a class method:
def self.last_from_this_year
where("created_at >= ?", Time.mktime(Time.now.year, 1))
.where("created_at < ?", Time.mktime(Time.now.year, 12, 31))
.order('payment_id DESC')
.first
end
Then Order.last_from_this_year should work as it does on the console.

How to find data between two dates in ruby on rails

Query for finding data from now to 3 days ago not working.Although data is present but it cant retrieve data.
ViewsLog.where(:created_at=>Time.now..3.day.ago)
ViewsLog Load (0.5ms) SELECT "views_logs".* FROM "views_logs" WHERE ("views_logs"."created_at" BETWEEN '2015-12-09 08:15:21.586416' AND '2015-12-06 08:15:21.586513')
#<ActiveRecord::Relation []>
You have inverted the parameters - it should be
ViewsLog.where(:created_at=>3.days.ago..Time.now)
You can use it like this:
ViewsLog.where("date(created_at) between :start and :end", {start: 3.days.ago.to_date, end: Date.today})
This should work well for you.
Create a scope in the model ViewsLog
scope :record_between, lambda {|start_date, end_date| where("created_at >= ? AND created_at <= ?", start_date, end_date )}
or
scope :record_between, lambda {|start_date, end_date| where(created_at: start_date..end_date )}
Now it will be generic for any two dates data
Now you can do
ViewsLog.record_between(3.day.ago, Time.now)

Rails scope filter by date range

There are many questions relate to rails date range problem but mine is a little more complicated.
I have two models: house and booking. A House has_many bookings. A Booking has two attributes in date format: check_in and check_out.
What I want to achieve: Giving a valid date range, show all houses that are available during this range. In detail:
The start date of the range should not be in any booking.
The end date of the range should not be in any booking.
There should not be any booking between the start and the end.
Can this be done using the rails scope?
UPDATE:
I found the code below that can check scope date interval that overlaps.
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
How can I transfer this to my problem?
scope :overlapping, (lambda do |start_date, end_date|
House.includes(:bookings).where("bookings.check_in < ? AND bookings.check_out > ?",
start_date, end_date).references(:bookings).uniq
end)
I went ahead and deleted the >= and <= operators in favor of > and < to explicitly show these bookings being outside of the given range, but you can adjust them per your needs!
Update
Changed query to use #includes instead of #joins, since we're querying the attached table.
Yes it is possible to have this query through scope. Put this scope in house model.
scope :overlapping, -> (start_date, end_date) {
includes(:bookings).where('bookings.check_in < ? AND bookings.check_out > ?',
start_date.to_date, end_date.to_date)
}
And call as House.overlapping('2015-07-01', '2015-07-09')

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

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