Search code examples
ruby-on-railsruby-on-rails-4fields-for

Using fields_for in Rails 4...does not save the new fields


I am trying to use the fields_for helper method on a project I am working on. The original form works and saves just fine. The new attributes do not save and I get a NoMethodError and a undefined method. What am I missing?!

Here is my listing model:

class Listing < ActiveRecord::Base
has_one :listing_commerical_attribute
accepts_nested_attributes_for :listing_commerical_attribute, :allow_destroy => true

Here is my listing_commercial_attribute model:

class ListingCommercialAttribute < ActiveRecord::Base
  belongs_to :listing
  accepts_nested_attributes_for :listing
end

Here is my controller:

def new
  @listing.build_listing_commercial_attribute

  respond_to do |format|
    format.html # new.html.erb
    format.json { render json: @listing }
  end
end

private

def commercial_params
  params.require(:commerical_listing_attribute)
      .permit(:gas_pipe_size,
              :amperage,
              :basement_ceiling_height,
              :ceiling_height,
              :door_size,
              :zoning,
              :previous_use,
              :community_board,
              :delivery_date,
              :key_money,
              :security_deposit,
              :price_per_sq_ft,
              :did_size)
end

Here is my _form.html.erb:

<h2 class="text-center">Commercial</h2>
 <%= f.fields_for :listing_commerical_attributes do |ff| %>
<div class="field">
  <%= ff.label :gas_pipe_size, "Gas Pipe Size", class: "general-text-label" %>
  <%= ff.number_field :gas_pipe_size, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :amperage, "Amperage", class: "general-text-label" %>
  <%= ff.number_field :amperage, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :ceiling_height, "Ceiling Height", class: "general-text-label" %>
  <%= ff.number_field :ceiling_height, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :basement_ceiling_height, "Basement Ceiling Height", class: "general-text-label" %>
  <%= ff.number_field :basement_ceiling_height, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :door_size, "Door Size", class: "general-text-label" %>
  <%= ff.number_field :door_size, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :zoning, "Zoning", class: "general-text-label" %>
  <%= ff.text_field :zoning, class: "general-text-field" %>
</div>
<div class="field">
  <label for="tenant_improvements" class="general-text-label">Tenant Improvements <small>(If Applicable)</small></label>
  <%= ff.text_area :tenant_improvements, :rows => "4", class: "general-text-area" %>
</div>
<div class="field">
  <label for="previous_use" class="general-text-label">Previous Use <small>(If Applicable)</small></label>
  <%= ff.text_area :previous_use, :rows => "4", class: "general-text-area" %>
</div>
<div class= "field">
  <%= ff.label :community_board, "Community Board", class: "general-text-label" %>
  <%= ff.text_field :community_board, class: "general-text-field" %>
</div>
<div class="field">
  <%= ff.label :delivery_date, "Delivery Date", class: "general-text-label" %>
  <div class="input-group">
    <span class="input-group-addon"><i class="nklyn-icon-calendar"></i></span>
    <%= ff.text_field :delivery_date, :class => "datepicker general-text-field" %>
</div>
<div class="field">
  <%= ff.label :key_money, "Key Money", class: "general-text-label" %>
  <div class="input-group">
    <span class="input-group-addon"><i class="nklyn-icon-money-bills"></i></span>
    <%= f.text_field :key_money, class: "general-text-field", value: number_with_precision(f.object.price, delimiter: ',', precision: 0) %>
  </div>
</div>
<div class="field">
  <%= ff.label :security_deposit, "Security Deposit", class: "general-text-label" %>
  <div class="input-group">
    <span class="input-group-addon"><i class="nklyn-icon-money-bills"></i></span>
    <%= f.text_field :security_deposit, class: "general-text-field", value: number_with_precision(f.object.price, delimiter: ',', precision: 0) %>
  </div>
</div>
<div class="field">
  <%= ff.label :price_per_sq_ft, "Price Per Sq Ft", class: "general-text-label" %>
  <div class="input-group">
    <span class="input-group-addon"><i class="nklyn-icon-money-bills"></i></span>
    <%= f.text_field :price_per_sq_ft, class: "general-text-field", value: number_with_precision(f.object.price, delimiter: ',', precision: 0) %>
  </div>
</div>
<div class="field">
  <%= ff.label :did_size, "Drive In Doors Size", class: "general-text-label" %>
  <%= ff.number_field :did_size, class: "general-text-field" %>
</div>
<% end %>

Update

  1. I made the change to the ListingCommercialAttribute model and removed the accepts nested attributes for.

  2. I changed the f.fields_for to singular instead of plural.

  3. I added in the nested attributes after the parent (see below)

    def listing_params
       params.require(:listing)
          .permit(:access,
              :address,
              :apartment,
              :cats_ok,
              :cross_streets,
              :dogs_ok,
              :latitude,
              :longitude,
              :amenities,
              :date_available,
              :bathrooms,
              :bedrooms,
              :description,
              :fee,
              :exclusive,
              :featured,
              :rental,
              :residential,
              :landlord_contact,
              :listing_agent_id,
              :sales_agent_id,
              :neighborhood_id,
              :pets,
              :photo,
              :photo_tag,
              :primaryphoto,
              :price,
              :square_feet,
              :station,
              :status,
              :subway_line,
              :term,
              :title,
              :utilities,
              :move_in_cost,
              :owner_pays,
              :private,
              :office_id,
              :full_address,
              :zip,
              :convertible,
              :landlord_llc,
              :pinned,
              :image,
              listing_commercial_attribute_attributes: [
              :gas_pipe_size,
              :amperage,
              :basement_ceiling_height,
              :ceiling_height,
              :door_size,
              :zoning,
              :previous_use,
              :community_board,
              :delivery_date,
              :key_money,
              :security_deposit,
              :price_per_sq_ft,
              :did_size])
    end
    
  4. Here are my new controller actions:

    def edit
       @listing.attributes = listing_params
    end
    
    def create
       @listing.attributes = listing_params
    
       respond_to do |format|
         if @listing.save
            format.html { redirect_to @listing, notice: 'Listing was successfully created.' }
            format.json { render json: @listing, status: :created, location: @listing }
         else
            format.html { render action: "new", notice: "Correct the mistakes below to create the new listing" }
            format.json { render json: @listing.errors, status: :unprocessable_entity }
        end
      end
    end
    

But now I am getting a NoMethodError in Listings#show error. I created a partial for the commercial attributes. Shouldn't they be included now that they are in the strong params, or am I totally misunderstanding that?!

Here is the partial:

    Gas Pipe Size: <%= listing_commercial_attributes.gas_pipe_size(@listing) %>
    Amperage: <%= listing_commercial_attribute.amperage(@listing) %>
    Basement Ceiling Height: <%= listing_commercial_attribute.basement_celing_height(@listing) %>
    Ceiling Height: <%= listing_commercial_attribute.ceiling_height(@listing) %>
    Door Size: <%= listing_commercial_attribute.door_size(@listing) %>
    Zoning: <%= listing_commercial_attribute.zoning(@listing) %>
    Build to Suit: <%= listing_commercial_attribute.build_to_suit(@listing) %>
    Previous Use: <%= listing_commercial_attribute.previous_use(@listing) %>
    Community Board: <%= listing_commercial_attribute.community_board(@listing) %>
    Delivery Date: <%= listing_commercial_attribute.delivery_date(@listing) %>
    Key Money: <%= listing_commercial_attribute.key_money(@listing) %>

Update #2

I changed it to singular.

Here is the complete error.

NameError in Listings#show

Showing /Users/Code/app/views/listings/_commercial_attributes.html.erb where line #1 raised:

undefined local variable or method `listing_commercial_attribute' for #<#:0x007f86606f6a10> Did you mean? listing_collection_url

  • Gas Pipe Size: <%= listing_commercial_attribute.gas_pipe_size(@listing) %>
  • Amperage: <%= listing_commercial_attribute.amperage(@listing) %>
  • Basement Ceiling Height: <%= listing_commercial_attribute.basement_celing_height(@listing) %>
  • Ceiling Height: <%= listing_commercial_attribute.ceiling_height(@listing) %>
  • Door Size: <%= listing_commercial_attribute.door_size(@listing) %>
  • Zoning: <%= listing_commercial_attribute.zoning(@listing) %>

Trace of template inclusion: app/views/listings/_listing_content_area.html.erb, app/views/listings/show.html.erb


Update #3

  def show
      @my_listing_collections = ListingCollection.with_agent(current_agent).order("created_at DESC")
      @listing_commercial_attributes = ListingCommercialAttribute.find(params[:id])
      @regions = Region.order(name: :asc)
      @listing = Listing.includes(:photos, :likes, :interested_agents).find(params[:id])

      if @listing.private && cannot?(:create, Listing)
        redirect_to listings_path, notice: 'This listing is no longer available'
      else
        agent = Agent.where(id: params[:agent_id]).first
        @page = Listings::ShowView.new(@listing, agent)

        respond_to do |format|
          format.html
        end
      end
    end

I keep getting this error:

ActiveRecord::RecordNotFound in ListingsController#show

Couldn't find ListingCommercialAttribute with 'id'=5755

It is searching for the commercial attribute with an id of 5755, but that is the listing id. I'm not sure what to pass in there...


Solution

    1. Do not define accepts_nested_attributes_for on both models. Only on the parent model. Otherwise you'll run into circular dependency issues. In this case the parent model looks like it's a Listing, so remove accepts_nested_attributes_for :listing from ListingCommercialAttribute.
    2. The first argument to f.fields_for should be the name of the association and yours is slightly off. You have has_one : listing_commerical_attribute so you want f.fields_for : listing_commerical_attribute.
    3. The Strong Parameters should require your parent object first and include nested objects second. Also, you must append _attributes to the end of your nested attribute name.

    So, for 3:

    def listing_params
      params.require(:listing)
            .permit(:id,
                    # ...
                    listing_commercial_attribute_attributes: [ # Note: _attributes
                      :gas_pipe_size,
                      # ...
                    ])
    end
    
    1. In the create/edit actions, be sure to set the params from the strong parameters method: @listing.attributes = listing_params.

    Read more in the docs on accepts_nested_attributes_for and Strong Parameters.