Rails nested form not saving children
Asked Answered
S

3

7

My User model has_many Responses. I'm trying to create a nested form to create a new user with three child responses. My code looks identical to the Rails cast, but although it will save the user it does not save their responses. Can anyone see what is wrong?

users_controller.rb

class UsersController < ApplicationController
  def new
    @user = User.new
    3.times{@user.responses.build}
    @responses = @user.responses
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user #todo: where do we want to redirect?
    else
      render 'new'
    end
  end

  def show
    @user = User.find(params[:id])
    @responses = @user.responses
  end

  def index
    @users = User.all
  end

  private

    def user_params
      params.require(:user).permit(:username, :email, :responses)
    end
end

user.rb

class User < ActiveRecord::Base
  attr_accessor :responses_attributes
  has_many :responses, :dependent => :destroy
  accepts_nested_attributes_for :responses#, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true

  before_save { self.email = email.downcase }

  validates :username,  length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } ,
            uniqueness: {case_sensitive: false};

  validates :responses, presence: true
end

response.rb

class Response < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user_id, :content
end

users/new.html.erb (the form)

<h1>This is a test form for adding Users with child Responses</h1>

<%= form_for @user do |f| %>
    <%= render 'shared/error_messages' %>

    <%= f.label :username %>
    <%= f.text_field :username %>

    <%= f.label :email %>
    <%= f.text_field :email %>
    <p>
    <%= f.fields_for :responses do |builder| %>
          <%= builder.label :content, "Response" %>
          <%= builder.text_field :content %>
    <% end %>
    </p>
    <%= f.submit "Submit" %>
<% end %>

Edit:

I have change the strong parameters in the Users controller to:

def user_params
  params.require(:user).permit(:username, :email, responses_attributes: [:content])
end

And I have updated the User model to:

class User < ActiveRecord::Base
  has_many :responses, :dependent => :destroy
  accepts_nested_attributes_for :responses#, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true

  before_save { self.email = email.downcase }

  validates :username,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } ,
            uniqueness: {case_sensitive: false};

  validates_associated :responses, presence: true
end

Now it continues to fail my validations for the responses, and also fails my validations for username and email.

Smaragdite answered 25/1, 2014 at 5:9 Comment(2)
@iandotkelly, It is simply failing my validations for response presence; if I take out the validations it saves it properly but without the attached responses.Smaragdite
Rails is definitely a bad parent if it's not saving childrenUnclassified
R
6

The problem is in your Response class:

validates_presence_of :user_id, :content

This validation requires the existence of user_id, which means the User has to be created before Response. To make it work you can try couple of things:

  • Change validates_presence_of :user_id to validates_presence_of :user to make it validate the User object instead of the user's id.

  • Use inverse_of option. See the Rails API doc.

Updated code.

user.rb

class User < ActiveRecord::Base
  attr_accessible :responses_attributes
  has_many :responses, :dependent => :destroy, :inverse_of => :user
  accepts_nested_attributes_for :responses

  # ...
end

response.rb

class Response < ActiveRecord::Base
  belongs_to :user, :inverse_of => :responses
  validates_presence_of :user, :content
end
Ressieressler answered 25/1, 2014 at 16:16 Comment(0)
G
1

Answer for RoR v3:

The problem is the following line in your User model:

attr_accessor :responses_attributes

Replace it with:

attr_accessible :responses_attributes

The answer to question Difference between attr_accessor and attr_accessible should help you understand the difference between the two.

Also your validation validates :responses, presence: true needs to be updated to use validates_associated:

validates_associated :responses, presence: true

Answer for RoR v4:

I hadn't noticed the use of Strong parameters ealier, apologies for this. A couple of changes should fix your issue:

  1. Remove attr_accessor :responses_attributes from User model.
  2. Replace validates :responses, presence: true with validates_associated :responses, presence: true
  3. Define responses attributes in permit list.

Something like follows:

class User < ActiveRecord::Base
  # attr_accessor :responses_attributes # Remove this line
  has_many :responses, :dependent => :destroy
  accepts_nested_attributes_for :responses#, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true

  ...

  validates_associated :responses, presence: true # Update here
end

#Users Controller
class UsersController < ApplicationController
  ...
  private

    def user_params
      params.require(:user).permit(:username, :email, :responses_attributes[:content])
    end
end
Gonad answered 25/1, 2014 at 5:15 Comment(3)
I replaced this line with "attr_accessible :responses_attributes, :responses, :email, :username". I had to add 'protected_attributes' to the Gemfile to get Rails to accept attr_accessible. The responses are still not being saved.Smaragdite
@user3234309, please see the updates. I didn't notice the use of strong parameters earlier. With the Rails4 way above, you can remove the protected_attributes gem.Gonad
after removing the attr_accessible line and the gem now it is failing my validations for the username and email as well. I have added "responses_attributes: [:content]" to the strong parameters. Thank you for your help!Smaragdite
L
0

Your code looks almost correct:

user.rb

class User < ActiveRecord::Base
  has_many :responses, :dependent => :destroy
  accepts_nested_attributes_for :responses

  before_save { self.email = email.downcase }

  validates :username,  length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } ,
            uniqueness: {case_sensitive: false};
end

users_controller.rb

private

def user_params
   params.require(:user).permit(:username, :email, responses_attributes: [:content])
end
Langland answered 25/1, 2014 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.