Graham S.
Graham S.

Reputation: 1530

"NoMethodError" in my controller when trying to use "build_association" method on object? Rails 4

So I have three tables: locations, items, and ingredients, that have the following relationships between each other:

When viewing an item (at show.html.haml), I want to be able to add in ingredients that it contains right there on the page, while using AJAX remote: true for the form. However, once I get to the create action of the IngredientsController, I am unable to do this:

@ingredient = item.build_ingredient(:name => ingredient_params[:name], etc..)

Point is, the controller keeps throwing NoMethodError for build_ingredient, create_ingredient, create, etc. Nothing works. But the relationship between Location and Items works perfectly fine.

The weird thing for me is, though, that this works:

@ingredient = Ingredient.new
@ingredient.build_item(hashes for an Item object here, etc..)

I am still quite a bit new to Rails and I practice every day. Normally I would eventually come across a solution online through Google, but this one has me particularly stumped and after reading the Rails documentation on associations, I have still not come across anything that directly addresses a kind of "3-Layered" association between models.

So here's what I have:

Ingredients Controller

class IngredientsController < ApplicationController
  def create

    # This works
    location = Location.find_by(:location_id => ingredient_params[:id])

    # This also works
    item = location.items.where(:item_id => ingredient_params[:menu_item_id])

    # This does not work
    @ingredient = item.build_ingredient(:name => ingredient_params[:name], :unit => ingredient_params[:unit], :value => ingredient_params[:value])

    respond_to do |format|
      format.js { render nothing: true }
    end
  end

  private def ingredient_params
    params.require(:ingredient).permit(:id, :menu_item_id, :name, :unit, :value)
  end
end

show.html.haml (Items)

.panel.panel-default
  = form_for @ingredient, remote: true do |f|
    .panel-heading
      New Ingredient
    .panel-body
      = f.hidden_field :id, :value => params[:id]
      = f.hidden_field :menu_item_id, :value => params[:menu_item_id]
      = f.select :name, options_from_collection_for_select(Supplylist.all, "id", "item"), { :prompt => "Select..." }, :class => "form-control ingredient-select"
      %br
      = f.text_field :value, :class => "form-control"
      %br
      = f.select :unit, options_from_collection_for_select(UnitType.all, "id", "name"), { :prompt => "Select..." }, :class => "form-control ingredient-select"
    .panel-footer
      = f.submit "Add", :class => "btn btn-success"

Location Model

class Location < ActiveRecord::Base
  has_many :items

  @url

  def getResponse
      response = RestClient.get(
        @url,
        {:content_type => :json, :'Api-Key' => '000000000000000000000'})

      return response
  end

  def setURL(url)
      @url = url
  end

  def getJSON
      return getResponse()
  end

  def getData(url)
      self.setURL(url)
      return JSON.parse(getJSON())
  end
end

Item Model

class Item < ActiveRecord::Base
  belongs_to :location
  has_many :ingredients
end

Ingredients Model

class Ingredient < ActiveRecord::Base
  belongs_to :item
end

Also, just to note:

Both columns were created during model creation using location:belongs_to and item:belongs_to.

Upvotes: 1

Views: 87

Answers (1)

infused
infused

Reputation: 24337

First of all item does not contain a single item. Change your item query to:

item = location.items.where(:item_id => ingredient_params[:menu_item_id]).first

Then you can use:

item.ingredients.build(:name => ingredient_params[:name], etc..)

build_<association_name> is only valid for belongs_to or has_one associations.

Upvotes: 1

Related Questions