2016-09-14 4 views
0

У меня есть модель Player, кто has_many :positions. Подумайте, как игрок играет в спорт, где они могут играть на разных позициях на поле (т. Е. Вперёд, левое крыло, защитная полузащита).Как структурировать отношения между моей моделью позиции и моей моделью Игрока?

Я не могу понять, как смоделировать это, с точки зрения БД.

Изначально мой 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 подходит? Некоторое время я не использовал такие отношения.

ответ

1

Вы можете использовать Rolify камень, который помогает в управлении несколькими ролями для той же модели сказать Player. Определите роли и свяжите их с игроком. См. Официальную документацию.

В случае, если есть некоторые ограничения, такие как определенный игрок, могут играть только в 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

Вы можете использовать многие ко многим присоединиться к таблице:

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, если вы не укажете имя класса.

1

Вы уже получили две большие ответы, но я добавлю альтернативу. Следующий подход будет работать нормально, если вам не нужен код в вашей модели Position. В этом случае вам не нужна модель.

В вашей базе данных есть возможность добавить дополнительный столбец positions, чтобы сохранить позиции каждого игрока. В зависимости от используемого сервера базы данных вы можете использовать собственный тип столбца массива (и индекс), а также писать специализированные запросы. PostgreSQL, например, поддерживает array columns и имеет специальный array functions, который позволяет вам находить нужные вам строки. Я не уверен, поддерживают ли другие серверы баз данных эти функции.

Рельсы поддерживают array columns при использовании адаптера базы данных pg. Вот как вы создаете запись игрока:

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')