我有一个Player
模型,has_many :positions
。把球员想象成一个体育运动的人,他们可以在球场上打多个位置(即前锋,左翼,防守型中场)。
从DB的角度来看,我无法弄清楚如何对此进行建模。
最初我的Position.rb
看起来像这样:
# == Schema Information
#
# Table name: positions
#
# id :integer not null, primary key
# position_type :integer
# player_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Position < ActiveRecord::Base
belongs_to :player
enum position_type: { goalkeeper: 0, center_back: 1, left_back: 2, right_back: 3, left_wing_back: 4, right_wing_back: 5, defending_midfielder: 6, central_midfielder: 7, attacking_midfielder: 8, left_midfield: 9, right_midfield: 10, left_wing: 11, right_wing: 12, center_forward: 13 }
end
但这感觉不对。
我应该做一些像常规Position
模型那样的事情,然后只为每个职位创建唯一的记录,然后在两者之间建立HABTM关系吗?
这是HABTM合适的一种情况吗?我有一段时间没有使用这种关系。
答案 0 :(得分:1)
您可以使用Rolify gem ,这有助于管理多个角色,因为相同型号的人称为播放器。定义角色并与玩家相关联。参考官方文档。
如果有一些限制,比如特定玩家只能在2或3个位置玩。 您可以使用Cancan来处理授权。
如果您不想使用宝石,请继续为位置和玩家关联创建新模型。类似于下面的代码,可以在同一个地方进行进一步的优化。
class Position < ActiveRecord::Base
#Define a table with the values present in enum which should be mostly one time operation.
end
class Player < ActiveRecord::Base
has_many :player_positions, :dependent => :destroy
has_many :positions, :through => :player_positions
end
class PlayerPosition < ActiveRecord::Base
belongs_to :player
belongs_to :position
#this table will have player_id and position_id as attributes
#Also have a active_position_id since one player can have only one position at a certain time when the game is played.
end
您将结束让一名玩家在玩家位置表中有多个与位置相关的条目。为了管理玩家的实时位置,有一个active_position_id,因为一个玩家在玩游戏的某个时间只能有一个位置。
答案 1 :(得分:1)
您可以使用多对多联接表:
class Player < ActiveRecord::Base
has_many :player_postitions
has_many :postitions, through: :player_postitions
end
class PlayerPostition < ActiveRecord::Base
belongs_to :player
belongs_to :position
end
class Position < ActiveRecord::Base
enum position_type: { goalkeeper: 0, center_back: 1, left_back: 2, right_back: 3, left_wing_back: 4, right_wing_back: 5, defending_midfielder: 6, central_midfielder: 7, attacking_midfielder: 8, left_midfield: 9, right_midfield: 10, left_wing: 11, right_wing: 12, center_forward: 13 }
has_many :player_postitions
has_many :players, through: :player_postitions
end
我们将has_many
与through
选项一起使用,而不是has_and_belongs_to_many
。
has_many
和has_and_belongs_to_many
之间的核心差异在于,后者是直接的,没有介入模型。这是非常有限的,因为您无法直接查询连接表或将元数据附加到关系。例如,您无法创建三向连接:
class Player < ActiveRecord::Base
has_many :player_postitions
has_many :postitions, through: :player_postitions
has_many :games, through: :player_postitions
end
class PlayerPostition < ActiveRecord::Base
belongs_to :player
belongs_to :position
belongs_to :game
end
class Game < ActiveRecord::Base
has_many :player_postitions
has_many :players, through: :player_postitions
has_many :positions, through: :player_postitions
end
另一个重要区别是连接表的命名。对于HABTM,您可以为players_postions
命名联接表player_postions
和has_many through:
,因为ActiveRecord基于关系名称进行常量查找。除非您指定类名,否则has_many :players_postitions
会导致AR查找Players::Postitions
。
答案 2 :(得分:1)
你已经有了两个很好的答案,但我会添加一个替代方案。如果您不需要Position
模型中的任何代码,则以下方法可以正常工作。在这种情况下,你根本不需要这个模型。
您可以在数据库中添加额外的positions
列来存储每个玩家的位置。根据您使用的数据库服务器,您可以使用本机数组列类型(和索引),还可以编写专门的查询。例如,PostgreSQL支持array columns并具有特殊的array functions,允许您查找所需的行。我不确定其他数据库服务器是否支持这些功能。
Rails支持array columns。以下是创建玩家记录的方法:
Player.create(name: 'John Doe', positions: %w(center_back left_back))
以下是您如何找到可以成为中后卫(以及任何其他位置)的球员:
Player.where("'center_back' = ANY(positions)")
如果您想进一步了解,还可以向Player
模型添加范围,以隐藏所需的SQL函数:
class Player < ActiveRecord::Base
scope :for_position, -> (position) { where("'#{position}' = ANY(positions)") }
end
Player.for_position('center_back')