Allow unscoping of preload and eager_load associations

Adds the ability to unscope preload and eager_load associations.
This is the same functionality as `unscope(:includes)` which
may chose to use eager_loading or preloading based on the
overall query complexity. In cases where the type of association
loading is explicit (eager_load and preload), this allows for
unscoping the explicit association loading.  This is helpful when
taking an existing query and performing an aggregate when
has_many associations have explicitly asked for eager_load or
preload. In the example below, unscoping the two has_many
associations removes the extra preload query and eager_load
join.

        query.eager_load!(:has_many_association1)
        query.preload!(:has_many_association2)
        query.unscope(:eager_load, :preload).group(:id).select(:id)

There are also cases where depending on the aggregation
and select the explict preload fails when the select does not
include the preload association key. This issue led me to
write this fix.
This commit is contained in:
Dave Morehouse 2022-05-21 09:52:10 -05:00
parent aab04ec39c
commit e2c720fbb8
3 changed files with 28 additions and 1 deletions

View File

@ -1,3 +1,15 @@
* Allow unscoping of preload and eager_load associations
Added the ability to unscope preload and eager_load associations just like
includes, joins, etc. See ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES
for the full list of supported unscopable scopes.
```ruby
query.unscope(:eager_load, :preload).group(:id).select(:id)
```
*David Morehouse*
* Clear locking column on #dup
This change fixes not to duplicate locking_column like id and timestamps.

View File

@ -569,7 +569,8 @@ module ActiveRecord
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
:limit, :offset, :joins, :left_outer_joins, :annotate,
:includes, :from, :readonly, :having, :optimizer_hints])
:includes, :eager_load, :preload, :from, :readonly,
:having, :optimizer_hints])
# Removes an unwanted relation that is already defined on a chain of relations.
# This is useful when passing around chains of relations and would like to

View File

@ -402,6 +402,20 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
def test_unscope_eager_load
expected = Developer.all.collect(&:name)
received = Developer.eager_load(:projects).select(:id).unscope(:eager_load, :select)
assert_equal expected, received.collect(&:name)
assert_equal false, received.first.projects.loaded?
end
def test_unscope_preloads
expected = Developer.all.collect(&:name)
received = Developer.preload(:projects).select(:id).unscope(:preload, :select)
assert_equal expected, received.collect(&:name)
assert_equal false, received.first.projects.loaded?
end
def test_unscope_having
expected = DeveloperOrderedBySalary.all.collect(&:name)
received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect(&:name)