David Frey
David Frey

Reputation: 198

accepts_nested_attributes_for not saving nested attributes

I've been stuck on this one problem for days now. This is my first Rails app, and I'm almost finished, only to be slowed considerably by this.

I read that using accepts_nested_attributes_for is the best solution to my problem of nesting Ingredients within a Recipe for use with forms, but so far I have had no luck. I have read everything I can find on the subject. The API says the model now has a (in my case) ingredients_attributes= method, which I do not yet see. I have attempted using update_attributes in the console with the hash

recipe.update_attrubutes {:ingredients_attributes=>[{:name=>"Test Ingredient"}]}

This comes back true, but shows no changes in the recipe object.

I have attempted many approaches, my first being just using fields_for inside of a forms_for inside of my View. Since this wasn't working and I was testing the code to no avail, I started looking deeper and the problem definitely goes deeper than the View.

Any help would be much appreciated. My code follows

Recipe Model

Sorry about the confusing conversions between the db-styled names and the display-styled names. This is my best fix so far to maintain the both of them.

class Recipe < ActiveRecord::Base

  DISH_TYPES={""=>"", "Breakfast"=>"breakfast", "Lunch"=>"lunch", "Soup"=>"soup", "Entree"=>"entree", "Desert"=>"desert"}
  SEASONS={"Any Season"=>"any", "Fall"=>"fall", "Winter"=>"winter", "Spring"=>"spring", "Summer"=>"summer"}
  DIETS={""=>"", "Vegan"=>"vegan", "Vegetarian"=>"vegetarian", "Omnivore"=>"omnivore"}

  DISH_TYPES_R={""=>"", "breakfast"=>"Breakfast", "lunch"=>"Lunch", "soup"=>"Soup", "entree"=>"Entree", "desert"=>"Desert"}
  SEASONS_R={"any"=>"Any Season", "fall"=>"Fall", "winter"=>"Winter", "spring"=>"Spring", "summer"=>"Summer"}
  DIETS_R={""=>"", "vegan"=>"Vegan", "vegetarian"=>"Vegetarian", "omnivore"=>"Omnivore"}

  attr_protected :user_id
  # Do NOT include user_id in the attr_accessible method, to avoid
  # the possibility of it being changed externally.
  belongs_to :user
  validates_presence_of :user  
  has_many :ingredients, dependent: :destroy # , inverse_of: :recipe

  # Allows for forms to write attributes down the hierarchy.
  accepts_nested_attributes_for :ingredients, allow_destroy: true , reject_if: lambda { |a| a[:content].blank? }

  before_save do
    # Lowercase and convert to strings all of the attributes
    # that require a specific format, namely the values in the
    # constant hashes above.
    STRING_ATTRIBUTES.each do |s|
      self.send("#{s}=".to_sym, self.send(s).downcase.to_s)
    end
  end

  validates :user_id, presence: true
  validates :name,  presence: true,
                    length: { maximum: 64 } #,uniqueness: true
  validates :dish_type, inclusion: { in: DISH_TYPES.values }
  validates :season, inclusion: { in: SEASONS.values }
  validates :diet, inclusion: { in: DIETS.values}
  validates :directions,  presence: true,
                          length: { maximum: 8192 }

  validates_associated :ingredients


  default_scope order: "recipes.created_at DESC"


  def method_missing (method)
    method = method.to_s
    if method.slice!("display_")
      if STRING_ATTRIBUTES.include?(method.to_sym)
        hash_name = method.upcase + 'S_R'
        Recipe.const_get(hash_name)[self.send(method.to_sym)]
      else
        method
      end
    else 
      method.class
    end
  end

  private

    STRING_ATTRIBUTES = [:dish_type, :season, :diet]

end

Ingredient Model

class Ingredient < ActiveRecord::Base

  attr_protected :id, :recipe_id
  belongs_to :recipe

  validates_presence_of :name
  #validates_presence_of :recipe
end

Recipe Controller

I've read that I shouldn't have to change anything in the controller. I have only added one line, so that the Ingredient form shows up in my view

class RecipesController < ApplicationController
  before_filter :signed_in_user, only: [:create, :edit, :destroy]

  def index
    @recipes = Recipe.all
  end

  def new
    if signed_in?
      @recipe = current_user.recipes.build 
      @recipe.ingredients.build
    else
      flash[:error] = "First you have to register! Sign up here and start adding recipes ASAP."
      redirect_to signup_path
    end
  end

  def create
    @new_recipe = current_user.recipes.build(params[:recipe])
    if @new_recipe.save
      flash[:success] = "You've successfully added #{@new_recipe.name}!"
      redirect_to @new_recipe
    else
      redirect_to 'new'
    end
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  def show
    @recipe = Recipe.find(params[:id].to_i)
  end

end

Ingredients Controller

I don't believe there is any use for an Ingredient Controller, since the ingredients (for now) will only be accessed through their parent Recipe.

I will include the views upon request, but as I stated earlier, I don't believe it is that high-level.

Upvotes: 0

Views: 2407

Answers (1)

Phobos98
Phobos98

Reputation: 456

  1. You need to set attr_accessible for Recipe model and add there :ingredients_attributes
  2. As i see in your config, you have reject_if: lambda { |a| a[:content].blank? } for ingrediens_attributes and you are setting it only with name

Upvotes: 1

Related Questions