Reputation: 250
I am trying to save an object which has a recipe and multiple ingredients inside it. The data comes from my angular 2 app which passes the object as JSON. My rails 5 api application will receive the recipe object and save it directly to my DB using the strong parameters. Right now I can save the recipe object to the database but for some reason the ingredients inside it isn't being saved. I have checked the rails documentation and I found no problem with my current code. Any thoughts would be appreciated.
recipe.rb
class Recipe < ApplicationRecord
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
ingredient.rb
class Ingredient < ApplicationRecord
belongs_to :recipe
end
recipe_controller.rb
def create
@recipe = Recipe.new(recipe_params)
if @recipe.save
render json: @recipe, status: :created, location: @recipe
else
render json: @recipe.errors, status: :unprocessable_entity
end
end
def recipe_params
params.require(:recipe).permit(:name , :description, :imagePath, ingredients_attributes: [ :id, :name, :amount])
end
my console log
Started POST "/recipes" for ::1 at 2017-01-09 11:40:44 +0900
ActiveRecord::SchemaMigrationLoad(0.3ms)SELECT`schema_migrations`.* FROM `schema_migrations`
Processing by RecipesController#create as HTML Parameters: {"name"=>"Spaghetti", "imagePath"=>"http://cdn2.tmbi.com/TOH/Images/Photos/37/300x300/exps36749_SD143203D10__25_1b.jpg", "description"=>"Delicious spaghetti", "ingredients"=>[{"name"=>"Tomato", "amount"=>1}, {"name"=>"Pasta", "amount"=>1}], "recipe"=>{"name"=>"Spaghetti", "description"=>"Delicious spaghetti", "imagePath"=>"http://cdn2.tmbi.com/TOH/Images/Photos/37/300x300/exps36749_SD143203D10__25_1b.jpg"}}
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO `recipes` (`name`, `description`, `imagePath`, `created_at`, `updated_at`) VALUES ('Spaghetti', 'Delicious spaghetti', 'http://cdn2.tmbi.com/TOH/Images/Photos/37/300x300/exps36749_SD143203D10__25_1b.jpg', '2017-01-09 02:40:44', '2017-01-09 02:40:44')
(0.7ms) COMMIT
Completed 201 Created in 9ms (Views: 1.0ms | ActiveRecord: 2.1ms)
Upvotes: 0
Views: 1028
Reputation: 376
Your console log shows that the params include the key ingredients
. For nested attributes, you have to use ingredients_attributes
instead. So make sure your params look like this:
{
"name" => "Spaghetti",
"description" => "Delicious spaghetti",
"ingredient_attributes" => [
{"name" => "Tomato", "amount" => 1},
{"name" => "Pasta", "amount" => 1}
]
}
Why can't we simply use ingredients
here? Well, this would result in the following code being executed internally:
recipe.ingredients = [{"name" => "Tomato", … }]
But recipe.ingredients=
is the attribute writer of the relation, and it only accepts an array of Ingredient
instances. So you’d have to use it like this:
recipe.ingredients = [Ingredient.new("name" => "tomato", …)]
Under the hood, using recipe.ingredients_attributes=
essentially does this conversion from plain hashes into instances of Ingredient
.
Upvotes: 0
Reputation: 11
In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this https://github.com/rails/rails/pull/18937change. If you display the @recipe.errors.messages, you will find
{:"ingredient.recipe"=>["must exist"]}
Just add 'optional:true' to your belong_to line in the ingredient model.
class Ingredient < ApplicationRecord
belongs_to :recipe, optional: true
end
Upvotes: 1