sequel 5.39.0 → 5.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +52 -0
- data/MIT-LICENSE +1 -1
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/sql.rdoc +1 -1
- data/doc/testing.rdoc +3 -0
- data/lib/sequel/adapters/ado.rb +16 -16
- data/lib/sequel/adapters/jdbc.rb +2 -2
- data/lib/sequel/adapters/shared/postgres.rb +4 -2
- data/lib/sequel/adapters/shared/sqlite.rb +37 -3
- data/lib/sequel/core.rb +11 -0
- data/lib/sequel/database/misc.rb +1 -2
- data/lib/sequel/database/schema_generator.rb +35 -47
- data/lib/sequel/database/schema_methods.rb +4 -0
- data/lib/sequel/dataset/features.rb +10 -0
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/sql.rb +32 -10
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/date_arithmetic.rb +36 -24
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +8 -0
- data/lib/sequel/extensions/migration.rb +2 -0
- data/lib/sequel/extensions/named_timezones.rb +5 -1
- data/lib/sequel/extensions/pg_array.rb +1 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +34 -8
- data/lib/sequel/extensions/pg_row.rb +1 -0
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/model/associations.rb +68 -13
- data/lib/sequel/model/base.rb +23 -6
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_validations.rb +15 -1
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +7 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/json_serializer.rb +37 -22
- data/lib/sequel/plugins/nested_attributes.rb +8 -3
- data/lib/sequel/plugins/pg_array_associations.rb +10 -4
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/serialization.rb +8 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +6 -2
- data/lib/sequel/version.rb +1 -1
- metadata +36 -22
@@ -6,6 +6,14 @@
|
|
6
6
|
#
|
7
7
|
# Sequel.extension :blank
|
8
8
|
|
9
|
+
[FalseClass, Object, NilClass, Numeric, String, TrueClass].each do |klass|
|
10
|
+
# :nocov:
|
11
|
+
if klass.method_defined?(:blank?)
|
12
|
+
klass.send(:alias_method, :blank?, :blank?)
|
13
|
+
end
|
14
|
+
# :nocov:
|
15
|
+
end
|
16
|
+
|
9
17
|
class FalseClass
|
10
18
|
# false is always blank
|
11
19
|
def blank?
|
@@ -8,9 +8,10 @@
|
|
8
8
|
# DB.extension :date_arithmetic
|
9
9
|
#
|
10
10
|
# Then you can use the Sequel.date_add and Sequel.date_sub methods
|
11
|
-
# to return Sequel expressions
|
11
|
+
# to return Sequel expressions (this example shows the only supported
|
12
|
+
# keys for the second argument):
|
12
13
|
#
|
13
|
-
# add = Sequel.date_add(:date_column, years: 1, months: 2, days:
|
14
|
+
# add = Sequel.date_add(:date_column, years: 1, months: 2, weeks: 2, days: 1)
|
14
15
|
# sub = Sequel.date_sub(:date_column, hours: 1, minutes: 2, seconds: 3)
|
15
16
|
#
|
16
17
|
# In addition to specifying the interval as a hash, there is also
|
@@ -49,14 +50,12 @@ module Sequel
|
|
49
50
|
# Options:
|
50
51
|
# :cast :: Cast to the specified type instead of the default if casting
|
51
52
|
def date_sub(expr, interval, opts=OPTS)
|
52
|
-
|
53
|
-
|
54
|
-
interval.each{|k,v| h[k] = -v unless v.nil?}
|
55
|
-
h
|
56
|
-
else
|
57
|
-
-interval
|
53
|
+
if defined?(ActiveSupport::Duration) && interval.is_a?(ActiveSupport::Duration)
|
54
|
+
interval = interval.parts
|
58
55
|
end
|
59
|
-
|
56
|
+
parts = {}
|
57
|
+
interval.each{|k,v| parts[k] = -v unless v.nil?}
|
58
|
+
DateAdd.new(expr, parts, opts)
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
@@ -113,12 +112,12 @@ module Sequel
|
|
113
112
|
end
|
114
113
|
when :mssql, :h2, :access, :sqlanywhere
|
115
114
|
units = case db_type
|
116
|
-
when :mssql, :sqlanywhere
|
117
|
-
MSSQL_DURATION_UNITS
|
118
115
|
when :h2
|
119
116
|
H2_DURATION_UNITS
|
120
117
|
when :access
|
121
118
|
ACCESS_DURATION_UNITS
|
119
|
+
else
|
120
|
+
MSSQL_DURATION_UNITS
|
122
121
|
end
|
123
122
|
each_valid_interval_unit(h, units) do |value, sql_unit|
|
124
123
|
expr = Sequel.function(:DATEADD, sql_unit, value, expr)
|
@@ -186,22 +185,35 @@ module Sequel
|
|
186
185
|
# ActiveSupport::Duration :: Converted to a hash using the interval's parts.
|
187
186
|
def initialize(expr, interval, opts=OPTS)
|
188
187
|
@expr = expr
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
188
|
+
|
189
|
+
h = Hash.new(0)
|
190
|
+
interval = interval.parts unless interval.is_a?(Hash)
|
191
|
+
interval.each do |unit, value|
|
192
|
+
# skip nil values
|
193
|
+
next unless value
|
194
|
+
|
195
|
+
# Convert weeks to days, as ActiveSupport::Duration can use weeks,
|
196
|
+
# but the database-specific literalizers only support days.
|
197
|
+
if unit == :weeks
|
198
|
+
unit = :days
|
199
|
+
value *= 7
|
196
200
|
end
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
201
|
+
|
202
|
+
unless DatasetMethods::DURATION_UNITS.include?(unit)
|
203
|
+
raise Sequel::Error, "Invalid key used in DateAdd interval hash: #{unit.inspect}"
|
204
|
+
end
|
205
|
+
|
206
|
+
# Attempt to prevent SQL injection by users who pass untrusted strings
|
207
|
+
# as interval values. It doesn't make sense to support literal strings,
|
208
|
+
# due to the numeric adding below.
|
209
|
+
if value.is_a?(String)
|
210
|
+
raise Sequel::InvalidValue, "cannot provide String value as interval part: #{value.inspect}"
|
211
|
+
end
|
212
|
+
|
213
|
+
h[unit] += value
|
202
214
|
end
|
203
215
|
|
204
|
-
@interval.freeze
|
216
|
+
@interval = Hash[h].freeze
|
205
217
|
@cast_type = opts[:cast] if opts[:cast]
|
206
218
|
freeze
|
207
219
|
end
|
@@ -105,6 +105,14 @@ class String
|
|
105
105
|
yield Inflections if block_given?
|
106
106
|
Inflections
|
107
107
|
end
|
108
|
+
|
109
|
+
%w'classify constantize dasherize demodulize foreign_key humanize pluralize singularize tableize underscore'.each do |m|
|
110
|
+
# :nocov:
|
111
|
+
if method_defined?(m)
|
112
|
+
alias_method(m, m)
|
113
|
+
end
|
114
|
+
# :nocov:
|
115
|
+
end
|
108
116
|
|
109
117
|
# By default, camelize converts the string to UpperCamelCase. If the argument to camelize
|
110
118
|
# is set to :lower then camelize produces lowerCamelCase.
|
@@ -68,7 +68,9 @@ module Sequel
|
|
68
68
|
# Allow calling private methods for backwards compatibility
|
69
69
|
@db.send(method_sym, *args, &block)
|
70
70
|
end
|
71
|
+
# :nocov:
|
71
72
|
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
73
|
+
# :nocov:
|
72
74
|
|
73
75
|
# This object responds to all methods the database responds to.
|
74
76
|
def respond_to_missing?(meth, include_private)
|
@@ -84,9 +84,9 @@ module Sequel
|
|
84
84
|
def convert_output_time_other(v, output_timezone)
|
85
85
|
Time.at(v.to_i, :in => output_timezone)
|
86
86
|
end
|
87
|
-
else
|
88
87
|
# :nodoc:
|
89
88
|
# :nocov:
|
89
|
+
else
|
90
90
|
def convert_input_time_other(v, input_timezone)
|
91
91
|
local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
92
92
|
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
@@ -105,6 +105,8 @@ module Sequel
|
|
105
105
|
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
106
106
|
end
|
107
107
|
end
|
108
|
+
# :nodoc:
|
109
|
+
# :nocov:
|
108
110
|
end
|
109
111
|
|
110
112
|
# Handle both TZInfo 1 and TZInfo 2
|
@@ -142,6 +144,8 @@ module Sequel
|
|
142
144
|
# Convert timezone offset from UTC to the offset for the output_timezone
|
143
145
|
(v - local_offset).new_offset(local_offset)
|
144
146
|
end
|
147
|
+
# :nodoc:
|
148
|
+
# :nocov:
|
145
149
|
end
|
146
150
|
|
147
151
|
# Returns TZInfo::Timezone instance if given a String.
|
@@ -213,6 +213,7 @@ module Sequel
|
|
213
213
|
scalar_typecast_method = :"typecast_value_#{opts.fetch(:scalar_typecast, type)}"
|
214
214
|
define_method(meth){|v| typecast_value_pg_array(v, creator, scalar_typecast_method)}
|
215
215
|
private meth
|
216
|
+
alias_method(meth, meth)
|
216
217
|
end
|
217
218
|
|
218
219
|
@schema_type_classes[:"#{type}_array"] = PGArray
|
@@ -34,6 +34,13 @@
|
|
34
34
|
|
35
35
|
require 'active_support/duration'
|
36
36
|
|
37
|
+
# :nocov:
|
38
|
+
begin
|
39
|
+
require 'active_support/version'
|
40
|
+
rescue LoadError
|
41
|
+
end
|
42
|
+
# :nocov:
|
43
|
+
|
37
44
|
module Sequel
|
38
45
|
module Postgres
|
39
46
|
module IntervalDatabaseMethods
|
@@ -61,34 +68,47 @@ module Sequel
|
|
61
68
|
|
62
69
|
# Creates callable objects that convert strings into ActiveSupport::Duration instances.
|
63
70
|
class Parser
|
71
|
+
# Whether ActiveSupport::Duration.new takes parts as array instead of hash
|
72
|
+
USE_PARTS_ARRAY = !defined?(ActiveSupport::VERSION::STRING) || ActiveSupport::VERSION::STRING < '5.1'
|
73
|
+
|
74
|
+
if defined?(ActiveSupport::Duration::SECONDS_PER_MONTH)
|
75
|
+
SECONDS_PER_MONTH = ActiveSupport::Duration::SECONDS_PER_MONTH
|
76
|
+
SECONDS_PER_YEAR = ActiveSupport::Duration::SECONDS_PER_YEAR
|
77
|
+
# :nocov:
|
78
|
+
else
|
79
|
+
SECONDS_PER_MONTH = 2592000
|
80
|
+
SECONDS_PER_YEAR = 31557600
|
81
|
+
# :nocov:
|
82
|
+
end
|
83
|
+
|
64
84
|
# Parse the interval input string into an ActiveSupport::Duration instance.
|
65
85
|
def call(string)
|
66
86
|
raise(InvalidValue, "invalid or unhandled interval format: #{string.inspect}") unless matches = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d{2,10}):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/.match(string)
|
67
87
|
|
68
88
|
value = 0
|
69
|
-
parts =
|
89
|
+
parts = {}
|
70
90
|
|
71
91
|
if v = matches[1]
|
72
92
|
v = v.to_i
|
73
|
-
value +=
|
74
|
-
parts
|
93
|
+
value += SECONDS_PER_YEAR * v
|
94
|
+
parts[:years] = v
|
75
95
|
end
|
76
96
|
if v = matches[2]
|
77
97
|
v = v.to_i
|
78
|
-
value +=
|
79
|
-
parts
|
98
|
+
value += SECONDS_PER_MONTH * v
|
99
|
+
parts[:months] = v
|
80
100
|
end
|
81
101
|
if v = matches[3]
|
82
102
|
v = v.to_i
|
83
103
|
value += 86400 * v
|
84
|
-
parts
|
104
|
+
parts[:days] = v
|
85
105
|
end
|
86
106
|
if matches[5]
|
87
107
|
seconds = matches[5].to_i * 3600 + matches[6].to_i * 60
|
88
108
|
seconds += matches[8] ? matches[7].to_f : matches[7].to_i
|
89
109
|
seconds *= -1 if matches[4] == '-'
|
90
110
|
value += seconds
|
91
|
-
parts
|
111
|
+
parts[:seconds] = seconds
|
92
112
|
elsif matches[9] || matches[10] || matches[11]
|
93
113
|
seconds = 0
|
94
114
|
if v = matches[9]
|
@@ -101,8 +121,14 @@ module Sequel
|
|
101
121
|
seconds += matches[12] ? v.to_f : v.to_i
|
102
122
|
end
|
103
123
|
value += seconds
|
104
|
-
parts
|
124
|
+
parts[:seconds] = seconds
|
125
|
+
end
|
126
|
+
|
127
|
+
# :nocov:
|
128
|
+
if USE_PARTS_ARRAY
|
129
|
+
parts = parts.to_a
|
105
130
|
end
|
131
|
+
# :nocov:
|
106
132
|
|
107
133
|
ActiveSupport::Duration.new(value, parts)
|
108
134
|
end
|
@@ -263,7 +263,9 @@ module Sequel
|
|
263
263
|
# yielding each row to the block.
|
264
264
|
def eager_load_results(eo, &block)
|
265
265
|
rows = eo[:rows]
|
266
|
-
|
266
|
+
unless eo[:initialize_rows] == false
|
267
|
+
Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
|
268
|
+
end
|
267
269
|
if eo[:id_map]
|
268
270
|
ids = eo[:id_map].keys
|
269
271
|
return ids if ids.empty?
|
@@ -311,7 +313,8 @@ module Sequel
|
|
311
313
|
objects = loader.all(ids)
|
312
314
|
end
|
313
315
|
|
314
|
-
objects.each(&block)
|
316
|
+
Sequel.synchronize_with(eo[:mutex]){objects.each(&block)}
|
317
|
+
|
315
318
|
if strategy == :ruby
|
316
319
|
apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
|
317
320
|
end
|
@@ -1929,7 +1932,22 @@ module Sequel
|
|
1929
1932
|
# can be easily overridden in the class itself while allowing for
|
1930
1933
|
# super to be called.
|
1931
1934
|
def association_module_def(name, opts=OPTS, &block)
|
1932
|
-
association_module(opts)
|
1935
|
+
mod = association_module(opts)
|
1936
|
+
mod.send(:define_method, name, &block)
|
1937
|
+
mod.send(:alias_method, name, name)
|
1938
|
+
end
|
1939
|
+
|
1940
|
+
# Add a method to the module included in the class, so the method
|
1941
|
+
# can be easily overridden in the class itself while allowing for
|
1942
|
+
# super to be called. This method allows passing keywords through
|
1943
|
+
# the defined methods.
|
1944
|
+
def association_module_delegate_def(name, opts, &block)
|
1945
|
+
mod = association_module(opts)
|
1946
|
+
mod.send(:define_method, name, &block)
|
1947
|
+
# :nocov:
|
1948
|
+
mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
|
1949
|
+
# :nocov:
|
1950
|
+
mod.send(:alias_method, name, name)
|
1933
1951
|
end
|
1934
1952
|
|
1935
1953
|
# Add a private method to the module included in the class.
|
@@ -1981,17 +1999,17 @@ module Sequel
|
|
1981
1999
|
|
1982
2000
|
if adder = opts[:adder]
|
1983
2001
|
association_module_private_def(opts[:_add_method], opts, &adder)
|
1984
|
-
|
2002
|
+
association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
|
1985
2003
|
end
|
1986
2004
|
|
1987
2005
|
if remover = opts[:remover]
|
1988
2006
|
association_module_private_def(opts[:_remove_method], opts, &remover)
|
1989
|
-
|
2007
|
+
association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
|
1990
2008
|
end
|
1991
2009
|
|
1992
2010
|
if clearer = opts[:clearer]
|
1993
2011
|
association_module_private_def(opts[:_remove_all_method], opts, &clearer)
|
1994
|
-
|
2012
|
+
association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
|
1995
2013
|
end
|
1996
2014
|
end
|
1997
2015
|
|
@@ -2423,6 +2441,9 @@ module Sequel
|
|
2423
2441
|
run_association_callbacks(opts, :after_add, o)
|
2424
2442
|
o
|
2425
2443
|
end
|
2444
|
+
# :nocov:
|
2445
|
+
ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
|
2446
|
+
# :nocov:
|
2426
2447
|
|
2427
2448
|
# Add/Set the current object to/as the given object's reciprocal association.
|
2428
2449
|
def add_reciprocal_object(opts, o)
|
@@ -2565,6 +2586,9 @@ module Sequel
|
|
2565
2586
|
associations[opts[:name]] = []
|
2566
2587
|
ret
|
2567
2588
|
end
|
2589
|
+
# :nocov:
|
2590
|
+
ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
|
2591
|
+
# :nocov:
|
2568
2592
|
|
2569
2593
|
# Remove the given associated object from the given association
|
2570
2594
|
def remove_associated_object(opts, o, *args)
|
@@ -2586,6 +2610,9 @@ module Sequel
|
|
2586
2610
|
run_association_callbacks(opts, :after_remove, o)
|
2587
2611
|
o
|
2588
2612
|
end
|
2613
|
+
# :nocov:
|
2614
|
+
ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
|
2615
|
+
# :nocov:
|
2589
2616
|
|
2590
2617
|
# Check that the object from the associated table specified by the primary key
|
2591
2618
|
# is currently associated to the receiver. If it is associated, return the object, otherwise
|
@@ -3350,15 +3377,30 @@ module Sequel
|
|
3350
3377
|
egl.dup
|
3351
3378
|
end
|
3352
3379
|
|
3353
|
-
# Eagerly load all specified associations
|
3380
|
+
# Eagerly load all specified associations.
|
3354
3381
|
def eager_load(a, eager_assoc=@opts[:eager])
|
3355
3382
|
return if a.empty?
|
3383
|
+
|
3384
|
+
# Reflections for all associations to eager load
|
3385
|
+
reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
|
3386
|
+
|
3387
|
+
perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
|
3388
|
+
|
3389
|
+
reflections.each do |r|
|
3390
|
+
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
|
3391
|
+
end
|
3392
|
+
|
3393
|
+
nil
|
3394
|
+
end
|
3395
|
+
|
3396
|
+
# Prepare a hash loaders and eager options which will be used to implement the eager loading.
|
3397
|
+
def prepare_eager_load(a, reflections, eager_assoc)
|
3398
|
+
eager_load_data = {}
|
3399
|
+
|
3356
3400
|
# Key is foreign/primary key name symbol.
|
3357
3401
|
# Value is hash with keys being foreign/primary key values (generally integers)
|
3358
3402
|
# and values being an array of current model objects with that specific foreign/primary key
|
3359
3403
|
key_hash = {}
|
3360
|
-
# Reflections for all associations to eager load
|
3361
|
-
reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
|
3362
3404
|
|
3363
3405
|
# Populate the key_hash entry for each association being eagerly loaded
|
3364
3406
|
reflections.each do |r|
|
@@ -3389,7 +3431,6 @@ module Sequel
|
|
3389
3431
|
id_map = nil
|
3390
3432
|
end
|
3391
3433
|
|
3392
|
-
loader = r[:eager_loader]
|
3393
3434
|
associations = eager_assoc[r[:name]]
|
3394
3435
|
if associations.respond_to?(:call)
|
3395
3436
|
eager_block = associations
|
@@ -3397,9 +3438,23 @@ module Sequel
|
|
3397
3438
|
elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
|
3398
3439
|
eager_block, associations = pr_assoc
|
3399
3440
|
end
|
3400
|
-
|
3401
|
-
|
3402
|
-
end
|
3441
|
+
|
3442
|
+
eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
|
3443
|
+
end
|
3444
|
+
|
3445
|
+
eager_load_data
|
3446
|
+
end
|
3447
|
+
|
3448
|
+
# Using the hash of loaders and eager options, perform the eager loading.
|
3449
|
+
def perform_eager_loads(eager_load_data)
|
3450
|
+
eager_load_data.map do |loader, eo|
|
3451
|
+
perform_eager_load(loader, eo)
|
3452
|
+
end
|
3453
|
+
end
|
3454
|
+
|
3455
|
+
# Perform eager loading for a single association using the loader and eager options.
|
3456
|
+
def perform_eager_load(loader, eo)
|
3457
|
+
loader.call(eo)
|
3403
3458
|
end
|
3404
3459
|
|
3405
3460
|
# Return a subquery expression for filering by a many_to_many association
|