开发者

Rails 3: how to write DRYer scopes

开发者 https://www.devze.com 2023-03-05 10:18 出处:网络
I\'m finding myself writing very similar code in two places, once to define a (virtual) boolean attribute on a model, and once to define a scope to find records that match that condition. In essence,

I'm finding myself writing very similar code in two places, once to define a (virtual) boolean attribute on a model, and once to define a scope to find records that match that condition. In essence,

scope :something, where(some_complex_conditions)

def something?
  some_complex_conditions
end

A simple example: I'm modelling a club membership; a Member pays a Fee, which is valid only in a certain year.

class Member < ActiveRecord::Base
  has_many :payments
  has_many :fees, :through => :payments

  scope :current, joins(:fees).merge(Fee.current)

  def current?
    fees.current.exists?
  end
end

class Fee < ActiveRecord::Base
  has_many :payments
  has_many :members, :through => :payments

  scope :current, wh开发者_高级运维ere(:year => Time.now.year)

  def current?
    year == Time.now.year
  end
end

Is there a DRYer way to write a scopes that make use of virtual attributes (or, alternatively, to determine whether a model is matched by the conditions of a scope)?

I'm pretty new to Rails so please do point out if I'm doing something stupid!


This in not an answer to the question, but your code has a bug (in case you use something similar in production): Time.now.year will return the year the server was started. You want to run this scope in a lambda to have it behave as expected.

scope :current, lambda { where(:year => Time.now.year) }


No, there's no better way to do what you're trying to do (other than to take note of Geraud's comment). In your scope you're defining a class-level filter which will generate SQL to be used in restricting the results your finders return, in the attribute you're defining an instance-level test to be run on a specific instance of this class.

Yes, the code is similar, but it's performing different functions in different contexts.


Yes, you can use one or more parameters with a lambda in your scopes. Suppose that you have a set of items, and you want to get back those that are either 'Boot' or 'Helmet' :

  scope :item_type, lambda { |item_type|
    where("game_items.item_type = ?", item_type )
  } 

You can now do game_item.item_type('Boot') to get only the boots or game_item.item_type('Helmet') to get only the helmets. The same applies in your case. You can just have a parameter in your scope, in order to check one or more conditions on the same scope, in a DRYer way.

0

精彩评论

暂无评论...
验证码 换一张
取 消