Csteele5
Csteele5

Reputation: 1292

Can't get accepts_nested_attributes_for to work

I have two models, A Workout and a WorkoutEquipment.

class Workout < ActiveRecord::Base

  belongs_to :exercise
  belongs_to :session
  belongs_to :workout_equipments

  accepts_nested_attributes_for :workout_equipments
end

class WorkoutEquipment < ActiveRecord::Base

  has_many :workout
  has_many :equipment

end

My /new looks like this:

enter image description here

And the form behind that looks like this:

<%= form_for(@workout) do |f| %>
  <% if @workout.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@workout.errors.count, "error") %> prohibited this workout from being saved:</h2>

      <ul>
      <% @workout.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>


  <div class="field">
    <%= f.label :exercise_id %><br>
    <%= f.select :exercise_id, Exercise.all.collect{|t| [t.name, t.id]}, {prompt: true} %>
  </div>
  <div class="field">
    <%= f.label :session_id %><br>
    <%= f.select :session_id, Session.all.collect{|t| [t.when, t.id]}, {prompt: true} %>
  </div>

    <%= fields_for(:workout_equipment) do |u| %>
        <div class="field">
        <%= u.label :equipment_id %><br>
        <%= u.select :equipment_id, Equipment.all.collect{|t| [t.name, t.id]}, {prompt: true} %>
        </div>
        <div class="field">
        <%= u.label :equipment_weight %><br>
        <%= u.number_field :weight %>
        </div>
    <% end %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

However, when I submit, despite the accepts_nested_attributes_for :workout_equipments, the workout_equipments.id is not being placed into the workout object. The output log looks like this:

ActiveRecord::SchemaMigration Load (0.1ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by WorkoutsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"OSh3xA+usL4/0MAtvRqKio2Vcz/ziLdQ1Rj2vh5zLCXxlVhofoCGh88ssEn+IOc2AXhn3sXWsZn3bIO93hyJMA==", "workout"=>{"exercise_id"=>"4", "session_id"=>"3"}, "workout_equipment"=>{"equipment_id"=>"4", "weight"=>"9001"}, "commit"=>"Create Workout"}
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "workout_equipments" ("equipment_id", "weight", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["equipment_id", 4], ["weight", 9001.0], ["created_at", "2016-01-27 02:56:56.611731"], ["updated_at", "2016-01-27 02:56:56.611731"]]
   (6.1ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "workouts" ("exercise_id", "session_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["exercise_id", 4], ["session_id", 3], ["created_at", "2016-01-27 02:56:56.640104"], ["updated_at", "2016-01-27 02:56:56.640104"]]
   (7.4ms)  commit transaction
   (0.0ms)  begin transaction
   (0.0ms)  commit transaction

As you can see, only exercise_id and session_id are being recorded. My relevant controller actions are the following:

def create
    @workout_equipment = WorkoutEquipment.create(workout_equipment_params)
    @workout = Workout.create(workout_params)

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

def workout_equipment_params
      params.require(:workout_equipment).permit(:workout_id, :equipment_id, :weight)
end

def workout_params
      params.require(:workout).permit(:exercise_id, :session_id, :workout_equipments_id)
end

Is there something else I have to do for the params methods?

Upvotes: 1

Views: 173

Answers (2)

Richard Peck
Richard Peck

Reputation: 76774

You need to change your association names (you're using plural when it should be singular):

#app/models/workout.rb
class Workout < ActiveRecord::Base
   belongs_to :workout
   belongs_to :session
   belongs_to :workout_equipment
   accepts_nested_attributes_for :workout_equipment
end

#app/models/workout_equipment.rb
class WorkoutEquipment < ActiveRecord::Base
  has_many :workouts
end

#app/controllers/workouts_controller.rb
class WorkoutsController < ApplicationController
   def new
     @equipment = Equipment.all
     @workout = Workout.new
     @workout.build_workout_exercise
   end

   def create
     @workout = Workout.new workout_params
     @workout.save
   end

   private

   def workout_params
     params.require(:workout).permit(workout_exercise_attributes: [:equipment_id, :weight])
   end
end

#app/views/workouts/new.html.erb
<%= form_for @workout do |f| %>
  <%= f.fields_for :workout_exericse do |w| %>
     <%= w.collection_select :equipment_id, @equipment, :id, :name %>
     <%= w.number_field :weight %>
  <% end %>
  <%= f.submit %>
<% end %>

Upvotes: 1

Harish Ramachandran
Harish Ramachandran

Reputation: 293

In your model, the belongs_to & accepts_nested_attributes_for methods should take :workout_equipment (note it's in the singular, not plural, form)

    class Workout < ActiveRecord::Base

      belongs_to :exercise
      belongs_to :session
      belongs_to :workout_equipment

      accepts_nested_attributes_for :workout_equipment
    end

In your controller, you don't need to build a workout_equipment object because nested_attributes does that for you:

    def create
        @workout = Workout.create(workout_params)

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

By the same token, you don't need workout_equipment_params and should enrich your workout_params method:

    def workout_params
          params.require(:workout).permit(:exercise_id, :session_id, workout_equipment_attributes: [:id, :workout_id, :equipment_id, :weight])
    end

I may have misunderstood the attributes you have in each of your models so modify the params accordingly. In general, you'd be sending the nested_attributes into a workout_equipment_attributes hash.

Good luck!

Upvotes: 2

Related Questions