DavidM
DavidM

Reputation: 173

Params issue with nested attributes

I'm having trouble with what I feel should be a simple issue. I'm trying to create a basic match score reporting app and I'm trying to create a match and the match players simultaneously.

My models:

class Match < ApplicationRecord
  has_many :match_players

  accepts_nested_attributes_for :match_players
end

class MatchPlayer < ApplicationRecord
  belongs_to :player
  belongs_to :match
end

My form:

.container
  %h1 Log a completed match

  = simple_form_for @match do |f|
    = f.input :played_at, html5: true
    = f.simple_fields_for :match_player do |mp|
      = mp.input :player_id, collection: Player.all, label_method: lambda { |player| player.first_name + " " + player.last_name }
    = f.submit "Submit Match Score"

My controller action and params:

def create
    @match = Match.new(match_params)

    if @match.save
      player = MatchPlayer.create(player: current_player, match: @match)
      opponent = MatchPlayer.create(player: Player.find(match_params[:match_player_attributes][:player_id], match: @match))
    else
      render :new
    end
  end

  private

  def match_params
    params.require(:match).permit(:played_at, match_player_attributes: [:player_id])
  end

Right now I'm getting a found unpermitted parameter: :match_player issue. If I change match_player_attributes to match_player in my params then I get unknown attribute 'match_player' for Match. The error is occurring on the first line of the create action (@match = Match.new(match_params))

Any help would be appreciated!


Edit following suggestions:

Controller:

def new
    @match = Match.new
    @match.match_players.build
  end

  def create
    @match = Match.new(match_params)

    if @match.save!
      player = MatchPlayer.create(player: current_player, match: @match)
      opponent = Player.find(match_params[:match_players_attributes]["0"][:player_id])
      opponent_match_player = MatchPlayer.create(player: opponent, match: @match)

      redirect_to(root_path, notice: "Success!")
    else
      render :new
    end
  end

 private

  def match_params
    params.require(:match).permit(:played_at, match_players_attributes: [:player_id])
  end

Form:

= simple_form_for @match do |f|
    = f.input :played_at, html5: true
    = f.simple_fields_for :match_players do |mp|
      = mp.input :player_id, collection: Player.all, label_method: lambda { |player| player.first_name + " " + player.last_name }
    = f.submit "Submit Match Score"

Now it's creating 3 match_players, one for the current_player and 2 of the opponent. What's going on?

Upvotes: 0

Views: 72

Answers (2)

Oleksandr Holubenko
Oleksandr Holubenko

Reputation: 4440

Looks like the problem in the simple typo,

Try to change:

= f.simple_fields_for :match_player do |mp|

to

= f.simple_fields_for :match_players do |mp|

Also

def match_params
    params.require(:match).permit(:played_at, match_player_attributes: [:player_id])
  end

to

def match_params
    params.require(:match).permit(:played_at, match_players_attributes: [:player_id])
  end

Here is wiki with examples

UPD: From wiki I shared with, you can find this point:

accepts_nested_attributes_for - an ActiveRecord class method that goes in your model code - it lets you create and update child objects through the associated parent. An in depth explanation is available in the ActiveRecord documentation.

It means that you don't need to create opponent manually

opponent = Player.find(match_params[:match_players_attributes]["0"][:player_id])
opponent_match_player = MatchPlayer.create(player: opponent, match: @match)

Because when you send your params with nested attributes: match_players_attributes: [:player_id] inside of match creation process match_player will be created automatically

Upvotes: 1

Sohail Aslam
Sohail Aslam

Reputation: 727

I can see in your model, you have:

has_many :match_players

hence, in your controller and in your form, you must use match_players (plural not singular)

Thus, in your controller, you will have:

def match_params
    params.require(:match).permit(:played_at, match_players_attributes: [:id, :player_id])
end

And, in your form:

...    
= f.simple_fields_for :match_players do |mp|
...

Notice the last s of match_player in form and in controller.

Upvotes: 1

Related Questions