Johji
Johji

Reputation: 250

Rails 5 strong parameters with nested multiple attributes

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

Answers (2)

Stefan Daschek
Stefan Daschek

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

JamesAspinwall
JamesAspinwall

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

Related Questions