emersonthis
emersonthis

Reputation: 33378

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.

Upvotes: 0

Views: 723

Answers (1)

Dario Barrionuevo
Dario Barrionuevo

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

Related Questions