Combine Model Scopes the Rails Way
I accidentally noticed that two of my models have some resemblance. Their names are GameItem and OwnedItem. A GameItem is a just an item of the game, while an OwnedItem represents if a player has that item, if it's on his/her inventory or warehouse and more. My models are now like ( i removed validations and some irrelevant code for simplicity) :
class OwnedItem < ActiveRecord::Base
belongs_to :user
belongs_to :game开发者_如何学Python_item
belongs_to :ownable, :polymorphic => true # [warehouse|inventory]
scope :equipped, where(:is_equipped => 1).includes(:game_item)
scope :item, lambda { |item_type|
joins(:game_item).
where("game_items.item_type = ?", item_type ).
limit(1)
}
scope :inventory, where(:ownable_type => 'Inventory')
scope :warehouse, where(:ownable_type => 'Warehouse')
end
class GameItem < ActiveRecord::Base
scope :can_be_sold, where(:is_sold => 1)
scope :item_type, lambda { |item_type|
where("game_items.item_type = ?", item_type )
}
scope :item_types, lambda { |item_types|
where("game_items.item_type IN (?)", item_types )
}
scope :class_type, lambda { |class_type|
where("game_items.class_type = ?", class_type )
}
scope :grade, lambda { |grade|
where("game_items.grade = ?", grade )
}
end
Notice the issue with game_item.item_type. I reference it in owned_item model, thus breaking encapsulation and repeating myself. How can i actually be able to do something like :
user.inventory.owned_items.item_type('Weapon').equipped
that is, without actually adding repeated code in my OwnedItem model, but getting that information out of the GameItem model ?
I think you've defined the relationships here in a way that's going to cause you trouble. You may find it's better off to use a simple user to item join model, something like this:
class User < ActiveRecord::Base
has_many :owned_items
has_many :game_items, :through => :owned_items
end
class OwnedItem < ActiveRecord::Base
belongs_to :user
belongs_to :game_item
# Has 'location' field: 'warehouse' or 'inventory'
end
class GameItem < ActiveRecord::Base
has_many :owned_items
has_many :users, :through => :owned_items
end
This is a common pattern where you have users and some kind of thing which they will own an instance of. The relationship table in the middle, OwnedItem, is used to establish, among other things, any unique characteristics of this particular instance of GameItem as well as the location of it relative to the user.
Generally this sort of structure avoids using polymorphic associations which can be trouble if used too casually. Whenever possible, try and avoid polymorphic associations unless they are on the very edge of your relationships. Putting them in the middle massively complicates joins and makes indexes a lot harder to tune.
As a note about the original, you can roll up a lot of that into a simple scope that uses the hash method for where
:
scope :with_item_type, lambda { |types|
where('game_items.item_type' => types)
}
This will take either an array or string argument and will use IN
or =
accordingly. It's actually quite handy to do it this way because you won't need to remember which one to use.
精彩评论