ln9187
ln9187

Reputation: 740

Rails 5 update has_many through associates models

I have following models

class TeamPlayer < ApplicationRecord
  belongs_to :team
  belongs_to :player
  belongs_to :role
end

class Team < ApplicationRecord
  has_many :team_players
  has_many :players, :through => :team_players
end

class Role < ApplicationRecord
  has_many :team_players
  has_many :players, :through => :team_players
end

class Player < ApplicationRecord
  has_many :team_players
  has_many :teams, :through => :team_players

  has_many :roles, :through => :team_players
end

Basically, I want to assign different roles to different players in a team.

id  team_id     player_id   role_id 
2   1           2           1   
3   3           2           1   
4   1           1           2   

What should it look like in my teams_controller.rb to add new player with a role, to update a player with new role and to remove that player from my team?

Upvotes: 2

Views: 352

Answers (1)

eggroll
eggroll

Reputation: 1079

This is only the start of a possible solution and it is pretty similar to what you have with some model and database validations added. Some of these validations ensure the uniqueness of every three-way relationship (FilledTeamRole), so either the error of attempting to create a duplicate record would need to be handled or you could filter the possible ids of each class that could be selected so that a duplicate cannot be created.

A complete solution would depend on what other associations you want between the Team, Player and Role classes other than one that requires all three. For example, do you want/need an association between Team and Player where a relationship exists between only those two classes without the necessity of a Role (TeamPlayer id: 1, team_id: 1, player_id: 1). If those relationships are desired, then additional code will be needed to achieve this, which I have and can provide as a suggestion.

As far as what your controller would look like, you could use the filled_team_roles controller (or perhaps create a dashboard controller), provide instance variables @teams, @players and @roles to populate drop-down menus for each class within a form to create the filled_team_roles relationship. You could also have additional forms within each of the other classes where, using two drop-downs instead of three with the third value the selected model id of the class whose controller the form is in (e.g. the edit action in the players_controller with drop-downs for team and role)

~/app/models/team.rb

class Team < ApplicationRecord
  has_many :filled_team_roles, dependent: :destroy
  validates :name, uniqueness: { scope: [:sport, :city] }
  scope :by_name_asc, -> { order(name: :asc) }
end

~/app/models/player.rb

class Player < ApplicationRecord
  has_many :filled_team_roles, dependent: :destroy
  validates_uniqueness_of :ssn
  scope :by_name_asc, -> { order(last_name: :asc, first_name: :asc) }
end

~/app/models/role.rb

class Role < ApplicationRecord
  has_many :filled_team_roles, dependent: :destroy
  validates_uniqueness_of :name
  scope :by_name_asc, -> { order(name: :asc) }
end

~/app/models/filled_team_role.rb

class FilledTeamRole < ApplicationRecord
  belongs_to :team
  belongs_to :player
  belongs_to :role
  validates :team_id,   presence: true
  validates :player_id, presence: true
  validates :role_id,   presence: true
  validates :team_id, uniqueness: { scope: [:player_id, :role_id] }    
end

~/db/migrate/20170127041000_create_team.rb

class CreateTeam < ActiveRecord::Migration[5.0]
  def change
    create_table :teams do |t|
      t.string :name
      t.string :sport
      t.string :city
      t.string :state
      t.string :country
      t.timestamps null: false
    end
    add_index :teams, [:name, :sport, :city], unique: true
  end
end

~/db/migrate/20170127041100_create_player.rb

class CreatePlayer < ActiveRecord::Migration[5.0]
  def change
    create_table :players do |t|
      t.string :first_name
      t.string :last_name, index: true
      t.string :full_name_surname_first
      t.string :ssn, index: { unique: true }
      t.timestamps null: false
    end
  end
end

~/db/migrate/20170127041200_create_role.rb

class CreateRole < ActiveRecord::Migration[5.0]
  def change
    create_table :roles do |t|
      t.string :name, index: { unique: true }
      t.timestamps null: false
    end
  end
end

~/db/migrate/20170127051300_create_filled_team_role.rb

class CreateFilledTeamRole < ActiveRecord::Migration[5.0]
  def change
    create_table :filled_team_roles do |t|
      t.timestamps null: false
      t.references :team
      t.references :role
      t.references :player
    end
    add_index :filled_team_roles,
             [:team_id, :player_id, :role_id],
               unique: true,
               name: 'index_filled_team_roles_unique_combination_of_foreign_keys'
  end
end

~/db/seeds.rb

Team.create(name: 'Los Angeles Dodgers', sport: 'baseball', city: 'Los Angeles', state: 'CA', country: 'United States')
Team.create(name: 'New York Yankees',    sport: 'baseball', city: 'New York',    state: 'NY', country: 'United States')
Team.create(name: 'Chicago Cubs',        sport: 'baseball', city: 'Chicago',     state: 'IL', country: 'United States')
Team.create(name: 'St. Louis Cardinals', sport: 'baseball', city: 'St. Louis',   state: 'MO', country: 'United States')
Player.create(first_name: 'Max',   last_name: 'Walker', full_name_surname_first: 'Walker, Max', ssn: '123-45-6789')
Player.create(first_name: 'Homer', last_name: 'Winn',   full_name_surname_first: 'Winn, Homer', ssn: '234-56-7890')
Player.create(first_name: 'Will',  last_name: 'Steel',  full_name_surname_first: 'Steel, Will', ssn: '345-67-8901')
Player.create(first_name: 'Lucky', last_name: 'Snag',   full_name_surname_first: 'Snag, Lucky', ssn: '456-78-9012')
Role.create(name: 'pitcher')
Role.create(name: 'catcher')
Role.create(name: 'first baseman')
Role.create(name: 'second baseman')
Role.create(name: 'shortstop')
Role.create(name: 'third baseman')
Role.create(name: 'right fielder')
Role.create(name: 'center fielder')
Role.create(name: 'left fielder')
FilledTeamRole.create(team_id: 1, player_id: 1, role_id: 1)
FilledTeamRole.create(team_id: 2, player_id: 2, role_id: 2)
FilledTeamRole.create(team_id: 3, player_id: 3, role_id: 3)
FilledTeamRole.create(team_id: 4, player_id: 4, role_id: 4)

Upvotes: 2

Related Questions