sequel 5.39.0 → 5.64.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +318 -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 +119 -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/release_notes/5.64.0.txt +50 -0
  40. data/doc/schema_modification.rdoc +1 -1
  41. data/doc/security.rdoc +9 -9
  42. data/doc/sql.rdoc +27 -15
  43. data/doc/testing.rdoc +22 -11
  44. data/doc/transactions.rdoc +6 -6
  45. data/doc/virtual_rows.rdoc +2 -2
  46. data/lib/sequel/adapters/ado/access.rb +1 -1
  47. data/lib/sequel/adapters/ado.rb +17 -17
  48. data/lib/sequel/adapters/amalgalite.rb +3 -5
  49. data/lib/sequel/adapters/ibmdb.rb +2 -2
  50. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  51. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  52. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  53. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  54. data/lib/sequel/adapters/jdbc.rb +16 -18
  55. data/lib/sequel/adapters/mysql.rb +80 -67
  56. data/lib/sequel/adapters/mysql2.rb +54 -49
  57. data/lib/sequel/adapters/odbc.rb +6 -2
  58. data/lib/sequel/adapters/oracle.rb +4 -3
  59. data/lib/sequel/adapters/postgres.rb +83 -40
  60. data/lib/sequel/adapters/shared/access.rb +11 -1
  61. data/lib/sequel/adapters/shared/db2.rb +30 -0
  62. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  63. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  64. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  65. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  66. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  67. data/lib/sequel/adapters/shared/sqlite.rb +103 -11
  68. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  69. data/lib/sequel/adapters/sqlite.rb +60 -18
  70. data/lib/sequel/adapters/tinytds.rb +1 -1
  71. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  72. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  73. data/lib/sequel/ast_transformer.rb +6 -0
  74. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  75. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  76. data/lib/sequel/connection_pool/single.rb +6 -8
  77. data/lib/sequel/connection_pool/threaded.rb +8 -8
  78. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  79. data/lib/sequel/connection_pool.rb +47 -30
  80. data/lib/sequel/core.rb +28 -18
  81. data/lib/sequel/database/connecting.rb +26 -2
  82. data/lib/sequel/database/misc.rb +69 -14
  83. data/lib/sequel/database/query.rb +73 -2
  84. data/lib/sequel/database/schema_generator.rb +45 -52
  85. data/lib/sequel/database/schema_methods.rb +17 -1
  86. data/lib/sequel/dataset/actions.rb +107 -13
  87. data/lib/sequel/dataset/features.rb +20 -0
  88. data/lib/sequel/dataset/misc.rb +1 -1
  89. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  90. data/lib/sequel/dataset/query.rb +118 -16
  91. data/lib/sequel/dataset/sql.rb +177 -47
  92. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  93. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  94. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  95. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  96. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  97. data/lib/sequel/extensions/blank.rb +8 -0
  98. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  99. data/lib/sequel/extensions/core_refinements.rb +36 -11
  100. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  101. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  102. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  103. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  104. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  105. data/lib/sequel/extensions/inflector.rb +9 -1
  106. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  107. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  108. data/lib/sequel/extensions/migration.rb +7 -2
  109. data/lib/sequel/extensions/named_timezones.rb +26 -6
  110. data/lib/sequel/extensions/pagination.rb +1 -1
  111. data/lib/sequel/extensions/pg_array.rb +23 -3
  112. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  113. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  114. data/lib/sequel/extensions/pg_enum.rb +1 -1
  115. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  116. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  117. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  118. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  119. data/lib/sequel/extensions/pg_inet.rb +10 -11
  120. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  121. data/lib/sequel/extensions/pg_interval.rb +45 -19
  122. data/lib/sequel/extensions/pg_json.rb +13 -15
  123. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  124. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  125. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  126. data/lib/sequel/extensions/pg_range.rb +10 -23
  127. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  128. data/lib/sequel/extensions/pg_row.rb +19 -13
  129. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  130. data/lib/sequel/extensions/query.rb +2 -0
  131. data/lib/sequel/extensions/s.rb +2 -1
  132. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  133. data/lib/sequel/extensions/server_block.rb +8 -12
  134. data/lib/sequel/extensions/sql_comments.rb +110 -3
  135. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  136. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  137. data/lib/sequel/extensions/string_agg.rb +1 -1
  138. data/lib/sequel/extensions/string_date_time.rb +19 -23
  139. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  140. data/lib/sequel/model/associations.rb +330 -96
  141. data/lib/sequel/model/base.rb +51 -27
  142. data/lib/sequel/model/errors.rb +10 -1
  143. data/lib/sequel/model/inflections.rb +1 -1
  144. data/lib/sequel/model/plugins.rb +5 -0
  145. data/lib/sequel/plugins/association_proxies.rb +2 -0
  146. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  147. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  148. data/lib/sequel/plugins/auto_validations.rb +87 -15
  149. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  150. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  151. data/lib/sequel/plugins/column_encryption.rb +728 -0
  152. data/lib/sequel/plugins/composition.rb +10 -4
  153. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  154. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  155. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  156. data/lib/sequel/plugins/dirty.rb +1 -1
  157. data/lib/sequel/plugins/enum.rb +124 -0
  158. data/lib/sequel/plugins/finder.rb +3 -1
  159. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  160. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  161. data/lib/sequel/plugins/json_serializer.rb +39 -24
  162. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  163. data/lib/sequel/plugins/list.rb +3 -1
  164. data/lib/sequel/plugins/many_through_many.rb +109 -10
  165. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  166. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  167. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  168. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  169. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  170. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  171. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  172. data/lib/sequel/plugins/serialization.rb +9 -3
  173. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  174. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  175. data/lib/sequel/plugins/sql_comments.rb +189 -0
  176. data/lib/sequel/plugins/static_cache.rb +1 -1
  177. data/lib/sequel/plugins/subclasses.rb +28 -11
  178. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  179. data/lib/sequel/plugins/timestamps.rb +1 -1
  180. data/lib/sequel/plugins/unused_associations.rb +521 -0
  181. data/lib/sequel/plugins/update_or_create.rb +1 -1
  182. data/lib/sequel/plugins/validate_associated.rb +22 -12
  183. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  184. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  185. data/lib/sequel/sql.rb +1 -1
  186. data/lib/sequel/timezones.rb +12 -14
  187. data/lib/sequel/version.rb +1 -1
  188. metadata +99 -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,26 @@ 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
+ # :graph_use_association_block :: Makes eager_graph consider the association block. Without this, eager_graph
1726
+ # ignores the bock and only use the :graph_* options.
1727
+ # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1728
+ # uses instance specific state, or transient state (accessing current date/time, etc.).
1684
1729
  # :limit :: Limit the number of records to the provided value. Use
1685
1730
  # an array with two elements for the value to specify a
1686
1731
  # limit (first element) and an offset (second element).
1687
1732
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1688
1733
  # to the module containing the model's columns.
1734
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1735
+ # method is never used.
1736
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1737
+ # method is never used.
1689
1738
  # :order :: the column(s) by which to order the association dataset. Can be a
1690
1739
  # singular column symbol or an array of column symbols.
1691
1740
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1698,6 +1747,7 @@ module Sequel
1698
1747
  # the current association's key(s). Set to nil to not use a reciprocal.
1699
1748
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1700
1749
  # to remove the association between the given object and the current object (*_to_many assocations).
1750
+ # Set to nil to not define a remove_* method for the association.
1701
1751
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1702
1752
  # that uses joins, which means it doesn't include the attributes from the
1703
1753
  # join table. If you want to include the join table attributes, you can
@@ -1706,6 +1756,7 @@ module Sequel
1706
1756
  # the same name in both the join table and the associated table.
1707
1757
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1708
1758
  # between the given object and the current object (*_to_one associations).
1759
+ # Set to nil to not define a setter method for the association.
1709
1760
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1710
1761
  # loading limited associations using the default :union strategy.
1711
1762
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1762,6 +1813,9 @@ module Sequel
1762
1813
  # underscored, sorted, and joined with '_'.
1763
1814
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1764
1815
  # methods. Should accept a dataset argument and return a modified dataset if present.
1816
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1817
+ # a join between to the join table and the associated table, use a separate query for the
1818
+ # join table using the given Database object.
1765
1819
  # :left_key :: foreign key in join table that points to current model's
1766
1820
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1767
1821
  # Can use an array of symbols for a composite key association.
@@ -1791,7 +1845,9 @@ module Sequel
1791
1845
 
1792
1846
  if opts[:clone]
1793
1847
  cloned_assoc = association_reflection(opts[:clone])
1848
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1794
1849
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1850
+ orig_opts.delete(:class_name) if remove_class_name
1795
1851
  end
1796
1852
 
1797
1853
  opts = Hash[default_association_options]
@@ -1809,6 +1865,16 @@ module Sequel
1809
1865
  # in certain places to disable optimizations.
1810
1866
  opts[:instance_specific] = _association_instance_specific_default(name)
1811
1867
  end
1868
+ if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
1869
+ # For associations explicitly marked as instance specific, or that use the
1870
+ # :dataset option, where :allow_eager is not set, and no :eager_loader is
1871
+ # provided, disallow eager loading. In these cases, eager loading is
1872
+ # unlikely to work. This is not done for implicit setting of :instance_specific,
1873
+ # because implicit use is done by default for all associations with blocks,
1874
+ # and the vast majority of associations with blocks use the block for filtering
1875
+ # in a manner compatible with eager loading.
1876
+ opts[:allow_eager] = false
1877
+ end
1812
1878
  opts = assoc_class.new.merge!(opts)
1813
1879
 
1814
1880
  if opts[:clone] && !opts.cloneable?(cloned_assoc)
@@ -1838,8 +1904,7 @@ module Sequel
1838
1904
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1839
1905
  opts.delete(:class) unless opts[:class]
1840
1906
 
1841
- send(:"def_#{type}", opts)
1842
- def_association_instance_methods(opts)
1907
+ def_association(opts)
1843
1908
 
1844
1909
  orig_opts.delete(:clone)
1845
1910
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1929,7 +1994,22 @@ module Sequel
1929
1994
  # can be easily overridden in the class itself while allowing for
1930
1995
  # super to be called.
1931
1996
  def association_module_def(name, opts=OPTS, &block)
1932
- association_module(opts).send(:define_method, name, &block)
1997
+ mod = association_module(opts)
1998
+ mod.send(:define_method, name, &block)
1999
+ mod.send(:alias_method, name, name)
2000
+ end
2001
+
2002
+ # Add a method to the module included in the class, so the method
2003
+ # can be easily overridden in the class itself while allowing for
2004
+ # super to be called. This method allows passing keywords through
2005
+ # the defined methods.
2006
+ def association_module_delegate_def(name, opts, &block)
2007
+ mod = association_module(opts)
2008
+ mod.send(:define_method, name, &block)
2009
+ # :nocov:
2010
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
2011
+ # :nocov:
2012
+ mod.send(:alias_method, name, name)
1933
2013
  end
1934
2014
 
1935
2015
  # Add a private method to the module included in the class.
@@ -1938,6 +2018,13 @@ module Sequel
1938
2018
  association_module(opts).send(:private, name)
1939
2019
  end
1940
2020
 
2021
+ # Delegate to the type-specific association method to setup the
2022
+ # association, and define the association instance methods.
2023
+ def def_association(opts)
2024
+ send(:"def_#{opts[:type]}", opts)
2025
+ def_association_instance_methods(opts)
2026
+ end
2027
+
1941
2028
  # Adds the association method to the association methods module.
1942
2029
  def def_association_method(opts)
1943
2030
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1963,13 +2050,13 @@ module Sequel
1963
2050
  opts[:setter_method] = :"#{opts[:name]}="
1964
2051
  end
1965
2052
 
1966
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
2053
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1967
2054
  if opts[:block]
1968
2055
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1969
2056
  end
1970
2057
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1971
2058
  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)
2059
+ def_association_method(opts) unless opts[:no_association_method]
1973
2060
 
1974
2061
  return if opts[:read_only]
1975
2062
 
@@ -1981,17 +2068,17 @@ module Sequel
1981
2068
 
1982
2069
  if adder = opts[:adder]
1983
2070
  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)}
2071
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1985
2072
  end
1986
2073
 
1987
2074
  if remover = opts[:remover]
1988
2075
  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)}
2076
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1990
2077
  end
1991
2078
 
1992
2079
  if clearer = opts[:clearer]
1993
2080
  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)}
2081
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1995
2082
  end
1996
2083
  end
1997
2084
 
@@ -2013,7 +2100,7 @@ module Sequel
2013
2100
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
2014
2101
  end
2015
2102
  opts[:uses_left_composite_keys] = lcks.length > 1
2016
- opts[:uses_right_composite_keys] = rcks.length > 1
2103
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
2017
2104
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
2018
2105
  join_table = (opts[:join_table] ||= opts.default_join_table)
2019
2106
  opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -2022,8 +2109,75 @@ module Sequel
2022
2109
  opts[:after_load] ||= []
2023
2110
  opts[:after_load].unshift(:array_uniq!)
2024
2111
  end
2025
- opts[:dataset] ||= opts.association_dataset_proc
2026
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2112
+ if join_table_db = opts[:join_table_db]
2113
+ opts[:use_placeholder_loader] = false
2114
+ opts[:allow_eager_graph] = false
2115
+ opts[:allow_filtering_by] = false
2116
+ opts[:eager_limit_strategy] = nil
2117
+ join_table_ds = join_table_db.from(join_table)
2118
+ opts[:dataset] ||= proc do |r|
2119
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2120
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2121
+ if uses_rcks
2122
+ vals.delete_if{|v| v.any?(&:nil?)}
2123
+ else
2124
+ vals.delete(nil)
2125
+ end
2126
+ ds = ds.clone(:no_results=>true) if vals.empty?
2127
+ ds
2128
+ end
2129
+ opts[:eager_loader] ||= proc do |eo|
2130
+ h = eo[:id_map]
2131
+ assign_singular = opts.assign_singular?
2132
+ rpk = opts.right_primary_key
2133
+ name = opts[:name]
2134
+
2135
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2136
+
2137
+ if uses_rcks
2138
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2139
+ else
2140
+ join_map.delete(nil)
2141
+ end
2142
+
2143
+ eo = Hash[eo]
2144
+
2145
+ if join_map.empty?
2146
+ eo[:no_results] = true
2147
+ else
2148
+ join_map.each_value do |vs|
2149
+ vs.replace(vs.flat_map{|v| h[v]})
2150
+ vs.uniq!
2151
+ end
2152
+
2153
+ eo[:loader] = false
2154
+ eo[:right_keys] = join_map.keys
2155
+ end
2156
+
2157
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2158
+ rpkv = if uses_rcks
2159
+ assoc_record.values.values_at(*rpk)
2160
+ else
2161
+ assoc_record.values[rpk]
2162
+ end
2163
+
2164
+ objects = join_map[rpkv]
2165
+
2166
+ if assign_singular
2167
+ objects.each do |object|
2168
+ object.associations[name] ||= assoc_record
2169
+ end
2170
+ else
2171
+ objects.each do |object|
2172
+ object.associations[name].push(assoc_record)
2173
+ end
2174
+ end
2175
+ end
2176
+ end
2177
+ else
2178
+ opts[:dataset] ||= opts.association_dataset_proc
2179
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2180
+ end
2027
2181
 
2028
2182
  join_type = opts[:graph_join_type]
2029
2183
  select = opts[:graph_select]
@@ -2057,50 +2211,60 @@ module Sequel
2057
2211
  return if opts[:read_only]
2058
2212
 
2059
2213
  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)
2214
+ unless opts.has_key?(:setter)
2215
+ opts[:setter] = proc do |o|
2216
+ h = {}
2217
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2218
+ jtds = _join_table_dataset(opts).where(lh)
2064
2219
 
2065
- checked_transaction do
2066
- current = jtds.first
2220
+ checked_transaction do
2221
+ current = jtds.first
2067
2222
 
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
2072
-
2073
- if current
2074
- current_values = rcks.map{|k| current[k]}
2075
- jtds = jtds.where(rcks.zip(current_values))
2076
2223
  if o
2077
- if current_values != new_values
2078
- jtds.update(h)
2224
+ new_values = []
2225
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2226
+ end
2227
+
2228
+ if current
2229
+ current_values = rcks.map{|k| current[k]}
2230
+ jtds = jtds.where(rcks.zip(current_values))
2231
+ if o
2232
+ if current_values != new_values
2233
+ jtds.update(h)
2234
+ end
2235
+ else
2236
+ jtds.delete
2079
2237
  end
2080
- else
2081
- jtds.delete
2238
+ elsif o
2239
+ lh.each{|k,v| h[k] = v}
2240
+ jtds.insert(h)
2082
2241
  end
2083
- elsif o
2084
- lh.each{|k,v| h[k] = v}
2085
- jtds.insert(h)
2086
2242
  end
2087
2243
  end
2088
2244
  end
2089
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2245
+ if opts.fetch(:setter, true)
2246
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2247
+ end
2090
2248
  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)
2249
+ unless opts.has_key?(:adder)
2250
+ opts[:adder] = proc do |o|
2251
+ h = {}
2252
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2253
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2254
+ _join_table_dataset(opts).insert(h)
2255
+ end
2096
2256
  end
2097
2257
 
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
2258
+ unless opts.has_key?(:remover)
2259
+ opts[:remover] = proc do |o|
2260
+ _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
2261
+ end
2100
2262
  end
2101
2263
 
2102
- opts[:clearer] ||= proc do
2103
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2264
+ unless opts.has_key?(:clearer)
2265
+ opts[:clearer] = proc do
2266
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2267
+ end
2104
2268
  end
2105
2269
  end
2106
2270
  end
@@ -2157,8 +2321,12 @@ module Sequel
2157
2321
 
2158
2322
  return if opts[:read_only]
2159
2323
 
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)}
2324
+ unless opts.has_key?(:setter)
2325
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2326
+ end
2327
+ if opts.fetch(:setter, true)
2328
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2329
+ end
2162
2330
  end
2163
2331
 
2164
2332
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2225,49 +2393,59 @@ module Sequel
2225
2393
  cks.each{|k| ck_nil_hash[k] = nil}
2226
2394
 
2227
2395
  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)})))
2396
+ unless opts.has_key?(:setter)
2397
+ opts[:setter] = proc do |o|
2398
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2230
2399
 
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)}
2400
+ 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)))
2401
+ if old = up_ds.first
2402
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2403
+ end
2404
+ save_old = true
2234
2405
  end
2235
- save_old = true
2236
- end
2237
2406
 
2238
- if o
2239
- if !o.new? && !save_old
2240
- up_ds = up_ds.exclude(o.pk_hash)
2407
+ if o
2408
+ if !o.new? && !save_old
2409
+ up_ds = up_ds.exclude(o.pk_hash)
2410
+ end
2411
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2241
2412
  end
2242
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2243
- end
2244
2413
 
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
2414
+ checked_transaction do
2415
+ if save_old
2416
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2417
+ else
2418
+ up_ds.skip_limit_check.update(ck_nil_hash)
2419
+ end
2251
2420
 
2252
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2421
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2422
+ end
2253
2423
  end
2254
2424
  end
2255
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2425
+ if opts.fetch(:setter, true)
2426
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2427
+ end
2256
2428
  else
2257
2429
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2258
2430
 
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)
2431
+ unless opts.has_key?(:adder)
2432
+ opts[:adder] = proc do |o|
2433
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2434
+ o.save(save_opts)
2435
+ end
2262
2436
  end
2263
2437
 
2264
- opts[:remover] ||= proc do |o|
2265
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2266
- o.save(save_opts)
2438
+ unless opts.has_key?(:remover)
2439
+ opts[:remover] = proc do |o|
2440
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2441
+ o.save(save_opts)
2442
+ end
2267
2443
  end
2268
2444
 
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)
2445
+ unless opts.has_key?(:clearer)
2446
+ opts[:clearer] = proc do
2447
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2448
+ end
2271
2449
  end
2272
2450
  end
2273
2451
  end
@@ -2285,6 +2463,9 @@ module Sequel
2285
2463
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2286
2464
  def eager_graph_dataset(opts, eager_options)
2287
2465
  ds = opts.associated_class.dataset
2466
+ if opts[:graph_use_association_block] && (b = opts[:block])
2467
+ ds = b.call(ds)
2468
+ end
2288
2469
  if cb = eager_options[:callback]
2289
2470
  ds = cb.call(ds)
2290
2471
  end
@@ -2361,7 +2542,7 @@ module Sequel
2361
2542
 
2362
2543
  # Dataset for the join table of the given many to many association reflection
2363
2544
  def _join_table_dataset(opts)
2364
- ds = model.db.from(opts.join_table_source)
2545
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2365
2546
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2366
2547
  end
2367
2548
 
@@ -2382,7 +2563,12 @@ module Sequel
2382
2563
  if loader = _associated_object_loader(opts, dynamic_opts)
2383
2564
  loader.all(*opts.predicate_key_values(self))
2384
2565
  else
2385
- _associated_dataset(opts, dynamic_opts).all
2566
+ ds = _associated_dataset(opts, dynamic_opts)
2567
+ if ds.opts[:no_results]
2568
+ []
2569
+ else
2570
+ ds.all
2571
+ end
2386
2572
  end
2387
2573
  end
2388
2574
 
@@ -2423,6 +2609,9 @@ module Sequel
2423
2609
  run_association_callbacks(opts, :after_add, o)
2424
2610
  o
2425
2611
  end
2612
+ # :nocov:
2613
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2614
+ # :nocov:
2426
2615
 
2427
2616
  # Add/Set the current object to/as the given object's reciprocal association.
2428
2617
  def add_reciprocal_object(opts, o)
@@ -2565,6 +2754,9 @@ module Sequel
2565
2754
  associations[opts[:name]] = []
2566
2755
  ret
2567
2756
  end
2757
+ # :nocov:
2758
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2759
+ # :nocov:
2568
2760
 
2569
2761
  # Remove the given associated object from the given association
2570
2762
  def remove_associated_object(opts, o, *args)
@@ -2586,6 +2778,9 @@ module Sequel
2586
2778
  run_association_callbacks(opts, :after_remove, o)
2587
2779
  o
2588
2780
  end
2781
+ # :nocov:
2782
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2783
+ # :nocov:
2589
2784
 
2590
2785
  # Check that the object from the associated table specified by the primary key
2591
2786
  # is currently associated to the receiver. If it is associated, return the object, otherwise
@@ -2834,6 +3029,8 @@ module Sequel
2834
3029
  (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
3030
  l = args[0]
2836
3031
  if ar = model.association_reflections[l]
3032
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3033
+
2837
3034
  if multiple
2838
3035
  klass = ar.associated_class
2839
3036
  if is_ds
@@ -2955,7 +3152,7 @@ module Sequel
2955
3152
 
2956
3153
  # The secondary eager loading method. Loads all associations in a single query. This
2957
3154
  # 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.
3155
+ # or if you have done comparative benchmarking and determined it is faster.
2959
3156
  #
2960
3157
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2961
3158
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2984,6 +3181,8 @@ module Sequel
2984
3181
  # You can specify an custom alias and/or join type on a per-association basis by providing an
2985
3182
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2986
3183
  #
3184
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3185
+ #
2987
3186
  # Examples:
2988
3187
  #
2989
3188
  # # For each album, eager_graph load the artist
@@ -3327,7 +3526,7 @@ module Sequel
3327
3526
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
3328
3527
  # per-call determining of the alias base.
3329
3528
  def eager_graph_check_association(model, association)
3330
- if association.is_a?(SQL::AliasedExpression)
3529
+ reflection = if association.is_a?(SQL::AliasedExpression)
3331
3530
  expr = association.expression
3332
3531
  if expr.is_a?(SQL::Identifier)
3333
3532
  expr = expr.value
@@ -3336,10 +3535,17 @@ module Sequel
3336
3535
  end
3337
3536
  end
3338
3537
 
3339
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3538
+ check_reflection = check_association(model, expr)
3539
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
3340
3540
  else
3341
- check_association(model, association)
3541
+ check_reflection = check_association(model, association)
3542
+ end
3543
+
3544
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3545
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
3342
3546
  end
3547
+
3548
+ reflection
3343
3549
  end
3344
3550
 
3345
3551
  # The EagerGraphLoader instance used for converting eager_graph results.
@@ -3350,15 +3556,30 @@ module Sequel
3350
3556
  egl.dup
3351
3557
  end
3352
3558
 
3353
- # Eagerly load all specified associations
3354
- def eager_load(a, eager_assoc=@opts[:eager])
3559
+ # Eagerly load all specified associations.
3560
+ def eager_load(a, eager_assoc=@opts[:eager], m=model)
3355
3561
  return if a.empty?
3562
+
3563
+ # Reflections for all associations to eager load
3564
+ reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3565
+
3566
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3567
+
3568
+ reflections.each do |r|
3569
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3570
+ end
3571
+
3572
+ nil
3573
+ end
3574
+
3575
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3576
+ def prepare_eager_load(a, reflections, eager_assoc)
3577
+ eager_load_data = {}
3578
+
3356
3579
  # Key is foreign/primary key name symbol.
3357
3580
  # Value is hash with keys being foreign/primary key values (generally integers)
3358
3581
  # and values being an array of current model objects with that specific foreign/primary key
3359
3582
  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
3583
 
3363
3584
  # Populate the key_hash entry for each association being eagerly loaded
3364
3585
  reflections.each do |r|
@@ -3389,7 +3610,6 @@ module Sequel
3389
3610
  id_map = nil
3390
3611
  end
3391
3612
 
3392
- loader = r[:eager_loader]
3393
3613
  associations = eager_assoc[r[:name]]
3394
3614
  if associations.respond_to?(:call)
3395
3615
  eager_block = associations
@@ -3397,9 +3617,23 @@ module Sequel
3397
3617
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3398
3618
  eager_block, associations = pr_assoc
3399
3619
  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
3620
+
3621
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3622
+ end
3623
+
3624
+ eager_load_data
3625
+ end
3626
+
3627
+ # Using the hash of loaders and eager options, perform the eager loading.
3628
+ def perform_eager_loads(eager_load_data)
3629
+ eager_load_data.map do |loader, eo|
3630
+ perform_eager_load(loader, eo)
3631
+ end
3632
+ end
3633
+
3634
+ # Perform eager loading for a single association using the loader and eager options.
3635
+ def perform_eager_load(loader, eo)
3636
+ loader.call(eo)
3403
3637
  end
3404
3638
 
3405
3639
  # Return a subquery expression for filering by a many_to_many association