ryanb / nested_form

Rails plugin to conveniently handle multiple models in a single form.
MIT License
1.79k stars 505 forks source link

On create action, child models do not receive parent id #306

Closed stopachka closed 10 years ago

stopachka commented 10 years ago

Hi guys, I'm working on implementing nested_forms. I think I'm getting close, but when I try run the create action, I get an error

* Bullets job can't be blank
* Roles job can't be blank

It seems to me like the info for job_id(the parent of the has_many relationship) is not passing through the nested forms.

What do you guys think I'm doing off?

Here's the basic structure ->

When creating a new 'Job', a user will have the option to write in multiple bullet points about it, and multiple 'Roles'

Here's how the models are set up :

Job Model

# == Schema Information
#
# Table name: jobs
#
#  id             :integer          not null, primary key
#  job_title      :string(255)
#  job_summary    :string(255)
#  qualifications :string(255)
#  created_at     :datetime
#  updated_at     :datetime
#

class Job < ActiveRecord::Base
    validates :job_title, presence: true 
    validates :job_summary, presence: true
    validates :qualifications, presence: true 

    has_many :bullets, dependent: :destroy 
    has_many :roles, dependent: :destroy

    accepts_nested_attributes_for :bullets, :reject_if => lambda { |a| a[:bullet].blank? }, :allow_destroy => true
    accepts_nested_attributes_for :roles, :reject_if => lambda { |a| a[:role_title].blank? }, :allow_destroy => true
        #lambda { |a| a[:bullet].blank? } makes sure that on edit, if a value is left blank, then it doesn't save it

end

Bullet Model

# == Schema Information
#
# Table name: bullets
#
#  id         :integer          not null, primary key
#  job_id     :integer
#  bullet     :string(255)
#  created_at :datetime
#  updated_at :datetime
#

class Bullet < ActiveRecord::Base
    belongs_to :job
    validates :job_id, presence: true
    validates :bullet, presence: true
end

Role Model

# == Schema Information
#
# Table name: roles
#
#  id         :integer          not null, primary key
#  job_id     :integer
#  role_title :string(255)
#  role_desc  :string(255)
#  created_at :datetime
#  updated_at :datetime
#

class Role < ActiveRecord::Base
    belongs_to :job

    validates :job_id, presence: true
    validates :role_title, presence: true
    validates :role_desc,  presence: true
end

Jobs Controller :

class JobsController < ApplicationController
  before_action :signed_in_user, only: [:new, :create, :update, :show, :edit, :destroy] # index, at least partially is available to all viewers
  before_action :admin_user, only: [:new, :edit, :update, :create, :destroy] # only admins can make jobs

  def new 
    @job = Job.new
    3.times {
        @job.bullets.build
        @job.roles.build
    }
  end

  def create
     @job = Job.new(job_params)
     if @job.save
       redirect_to root_path, :flash => { :success => "Job created!" }
     else
       render 'new'
     end
  end

  def job_params 
    params.require(:job).permit(:job_title, :job_summary, :qualifications,
                                 bullets_attributes: [:id, :bullet, :_destroy],
                                 roles_attributes: [:id, :role_title,:role_desc, :_destroy])
  end

end

Jobs/New View

<% provide(:title, "Publish a new job") %>
<%= nested_form_for @job do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

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

  <div style="margin-left:30px">
      <%= f.fields_for :bullets do |bullet_form| %>
        <%= bullet_form.label :bullet %>
        <%= bullet_form.text_field :bullet %>
        <%= bullet_form.link_to_remove "Remove this bullet" %>
      <% end %>
      <p><%= f.link_to_add "Add a Bullet", :bullets %></p>
  </div>

  <%= f.label :job_summary %>
  <%= f.text_area :job_summary %>

  <%= f.label :qualifications %>
  <%= f.text_area :qualifications %>

  <div style="margin-left:30px">
      <%= f.fields_for :roles do |role_form| %>
        <%= role_form.label :role_title %>
        <%= role_form.text_field :role_title %>
        <%= role_form.label :role_desc %>
        <%= role_form.text_field :role_desc %>
        <%= role_form.link_to_remove "Remove this Role" %>
      <% end %>
      <p><%= f.link_to_add "Add a Role", :roles %></p>
  </div>

  <%= f.submit "Publish the Job", class: "button" %>
<% end %>

Logs

Here are some logs to make it a bit clearer:

1) When I submit the form, choosing only 1 bullet I get the following parameters in development:

 --- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=
job: !ruby/hash:ActionController::Parameters
  job_title: Job Title example
  bullets_attributes: !ruby/hash:ActionController::Parameters
    '0': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
      bullet: Example bullet
      _destroy: 'false'
  job_summary: Job Summary Example
  qualifications: Example Qualifications here
  roles_attributes: !ruby/hash:ActionController::Parameters
    '1387448871560': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
      role_title: Example Role Name
      role_desc: Example Role Description
      _destroy: 'false'
commit: Publish the Job
action: create
controller: jobs

On my Rails Server, this is what it's saying ->

Started POST "/jobs" for 127.0.0.1 at 2013-12-19 14:28:01 +0400
Processing by JobsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=", "job"=>{"job_title"=>"Job Title example", "bullets_attributes"=>{"0"=>{"bullet"=>"Example bullet", "_destroy"=>"false"}}, "job_summary"=>"Job Summary Example", "qualifications"=>"Example Qualifications here", "roles_attributes"=>{"1387448871560"=>{"role_title"=>"Example Role Name", "role_desc"=>"Example Role Description", "_destroy"=>"false"}}}, "commit"=>"Publish the Job"}
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."remember_token" = '449addc616f3035c031b3220404a4d5eb5351c74' LIMIT 1
   (0.2ms)  begin transaction
   (0.2ms)  rollback transaction
  Rendered shared/_error_messages.html.erb (2.4ms)
  Rendered jobs/new.html.erb within layouts/application (15.7ms)
  Rendered layouts/_shim.html.erb (0.1ms)
  Rendered layouts/_header.html.erb (1.3ms)
Completed 200 OK in 101ms (Views: 32.0ms | ActiveRecord: 0.7ms | Solr: 0.0ms)
stopachka commented 10 years ago

The problem was in the child models, with the validates for job_id

validates :job_id, presence: true

When I removed that validation, the form functioned correctly, and the job_id was added to the nested models.

My hunch is that maybe the validates occurs before rails creates a job_id, hence halting the whole proccess altogether.

lest commented 10 years ago

This is the way accepts_nested_attributes_for works in Rails. nested_form gem only wraps this with form helpers and some javascript.

Maybe setting inverse_of in your associations will help.