Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails & ActiveRecord: DRY use same logic in scope and boolean method

I have a model with a scope and a method like so:

class Model < ActiveRecord::Base

  scope :editable, where('updated_at > ? OR (updated_at IS NULL AND created_at > ?)', (Date.today - 3.days).beginning_of_day, (Date.today - 3.days).beginning_of_day)

  def editable?
    return (self.updated_at || self.created_at) > (Date.today - 3.days).beginning_of_day
  end

end

I have the feeling I should not be writing two times the same logic in both the scope and the method. Is there a way I can avoid that?

I'm on Rails 3.2

Thanks

like image 620
Augustin Riedinger Avatar asked Oct 02 '22 03:10

Augustin Riedinger


2 Answers

Unfortunately there is no easy way to do this at the moment. The closest you can get is

def editable?
  self.class.editable.exists? self
end

However it will call database to check whether given record exists or not, so it is useless for new records or for records you want to update.

The reason why there is no such a functionality is that this would require translation from SQL/Arel to ruby code, which might not be simple at all (as other model may be involved with JOIN statement). However If you have squeel gem installed, you should be able to write:

class Model < ActiveRecord::Base

  editable = -> {
    (updated_at > (Date.today - 3.days).beginning_of_day) | ((updated_at == nil) & created_at > (Date.today - 3.days).beginning_of_day)
  }

  scope :editable, where &editable

  def editable?
    instance_eval &editable
  end

end

which is pretty much what you need. (not tested!)

UPDATE: Note that squeel gem is not currently maintained, which might be dangerous.

like image 133
BroiSatse Avatar answered Oct 03 '22 18:10

BroiSatse


As far as I can see there is no code duplication there.

The scope will be called on the class, Model.editable, retrieving all the records that match that scope. editable? will be called on an instance of Model, eg: @model.editable? returning true or false. The first is a query, the second a logic expression.

One way to reuse your scope in the editable? method would be the following:

class Model < ActiveRecord::Base

  scope :editable, where('updated_at > ? OR (updated_at IS NULL AND created_at > ?)', (Date.today - 3.days).beginning_of_day, (Date.today - 3.days).beginning_of_day)

  def editable?
    Model.editable.include?(self)
  end
end

However, this is not recommended as it will cause unnecessary query to the database.

like image 43
gizotti Avatar answered Oct 03 '22 18:10

gizotti