0

EDIT 3: Just to clarify, the goal and problem is to create 2 new records from the same form of which one is the parent and one is the child. The child needs the parent ID, but the parent is created from the same form that the child is.

EDIT 2: I think I'm getting closer. See log file at end, the deal is successfully saved and it looks like the client entry is starting to commit but then not saving. Code is updated below for changes.

I followed the Railscast #196 for nested forms and I am successfully able to edit, add and delete from nested forms as long as the record is already created. Now I am trying to use nested forms to create new records. I think I'm 99% of the way there, but I'm missing something I can't see anymore. I just need to figure out how to pass the id of the parent to the child.

In addition to the Railscast I used this answer to set inverse_of relationships and call the save order (using create instead of save though). I'm pretty sure the problem is in the form or the controller (but models are listed below too)

Nested Form (I tried to simplify to make it easier to read)

EDIT 2: remove hidden field

<%= form_for(@deal) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

  <div class="deal-<%= @deal.id %>" >
    <div class="form-group">
      <%= f.label :headline %>
      <%= f.text_field :headline, required: true, placeholder: "Headline" %>
    </div>

    <div class="form-group" id="clients">
      <%= f.fields_for :clients do |client_form| %>
        <div class="field">
          <%= client_form.label :client %><br />
          <%= client_form.text_field :name, placeholder: "Client name" %>
        </div>
      <% end %>
      <%= link_to_add_fields "Add client", f, :clients %>
    </div>  

    <div class="form-group">
      <%= f.label :matter %>
      <%= f.text_field :matter, placeholder: "Matter", rows: "4" %>
    </div>

    <div class="form-group">
      <%= f.label :summary %>
      <%= f.text_area :summary, placeholder: "Deal summary", rows: "4" %>
    </div>

    <div class="form-group">
      <div class="action-area">
        <%= f.submit "Add deal" %>
      </div>
    </div>

  </div>
<% end %>

Controller

EDIT 2: include deal_id param & change save calls

class DealsController < ApplicationController
  before_action :require_login

  def new
    @deal = Deal.new
    @client = @deal.clients
  end

  def create
    @deal = current_user.deals.create(deal_params)
    if @deal.save
      flash[:success] = "Your deal was created!"
      redirect_to root_url
    else
      render 'deals/new'
    end
  end

  private

  def deal_params
    params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
  end
end

EDIT 2: No longer yields errors in browser and success flash message is triggered

EDIT 2: Here is the console output on submit (the record is saved and can be viewed it just doesn't have a client)

Started POST "/deals" for ::1 at 2017-04-26 00:13:08 +0200
Processing by DealsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"hbvpS6KsZOorR3u4LgNoG5WHgerok6j3yYzO+dFUHs9thsxRi+rbUkm88nb7A5WvlmWZEcvaDvCKywufP3340w==", "deal"=>{"headline"=>"headline", "client"=>{"name"=>"aaa"}, "matter"=>"", "summary"=>""}, "commit"=>"Add deal"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Unpermitted parameter: client
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "deals" ("headline", "matter", "summary", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["headline", "headline"], ["matter", ""], ["summary", ""], ["user_id", 1], ["created_at", 2017-04-25 22:13:08 UTC], ["updated_at", 2017-04-25 22:13:08 UTC]]
   (3.8ms)  commit transaction
   (0.1ms)  begin transaction
   (0.1ms)  commit transaction
Redirected to http://localhost:3000/
Completed 302 Found in 16ms (ActiveRecord: 4.6ms)
Models for reference

Users

class User < ApplicationRecord
  has_many :deals
end

Deals

class Deal < ApplicationRecord
  belongs_to :user
  has_many :clients, inverse_of: :deal
  validates :headline, presence: true
  accepts_nested_attributes_for :clients, allow_destroy: true
end

Clients

class Client < ApplicationRecord
  belongs_to :deal, inverse_of: :clients
  validates :name, presence: true
  validates :deal_id, presence: true
end
Community
  • 1
  • 1

2 Answers2

0

You are missing the deal_id on the params

  def deal_params
    params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
  end

and on the create of deal, you can make something like this

 def create
    @deal = Deal.new(deal_params)
    if @deal.save
    ...

and just add a hidden field on the form for the user_id parameter.

xploshioOn
  • 4,035
  • 2
  • 28
  • 37
  • If I add `:deal_id` to params and a `hidden_field` to the form it does show up in the hash, but without a value `"clients_attributes"=>{"0"=>{"name"=>"aaaa", "deal_id"=>""}}`. That's if I leave the create method as `@deal = current_user.deals.create(deal_params)` If I change to to `Deal.new(deal_params)` I get an additional error that the user doesn't exist on submit. – oneWorkingHeadphone Apr 25 '17 at 12:05
  • I changed the code in the question to reflect the changes I tried – oneWorkingHeadphone Apr 25 '17 at 12:18
  • As happens to me often, on reading this again I see that your comment would add the user_id to the HTML, but this is already successfully built from the association. It's the Deal ID (which is created from the same form) that is required. I'll edit again and see if I can make that clear – oneWorkingHeadphone Apr 26 '17 at 10:12
  • Yes, I know that this part was working, that was just an advise, for a more generic way, if you later want to create a deal from another user that isn't current user, you don't need another action, that create would be generic. In tve changes ir tve controller in your edit, you changed new, you need to change create. New could be as it were before. And change the create to the way I sent, just to try, everything seems to be correct, the only weird thing that I see is that client param is singular and it needs to be plural, that's way isn't saved, it's an unpermited parameter. Try with that – xploshioOn Apr 26 '17 at 13:40
0

The problem is the validations in the client model. Removing the validations will allow the records to be correctly saved from the nested form.

  1. In the Client model remove the name and deal_id validators

    class Client < ApplicationRecord
      belongs_to :deal, inverse_of: :clients
    end
    
  2. In the controller add build to the new action so it's nicer for the users:

  3. def new
      @deal = Deal.new
      @client = @deal.clients.build
    end
    

.build is not strictly necessary since the helper method that was created from the Rails Cast will create new client entries on demand, but with it the user is presented with the placeholder first entry which is ignored if blank.

  1. To keep empty client records from saving I added a reject_if: proc to the deal model

    class Deal < ApplicationRecord
      belongs_to :user
      has_many :clients, inverse_of: :deal
      validates :headline, presence: true
      accepts_nested_attributes_for :clients, allow_destroy: true, reject_if: proc { |attributes| attributes['name'].blank? }
    end
    

I'll leave this open/unanswered in case someone can explain better why my solution worked and if there's a better way to do it.