Search code examples
mysqlruby-on-railsjsonnested-attributesruby-on-rails-5.1

Nested Attributes With JSON Rails One to Many


I'm figuring a problem with a nested_attribute.

team.rb:

class Team < ApplicationRecord
  has_many :players, dependent: :destroy
  accepts_nested_attributes_for :players, allow_destroy: true
end

console output:

Processing by TeamsController#create as JSON
  Parameters: {"team"=>{"id"=>nil, "name"=>"testes",
  "players_attributes"=>[{"id"=>nil, "name"=>"dadada", "_destroy"=>false, "team_id"=>nil}]}}
Unpermitted parameter: id

So, i'm ignoring team_id in controller for create and sending it as null same to player_id. What rails is getting in controller after permit is:

team: {name:'testes team', players_attributes: [{ name: 'testes'}]}

In my opinion (prob my mistake) rails should feed this relation in exactly this way. I tested it removing the nested attribute id and team_id but doesn't works.

Rails return:

bodyText: "{"players.team":["must exist"]}

controller:

def create
  @team = Team.create(team_params)

  @team.players.each do |player|
    player.team_id = 1
  end

  respond_to do |format|
    if @team.save
      format.html { redirect_to @team, notice: 'Team was successfully created.' }
      format.json { render :show,  status: :created, location: @team }
    else
      format.html { render :new }
      format.json { render json: @team.errors, status: :unprocessable_entity }
    end
  end
end

def team_params
  params.require(:team).permit(:name, players_attributes: [:name, :positions, :_destroy])
end

gambiarra:

@team.players.each do |player|
  player.team_id = 1
end

If i do this to the nested attribute BEFORE save team it works, team 1 must exists for it to work. If i save only the team and after create the relation it DOESN'T works either, only if I set the "gambiarra" solution.

How to solve this relation? As mentioned, my controller is filtering for only attributes for nested data. If i submit with HTML, works fine, if i use a JSON as nested objects, it doesn't work unless i force the relation to find a team_id for my player before save and so on, rails will save and commit the right player as is expected to do without a team_id in my player.


Solution

  • The structure of the params you are sending is incorrect, rails expects something like this in order to work with nested attributes:

    {
      "computer": {
        "speakers_attributes": {
          "0": {
            "power": "1"
          }
        }
      }
    }
    

    Notice three things:

    1. computer: null was removed; you don't need to specify computer attribute since its value will be set with the id of the new computer to be created.

    2. "0": was added; because of the has_many :speakers associations, you can create more than one Speaker (you will use 1: { ... }, 2: { ... }, and so on).

    3. speaker: was changed to speakers_attributes; that's the way rails recognizes nested attributes values.

    Now that the parameters had been set correctly, you need to do two more things:

    1. Confirm that your associations are set correctly

      class Computer < ApplicationRecord
        has_many :speakers, dependent: :destroy
        accepts_nested_attributes_for :speakers, allow_destroy: true
      end
      
      class Speaker < ApplicationRecord
        belongs_to :computer
      end
      
    2. Properly setup your controller

      class ComputersController < ApplicationController
        def new
          @computer = Computer.new
          @computer.speakers.build
        end
      
        def create
          @computer = Computer.create(computer_params)
      
          if @computer.save
            # handle success
          else
            # handle error
          end
        end
      
        # other actions
      
        private
        def computer_params
          params.require(:computer).permit(speakers_attributes: [:power])
        end
      end
      

    Here @computer.speakers.build is neessary only if you will be creating nested forms using form helpers.