Reputation: 33378
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.
Upvotes: 0
Views: 723
Reputation: 3267
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.
Upvotes: 2