sequel 5.39.0 → 5.63.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +308 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +89 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/schema_modification.rdoc +1 -1
  40. data/doc/security.rdoc +9 -9
  41. data/doc/sql.rdoc +27 -15
  42. data/doc/testing.rdoc +22 -11
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +2 -2
  45. data/lib/sequel/adapters/ado/access.rb +1 -1
  46. data/lib/sequel/adapters/ado.rb +17 -17
  47. data/lib/sequel/adapters/amalgalite.rb +3 -5
  48. data/lib/sequel/adapters/ibmdb.rb +2 -2
  49. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  50. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  51. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc.rb +16 -18
  54. data/lib/sequel/adapters/mysql.rb +80 -67
  55. data/lib/sequel/adapters/mysql2.rb +54 -49
  56. data/lib/sequel/adapters/odbc.rb +6 -2
  57. data/lib/sequel/adapters/oracle.rb +3 -3
  58. data/lib/sequel/adapters/postgres.rb +83 -40
  59. data/lib/sequel/adapters/shared/access.rb +11 -1
  60. data/lib/sequel/adapters/shared/db2.rb +30 -0
  61. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  62. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  63. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  64. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  65. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
  66. data/lib/sequel/adapters/shared/sqlite.rb +102 -11
  67. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  68. data/lib/sequel/adapters/sqlite.rb +60 -18
  69. data/lib/sequel/adapters/tinytds.rb +1 -1
  70. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  71. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  72. data/lib/sequel/ast_transformer.rb +6 -0
  73. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  74. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  75. data/lib/sequel/connection_pool/single.rb +6 -8
  76. data/lib/sequel/connection_pool/threaded.rb +8 -8
  77. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  78. data/lib/sequel/connection_pool.rb +47 -30
  79. data/lib/sequel/core.rb +28 -18
  80. data/lib/sequel/database/connecting.rb +26 -2
  81. data/lib/sequel/database/misc.rb +69 -14
  82. data/lib/sequel/database/query.rb +38 -1
  83. data/lib/sequel/database/schema_generator.rb +45 -52
  84. data/lib/sequel/database/schema_methods.rb +17 -1
  85. data/lib/sequel/dataset/actions.rb +107 -13
  86. data/lib/sequel/dataset/features.rb +20 -0
  87. data/lib/sequel/dataset/misc.rb +1 -1
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +118 -16
  90. data/lib/sequel/dataset/sql.rb +177 -47
  91. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  92. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  93. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  94. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  98. data/lib/sequel/extensions/core_refinements.rb +36 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  100. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  101. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  102. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  103. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  104. data/lib/sequel/extensions/inflector.rb +9 -1
  105. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  106. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  107. data/lib/sequel/extensions/migration.rb +7 -2
  108. data/lib/sequel/extensions/named_timezones.rb +26 -6
  109. data/lib/sequel/extensions/pagination.rb +1 -1
  110. data/lib/sequel/extensions/pg_array.rb +23 -3
  111. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  112. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  113. data/lib/sequel/extensions/pg_enum.rb +1 -1
  114. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  115. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  116. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  117. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  118. data/lib/sequel/extensions/pg_inet.rb +10 -11
  119. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  120. data/lib/sequel/extensions/pg_interval.rb +45 -19
  121. data/lib/sequel/extensions/pg_json.rb +13 -15
  122. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  123. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  124. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  125. data/lib/sequel/extensions/pg_range.rb +10 -23
  126. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  127. data/lib/sequel/extensions/pg_row.rb +19 -13
  128. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  129. data/lib/sequel/extensions/query.rb +2 -0
  130. data/lib/sequel/extensions/s.rb +2 -1
  131. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  132. data/lib/sequel/extensions/server_block.rb +8 -12
  133. data/lib/sequel/extensions/sql_comments.rb +110 -3
  134. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  135. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  136. data/lib/sequel/extensions/string_agg.rb +1 -1
  137. data/lib/sequel/extensions/string_date_time.rb +19 -23
  138. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  139. data/lib/sequel/model/associations.rb +325 -96
  140. data/lib/sequel/model/base.rb +51 -27
  141. data/lib/sequel/model/errors.rb +10 -1
  142. data/lib/sequel/model/inflections.rb +1 -1
  143. data/lib/sequel/model/plugins.rb +5 -0
  144. data/lib/sequel/plugins/association_proxies.rb +2 -0
  145. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  146. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  147. data/lib/sequel/plugins/auto_validations.rb +87 -15
  148. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  149. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  150. data/lib/sequel/plugins/column_encryption.rb +728 -0
  151. data/lib/sequel/plugins/composition.rb +10 -4
  152. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  153. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  154. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  155. data/lib/sequel/plugins/dirty.rb +1 -1
  156. data/lib/sequel/plugins/enum.rb +124 -0
  157. data/lib/sequel/plugins/finder.rb +3 -1
  158. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  159. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  160. data/lib/sequel/plugins/json_serializer.rb +39 -24
  161. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  162. data/lib/sequel/plugins/list.rb +3 -1
  163. data/lib/sequel/plugins/many_through_many.rb +108 -9
  164. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  165. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  166. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  167. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  168. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  169. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  170. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  171. data/lib/sequel/plugins/serialization.rb +9 -3
  172. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  173. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  174. data/lib/sequel/plugins/sql_comments.rb +189 -0
  175. data/lib/sequel/plugins/static_cache.rb +1 -1
  176. data/lib/sequel/plugins/subclasses.rb +28 -11
  177. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  178. data/lib/sequel/plugins/timestamps.rb +1 -1
  179. data/lib/sequel/plugins/unused_associations.rb +521 -0
  180. data/lib/sequel/plugins/update_or_create.rb +1 -1
  181. data/lib/sequel/plugins/validate_associated.rb +22 -12
  182. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  183. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  184. data/lib/sequel/sql.rb +1 -1
  185. data/lib/sequel/timezones.rb +12 -14
  186. data/lib/sequel/version.rb +1 -1
  187. metadata +97 -43
@@ -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?
@@ -272,7 +274,9 @@ module Sequel
272
274
  cascade = eo[:associations]
273
275
  eager_limit = nil
274
276
 
275
- if eo[:eager_block] || eo[:loader] == false
277
+ if eo[:no_results]
278
+ no_results = true
279
+ elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
276
280
  ds = eager_loading_dataset(eo)
277
281
 
278
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -295,6 +299,11 @@ module Sequel
295
299
  strategy = :ruby if strategy == :correlated_subquery
296
300
  strategy = nil if strategy == :ruby && assign_singular?
297
301
  objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
302
+
303
+ if strategy == :window_function
304
+ delete_rn = ds.row_number_column
305
+ objects.each{|obj| obj.values.delete(delete_rn)}
306
+ end
298
307
  elsif strategy == :union
299
308
  objects = []
300
309
  ds = associated_dataset
@@ -311,7 +320,8 @@ module Sequel
311
320
  objects = loader.all(ids)
312
321
  end
313
322
 
314
- objects.each(&block)
323
+ Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
324
+
315
325
  if strategy == :ruby
316
326
  apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
317
327
  end
@@ -635,9 +645,7 @@ module Sequel
635
645
  # given the hash passed to the eager loader.
636
646
  def eager_loading_dataset(eo=OPTS)
637
647
  ds = eo[:dataset] || associated_eager_dataset
638
- if id_map = eo[:id_map]
639
- ds = ds.where(eager_loading_predicate_condition(id_map.keys))
640
- end
648
+ ds = eager_loading_set_predicate_condition(ds, eo)
641
649
  if associations = eo[:associations]
642
650
  ds = ds.eager(associations)
643
651
  end
@@ -664,6 +672,15 @@ module Sequel
664
672
  self[:model].default_eager_limit_strategy || :ruby
665
673
  end
666
674
 
675
+ # Set the predicate condition for the eager loading dataset based on the id map
676
+ # in the eager loading options.
677
+ def eager_loading_set_predicate_condition(ds, eo)
678
+ if id_map = eo[:id_map]
679
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
680
+ end
681
+ ds
682
+ end
683
+
667
684
  # The predicate condition to use for the eager_loader.
668
685
  def eager_loading_predicate_condition(keys)
669
686
  {predicate_key=>keys}
@@ -805,7 +822,7 @@ module Sequel
805
822
 
806
823
  # Whether the placeholder loader can be used to load the association.
807
824
  def use_placeholder_loader?
808
- self[:use_placeholder_loader]
825
+ self[:use_placeholder_loader] && _associated_dataset.supports_placeholder_literalizer?
809
826
  end
810
827
  end
811
828
 
@@ -1315,7 +1332,7 @@ module Sequel
1315
1332
 
1316
1333
  # many_to_many associations need to select a key in an associated table to eagerly load
1317
1334
  def eager_loading_use_associated_key?
1318
- true
1335
+ !separate_query_per_table?
1319
1336
  end
1320
1337
 
1321
1338
  # The source of the join table. This is the join table itself, unless it
@@ -1372,10 +1389,30 @@ module Sequel
1372
1389
  cached_fetch(:select){default_select}
1373
1390
  end
1374
1391
 
1392
+ # Whether a separate query should be used for the join table.
1393
+ def separate_query_per_table?
1394
+ self[:join_table_db]
1395
+ end
1396
+
1375
1397
  private
1376
1398
 
1399
+ # Join to the the join table, unless using a separate query per table.
1377
1400
  def _associated_dataset
1378
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1401
+ if separate_query_per_table?
1402
+ super
1403
+ else
1404
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1405
+ end
1406
+ end
1407
+
1408
+ # Use the right_keys from the eager loading options if
1409
+ # using a separate query per table.
1410
+ def eager_loading_set_predicate_condition(ds, eo)
1411
+ if separate_query_per_table?
1412
+ ds.where(right_primary_key=>eo[:right_keys])
1413
+ else
1414
+ super
1415
+ end
1379
1416
  end
1380
1417
 
1381
1418
  # The default selection for associations that require joins. These do not use the default
@@ -1592,6 +1629,7 @@ module Sequel
1592
1629
  # === Multiple Types
1593
1630
  # :adder :: Proc used to define the private _add_* method for doing the database work
1594
1631
  # to associate the given object to the current object (*_to_many assocations).
1632
+ # Set to nil to not define a add_* method for the association.
1595
1633
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1596
1634
  # after a new item is added to the association.
1597
1635
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1602,6 +1640,8 @@ module Sequel
1602
1640
  # after an item is set using the association setter method.
1603
1641
  # :allow_eager :: If set to false, you cannot load the association eagerly
1604
1642
  # via eager or eager_graph
1643
+ # :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
1644
+ # :allow_filtering_by :: If set to false, you cannot use the association when filtering
1605
1645
  # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
1606
1646
  # before a new item is added to the association.
1607
1647
  # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1620,6 +1660,7 @@ module Sequel
1620
1660
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1621
1661
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1622
1662
  # to remove all objects associated to the current object (*_to_many assocations).
1663
+ # Set to nil to not define a remove_all_* method for the association.
1623
1664
  # :clone :: Merge the current options and block into the options and block used in defining
1624
1665
  # the given association. Can be used to DRY up a bunch of similar associations that
1625
1666
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1674,18 +1715,24 @@ module Sequel
1674
1715
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1675
1716
  # the association via +eager_graph+, instead of the default conditions specified by the
1676
1717
  # 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
1718
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1678
1719
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1679
1720
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1680
1721
  # automatically qualified when using eager_graph.
1681
1722
  # :graph_select :: A column or array of columns to select from the associated table
1682
1723
  # when eagerly loading the association via +eager_graph+. Defaults to all
1683
1724
  # columns in the associated table.
1725
+ # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1726
+ # uses instance specific state, or transient state (accessing current date/time, etc.).
1684
1727
  # :limit :: Limit the number of records to the provided value. Use
1685
1728
  # an array with two elements for the value to specify a
1686
1729
  # limit (first element) and an offset (second element).
1687
1730
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1688
1731
  # to the module containing the model's columns.
1732
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1733
+ # method is never used.
1734
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1735
+ # method is never used.
1689
1736
  # :order :: the column(s) by which to order the association dataset. Can be a
1690
1737
  # singular column symbol or an array of column symbols.
1691
1738
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1698,6 +1745,7 @@ module Sequel
1698
1745
  # the current association's key(s). Set to nil to not use a reciprocal.
1699
1746
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1700
1747
  # to remove the association between the given object and the current object (*_to_many assocations).
1748
+ # Set to nil to not define a remove_* method for the association.
1701
1749
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1702
1750
  # that uses joins, which means it doesn't include the attributes from the
1703
1751
  # join table. If you want to include the join table attributes, you can
@@ -1706,6 +1754,7 @@ module Sequel
1706
1754
  # the same name in both the join table and the associated table.
1707
1755
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1708
1756
  # between the given object and the current object (*_to_one associations).
1757
+ # Set to nil to not define a setter method for the association.
1709
1758
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1710
1759
  # loading limited associations using the default :union strategy.
1711
1760
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1762,6 +1811,9 @@ module Sequel
1762
1811
  # underscored, sorted, and joined with '_'.
1763
1812
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1764
1813
  # methods. Should accept a dataset argument and return a modified dataset if present.
1814
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1815
+ # a join between to the join table and the associated table, use a separate query for the
1816
+ # join table using the given Database object.
1765
1817
  # :left_key :: foreign key in join table that points to current model's
1766
1818
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1767
1819
  # Can use an array of symbols for a composite key association.
@@ -1791,7 +1843,9 @@ module Sequel
1791
1843
 
1792
1844
  if opts[:clone]
1793
1845
  cloned_assoc = association_reflection(opts[:clone])
1846
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1794
1847
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1848
+ orig_opts.delete(:class_name) if remove_class_name
1795
1849
  end
1796
1850
 
1797
1851
  opts = Hash[default_association_options]
@@ -1809,6 +1863,16 @@ module Sequel
1809
1863
  # in certain places to disable optimizations.
1810
1864
  opts[:instance_specific] = _association_instance_specific_default(name)
1811
1865
  end
1866
+ if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
1867
+ # For associations explicitly marked as instance specific, or that use the
1868
+ # :dataset option, where :allow_eager is not set, and no :eager_loader is
1869
+ # provided, disallow eager loading. In these cases, eager loading is
1870
+ # unlikely to work. This is not done for implicit setting of :instance_specific,
1871
+ # because implicit use is done by default for all associations with blocks,
1872
+ # and the vast majority of associations with blocks use the block for filtering
1873
+ # in a manner compatible with eager loading.
1874
+ opts[:allow_eager] = false
1875
+ end
1812
1876
  opts = assoc_class.new.merge!(opts)
1813
1877
 
1814
1878
  if opts[:clone] && !opts.cloneable?(cloned_assoc)
@@ -1838,8 +1902,7 @@ module Sequel
1838
1902
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1839
1903
  opts.delete(:class) unless opts[:class]
1840
1904
 
1841
- send(:"def_#{type}", opts)
1842
- def_association_instance_methods(opts)
1905
+ def_association(opts)
1843
1906
 
1844
1907
  orig_opts.delete(:clone)
1845
1908
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1929,7 +1992,22 @@ module Sequel
1929
1992
  # can be easily overridden in the class itself while allowing for
1930
1993
  # super to be called.
1931
1994
  def association_module_def(name, opts=OPTS, &block)
1932
- association_module(opts).send(:define_method, name, &block)
1995
+ mod = association_module(opts)
1996
+ mod.send(:define_method, name, &block)
1997
+ mod.send(:alias_method, name, name)
1998
+ end
1999
+
2000
+ # Add a method to the module included in the class, so the method
2001
+ # can be easily overridden in the class itself while allowing for
2002
+ # super to be called. This method allows passing keywords through
2003
+ # the defined methods.
2004
+ def association_module_delegate_def(name, opts, &block)
2005
+ mod = association_module(opts)
2006
+ mod.send(:define_method, name, &block)
2007
+ # :nocov:
2008
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
2009
+ # :nocov:
2010
+ mod.send(:alias_method, name, name)
1933
2011
  end
1934
2012
 
1935
2013
  # Add a private method to the module included in the class.
@@ -1938,6 +2016,13 @@ module Sequel
1938
2016
  association_module(opts).send(:private, name)
1939
2017
  end
1940
2018
 
2019
+ # Delegate to the type-specific association method to setup the
2020
+ # association, and define the association instance methods.
2021
+ def def_association(opts)
2022
+ send(:"def_#{opts[:type]}", opts)
2023
+ def_association_instance_methods(opts)
2024
+ end
2025
+
1941
2026
  # Adds the association method to the association methods module.
1942
2027
  def def_association_method(opts)
1943
2028
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1963,13 +2048,13 @@ module Sequel
1963
2048
  opts[:setter_method] = :"#{opts[:name]}="
1964
2049
  end
1965
2050
 
1966
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
2051
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1967
2052
  if opts[:block]
1968
2053
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1969
2054
  end
1970
2055
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1971
2056
  opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1972
- def_association_method(opts)
2057
+ def_association_method(opts) unless opts[:no_association_method]
1973
2058
 
1974
2059
  return if opts[:read_only]
1975
2060
 
@@ -1981,17 +2066,17 @@ module Sequel
1981
2066
 
1982
2067
  if adder = opts[:adder]
1983
2068
  association_module_private_def(opts[:_add_method], opts, &adder)
1984
- association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
2069
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1985
2070
  end
1986
2071
 
1987
2072
  if remover = opts[:remover]
1988
2073
  association_module_private_def(opts[:_remove_method], opts, &remover)
1989
- association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
2074
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1990
2075
  end
1991
2076
 
1992
2077
  if clearer = opts[:clearer]
1993
2078
  association_module_private_def(opts[:_remove_all_method], opts, &clearer)
1994
- association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
2079
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1995
2080
  end
1996
2081
  end
1997
2082
 
@@ -2013,7 +2098,7 @@ module Sequel
2013
2098
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
2014
2099
  end
2015
2100
  opts[:uses_left_composite_keys] = lcks.length > 1
2016
- opts[:uses_right_composite_keys] = rcks.length > 1
2101
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
2017
2102
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
2018
2103
  join_table = (opts[:join_table] ||= opts.default_join_table)
2019
2104
  opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -2022,8 +2107,75 @@ module Sequel
2022
2107
  opts[:after_load] ||= []
2023
2108
  opts[:after_load].unshift(:array_uniq!)
2024
2109
  end
2025
- opts[:dataset] ||= opts.association_dataset_proc
2026
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2110
+ if join_table_db = opts[:join_table_db]
2111
+ opts[:use_placeholder_loader] = false
2112
+ opts[:allow_eager_graph] = false
2113
+ opts[:allow_filtering_by] = false
2114
+ opts[:eager_limit_strategy] = nil
2115
+ join_table_ds = join_table_db.from(join_table)
2116
+ opts[:dataset] ||= proc do |r|
2117
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2118
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2119
+ if uses_rcks
2120
+ vals.delete_if{|v| v.any?(&:nil?)}
2121
+ else
2122
+ vals.delete(nil)
2123
+ end
2124
+ ds = ds.clone(:no_results=>true) if vals.empty?
2125
+ ds
2126
+ end
2127
+ opts[:eager_loader] ||= proc do |eo|
2128
+ h = eo[:id_map]
2129
+ assign_singular = opts.assign_singular?
2130
+ rpk = opts.right_primary_key
2131
+ name = opts[:name]
2132
+
2133
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2134
+
2135
+ if uses_rcks
2136
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2137
+ else
2138
+ join_map.delete(nil)
2139
+ end
2140
+
2141
+ eo = Hash[eo]
2142
+
2143
+ if join_map.empty?
2144
+ eo[:no_results] = true
2145
+ else
2146
+ join_map.each_value do |vs|
2147
+ vs.replace(vs.flat_map{|v| h[v]})
2148
+ vs.uniq!
2149
+ end
2150
+
2151
+ eo[:loader] = false
2152
+ eo[:right_keys] = join_map.keys
2153
+ end
2154
+
2155
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2156
+ rpkv = if uses_rcks
2157
+ assoc_record.values.values_at(*rpk)
2158
+ else
2159
+ assoc_record.values[rpk]
2160
+ end
2161
+
2162
+ objects = join_map[rpkv]
2163
+
2164
+ if assign_singular
2165
+ objects.each do |object|
2166
+ object.associations[name] ||= assoc_record
2167
+ end
2168
+ else
2169
+ objects.each do |object|
2170
+ object.associations[name].push(assoc_record)
2171
+ end
2172
+ end
2173
+ end
2174
+ end
2175
+ else
2176
+ opts[:dataset] ||= opts.association_dataset_proc
2177
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2178
+ end
2027
2179
 
2028
2180
  join_type = opts[:graph_join_type]
2029
2181
  select = opts[:graph_select]
@@ -2057,50 +2209,60 @@ module Sequel
2057
2209
  return if opts[:read_only]
2058
2210
 
2059
2211
  if one_through_one
2060
- opts[:setter] ||= proc do |o|
2061
- h = {}
2062
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2063
- jtds = _join_table_dataset(opts).where(lh)
2064
-
2065
- checked_transaction do
2066
- current = jtds.first
2212
+ unless opts.has_key?(:setter)
2213
+ opts[:setter] = proc do |o|
2214
+ h = {}
2215
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2216
+ jtds = _join_table_dataset(opts).where(lh)
2067
2217
 
2068
- if o
2069
- new_values = []
2070
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2071
- end
2218
+ checked_transaction do
2219
+ current = jtds.first
2072
2220
 
2073
- if current
2074
- current_values = rcks.map{|k| current[k]}
2075
- jtds = jtds.where(rcks.zip(current_values))
2076
2221
  if o
2077
- if current_values != new_values
2078
- jtds.update(h)
2222
+ new_values = []
2223
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2224
+ end
2225
+
2226
+ if current
2227
+ current_values = rcks.map{|k| current[k]}
2228
+ jtds = jtds.where(rcks.zip(current_values))
2229
+ if o
2230
+ if current_values != new_values
2231
+ jtds.update(h)
2232
+ end
2233
+ else
2234
+ jtds.delete
2079
2235
  end
2080
- else
2081
- jtds.delete
2236
+ elsif o
2237
+ lh.each{|k,v| h[k] = v}
2238
+ jtds.insert(h)
2082
2239
  end
2083
- elsif o
2084
- lh.each{|k,v| h[k] = v}
2085
- jtds.insert(h)
2086
2240
  end
2087
2241
  end
2088
2242
  end
2089
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2243
+ if opts.fetch(:setter, true)
2244
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2245
+ end
2090
2246
  else
2091
- opts[:adder] ||= proc do |o|
2092
- h = {}
2093
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2094
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2095
- _join_table_dataset(opts).insert(h)
2247
+ unless opts.has_key?(:adder)
2248
+ opts[:adder] = proc do |o|
2249
+ h = {}
2250
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2251
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2252
+ _join_table_dataset(opts).insert(h)
2253
+ end
2096
2254
  end
2097
2255
 
2098
- opts[:remover] ||= proc do |o|
2099
- _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
2256
+ unless opts.has_key?(:remover)
2257
+ opts[:remover] = proc do |o|
2258
+ _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
2259
+ end
2100
2260
  end
2101
2261
 
2102
- opts[:clearer] ||= proc do
2103
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2262
+ unless opts.has_key?(:clearer)
2263
+ opts[:clearer] = proc do
2264
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2265
+ end
2104
2266
  end
2105
2267
  end
2106
2268
  end
@@ -2157,8 +2319,12 @@ module Sequel
2157
2319
 
2158
2320
  return if opts[:read_only]
2159
2321
 
2160
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2161
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2322
+ unless opts.has_key?(:setter)
2323
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2324
+ end
2325
+ if opts.fetch(:setter, true)
2326
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2327
+ end
2162
2328
  end
2163
2329
 
2164
2330
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2225,49 +2391,59 @@ module Sequel
2225
2391
  cks.each{|k| ck_nil_hash[k] = nil}
2226
2392
 
2227
2393
  if one_to_one
2228
- opts[:setter] ||= proc do |o|
2229
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2394
+ unless opts.has_key?(:setter)
2395
+ opts[:setter] = proc do |o|
2396
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2230
2397
 
2231
- 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)))
2232
- if old = up_ds.first
2233
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2398
+ 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)))
2399
+ if old = up_ds.first
2400
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2401
+ end
2402
+ save_old = true
2234
2403
  end
2235
- save_old = true
2236
- end
2237
2404
 
2238
- if o
2239
- if !o.new? && !save_old
2240
- up_ds = up_ds.exclude(o.pk_hash)
2405
+ if o
2406
+ if !o.new? && !save_old
2407
+ up_ds = up_ds.exclude(o.pk_hash)
2408
+ end
2409
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2241
2410
  end
2242
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2243
- end
2244
2411
 
2245
- checked_transaction do
2246
- if save_old
2247
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2248
- else
2249
- up_ds.skip_limit_check.update(ck_nil_hash)
2250
- end
2412
+ checked_transaction do
2413
+ if save_old
2414
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2415
+ else
2416
+ up_ds.skip_limit_check.update(ck_nil_hash)
2417
+ end
2251
2418
 
2252
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2419
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2420
+ end
2253
2421
  end
2254
2422
  end
2255
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2423
+ if opts.fetch(:setter, true)
2424
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2425
+ end
2256
2426
  else
2257
2427
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2258
2428
 
2259
- opts[:adder] ||= proc do |o|
2260
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2261
- o.save(save_opts)
2429
+ unless opts.has_key?(:adder)
2430
+ opts[:adder] = proc do |o|
2431
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2432
+ o.save(save_opts)
2433
+ end
2262
2434
  end
2263
2435
 
2264
- opts[:remover] ||= proc do |o|
2265
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2266
- o.save(save_opts)
2436
+ unless opts.has_key?(:remover)
2437
+ opts[:remover] = proc do |o|
2438
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2439
+ o.save(save_opts)
2440
+ end
2267
2441
  end
2268
2442
 
2269
- opts[:clearer] ||= proc do
2270
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2443
+ unless opts.has_key?(:clearer)
2444
+ opts[:clearer] = proc do
2445
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2446
+ end
2271
2447
  end
2272
2448
  end
2273
2449
  end
@@ -2361,7 +2537,7 @@ module Sequel
2361
2537
 
2362
2538
  # Dataset for the join table of the given many to many association reflection
2363
2539
  def _join_table_dataset(opts)
2364
- ds = model.db.from(opts.join_table_source)
2540
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2365
2541
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2366
2542
  end
2367
2543
 
@@ -2382,7 +2558,12 @@ module Sequel
2382
2558
  if loader = _associated_object_loader(opts, dynamic_opts)
2383
2559
  loader.all(*opts.predicate_key_values(self))
2384
2560
  else
2385
- _associated_dataset(opts, dynamic_opts).all
2561
+ ds = _associated_dataset(opts, dynamic_opts)
2562
+ if ds.opts[:no_results]
2563
+ []
2564
+ else
2565
+ ds.all
2566
+ end
2386
2567
  end
2387
2568
  end
2388
2569
 
@@ -2423,6 +2604,9 @@ module Sequel
2423
2604
  run_association_callbacks(opts, :after_add, o)
2424
2605
  o
2425
2606
  end
2607
+ # :nocov:
2608
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2609
+ # :nocov:
2426
2610
 
2427
2611
  # Add/Set the current object to/as the given object's reciprocal association.
2428
2612
  def add_reciprocal_object(opts, o)
@@ -2565,6 +2749,9 @@ module Sequel
2565
2749
  associations[opts[:name]] = []
2566
2750
  ret
2567
2751
  end
2752
+ # :nocov:
2753
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2754
+ # :nocov:
2568
2755
 
2569
2756
  # Remove the given associated object from the given association
2570
2757
  def remove_associated_object(opts, o, *args)
@@ -2586,6 +2773,9 @@ module Sequel
2586
2773
  run_association_callbacks(opts, :after_remove, o)
2587
2774
  o
2588
2775
  end
2776
+ # :nocov:
2777
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2778
+ # :nocov:
2589
2779
 
2590
2780
  # Check that the object from the associated table specified by the primary key
2591
2781
  # is currently associated to the receiver. If it is associated, return the object, otherwise
@@ -2834,6 +3024,8 @@ module Sequel
2834
3024
  (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
2835
3025
  l = args[0]
2836
3026
  if ar = model.association_reflections[l]
3027
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3028
+
2837
3029
  if multiple
2838
3030
  klass = ar.associated_class
2839
3031
  if is_ds
@@ -2955,7 +3147,7 @@ module Sequel
2955
3147
 
2956
3148
  # The secondary eager loading method. Loads all associations in a single query. This
2957
3149
  # method should only be used if you need to filter or order based on columns in associated tables,
2958
- # or if you have done comparative benchmarking it and determined it is faster.
3150
+ # or if you have done comparative benchmarking and determined it is faster.
2959
3151
  #
2960
3152
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2961
3153
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2984,6 +3176,8 @@ module Sequel
2984
3176
  # You can specify an custom alias and/or join type on a per-association basis by providing an
2985
3177
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2986
3178
  #
3179
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3180
+ #
2987
3181
  # Examples:
2988
3182
  #
2989
3183
  # # For each album, eager_graph load the artist
@@ -3327,7 +3521,7 @@ module Sequel
3327
3521
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
3328
3522
  # per-call determining of the alias base.
3329
3523
  def eager_graph_check_association(model, association)
3330
- if association.is_a?(SQL::AliasedExpression)
3524
+ reflection = if association.is_a?(SQL::AliasedExpression)
3331
3525
  expr = association.expression
3332
3526
  if expr.is_a?(SQL::Identifier)
3333
3527
  expr = expr.value
@@ -3336,10 +3530,17 @@ module Sequel
3336
3530
  end
3337
3531
  end
3338
3532
 
3339
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3533
+ check_reflection = check_association(model, expr)
3534
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
3340
3535
  else
3341
- check_association(model, association)
3536
+ check_reflection = check_association(model, association)
3537
+ end
3538
+
3539
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3540
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
3342
3541
  end
3542
+
3543
+ reflection
3343
3544
  end
3344
3545
 
3345
3546
  # The EagerGraphLoader instance used for converting eager_graph results.
@@ -3350,15 +3551,30 @@ module Sequel
3350
3551
  egl.dup
3351
3552
  end
3352
3553
 
3353
- # Eagerly load all specified associations
3354
- def eager_load(a, eager_assoc=@opts[:eager])
3554
+ # Eagerly load all specified associations.
3555
+ def eager_load(a, eager_assoc=@opts[:eager], m=model)
3355
3556
  return if a.empty?
3557
+
3558
+ # Reflections for all associations to eager load
3559
+ reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3560
+
3561
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3562
+
3563
+ reflections.each do |r|
3564
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3565
+ end
3566
+
3567
+ nil
3568
+ end
3569
+
3570
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3571
+ def prepare_eager_load(a, reflections, eager_assoc)
3572
+ eager_load_data = {}
3573
+
3356
3574
  # Key is foreign/primary key name symbol.
3357
3575
  # Value is hash with keys being foreign/primary key values (generally integers)
3358
3576
  # and values being an array of current model objects with that specific foreign/primary key
3359
3577
  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
3578
 
3363
3579
  # Populate the key_hash entry for each association being eagerly loaded
3364
3580
  reflections.each do |r|
@@ -3389,7 +3605,6 @@ module Sequel
3389
3605
  id_map = nil
3390
3606
  end
3391
3607
 
3392
- loader = r[:eager_loader]
3393
3608
  associations = eager_assoc[r[:name]]
3394
3609
  if associations.respond_to?(:call)
3395
3610
  eager_block = associations
@@ -3397,9 +3612,23 @@ module Sequel
3397
3612
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3398
3613
  eager_block, associations = pr_assoc
3399
3614
  end
3400
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
3401
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3402
- end
3615
+
3616
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3617
+ end
3618
+
3619
+ eager_load_data
3620
+ end
3621
+
3622
+ # Using the hash of loaders and eager options, perform the eager loading.
3623
+ def perform_eager_loads(eager_load_data)
3624
+ eager_load_data.map do |loader, eo|
3625
+ perform_eager_load(loader, eo)
3626
+ end
3627
+ end
3628
+
3629
+ # Perform eager loading for a single association using the loader and eager options.
3630
+ def perform_eager_load(loader, eo)
3631
+ loader.call(eo)
3403
3632
  end
3404
3633
 
3405
3634
  # Return a subquery expression for filering by a many_to_many association