- encapsulates logic which applies to the association
- provides more expressive access to associated model objects
- is combinable with collection proxy object methods
class Cook < ActiveRecord::Base has_many :recipes end class Recipe < ActiveRecord::Base def self.named_like name where("#{table_name}.name LIKE ?", "%#{name}%") end def name_with_time "#{name} (#{time})" end endwhereas a cook has many recipes.
Encapsulate logic
Getting all recipes as a comma separated String is coded quickly:Cook.first.recipes.map(&:name_with_time).join(', ') => "Chocolate Chunk Cookies (30), Oatmeal Shortbread (45)"but can be refactored from the reuseability perspective by moving the logic into the Cook association to Recipe:
class Cook < ActiveRecord::Base has_many :recipes do def to_s map(&:name_with_time).join(', ') end end endNow the logic, how to get recipes String does not need to be reproduced any more:
Cook.first.recipes.to_s => "Chocolate Chunk Cookies (30), Oatmeal Shortbread (45)"
Increase Expressiveness
Another scenario is to search all the cooks recipes by a term:Cook.first.recipes.named_like('cookie') => [#<Recipe id: 1, name: "Chocolate Chunk Cookies", time: 30>]is pretty expressive, but can be improved by extending the association once again:
class Cook < ActiveRecord::Base has_many :recipes do def [] term named_like term end end endand sending the message:
Cook.first.recipes['cookie'] => [#<Recipe id: 1, name: "Chocolate Chunk Cookies", time: 30>]
Combine extensions
Proxy objects extensions also can be combined easily, which makes logic flexible and reusable:class Cook < ActiveRecord::Base has_many :recipes do def to_s map(&:name_with_time).join(', ') end def [] term named_like term end end endand combining both:
Cook.first.recipes['cookie'].to_s => "Chocolate Chunk Cookies (30)"whereas the chain order matters.
Of course the association proxy extensions also can be combined with other CollectionAssociation methods like:
Cook.first.recipes['cookie'].count => 1which generates a different SQL statements projection part as expected. Accessing the Proxy objects inside the extension is also easy as:
class Cook < ActiveRecord::Base has_many :recipes do def [] term named_like term end def shallow_copy proxy_association.proxy.map(&:dup) end end endThe message receiver Cook#shallow_copy simply clones the associated objects:
Cook.first.recipes['cookie'].shallow_copy => [#<Recipe name: "Chocolate Chunk Cookies", time: 30>]Please note the cloned recipe has no ID, since it is a new record.
The collection proxy object provides some more helpful accessors like ActiveRecord::Associations::HasManyAssociation#owner and ActiveRecord::Associations::HasManyAssociation#reflection
Further articles of interest:
- Modularize ActiveRecord has_many proxy extensions!
- Scope the model!
- Stringify your ActiveRecord model objects!
Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17
Keine Kommentare:
Kommentar veröffentlichen