user3224820
user3224820

Reputation: 211

Nested Form Unknown Attribute Error

I'm trying to build a small expense tracking app. Using the nested_form gem to add line items. There is an Expense model which accepts nested attributes. Items belong to expenses.

class Expense < ActiveRecord::Base
    belongs_to :organization
    belongs_to :department
    has_many :expense_types
    has_many :items

    accepts_nested_attributes_for :items
end

The items model:

class Item < ActiveRecord::Base
    belongs_to :expense
end

The controller create action action looks like:

class ExpensesController < ApplicationController
    def new
        @expense = Expense.new
    end
    def create
            @expense = Expense.new(expense_params)
            if @expense.save
                flash[:notice] = "Expense Report Submitted"
                redirect_to @expense
            else
                render 'new'
            end
        end

        private
        def expense_params
            params.require(:expense).permit(:department_id, :expense_type_id, :notes, items_attributes: [:id, :description, :amount, :issue_date, :_destroy])
        end
    end

The new expense form looks like:

<%= nested_form_for (@expense) do |f| %>    
    <% if @expense.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(@expense.errors.count, "error") %> prohibited
          this expense from being saved:</h2>
        <ul>
        <% @expense.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
        </ul>
      </div>
    <% end %>
    <div class"row">
        <div class="col-md-8">
            <div class="form-group">
                <%= f.label :department_id %><br>
                <%= f.collection_select(:department_id, Department.all, :id, :department_name, prompt: true, class: "dropdown-menu") %>
            </div>
            <div class="form-group">
                <%= f.label :expense_type_id %><br>
                <%= f.collection_select(:expense_type_id, ExpenseType.all, :id, :expense_name, prompt: true, class: "form-control")  %>
            </div>
            <%= f.fields_for :items do |i| %>
                <div class="form-group">
                    <%= i.label :description%>
                    <%= i.text_field :description, class: "form-control" %>
                </div>
                <div class="form-group">
                    <%= i.label :amount%>
                    <%= i.text_field :amount, class: "form-control" %>
                </div>
                <div class="form-group">
                    <%= i.label :issue_date%>
                    <%= i.date_select :issue_date, class: "form-control" %>
                </div>
                <%= i.link_to_remove "Remove", class: "btn btn-default" %>
            <% end %>
            <div><p><%= f.link_to_add "Add Expense", :items, class: "btn btn-default" %></p></div>
            <div class="form-group">
                <%= f.label :notes %>
              <%= f.text_area :notes, class: "form-control" %>
            </div>
            <%= f.submit "Submit", class: "btn btn-primary" %>
          <% end %>
        </div>
    </div>

I was able to save expenses before adding the nested attributes. After doing that, whenever I hit the submit button, I get the ActiveRecord::UnknownAttributeError in ExpensesController#create error. It's weird to see unknown attribute: expense_id. Did I miss something here?

Upvotes: 2

Views: 5018

Answers (2)

Richard Peck
Richard Peck

Reputation: 76784

Nested Form

To the best of my knowledge, the nested_form gem basically just creates a fields_for instance for your form. If this is the case (it more or less has to be), then you've got several issues you need to consider

By the way, looking at the nested_form documentation, it seems it was primarily designed for Rails 3. When it mentioned about attr_accessible, it now means to focus on strong_params (if you're using Rails 4 of course)

--

Build

As mentioned by Mandeep, you need to ensure you're building the correct ActiveRecord objects for your form. form_for works by taking an ActiveRecord object & populating the various attributes etc with its own attributes.

This is how Rails creates the illusion of persistence with this functionality -- it takes the ActiveRecord & populates the same data recursively

In order to get fields_for to work (which is the basis of nested_attributes, you need to build the associative ActiveRecord object in the action which renders the form_for (in your case new):

#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
   def new
      @expense = Expense.new
      @expense.items.build #-> required for fields_for to work
   end
end

Remember, the nested_form gem is not magic - it is essentially just a javascript plugin to replicate the fields_for elements rendered in your form already, and then append them to the DOM.

It essentially uses the child_index: Time.now.to_i "trick" to surmount the incremental id isue

--

Attributes

Secondly, you need to appreciate the error you're receiving

expense_id could be an attribute in the items_attributes objects. I've not seen this with pure Rails, but perhaps the nested_form gem appends a particular attribute to the objects or something

Either way, I believe the problem will be how to associate your items objects with your parent Expense object. To do this, I would do the following:

  1. Check your params hash (see where expense_id is being passed)
  2. Update your strong_params to allow the expense_id

I KNOW THE PROBLEM

DATABASE - you likely don't have the expense_id column in the items table


Fix

You need to create a migration to put the expense_id foreign_key into your items table

To do this, you should open your CMD and perform the following:

$ rails generate migration AddExpenseIDToItems

Then you can change the migration to have the following line:

add_column :expense_id, :items

Then you just need to do:

$ rake db:migrate

This should resolve your issue

Upvotes: 3

Mandeep
Mandeep

Reputation: 9173

Your controllers new action should be like this

def new
  @expense = Expense.new      
  @item = @expense.items.build
end

Also if you are using rails 4 then you don't need nested_form_for gem. Checkout nested forms. In your form you can simply use

<%= form_for @expense do |f| %>
  // expense fields
  <%= f.fields_for @item do |e| %>
    // item fields
  <% end %>
  <%= f.submit %>
<% end %>

Upvotes: 2

Related Questions