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.