Matt Grande
Matt Grande

Reputation: 12157

Can a scope in a has_many fully replace the default join?

I have a model with a complicated join:

class Submission < ApplicationRecord
  has_many :histories,
           ->(s) { where(/* A complex query */) },
           class_name: 'SubmissionFieldHistory'
end

I'd like that scope query to replace the default conditions that gets generated by ActiveRecord, however it seems to be added to the conditions.

Is there a simple way to say has_many :histories, :but_join_it_how_i_say?

Upvotes: 0

Views: 154

Answers (1)

Jay-Ar Polidario
Jay-Ar Polidario

Reputation: 6603

Assumption:

If by "default conditions", you meant the default SQL condition WHERE HISTORIES_TABLE.submission_id = SOME_SUBMISSION_ID.

Solution 1:

To specify a custom query for has_many, use unscope:

class Submission < ApplicationRecord
  has_many :histories,
    ->(s) { unscope(:where).where(/* A complex query */) },
    class_name: 'SubmissionFieldHistory'
end

unscope(:where) above will remove any/all prior WHERE SQL queries in the chain, of which one of them includes the default WHERE HISTORIES_TABLE.submission_id = SOME_SUBMISSION_ID, of which is what you want to remove.

Usage:

submission = Submission.find(1)
submission.histories
# SubmissionFieldHistory Load (5.5ms)  SELECT  "submission_field_histories".* FROM "submission_field_histories"
#   WHERE /* A complex query */ LIMIT $1  [["LIMIT", 11]]

Solution 2:

However, because unscope(:where) removes all prior WHERE conditions, it also then removes the condition supplied by default_scope (if you have a default_scope inside the SubmissionFieldHistory). In order to also make it work for default_scope, you can use the more specific unscope(where: SOME_ATTRIBUTE) instead like below:

app/models/submission_field_history.rb

class SubmissionFieldHistory < ApplicationRecord
  # example: (default to not fetching anymore "deleted" records)
  default_scope { where(is_deleted: false) }

  belongs_to :submission
end

app/models/submission.rb

class Submission < ApplicationRecord
  has_many :histories,
    ->(s) { unscope(where: :submission_id).where(/* A complex query */) },
    class_name: 'SubmissionFieldHistory'

Usage:

submission = Submission.find(1)
submission.histories
# SubmissionFieldHistory Load (5.5ms)  SELECT "submission_field_histories".* FROM "submission_field_histories"
#   WHERE "submission_field_histories".is_deleted = false
#   AND /* A complex query */ LIMIT $1  [["LIMIT", 11]]

Upvotes: 1

Related Questions