default_scope snippets

ALERT: ActiveRecord#order and ActiveRecord#default_scope are dangerous

Tagged activerecord, default_scope, order  Languages ruby

Consider this model:

class Transaction
  default_scope -> { order("created_at ASC") }

  def self.latest
    order('created_at DESC')
  end

   def self.approved_for_customer(customer)
    where('approved is not null and customer_id = ?', customer).order('approved_at DESC')
  end
end

And this controller:

class TransactionsWorker
   def perform(...)
      transaction = Transaction.approved_for_customer.latest # WTF!
   end
end

The order will be "ORDER BY created_at ASC, approved_at DESC, created_at DESC", not "created_at DESC".

Solutions:

  • Don't return an instead of ActiveRecord::Relation if you use order, return an array (.all, .first, etc)
  • Don't use order and default_scope at all in models, ordering usually belongs in the business-logic layer.
  • Use reorder as the last method call if chaining ActiveRecord::Relation, e.g. Transaction.where('...').reorder(:approved_at)

A safer model:

class Transaction
   def self.latest
    order('created_at DESC').all
  end

   def self.for_customer(customer)
    where('approved is not null and customer_id = ?', customer).order('approved_at DESC').all
  end
end