将模型范围与Rails方式结合起来

时间:2011-05-28 06:09:22

标签: ruby-on-rails

我不小心注意到我的两个模特有一些相似之处。他们的名字是GameItem和OwnedItem。 GameItem只是游戏中的一个项目,而OwnedItem表示玩家是否拥有该项目,如果它在他/她的库存或仓库等等。我的模型现在就像(为了简单起见,我删除了验证和一些不相关的代码):

class OwnedItem < ActiveRecord::Base
    belongs_to :user
    belongs_to :game_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

请注意game_item.item_type的问题。我在own_item模型中引用它,从而打破封装并重复自己。我怎么能真正做到这样的事情:

user.inventory.owned_items.item_type('Weapon').equipped

即没有在我的OwnedItem模型中实际添加重复代码,而是从GameItem模型中获取该信息?

1 个答案:

答案 0 :(得分:2)

我认为你已经以一种会给你带来麻烦的方式定义了这里的关系。您可能会发现最好使用简单的用户来设置项目连接模型,如下所示:

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

这是一种常见的模式,在这种模式中,您拥有用户和某种他们将拥有实例的东西。中间的关系表OwnedItem用于建立此特定GameItem实例的任何独特特征以及它相对于用户的位置。

通常这种结构避免使用多态关联,如果过于随意使用则可能会出现问题。尽可能尝试避免多态关联,除非它们处于关系的最边缘。将它们放在中间会使连接复杂化并使索引更难调整。

作为关于原始内容的注释,您可以将大量内容汇总到使用where哈希方法的简单范围中:

scope :with_item_type, lambda { |types|
  where('game_items.item_type' => types)
}

这将采用数组或字符串参数,并相应地使用IN=。这样做实际上非常方便,因为你不需要记住使用哪一个。