Rails 6.1自引用has_many关联中where.missing方法失效问题求解
Hey there! Let's work through this issue with Rails 6.1's where.missing and your self-referential Item associations. The problem here boils down to how Rails handles self-referential relationships and scoped associations with the missing method—let's fix those scopes step by step.
Fixing no_child_items
First, let's tackle the scope to find items with no child items. While where.missing(:child_items) should work in theory for self-referential has_many associations, sometimes Rails can have trouble resolving the table aliases correctly in this scenario. A more reliable (and equally clean) approach is to use the traditional left_joins + null check:
scope :no_child_items, -> { left_joins(:child_items).where(child_items: { id: nil }) }
This works by performing a left join to the child items, then filtering for records where the child item's ID is null—meaning no matching child records exist. If you still want to use where.missing, double-check that your child_items association is correctly defined (yours looks good!), and make sure you're running a recent patch version of Rails 6.1 (some early 6.1 releases had edge case bugs with self-referential missing).
Fixing no_approved_child_items
For scoped associations like approved_child_items, where.missing can struggle to apply the scope's conditions properly. Again, using left_joins is the most dependable solution, since it automatically inherits the scope from the association:
scope :no_approved_child_items, -> { left_joins(:approved_child_items) .where(approved_child_items: { id: nil }) }
This will generate a left join that includes only approved child items, then filter for parent items where no such approved child exists. If you prefer a more explicit approach using exists?, you can also write it like this:
scope :no_approved_child_items, -> { where.not( exists?( Item.approved.where("parent_item_id = items.id") ) ) }
This directly checks that there are no approved items linked to the current parent item via parent_item_id.
Full Updated Model
Here's your complete model with the fixed scopes, plus a small recommendation for your belongs_to association (since it looks like items can exist without a parent):
class Item < ApplicationRecord belongs_to :parent_item, class_name: "Item", optional: true # Add optional: true if items can have no parent has_many :child_items, class_name: "Item", foreign_key: :parent_item_id has_many :approved_child_items, -> { approved }, class_name: "Item", foreign_key: :parent_item_id scope :approved, -> { where.not(approved_at: nil) } # Fixed scope for items with no child items scope :no_child_items, -> { left_joins(:child_items).where(child_items: { id: nil }) } # Fixed scope for items with no approved child items scope :no_approved_child_items, -> { left_joins(:approved_child_items) .where(approved_child_items: { id: nil }) } end
Key Notes
- Adding
optional: truetobelongs_to :parent_itemis important if your application allows items to exist without a parent (Rails 5+ makesbelongs_torequired by default). - The
left_joinsapproach is more universally reliable for self-referential and scoped associations, even ifwhere.missingworks in some cases.
内容的提问来源于stack exchange,提问作者sethherr

