I am attempting to create a quite complex nested form that relies on a polymorphic relationship. I have most of it working, however this one relationship will not perform a delete like I am expecting.
Recipe
has many RecipeSteps
,
RecipeSteps
can be polymorphic related to one of three things: Techniques
, Steps
, or Recipes
For some reason rails refuses to delete the RecipeStep
when I attempt to do so from recipes#edit
via a nested form and passing _delete: 1
to the RecipeStep
. The response is listed at the bottom of the code block below.
I have tried changing the belongs_to
associated with the RecipeStep
to requires: false
, I have tried adding dependent: destroy
on anything that would cause a FK error. And I have tried updating the recipes_controller.rb
Strong Params to allow all of this info through. I am including :_delete
, :id
, and all other params RecipeStep
requires.
I am not getting any errors, It just does not even attempt to do the delete.
I am using these gems:
Here is the relevant code:
# frozen_string_literal: true
# Adds the polymorphic relation to recipes through recipe_steps
module Stepable
extend ActiveSupport::Concern
included do
has_many :recipe_steps, as: :stepable
has_many :recipes, through: :recipe_steps
end
end
# frozen_string_literal: true
# == Schema Information
#
# Table name: recipes
#
# id :bigint(8) not null, primary key
# title :string
# description :text
# created_at :datetime not null
# updated_at :datetime not null
#
# Recipe's are containers that can have as many Steps, Techniques, or even
# other Recipes within them.
class Recipe < ApplicationRecord
include Stepable
has_many :recipe_steps
has_many :steps,
through: :recipe_steps,
source: 'stepable',
source_type: 'Step',
dependent: :destroy
has_many :techniques,
through: :recipe_steps,
source: 'stepable',
source_type: 'Technique',
dependent: :destroy
has_many :recipes,
through: :recipe_steps,
source: 'stepable',
source_type: 'Recipe',
dependent: :destroy
has_many :ingredients,
through: :steps
has_many :step_ingredients,
through: :steps,
dependent: :destroy
accepts_nested_attributes_for :recipes,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :techniques,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :steps,
reject_if: :all_blank,
allow_destroy: true
accepts_nested_attributes_for :recipe_steps,
reject_if: :all_blank,
allow_destroy: true
end
# frozen_string_literal: true
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy]
# GET /recipes
# GET /recipes.json
def index
@recipes = Recipe.all.decorate
end
# GET /recipes/1
# GET /recipes/1.json
def show
end
# GET /recipes/new
def new
@recipe = Recipe.new
end
# GET /recipes/1/edit
def edit
end
# POST /recipes
# POST /recipes.json
def create
@recipe = Recipe.new(recipe_params)
respond_to do |format|
if @recipe.save
format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' }
format.json { render :show, status: :created, location: @recipe }
else
format.html { render :new }
format.json { render json: @recipe.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /recipes/1
# PATCH/PUT /recipes/1.json
def update
respond_to do |format|
if @recipe.update(recipe_params)
format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' }
format.json { render :show, status: :ok, location: @recipe }
else
format.html { render :edit }
format.json { render json: @recipe.errors, status: :unprocessable_entity }
end
end
end
# DELETE /recipes/1
# DELETE /recipes/1.json
def destroy
@recipe.destroy
respond_to do |format|
format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_recipe
@recipe = Recipe.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def recipe_params
params.require(:recipe)
.permit(
:name,
:title,
:description,
recipe_steps_attributes: [
:id,
:position,
:stepable_type,
:stepable_id,
:recipe_id,
:_destroy
],
recipes_attributes: [
:id,
:_destroy
],
techniques_attributes: [
:id,
:title,
:description,
:_destroy
],
steps_attributes: [
:id,
:title,
:description,
:_destroy,
step_ingredients_attributes: [
:id,
:_destroy,
:ingredient_id,
measurements_attributes: [
:id,
:unit,
:scalar,
:purpose,
:_destroy
],
ingredient_attributes: [
:id,
:title,
:description,
:_destroy
]
]
]
)
end
end
# frozen_string_literal: true
# == Schema Information
#
# Table name: recipe_steps
#
# id :bigint(8) not null, primary key
# recipe_id :bigint(8)
# stepable_type :string
# stepable_id :bigint(8)
# position :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# RecipeStep is the through table for relating polymorphic stepable items to
# the recipe.
class RecipeStep < ApplicationRecord
belongs_to :stepable, polymorphic: true, required: false
belongs_to :recipe, required: false
before_create :set_position
accepts_nested_attributes_for :stepable,
reject_if: :all_blank,
allow_destroy: true
default_scope -> { order(position: :asc) }
private
def set_position
self.position = recipe.recipe_steps.count + 1
end
end
Started PATCH "/recipes/3" for ::1 at 2019-02-19 15:55:20 -0500
Processing by RecipesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"21JWhw/o6ysiInTGv4gJp8pTrvh5jpscpGw2Fhm2o3OyitieRz26nBGeFaZclH21zXHBhjtF7L9ujE3yILl+IQ==", "recipe"=>{"title"=>"Pizza", "description"=>"Three eggs with cilantro, tomatoes, onions, avocados and melted Emmental cheese. With a side of roasted potatoes, and your choice of toast or croissant.", "recipe_steps_attributes"=>{"0"=>{"position"=>"2", "_destroy"=>"1", "id"=>"11"}, "1"=>{"position"=>"10", "_destroy"=>"false", "id"=>"10"}}, "steps_attributes"=>{"0"=>{"title"=>"Ricotta Stuffed Ravioli", "description"=>"Two butter croissants of your choice (plain, almond or cheese). With a side of herb butter or house-made hazelnut spread.", "step_ingredients_attributes"=>{"0"=>{"ingredient_id"=>"1", "measurements_attributes"=>{"0"=>{"unit"=>"", "scalar"=>"0.7", "_destroy"=>"false", "id"=>"15"}, "1"=>{"unit"=>"", "scalar"=>"6.7", "_destroy"=>"false", "id"=>"16"}, "2"=>{"unit"=>"", "scalar"=>"5.8", "_destroy"=>"false", "id"=>"17"}}, "_destroy"=>"false", "id"=>"11"}}, "id"=>"10"}}}, "commit"=>"Save", "id"=>"3"}
Recipe Load (0.3ms) SELECT "recipes".* FROM "recipes" WHERE "recipes"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
↳ app/controllers/recipes_controller.rb:69
(0.2ms) BEGIN
↳ app/controllers/recipes_controller.rb:46
RecipeStep Load (0.8ms) SELECT "recipe_steps".* FROM "recipe_steps" WHERE "recipe_steps"."recipe_id" = $1 AND "recipe_steps"."id" IN ($2, $3) ORDER BY "recipe_steps"."position" ASC [["recipe_id", 3], ["id", 11], ["id", 10]]
↳ app/controllers/recipes_controller.rb:46
Step Load (1.0ms) SELECT "steps".* FROM "steps" INNER JOIN "recipe_steps" ON "steps"."id" = "recipe_steps"."stepable_id" WHERE "recipe_steps"."recipe_id" = $1 AND "recipe_steps"."stepable_type" = $2 AND "steps"."id" = $3 ORDER BY "recipe_steps"."position" ASC [["recipe_id", 3], ["stepable_type", "Step"], ["id", 10]]
↳ app/controllers/recipes_controller.rb:46
StepIngredient Load (0.4ms) SELECT "step_ingredients".* FROM "step_ingredients" WHERE "step_ingredients"."step_id" = $1 AND "step_ingredients"."id" = $2 [["step_id", 10], ["id", 11]]
↳ app/controllers/recipes_controller.rb:46
Measurement Load (0.4ms) SELECT "measurements".* FROM "measurements" WHERE "measurements"."step_ingredient_id" = $1 AND "measurements"."id" IN ($2, $3, $4) [["step_ingredient_id", 11], ["id", 15], ["id", 16], ["id", 17]]
↳ app/controllers/recipes_controller.rb:46
(0.2ms) COMMIT
↳ app/controllers/recipes_controller.rb:46
Redirected to http://localhost:3100/recipes/3
Completed 302 Found in 21ms (ActiveRecord: 3.3ms)
Started GET "/recipes/3" for ::1 at 2019-02-19 15:55:21 -0500
Processing by RecipesController#show as HTML
Important Info: "recipe_steps_attributes"=>{"0"=>{"position"=>"2", "_destroy"=>"1", "id"=>"11"}
As you can see, the _destroy
argument is properly set and no un-permitted parameter errors are sent. However there is still not even an attempt to delete the recipe_step
I have not included the views for this error because I believe that they are functioning correctly, as can be seen by the params being passed to the controller. If you think there may be an error with my simple form
and or cocoon
implementation please ask and I will add those pieces of code.
The issue is that Recipe
is related to RecipeStep
in two ways:
has_many :recipe_steps
has_many :recipe_steps, as: :stepable
via the stepable.rb concernHaving both of these be :recipe_steps
was the issue. I changed the concern to:
has_many :through_steps, as: :stepable, class_name: 'RecipeStep'
and this solved the deletion issue.