开发者

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.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜