Nick Kallen’s named_scope allows you to create readable, powerful class-specific finder scopes.

One thing that might bite new users is how it operates in the Class context. Specifically, Named Scopes created in a parent class, even those using the lambda syntax, are scoped to the parent class.

For example, we might have a generic SQL operation that finds records whose date field falls within a certain range in a parent class, and simply allow any child classes to provide the range.

class HighScore
  named_scope :for_date, lambda { |time| { :conditions => { :date => time_range_for(time) } } }
end
 
class DailyHighScore < HighScore
  def self.time_range_for time
    (time.beginning_of_day)..(time.beginning_of_day + 1.day)
  end
end
 
class WeeklyHighScore < HighScore
  def self.time_range_for time
    (time.beginning_of_week)..(time.beginning_of_week + 1.week)
  end
end

Wouldn’t it be lovely if we could just rely on the subclass to complete the conditions? Unfortunately, since time_range_for is not defined in the HighScore class, this code will not not work.

The solution is to create individual Named Scopes on the child classes. However, instead of declaring unique Named Scopes, common behavior is isolated in the parent class by providing a Named Scope builder.

class HighScore
  def self.has_named_scope_for_time_range
    named_scope :for_date, lambda { |time| { :conditions => { :date => time_range_for(time) } } }
  end
end
 
class DailyHighScore < HighScore
  def self.time_range_for time
    (time.beginning_of_day)..(time.beginning_of_day + 1.day)
  end
 
  has_named_scope_for_time_range
end
 
class WeeklyHighScore < HighScore
  def self.time_range_for time
    (time.beginning_of_week)..(time.beginning_of_week + 1.week)
  end
 
  has_named_scope_for_time_range
end

Now all child classes using has_named_scope_for_time_range get a for_date Scope that works just the way they want. An additional advantage of this approach is that the parent class no longer has the extraneous for_date Named Scope.

Leave a Comment

Enclose code in <code lang="ruby"></code> if you care.
Preview your comment using the button below.