Domanda

I'm using Rails 3 and have a form that incorporates fields from multiple associated records using fields_for. My models w/relationships are as follows:

class Company < ActiveRecord::Base
has_many: locations, dependent: :destroy
has_many :addresses, through: :locations
has_many :contacts

accepts_nested_attributes_for :locations, :addresses, :contacts
end

class Address < ActiveRecord::Base
has_many :locations
has_many :companies, through: :locations

accepts_nested_attributes_for :locations, :companies
end


class Contact < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company
end

class Location < ActiveRecord::Base
belongs_to :company
belongs_to :address
accepts_nested_attributes_for :company, :address
end

My controller currently looks like this:

class CompaniesController < ApplicationController
def new
@company = Company.new
@location = @company.locations.build
@address = @company.addresses.build
@contact = @company.contacts.build
end

def create
@company = Company.new(params[:company])
if @company.save
  #handle a successful save
  flash[:success] = "Company Created Successfully"
  redirect_to @company
    else 
      render 'new'
    end
  end
end

When the form is submitted I get this error: Can't mass-assign protected attributes: addresses_attributes, locations_attributes, contacts_attributes

I've tried changing the create method in the controller to the following:

def create
@company = Company.new(params[:company_name])
@company.addresses.build(params[:address]) 
@company.locations.build(params[:location])
@company.contacts.build(params[:contact]) 

if @company.save
  #handle a successful save
  flash[:success] = "Company Created Successfully"
      redirect_to @company
    else 
      render 'new'
    end
  end

The result with this create method is a server log that says:

> Processing by CompaniesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"SVDIk5IzY7foo9DULhzY+RWgh/HAA9NqRp6FafWwFDg=", "company"=>{"company_name"=>"New Co", "
addresses_attributes"=>{"0"=>{"address_line_1"=>"231 Main", "address_line_2"=>"", "address_line_3"=>"", "city"=>"Dallas", "state"=>"AL", 
"country"=>"USA", "zipcode"=>"74343"}}, "locations_attributes"=>{"0"=>{"location_type"=>"11", "location_name"=>"DFW"}}, "contacts_attribu
tes"=>{"0"=>{"first_name"=>"Joe", "last_name"=>"User", "title"=>"CEO"}}}, "commit"=>"Save Info"}
  SQL (24.9ms)  BEGIN TRANSACTION
  Address Exists (27.6ms)  EXEC sp_executesql N'SELECT TOP (1) 1 AS one FROM [addresses] WHERE ([addresses].[address_line_1] IS NULL AND 
[addresses].[address_line_2] IS NULL AND [addresses].[address_line_3] IS NULL AND [addresses].[address_line_4] IS NULL AND [addresses].[a
ddress_line_5] IS NULL AND [addresses].[city] IS NULL AND [addresses].[state] IS NULL AND [addresses].[county] IS NULL AND [addresses].[c
ountry] IS NULL AND [addresses].[zipcode] IS NULL)'
  SQL (50.8ms)  IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION
  CACHE (0.0ms)  SELECT @@TRANCOUNT
  Rendered companies/new.html.erb within layouts/application (9.4ms)
  Rendered layouts/_shim.html.erb (0.0ms)
  User Load (32.1ms)  EXEC sp_executesql N'SELECT TOP (1) [users].* FROM [users] WHERE [users].[remember_token] = N''TZlKZ6Sx06p3mMS9kUJY
GA'''

Notice that although the address_attributes are populated, the params[:address] that's queried is null for all fields. (*Note I have a validator in the address model to ensure each address is unique. There are currently no records in the address table).

How can I properly build and store the records for each model upon submit? Thanks!

UPDATE: I didn't have addresses_attributes, locations_attributes, and contacts_attributes listed in the Company model attr_accessible block. Adding these attributes seems to have resolved the problem of getting the child attributes loaded from the form in the controller and

@company = Company.new(params[:company])

now populates the addresses, locations and contacts, however when I call

if @company.save

the transaction still gets rolled back with the following server log

Started POST "/companies" for 127.0.0.1 at 2013-12-11 11:12:03 -0600
SQL (25.3ms) BEGIN TRANSACTION SQL (50.4ms) IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION CACHE (0.0ms) SELECT @@TRANCOUNT

Not sure why the transaction appears to get rolled back on save. I'm using sql server 2008 and tinytds if that helps.

È stato utile?

Soluzione 2

Ok, so there were multiple problems with my code. I've weeded through them all and wanted to post the final working solution, in case someone else is dealing with has_many through associations and nested forms.

My final models look like this (using Rails 3.2.13):

Company.rb

class Company < ActiveRecord::Base
attr_accessible :locations_attributes, :contacts_attributes, :company_name

has_many :locations, dependent: :destroy
has_many :addresses, through: :locations
has_many :contacts

accepts_nested_attributes_for :locations, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :addresses
accepts_nested_attributes_for :contacts

end

Location.rb

class Location < ActiveRecord::Base
attr_accessible  :address_attributes, :address, :created_by, :is_active, :location_name, :location_type, :region_id, :updated_by, :website


belongs_to :company
belongs_to :address

accepts_nested_attributes_for :address, :reject_if => :all_blank

end

Address.rb

class Address < ActiveRecord::Base
attr_accessible  :address_line_1, :address_line_2, :address_line_3, :address_line_4, :address_line_5, :city, :country, :county, :created_by, :province, :state, :updated_by, :zipcode

 has_many :locations, dependent: :destroy
 has_many :companies, through: :location

accepts_nested_attributes_for :locations

end

companies_controller.rb

    class CompaniesController < ApplicationController

    def new
    @company = Company.new
    @location= @company.locations.build
    @address = @company.addresses.build
    @contact = @company.contacts.build

    end

     def show
    @company = Company.find(params[:id])
  end


  def create
    @company = Company.new(params[:company])

    if @company.save
      #handle a successful save
      flash[:success] = "Company Created Successfully"
      redirect_to @company
    else 
      render 'new'
    end 


    end

    end

new.html.erb

<% provide(:title, 'Create Company')%>
<h1>Create Company</h1>

<div class="container">

<%= form_for(@company) do |f| %>


  <% if @company.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@company.errors.count, "error") %> prohibited this company from being saved:</h2>

      <ul>
      <% @company.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

<div class="row">   
    <%= f.label :company_name, "Company Name" %>
    <%= f.text_field :company_name %>
<hr>    
</div>

<div class="row">
<div class="col-md-6"><h3>Primary Location</h3><hr></div>
<div class="col-md-6"><h3>Location Information</h3><hr></div>   

</div>


    <%= f.fields_for(:locations) do |lf| %>
    <%= lf.fields_for(:address_attributes) do |af| %>
    <%= f.fields_for(:contacts) do |cf| %>  
<div class="row" >  
    <div class="col-md-6"><%= af.label "Address 1"%><%= af.text_field :address_line_1 %></div>
    <div class="col-md-6"><%= lf.label "Location Type" %> <%= lf.select(:location_type, options_for_select([["Headquarters",11], ["Office", 12]])) %></div>
</div>

<div class="row" >  
    <div class="col-md-6"><%= af.label "Address 2"%><%= af.text_field :address_line_2 %></div>
    <div class="col-md-6"><%= lf.label "Location Name" %> <%= lf.text_field :location_name %></div> 
</div>  


<div class="row" >  
    <div class="col-md-6"><%= af.label "Address 3"%><%= af.text_field :address_line_3 %></div>
    <div class="col-md-6"><h3>Other Location Information</h3><hr></div> 
</div>  
<div class="row" >  
    <div class="col-md-6"><%= af.label "City"%><%= af.text_field :city %></div>
    <div class="col-md-3"><button type="button" class="btn btn-primary btn-small btn-block">Services Offered</button></div> 
</div>  

<div class="row" >  
    <div class="col-md-6"><%= af.label "State"%><%= af.select(:state, options_for_select([["Alabama","AL"], ["Alaska","AK"]])) %></div>
    <div class="col-md-3"><button type="button" class="btn btn-primary btn-small btn-block">Materials Accepted</button></div>   
</div>  

<div class="row" >  
    <div class="col-md-6"><%= af.label "Country"%><%= af.select(:country, options_for_select([["United States","USA"], ["United Kingdom","UK"]])) %></div>
    <div class="col-md-3"><button type="button" class="btn btn-primary btn-small btn-block">Location Certifications</button></div>  
</div>  

<div class="row" >  
    <div class="col-md-6"><%= af.label "Zipcode"%><%= af.text_field :zipcode %></div>   
</div>  

<div class="row" >
<div class="col-md-12"><h3>Tell us about you</h3></div>
<hr>
</div>
<div class="row">
    <div class="col-md-6"><%= cf.label "First Name"%><%= cf.text_field :first_name %></div>
</div>
<div class="row">
    <div class="col-md-6"><%= cf.label "Last Name"%><%= cf.text_field :last_name %></div>
</div>      
<div class="row">
    <div class="col-md-6"><%= cf.label "Title"%><%= cf.text_field :title %></div>
</div>      
<div class="row">
    <div class="col-md-2"><%= f.submit "Save Info", class: "btn btn-small btn-primary" %></div>
</div>   
    <% end %>
    <% end %>
    <% end%>


<% end %>

The things to notice are how the accepts_nested_attributes_for statements link up the associations, the need to add the appropriate model_attributes in each models attr_accessible statement, and how the form nests the address form (af) within the locations_form (lf) using :addresses_attributes.

Additionally, I had to remove foreign Key (company_id, address_id) validations from Location.rb because they caused the transaction to rollback prior to the Address or Company record being created (a prereq for creating a location)

Altri suggerimenti

Nested attributes allow you to save attributes on associated records through the parent. Here the parent is the company record and the nested are location, address and contact. Only in company model there requires accepts_nested_attributes_for. There require no accepts_nested_attributes in address, contact and location models which are nested and not the parent.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top