sequel 5.41.0 → 5.46.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +1 -2
  4. data/doc/association_basics.rdoc +22 -3
  5. data/doc/release_notes/5.42.0.txt +136 -0
  6. data/doc/release_notes/5.43.0.txt +98 -0
  7. data/doc/release_notes/5.44.0.txt +32 -0
  8. data/doc/release_notes/5.45.0.txt +34 -0
  9. data/doc/release_notes/5.46.0.txt +87 -0
  10. data/doc/testing.rdoc +3 -0
  11. data/doc/virtual_rows.rdoc +1 -1
  12. data/lib/sequel/adapters/ado.rb +16 -16
  13. data/lib/sequel/adapters/odbc.rb +5 -1
  14. data/lib/sequel/adapters/shared/postgres.rb +0 -12
  15. data/lib/sequel/adapters/shared/sqlite.rb +8 -4
  16. data/lib/sequel/core.rb +11 -0
  17. data/lib/sequel/database/misc.rb +1 -2
  18. data/lib/sequel/database/schema_generator.rb +35 -47
  19. data/lib/sequel/database/schema_methods.rb +4 -0
  20. data/lib/sequel/dataset/query.rb +1 -3
  21. data/lib/sequel/dataset/sql.rb +7 -0
  22. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  23. data/lib/sequel/extensions/date_arithmetic.rb +29 -16
  24. data/lib/sequel/extensions/pg_enum.rb +1 -1
  25. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  26. data/lib/sequel/model/associations.rb +146 -75
  27. data/lib/sequel/model/base.rb +2 -2
  28. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  29. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  30. data/lib/sequel/plugins/column_encryption.rb +728 -0
  31. data/lib/sequel/plugins/composition.rb +2 -1
  32. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  33. data/lib/sequel/plugins/json_serializer.rb +37 -22
  34. data/lib/sequel/plugins/nested_attributes.rb +5 -2
  35. data/lib/sequel/plugins/pg_array_associations.rb +52 -38
  36. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  37. data/lib/sequel/plugins/serialization.rb +8 -3
  38. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
  39. data/lib/sequel/plugins/unused_associations.rb +500 -0
  40. data/lib/sequel/version.rb +1 -1
  41. metadata +19 -3
@@ -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: 3)
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
@@ -54,7 +55,6 @@ module Sequel
54
55
  end
55
56
  parts = {}
56
57
  interval.each{|k,v| parts[k] = -v unless v.nil?}
57
- parts
58
58
  DateAdd.new(expr, parts, opts)
59
59
  end
60
60
  end
@@ -185,22 +185,35 @@ module Sequel
185
185
  # ActiveSupport::Duration :: Converted to a hash using the interval's parts.
186
186
  def initialize(expr, interval, opts=OPTS)
187
187
  @expr = expr
188
- @interval = if interval.is_a?(Hash)
189
- interval.each_value do |v|
190
- # Attempt to prevent SQL injection by users who pass untrusted strings
191
- # as interval values.
192
- if v.is_a?(String) && !v.is_a?(LiteralString)
193
- raise Sequel::InvalidValue, "cannot provide String value as interval part: #{v.inspect}"
194
- end
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
200
+ end
201
+
202
+ unless DatasetMethods::DURATION_UNITS.include?(unit)
203
+ raise Sequel::Error, "Invalid key used in DateAdd interval hash: #{unit.inspect}"
195
204
  end
196
- Hash[interval]
197
- else
198
- h = Hash.new(0)
199
- interval.parts.each{|unit, value| h[unit] += value}
200
- Hash[h]
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
201
214
  end
202
215
 
203
- @interval.freeze
216
+ @interval = Hash[h].freeze
204
217
  @cast_type = opts[:cast] if opts[:cast]
205
218
  freeze
206
219
  end
@@ -42,7 +42,7 @@
42
42
  #
43
43
  # This extension integrates with the pg_array extension. If you plan
44
44
  # to use arrays of enum types, load the pg_array extension before the
45
- # pg_interval extension:
45
+ # pg_enum extension:
46
46
  #
47
47
  # DB.extension :pg_array, :pg_enum
48
48
  #
@@ -12,7 +12,9 @@
12
12
  #
13
13
  # How accurate this count is depends on the number of rows
14
14
  # added/deleted from the table since the last time it was
15
- # analyzed.
15
+ # analyzed. If the table has not been vacuumed or analyzed
16
+ # yet, this can return 0 or -1 depending on the PostgreSQL
17
+ # version in use.
16
18
  #
17
19
  # To load the extension into the database:
18
20
  #
@@ -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
- initialize_association_cache(rows) unless eo[:initialize_rows] == false
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
@@ -1592,6 +1595,7 @@ module Sequel
1592
1595
  # === Multiple Types
1593
1596
  # :adder :: Proc used to define the private _add_* method for doing the database work
1594
1597
  # to associate the given object to the current object (*_to_many assocations).
1598
+ # Set to nil to not define a add_* method for the association.
1595
1599
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1596
1600
  # after a new item is added to the association.
1597
1601
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1620,6 +1624,7 @@ module Sequel
1620
1624
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1621
1625
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1622
1626
  # to remove all objects associated to the current object (*_to_many assocations).
1627
+ # Set to nil to not define a remove_all_* method for the association.
1623
1628
  # :clone :: Merge the current options and block into the options and block used in defining
1624
1629
  # the given association. Can be used to DRY up a bunch of similar associations that
1625
1630
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1674,7 +1679,7 @@ module Sequel
1674
1679
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1675
1680
  # the association via +eager_graph+, instead of the default conditions specified by the
1676
1681
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1677
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1682
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1678
1683
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1679
1684
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1680
1685
  # automatically qualified when using eager_graph.
@@ -1686,6 +1691,10 @@ module Sequel
1686
1691
  # limit (first element) and an offset (second element).
1687
1692
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1688
1693
  # to the module containing the model's columns.
1694
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1695
+ # method is never used.
1696
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1697
+ # method is never used.
1689
1698
  # :order :: the column(s) by which to order the association dataset. Can be a
1690
1699
  # singular column symbol or an array of column symbols.
1691
1700
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1698,6 +1707,7 @@ module Sequel
1698
1707
  # the current association's key(s). Set to nil to not use a reciprocal.
1699
1708
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1700
1709
  # to remove the association between the given object and the current object (*_to_many assocations).
1710
+ # Set to nil to not define a remove_* method for the association.
1701
1711
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1702
1712
  # that uses joins, which means it doesn't include the attributes from the
1703
1713
  # join table. If you want to include the join table attributes, you can
@@ -1706,6 +1716,7 @@ module Sequel
1706
1716
  # the same name in both the join table and the associated table.
1707
1717
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1708
1718
  # between the given object and the current object (*_to_one associations).
1719
+ # Set to nil to not define a setter method for the association.
1709
1720
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1710
1721
  # loading limited associations using the default :union strategy.
1711
1722
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1838,8 +1849,7 @@ module Sequel
1838
1849
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1839
1850
  opts.delete(:class) unless opts[:class]
1840
1851
 
1841
- send(:"def_#{type}", opts)
1842
- def_association_instance_methods(opts)
1852
+ def_association(opts)
1843
1853
 
1844
1854
  orig_opts.delete(:clone)
1845
1855
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1953,6 +1963,13 @@ module Sequel
1953
1963
  association_module(opts).send(:private, name)
1954
1964
  end
1955
1965
 
1966
+ # Delegate to the type-specific association method to setup the
1967
+ # association, and define the association instance methods.
1968
+ def def_association(opts)
1969
+ send(:"def_#{opts[:type]}", opts)
1970
+ def_association_instance_methods(opts)
1971
+ end
1972
+
1956
1973
  # Adds the association method to the association methods module.
1957
1974
  def def_association_method(opts)
1958
1975
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1978,13 +1995,13 @@ module Sequel
1978
1995
  opts[:setter_method] = :"#{opts[:name]}="
1979
1996
  end
1980
1997
 
1981
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
1998
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1982
1999
  if opts[:block]
1983
2000
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1984
2001
  end
1985
2002
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1986
2003
  opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1987
- def_association_method(opts)
2004
+ def_association_method(opts) unless opts[:no_association_method]
1988
2005
 
1989
2006
  return if opts[:read_only]
1990
2007
 
@@ -2072,50 +2089,60 @@ module Sequel
2072
2089
  return if opts[:read_only]
2073
2090
 
2074
2091
  if one_through_one
2075
- opts[:setter] ||= proc do |o|
2076
- h = {}
2077
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2078
- jtds = _join_table_dataset(opts).where(lh)
2092
+ unless opts.has_key?(:setter)
2093
+ opts[:setter] = proc do |o|
2094
+ h = {}
2095
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2096
+ jtds = _join_table_dataset(opts).where(lh)
2079
2097
 
2080
- checked_transaction do
2081
- current = jtds.first
2098
+ checked_transaction do
2099
+ current = jtds.first
2082
2100
 
2083
- if o
2084
- new_values = []
2085
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2086
- end
2087
-
2088
- if current
2089
- current_values = rcks.map{|k| current[k]}
2090
- jtds = jtds.where(rcks.zip(current_values))
2091
2101
  if o
2092
- if current_values != new_values
2093
- jtds.update(h)
2102
+ new_values = []
2103
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2104
+ end
2105
+
2106
+ if current
2107
+ current_values = rcks.map{|k| current[k]}
2108
+ jtds = jtds.where(rcks.zip(current_values))
2109
+ if o
2110
+ if current_values != new_values
2111
+ jtds.update(h)
2112
+ end
2113
+ else
2114
+ jtds.delete
2094
2115
  end
2095
- else
2096
- jtds.delete
2116
+ elsif o
2117
+ lh.each{|k,v| h[k] = v}
2118
+ jtds.insert(h)
2097
2119
  end
2098
- elsif o
2099
- lh.each{|k,v| h[k] = v}
2100
- jtds.insert(h)
2101
2120
  end
2102
2121
  end
2103
2122
  end
2104
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2123
+ if opts.fetch(:setter, true)
2124
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2125
+ end
2105
2126
  else
2106
- opts[:adder] ||= proc do |o|
2107
- h = {}
2108
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2109
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2110
- _join_table_dataset(opts).insert(h)
2127
+ unless opts.has_key?(:adder)
2128
+ opts[:adder] = proc do |o|
2129
+ h = {}
2130
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2131
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2132
+ _join_table_dataset(opts).insert(h)
2133
+ end
2111
2134
  end
2112
2135
 
2113
- opts[:remover] ||= proc do |o|
2114
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2136
+ unless opts.has_key?(:remover)
2137
+ opts[:remover] = proc do |o|
2138
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2139
+ end
2115
2140
  end
2116
2141
 
2117
- opts[:clearer] ||= proc do
2118
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2142
+ unless opts.has_key?(:clearer)
2143
+ opts[:clearer] = proc do
2144
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2145
+ end
2119
2146
  end
2120
2147
  end
2121
2148
  end
@@ -2172,8 +2199,12 @@ module Sequel
2172
2199
 
2173
2200
  return if opts[:read_only]
2174
2201
 
2175
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2176
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2202
+ unless opts.has_key?(:setter)
2203
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2204
+ end
2205
+ if opts.fetch(:setter, true)
2206
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2207
+ end
2177
2208
  end
2178
2209
 
2179
2210
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2240,49 +2271,59 @@ module Sequel
2240
2271
  cks.each{|k| ck_nil_hash[k] = nil}
2241
2272
 
2242
2273
  if one_to_one
2243
- opts[:setter] ||= proc do |o|
2244
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2274
+ unless opts.has_key?(:setter)
2275
+ opts[:setter] = proc do |o|
2276
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2245
2277
 
2246
- if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2247
- if old = up_ds.first
2248
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2278
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2279
+ if old = up_ds.first
2280
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2281
+ end
2282
+ save_old = true
2249
2283
  end
2250
- save_old = true
2251
- end
2252
2284
 
2253
- if o
2254
- if !o.new? && !save_old
2255
- up_ds = up_ds.exclude(o.pk_hash)
2285
+ if o
2286
+ if !o.new? && !save_old
2287
+ up_ds = up_ds.exclude(o.pk_hash)
2288
+ end
2289
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2256
2290
  end
2257
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2258
- end
2259
2291
 
2260
- checked_transaction do
2261
- if save_old
2262
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2263
- else
2264
- up_ds.skip_limit_check.update(ck_nil_hash)
2265
- end
2292
+ checked_transaction do
2293
+ if save_old
2294
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2295
+ else
2296
+ up_ds.skip_limit_check.update(ck_nil_hash)
2297
+ end
2266
2298
 
2267
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2299
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2300
+ end
2268
2301
  end
2269
2302
  end
2270
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2303
+ if opts.fetch(:setter, true)
2304
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2305
+ end
2271
2306
  else
2272
2307
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2273
2308
 
2274
- opts[:adder] ||= proc do |o|
2275
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2276
- o.save(save_opts)
2309
+ unless opts.has_key?(:adder)
2310
+ opts[:adder] = proc do |o|
2311
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2312
+ o.save(save_opts)
2313
+ end
2277
2314
  end
2278
2315
 
2279
- opts[:remover] ||= proc do |o|
2280
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2281
- o.save(save_opts)
2316
+ unless opts.has_key?(:remover)
2317
+ opts[:remover] = proc do |o|
2318
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2319
+ o.save(save_opts)
2320
+ end
2282
2321
  end
2283
2322
 
2284
- opts[:clearer] ||= proc do
2285
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2323
+ unless opts.has_key?(:clearer)
2324
+ opts[:clearer] = proc do
2325
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2326
+ end
2286
2327
  end
2287
2328
  end
2288
2329
  end
@@ -3008,6 +3049,8 @@ module Sequel
3008
3049
  # You can specify an custom alias and/or join type on a per-association basis by providing an
3009
3050
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
3010
3051
  #
3052
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3053
+ #
3011
3054
  # Examples:
3012
3055
  #
3013
3056
  # # For each album, eager_graph load the artist
@@ -3374,15 +3417,30 @@ module Sequel
3374
3417
  egl.dup
3375
3418
  end
3376
3419
 
3377
- # Eagerly load all specified associations
3420
+ # Eagerly load all specified associations.
3378
3421
  def eager_load(a, eager_assoc=@opts[:eager])
3379
3422
  return if a.empty?
3423
+
3424
+ # Reflections for all associations to eager load
3425
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3426
+
3427
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3428
+
3429
+ reflections.each do |r|
3430
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3431
+ end
3432
+
3433
+ nil
3434
+ end
3435
+
3436
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3437
+ def prepare_eager_load(a, reflections, eager_assoc)
3438
+ eager_load_data = {}
3439
+
3380
3440
  # Key is foreign/primary key name symbol.
3381
3441
  # Value is hash with keys being foreign/primary key values (generally integers)
3382
3442
  # and values being an array of current model objects with that specific foreign/primary key
3383
3443
  key_hash = {}
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
3444
 
3387
3445
  # Populate the key_hash entry for each association being eagerly loaded
3388
3446
  reflections.each do |r|
@@ -3413,7 +3471,6 @@ module Sequel
3413
3471
  id_map = nil
3414
3472
  end
3415
3473
 
3416
- loader = r[:eager_loader]
3417
3474
  associations = eager_assoc[r[:name]]
3418
3475
  if associations.respond_to?(:call)
3419
3476
  eager_block = associations
@@ -3421,9 +3478,23 @@ module Sequel
3421
3478
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3422
3479
  eager_block, associations = pr_assoc
3423
3480
  end
3424
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
3425
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3426
- end
3481
+
3482
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3483
+ end
3484
+
3485
+ eager_load_data
3486
+ end
3487
+
3488
+ # Using the hash of loaders and eager options, perform the eager loading.
3489
+ def perform_eager_loads(eager_load_data)
3490
+ eager_load_data.map do |loader, eo|
3491
+ perform_eager_load(loader, eo)
3492
+ end
3493
+ end
3494
+
3495
+ # Perform eager loading for a single association using the loader and eager options.
3496
+ def perform_eager_load(loader, eo)
3497
+ loader.call(eo)
3427
3498
  end
3428
3499
 
3429
3500
  # Return a subquery expression for filering by a many_to_many association