Search code examples
ruby-on-railsrubyactiverecord

ActiveRecord: doing a join with unscoped doesn't skip the default_scope


I am having some issues when trying to skip a default_scope when doing an ActiveRecord join.

Even though the codebase is quite large, I am just showing the basics as I think it shows pretty well what the problem is:

class Client
  belongs_to :company
end

class Company
  default_scope { where(version_id: nil) }
end

I am building a complex report, so I need to join multiple tables and filter on them. However, I can't successfully skip the default scope when fetching Clients.

Client.joins(:company).to_sql
# SELECT clients.* FROM clients INNER JOIN companies
# ON companies.id = clients.company_id AND companies.version_id IS NULL

As you can see that is automatically including the Company default_scope. So I tried this:

Company.unscoped { Client.joins(:company) }.to_sql
# SELECT clients.* FROM clients INNER JOIN companies
# ON companies.id = clients.company_id AND companies.version_id IS NULL

Again, I got the same result, even when using unscoped with the block.

Then I decided to add a new association to the model, with the unscoped scope:

class Client
  belongs_to :company
  belongs_to :unscoped_company, -> { unscoped }, foreign_key: :company_id, class_name: "Company"
end

Having added that, I gave another try:

Client.joins(:unscoped_company).to_sql
# SELECT clients.* FROM clients INNER JOIN companies
# ON companies.id = clients.company_id AND companies.version_id IS NULL

And still the scoped is being applied.

Do you know how can I successfully join both tables without applying that default_scope? Removing that default_scope is not an option as It is a big application and changing that will require too much time.


Rails v4.2.7

Ruby v2.2.3


Solution

  • I did some research without finding any straight solution.

    Here a couple of workarounds. I cannot say if they're going to work in your chained joins.


    First basic, do it manually:
    Client.joins("INNER JOINS companies ON companies.id = clients.company_id").to_sql
    

    Other option define a `CompanyUnscoped` class which inherits from `Company`, removing the default_scope:
    class CompanyUnscoped < Company
    
      self.default_scopes = []
    
    end
    

    Don't forget to add this line to Client class:

    belongs_to :company_unscoped, foreign_key: :company_id
    

    Then you should be able to call

    Client.joins(:company_unscoped)
    #=> SELECT "clients".* FROM "clients" INNER JOIN "companies" ON "companies"."id" = "clients"."company_id"