Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails nested form not saving children

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.

like image 820
user3234309 Avatar asked Jan 25 '14 05:01

user3234309


2 Answers

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
like image 170
Blue Smith Avatar answered Nov 03 '22 18:11

Blue Smith


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
like image 43
vee Avatar answered Nov 03 '22 18:11

vee