sequel 5.41.0 → 5.46.0

Sign up to get free protection for your applications and to get access to all the features.
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