简体   繁体   中英

In Rails 5.1, how do I use one form to update two tables?

I have been trying to get one form to update two tables in Ruby on Rails. I have done a fairly exhaustive search on here and I'm finding the guides a little confusing. The closest answer I have found is this:

Ruby on Rails Saving in two tables from one form

I have mostly copied this answer but I still can't get it to work. Here are the relevant bits of code:

Models - I have two models, supplier and account. Each supplier should have one account.

class Supplier < ApplicationRecord
  has_one :account
  accepts_nested_attributes_for :account
end

class Account < ApplicationRecord
  belongs_to :supplier
end

Form - not sure if this is entirely correct

<h2>Please enter a supplier</h2>
  <%= form_for(@supplier) do |form| %>

<p>
  <%= form.label :name %><br>
  <%= form.text_field :name %>
</p>

<p>
 <%= form.fields_for :account do |f| %>
 <%= f.label :account_number %><br>
 <%= f.text_field :account_number %>
</p>

<p>
 <%= form.submit %>
</p>
 <% end %>
<% end %>

And finally, the SuppliersController

class SuppliersController < ApplicationController

  def index
    @suppliers = Supplier.all
  end

  def new
    @supplier = Supplier.new
    @supplier = build_account
  end

  def create
    @supplier = suppliers.build(supplier_params)

    if @supplier.save
      redirect_to suppliers_path
    else
      redirect_to root_path
    end
  end

  private

  def supplier_params
    params.require(:supplier).permit(:name, account_attributes: 
                                             [:account_number])
  end

end

I am getting an undefined method error on the second line of the new action of the SuppliersController and I don't know why.

Debugging printout for create action in SupplierController :

Started POST "/suppliers" for 127.0.0.1 at 2017-11-03 08:25:25 -0600 Processing by SuppliersController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"KEqvPgjrYmuBux3qWCQJLAkSLQ4z1ns4HsK2P9sWlhVegpjhik/JoWT3vlL/kP42XpU8adllOaqSA16izYJ0SA==", "supplier"=>{"name"=>"", "account_attributes"=>{"account_number"=>""}}, "commit"=>"Create Supplier"}

(0.1ms) begin transaction

SQL (0.5ms) INSERT INTO "suppliers" ("created_at", "updated_at", "name") VALUES (?, ?, ?) [["created_at", "2017-11-03 14:25:25.388933"], ["updated_at", "2017-11-03 14:25:25.388933"], ["name", "Bob"]]

(0.6ms) commit transaction (0.0ms) begin transaction

Supplier Load (1.0ms) SELECT "suppliers".* FROM "suppliers" WHERE "suppliers"."id" = ? LIMIT ? [["id", 6], ["LIMIT", 1]] SQL (0.3ms) INSERT INTO "accounts" ("created_at", "updated_at", "account_number", "supplier_id") VALUES (?, ?, ?, ?) [["created_at", "2017-11-03 14:25:25.424367"], ["updated_at", "2017-11-03 14:25:25.424367"], ["account_number", "456456456"], ["supplier_id", 6]]

(0.6ms) commit transaction

Supplier: #Supplier id: 6, created_at: "2017-11-03 14:25:25", updated_at: "2017-11-03 14:25:25", name: "Bob"

Account: #Account id: 1, created_at: "2017-11-03 14:25:25", updated_at: "2017-11-03 14:25:25", account_number: "456456456", supplier_id: 6

(0.0ms) begin transaction (0.0ms) commit transaction

Redirected to http://localhost:3000/suppliers Completed 302 Found in 51ms (ActiveRecord: 4.6ms)

Your issue is that build_account is a method on a supplier object. As you have your code, it is currently trying to run build_account as if it was a method on the SuppliersController

So, you should try updating your controller method for new to:

def new
  @supplier = Supplier.new
  @account = @supplier.build_account
end

This calls the build_account against the supplier object you have just instantiated, returning the result as an instantiated account object, associated with the supplier instance it belongs to.

The build_account method is generated through the association you have defined in your supplier model, where you say has_one :account . The docs here http://guides.rubyonrails.org/association_basics.html#has-one-association-reference describe all the methods that are created in this way.

EDIT

Following up on the question about the create action, the controller should be updated. In the comments we have debugged the models and they appear to work as we would hope, so once updated the controller should work as:

def create
  # Created a Supplier, but not yet persisted.
  @supplier = Supplier.new(supplier_params)

  # Persists the supplier, giving him an id, which can then be used
  # through implicit autosave functionality to take the nested params
  # to build the associated account. All of these operations are wrapped
  # in a single DB transaction, ensuring nothing is left dangling if
  # there is a validation failure
  if @supplier.save
    redirect_to suppliers_path
  else
    redirect_to root_path
  end
end

Based on the association autosave functionality, new associated records (account) should be automatically created based on the nested parameters being passed from the form, at the point the supplier is saved, without having to add any additional code to explicitly handle this. More autosave options are documented here: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

from the guides, this section

4.1.1 Methods Added by belongs_to

each instance of the Book model will have these methods:

So as my friend points below, I assume

@supplier = Supplier.build_account // wrong
@account = @supplier.build_account 

would be correct

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM