belongs_to 具有多态关系不会通过嵌套形式从 has_many 关系中删除
belongs_to with polymorphic relationship will not delete from a has_many relationship via nested form
我正在尝试创建一个依赖于多态关系的相当复杂的嵌套表单。我大部分都在工作,但是这种关系不会像我期望的那样执行删除。
Recipe
有很多 RecipeSteps
,
RecipeSteps
可以是与以下三个事物之一相关的多态:Techniques
、Steps
或 Recipes
出于某种原因,当我尝试通过嵌套形式从 recipes#edit
删除 RecipeStep
并将 _delete: 1
传递给 [=23] 时,rails 拒绝删除 RecipeStep
=].响应列在下面代码块的底部。
我曾尝试将与 RecipeStep
关联的 belongs_to
更改为 requires: false
,我曾尝试在任何会导致 FK 错误的地方添加 dependent: destroy
。我已经尝试更新 recipes_controller.rb
Strong Params 以允许所有这些信息通过。我包括 :_delete
、:id
和所有其他参数 RecipeStep
需要。
我没有收到任何错误,它甚至没有尝试删除。
我正在使用这些 gems:
- 茧
- 简单形式
相关代码如下:
多态关系关注
stepable.rb
# 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
食谱
recipe.rb - 型号
# 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
recipes_controller.rb - 控制器
# 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
recipe_step.rb - 型号
# 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" = LIMIT [["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" = AND "recipe_steps"."id" IN (, ) 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" = AND "recipe_steps"."stepable_type" = AND "steps"."id" = 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" = AND "step_ingredients"."id" = [["step_id", 10], ["id", 11]]
↳ app/controllers/recipes_controller.rb:46
Measurement Load (0.4ms) SELECT "measurements".* FROM "measurements" WHERE "measurements"."step_ingredient_id" = AND "measurements"."id" IN (, , ) [["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
重要信息:"recipe_steps_attributes"=>{"0"=>{"position"=>"2", "_destroy"=>"1", "id"=>"11"}
如您所见,_destroy
参数已正确设置,并且未发送任何不允许的参数错误。然而,仍然没有尝试删除 recipe_step
我没有包括这个错误的视图,因为我相信它们运行正常,正如传递给控制器的参数所看到的那样。如果您认为我的 simple form
和/或 cocoon
实现可能有错误,请询问,我会添加这些代码。
问题是 Recipe
在两个方面与 RecipeStep
相关:
has_many :recipe_steps
has_many :recipe_steps, as: :stepable
通过 stepable.rb 关注
这两个都是 :recipe_steps
是问题所在。我将关注点更改为:
has_many :through_steps, as: :stepable, class_name: 'RecipeStep'
这解决了删除问题。
我正在尝试创建一个依赖于多态关系的相当复杂的嵌套表单。我大部分都在工作,但是这种关系不会像我期望的那样执行删除。
Recipe
有很多 RecipeSteps
,
RecipeSteps
可以是与以下三个事物之一相关的多态:Techniques
、Steps
或 Recipes
出于某种原因,当我尝试通过嵌套形式从 recipes#edit
删除 RecipeStep
并将 _delete: 1
传递给 [=23] 时,rails 拒绝删除 RecipeStep
=].响应列在下面代码块的底部。
我曾尝试将与 RecipeStep
关联的 belongs_to
更改为 requires: false
,我曾尝试在任何会导致 FK 错误的地方添加 dependent: destroy
。我已经尝试更新 recipes_controller.rb
Strong Params 以允许所有这些信息通过。我包括 :_delete
、:id
和所有其他参数 RecipeStep
需要。
我没有收到任何错误,它甚至没有尝试删除。
我正在使用这些 gems:
- 茧
- 简单形式
相关代码如下:
多态关系关注
stepable.rb
# 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
食谱
recipe.rb - 型号
# 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
recipes_controller.rb - 控制器
# 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
recipe_step.rb - 型号
# 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" = LIMIT [["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" = AND "recipe_steps"."id" IN (, ) 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" = AND "recipe_steps"."stepable_type" = AND "steps"."id" = 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" = AND "step_ingredients"."id" = [["step_id", 10], ["id", 11]]
↳ app/controllers/recipes_controller.rb:46
Measurement Load (0.4ms) SELECT "measurements".* FROM "measurements" WHERE "measurements"."step_ingredient_id" = AND "measurements"."id" IN (, , ) [["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
重要信息:"recipe_steps_attributes"=>{"0"=>{"position"=>"2", "_destroy"=>"1", "id"=>"11"}
如您所见,_destroy
参数已正确设置,并且未发送任何不允许的参数错误。然而,仍然没有尝试删除 recipe_step
我没有包括这个错误的视图,因为我相信它们运行正常,正如传递给控制器的参数所看到的那样。如果您认为我的 simple form
和/或 cocoon
实现可能有错误,请询问,我会添加这些代码。
问题是 Recipe
在两个方面与 RecipeStep
相关:
has_many :recipe_steps
has_many :recipe_steps, as: :stepable
通过 stepable.rb 关注
这两个都是 :recipe_steps
是问题所在。我将关注点更改为:
has_many :through_steps, as: :stepable, class_name: 'RecipeStep'
这解决了删除问题。