ascherman
ascherman

Reputation: 1832

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

Upvotes: 5

Views: 2315

Answers (5)

iGian
iGian

Reputation: 11193

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"

Upvotes: 8

Arvind singh
Arvind singh

Reputation: 1402

Seems like there isn't any straightforward solution that you are looking for. There are couple of discussion threads for reference.

https://github.com/rails/rails/issues/20679

https://github.com/rails/rails/issues/20011

But I liked the way what @iGian has suggested.

Upvotes: 0

David Chandler
David Chandler

Reputation: 349

You might try https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-unscope but I would've expected https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-unscoped to work. You might try it again without a block, letting it come after the undesired scope is applied.

Upvotes: 0

Dyaniyal Wilson
Dyaniyal Wilson

Reputation: 1060

Apply directly as a class method

Client.unscoped.joins(:company).to_sql

Upvotes: 0

Jon
Jon

Reputation: 343

You can do it manually:

Client.joins('inner join company on companies.id = clients.company_id')

Upvotes: -1

Related Questions