如何设置has_many polymorphic self-referential关联满足菜谱组件配置需求
关联关系配置方案
我们通过新增多态中间表的方式同时满足两类需求,具体配置如下:
1. 生成中间表迁移文件
# db/migrate/[timestamp]_create_recipe_components.rb class CreateRecipeComponents < ActiveRecord::Migration[7.0] def change create_table :recipe_components do |t| t.references :recipe, null: false, foreign_key: true # 归属的主菜谱ID t.references :componentable, polymorphic: true, null: false # 多态关联字段,指向食材/子菜谱 t.timestamps end # 新增唯一索引,避免同一菜谱重复添加同一个组件 add_index :recipe_components, [:recipe_id, :componentable_type, :componentable_id], unique: true, name: 'index_recipe_components_unique' end end
2. 模型关联配置
- Ingredient模型配置
# app/models/ingredient.rb class Ingredient < ApplicationRecord # 反向关联:该食材被哪些菜谱引用 has_many :recipe_components, as: :componentable, dependent: :destroy has_many :used_in_recipes, through: :recipe_components, source: :recipe end
- Recipe模型配置
# app/models/recipe.rb class Recipe < ApplicationRecord # 菜谱归属菜谱书的关联可自行补充,此处省略 belongs_to :cookbook # 核心多态关联:当前菜谱的所有组件(包含食材、子菜谱) has_many :recipe_components, dependent: :destroy has_many :ingredient_components, through: :recipe_components, source: :componentable, source_type: 'Ingredient' has_many :sub_recipe_components, through: :recipe_components, source: :componentable, source_type: 'Recipe' # 反向关联:当前菜谱作为子菜谱被哪些父菜谱引用 has_many :as_sub_recipe_entries, class_name: 'RecipeComponent', as: :componentable, dependent: :destroy has_many :used_in_recipes, through: :as_sub_recipe_entries, source: :recipe end
核心能力实现
1. 查询菜谱是否被其他菜谱引用
在Recipe类中添加判断方法即可:
def referenced_by_others? used_in_recipes.exists? end
2. 统计菜谱的全部食材(递归包含子菜谱食材)
在Recipe类中添加递归查询方法,同时内置循环引用防护逻辑:
# 入参visited_recipes用于避免循环引用(比如A菜谱引用B,B又引用A的场景) def all_ingredients(visited_recipes = Set.new) return [] if visited_recipes.include?(self.id) visited_recipes.add(self.id) # 直接关联的食材 + 所有子菜谱的全量食材 ingredient_components + sub_recipe_components.flat_map { |sub_recipe| sub_recipe.all_ingredients(visited_recipes) } end
使用时直接调用your_recipe.all_ingredients即可拿到包含所有子菜谱食材的全量清单。
内容的提问来源于stack exchange,提问作者AELSchauer
相关产品推荐
相关产品推荐

