A given object should assume as little as possible about the structure or properties of anything else (including its subcomponents).
It also can be described by:
- Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
- Each unit should only talk to its friends; don't talk to strangers.
- Only talk to your immediate friends.
class Category < ActiveRecord::Base attr_accessible :name end class Food < ActiveRecord::Base attr_accessible :name, :category_id belongs_to :category end class Recipe < ActiveRecord::Base attr_accessible :name, :food_id belongs_to :food endand accessing the recipes category name in the ERB template in that way:
Recipe: <%= @recipe.name %> Recipe food category: <%= @recipe.food.category.name if @recipe.food and @recipe.food.category %>is definitely a bad practice for various reasons:
- The Recipe object has not only to know about its associated Food object, but also about how the food object is associated to the Category object.
- The path gets longer the more objects are involved and the more the Recipe object has to know about other objects associations.
- An object during the path could be nil, and hence the path could be broken. Therefore a long security condition statement is needed every time the path is gone.
- Repeating the security condition everywhere the path was used, is hard to maintain, if even only one association changed.
It can be fixed by hiding the knowledge about the directly associated object in an instance method like:
class Category < ActiveRecord::Base attr_accessible :name end class Food < ActiveRecord::Base attr_accessible :name, :category_id belongs_to :category def category_name category.name if category end end class Recipe < ActiveRecord::Base attr_accessible :name, :food_id belongs_to :food def food_category_name food.category_name if food end endThe Recipe object only knows about its associated Food object and how to get its category_name, without knowing HOW the Food object gets the category_name. But the Food object just knows how to get the Category name. The knowledge was appropriately hidden and can accessed like:
Recipe: <%= @recipe.name %> Recipe food category: <%= @recipe.food_category_name %>Well, Ruby on Rails is handy enough to offer some syntactic sugar for delegating methods with the help of Module#delegate. That is why the models look better delegating the Category name:
class Category < ActiveRecord::Base attr_accessible :name end class Food < ActiveRecord::Base attr_accessible :name, :category_id belongs_to :category delegate :name, to: :category, prefix: true, allow_nil: true end class Recipe < ActiveRecord::Base attr_accessible :name, :food_id belongs_to :food delegate :category_name, to: :food, prefix: true, allow_nil: true endThe detailed options can be read in the Module#delegate API documentation. The ERB template need not to be changed:
Recipe: <%= @recipe.name %> Recipe food category: <%= @recipe.food_category_name %>
Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17
Keine Kommentare:
Kommentar veröffentlichen