ruby-on-railsassociationseager-loadingdefault-scope

Unscope default scope on assciation without N+1 query in Rails 7


Let's say I have the following moduls:

Category has_many :products Product has_many :tags Tag has_one :author Author has default scope

default_scope { where(active: true) }

Now when showing categories, I want to show also all products and tags within the product, but I also want to include tags from inactive users.

The most straightforward solution is to remove default scope and create explicit scope instead and use it on all places. But issue is there is a bunch of places where I would need to add that, so that solution is not viable.

The other option is to add new scope to Product

def all_tags
  Tag.joins(:author).unscope(author: { where: :active }).where(product_id: id)
end

This works, but it can lead to N+1 issue If there are 10 products within each company with 5 tags in average per product, it would run 50 queries to DB and that's not what I want.

Is there any elegant way to solve N+1 issue?

Of course, I could lead all relevant tags in a controller, something like

@tags_per_product = Tag.joins(:author).unscope(author: { where: :active }).where(product_id: product_ids).group_by(&:product_id)

and then reuse tags_per_product in a view, but that looks a bit dirty

In Rails, it's always important to find neat and elegant way, so I would like to have it here

Thanks in advance


Solution

  • I solved it like this

    In tag.rb

    has_one :author, lambda {
        unscope(where: :active)
    }
    

    So basically this would work and in all context we'd return authors that are excluded by default scope

    If for some reason, we need to include those excluded only in certain contexts, I may do it by introducing new

    has_one :author_user
    

    or something like this