sequel 5.33.0 → 5.58.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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +318 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +40 -9
  5. data/doc/association_basics.rdoc +77 -13
  6. data/doc/cheat_sheet.rdoc +13 -5
  7. data/doc/code_order.rdoc +0 -12
  8. data/doc/dataset_filtering.rdoc +2 -2
  9. data/doc/fork_safety.rdoc +84 -0
  10. data/doc/migration.rdoc +12 -6
  11. data/doc/model_plugins.rdoc +1 -1
  12. data/doc/opening_databases.rdoc +15 -3
  13. data/doc/postgresql.rdoc +9 -1
  14. data/doc/querying.rdoc +7 -5
  15. data/doc/release_notes/5.34.0.txt +40 -0
  16. data/doc/release_notes/5.35.0.txt +56 -0
  17. data/doc/release_notes/5.36.0.txt +60 -0
  18. data/doc/release_notes/5.37.0.txt +30 -0
  19. data/doc/release_notes/5.38.0.txt +28 -0
  20. data/doc/release_notes/5.39.0.txt +19 -0
  21. data/doc/release_notes/5.40.0.txt +40 -0
  22. data/doc/release_notes/5.41.0.txt +25 -0
  23. data/doc/release_notes/5.42.0.txt +136 -0
  24. data/doc/release_notes/5.43.0.txt +98 -0
  25. data/doc/release_notes/5.44.0.txt +32 -0
  26. data/doc/release_notes/5.45.0.txt +34 -0
  27. data/doc/release_notes/5.46.0.txt +87 -0
  28. data/doc/release_notes/5.47.0.txt +59 -0
  29. data/doc/release_notes/5.48.0.txt +14 -0
  30. data/doc/release_notes/5.49.0.txt +59 -0
  31. data/doc/release_notes/5.50.0.txt +78 -0
  32. data/doc/release_notes/5.51.0.txt +47 -0
  33. data/doc/release_notes/5.52.0.txt +87 -0
  34. data/doc/release_notes/5.53.0.txt +23 -0
  35. data/doc/release_notes/5.54.0.txt +27 -0
  36. data/doc/release_notes/5.55.0.txt +21 -0
  37. data/doc/release_notes/5.56.0.txt +51 -0
  38. data/doc/release_notes/5.57.0.txt +23 -0
  39. data/doc/release_notes/5.58.0.txt +31 -0
  40. data/doc/sql.rdoc +14 -2
  41. data/doc/testing.rdoc +10 -1
  42. data/doc/transactions.rdoc +0 -8
  43. data/doc/validations.rdoc +1 -1
  44. data/doc/virtual_rows.rdoc +1 -1
  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/mysql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  54. data/lib/sequel/adapters/jdbc.rb +29 -19
  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 +8 -6
  58. data/lib/sequel/adapters/oracle.rb +5 -4
  59. data/lib/sequel/adapters/postgres.rb +27 -29
  60. data/lib/sequel/adapters/shared/access.rb +2 -0
  61. data/lib/sequel/adapters/shared/db2.rb +30 -0
  62. data/lib/sequel/adapters/shared/mssql.rb +84 -7
  63. data/lib/sequel/adapters/shared/mysql.rb +33 -2
  64. data/lib/sequel/adapters/shared/oracle.rb +82 -7
  65. data/lib/sequel/adapters/shared/postgres.rb +158 -20
  66. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  67. data/lib/sequel/adapters/shared/sqlite.rb +102 -10
  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 +2 -1
  71. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  72. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -1
  73. data/lib/sequel/ast_transformer.rb +6 -0
  74. data/lib/sequel/connection_pool/sharded_single.rb +9 -8
  75. data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
  76. data/lib/sequel/connection_pool/single.rb +7 -9
  77. data/lib/sequel/connection_pool/threaded.rb +1 -1
  78. data/lib/sequel/core.rb +33 -24
  79. data/lib/sequel/database/connecting.rb +3 -4
  80. data/lib/sequel/database/misc.rb +37 -12
  81. data/lib/sequel/database/query.rb +3 -1
  82. data/lib/sequel/database/schema_generator.rb +50 -53
  83. data/lib/sequel/database/schema_methods.rb +45 -23
  84. data/lib/sequel/database/transactions.rb +9 -6
  85. data/lib/sequel/dataset/actions.rb +61 -8
  86. data/lib/sequel/dataset/features.rb +15 -0
  87. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +114 -11
  90. data/lib/sequel/dataset/sql.rb +172 -46
  91. data/lib/sequel/deprecated.rb +3 -1
  92. data/lib/sequel/exceptions.rb +2 -0
  93. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  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/blank.rb +8 -0
  97. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  98. data/lib/sequel/extensions/core_refinements.rb +38 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +36 -24
  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 +3 -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 +139 -0
  106. data/lib/sequel/extensions/migration.rb +13 -2
  107. data/lib/sequel/extensions/named_timezones.rb +5 -1
  108. data/lib/sequel/extensions/pagination.rb +1 -1
  109. data/lib/sequel/extensions/pg_array.rb +1 -0
  110. data/lib/sequel/extensions/pg_array_ops.rb +6 -2
  111. data/lib/sequel/extensions/pg_enum.rb +3 -1
  112. data/lib/sequel/extensions/pg_extended_date_support.rb +2 -2
  113. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  114. data/lib/sequel/extensions/pg_hstore_ops.rb +55 -3
  115. data/lib/sequel/extensions/pg_inet.rb +2 -0
  116. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  117. data/lib/sequel/extensions/pg_interval.rb +35 -8
  118. data/lib/sequel/extensions/pg_json.rb +3 -5
  119. data/lib/sequel/extensions/pg_json_ops.rb +119 -4
  120. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  121. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  122. data/lib/sequel/extensions/pg_range.rb +7 -19
  123. data/lib/sequel/extensions/pg_range_ops.rb +39 -9
  124. data/lib/sequel/extensions/pg_row.rb +1 -1
  125. data/lib/sequel/extensions/pg_row_ops.rb +25 -1
  126. data/lib/sequel/extensions/query.rb +3 -0
  127. data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
  128. data/lib/sequel/extensions/s.rb +4 -1
  129. data/lib/sequel/extensions/schema_dumper.rb +16 -5
  130. data/lib/sequel/extensions/server_block.rb +8 -12
  131. data/lib/sequel/extensions/sql_comments.rb +110 -3
  132. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  133. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  134. data/lib/sequel/extensions/string_agg.rb +1 -1
  135. data/lib/sequel/extensions/string_date_time.rb +19 -23
  136. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  137. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  138. data/lib/sequel/extensions/to_dot.rb +9 -3
  139. data/lib/sequel/model/associations.rb +342 -114
  140. data/lib/sequel/model/base.rb +45 -24
  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 +8 -3
  144. data/lib/sequel/model.rb +3 -1
  145. data/lib/sequel/plugins/association_pks.rb +60 -18
  146. data/lib/sequel/plugins/association_proxies.rb +3 -0
  147. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  148. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  149. data/lib/sequel/plugins/auto_validations.rb +39 -5
  150. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  151. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  152. data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
  153. data/lib/sequel/plugins/column_encryption.rb +728 -0
  154. data/lib/sequel/plugins/composition.rb +8 -2
  155. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  156. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  157. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  158. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  159. data/lib/sequel/plugins/dirty.rb +44 -0
  160. data/lib/sequel/plugins/enum.rb +124 -0
  161. data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
  162. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  163. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  164. data/lib/sequel/plugins/json_serializer.rb +39 -24
  165. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  166. data/lib/sequel/plugins/many_through_many.rb +108 -9
  167. data/lib/sequel/plugins/nested_attributes.rb +8 -3
  168. data/lib/sequel/plugins/pg_array_associations.rb +58 -41
  169. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
  170. data/lib/sequel/plugins/prepared_statements.rb +15 -12
  171. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  172. data/lib/sequel/plugins/rcte_tree.rb +37 -35
  173. data/lib/sequel/plugins/serialization.rb +9 -3
  174. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  175. data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
  176. data/lib/sequel/plugins/sql_comments.rb +189 -0
  177. data/lib/sequel/plugins/static_cache.rb +1 -1
  178. data/lib/sequel/plugins/string_stripper.rb +1 -1
  179. data/lib/sequel/plugins/subclasses.rb +28 -11
  180. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  181. data/lib/sequel/plugins/timestamps.rb +1 -1
  182. data/lib/sequel/plugins/tree.rb +9 -4
  183. data/lib/sequel/plugins/unused_associations.rb +521 -0
  184. data/lib/sequel/plugins/update_or_create.rb +1 -1
  185. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  186. data/lib/sequel/plugins/validation_helpers.rb +18 -11
  187. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  188. data/lib/sequel/sql.rb +1 -1
  189. data/lib/sequel/timezones.rb +20 -17
  190. data/lib/sequel/version.rb +1 -1
  191. metadata +93 -39
@@ -164,11 +164,11 @@ module Sequel
164
164
  # range to return the object(s) at the correct offset/limit.
165
165
  def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
166
166
  name = self[:name]
167
+ return unless range = slice_range(limit_and_offset)
167
168
  if returns_array?
168
- range = slice_range(limit_and_offset)
169
169
  rows.each{|o| o.associations[name] = o.associations[name][range] || []}
170
- elsif sr = slice_range(limit_and_offset)
171
- offset = sr.begin
170
+ else
171
+ offset = range.begin
172
172
  rows.each{|o| o.associations[name] = o.associations[name][offset]}
173
173
  end
174
174
  end
@@ -263,7 +263,9 @@ module Sequel
263
263
  # yielding each row to the block.
264
264
  def eager_load_results(eo, &block)
265
265
  rows = eo[:rows]
266
- 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
276
280
  ds = eager_loading_dataset(eo)
277
281
 
278
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -311,7 +315,8 @@ module Sequel
311
315
  objects = loader.all(ids)
312
316
  end
313
317
 
314
- objects.each(&block)
318
+ Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
319
+
315
320
  if strategy == :ruby
316
321
  apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
317
322
  end
@@ -356,7 +361,7 @@ module Sequel
356
361
  def finalize
357
362
  return unless cache = self[:cache]
358
363
 
359
- finalize_settings.each do |meth, key|
364
+ finalizer = proc do |meth, key|
360
365
  next if has_key?(key)
361
366
 
362
367
  # Allow calling private methods to make sure caching is done appropriately
@@ -364,6 +369,13 @@ module Sequel
364
369
  self[key] = cache.delete(key) if cache.has_key?(key)
365
370
  end
366
371
 
372
+ finalize_settings.each(&finalizer)
373
+
374
+ unless self[:instance_specific]
375
+ finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
376
+ finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
377
+ end
378
+
367
379
  nil
368
380
  end
369
381
 
@@ -371,9 +383,7 @@ module Sequel
371
383
  FINALIZE_SETTINGS = {
372
384
  :associated_class=>:class,
373
385
  :associated_dataset=>:_dataset,
374
- :associated_eager_dataset=>:associated_eager_dataset,
375
386
  :eager_limit_strategy=>:_eager_limit_strategy,
376
- :filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset,
377
387
  :placeholder_loader=>:placeholder_loader,
378
388
  :predicate_key=>:predicate_key,
379
389
  :predicate_keys=>:predicate_keys,
@@ -432,7 +442,11 @@ module Sequel
432
442
  if use_placeholder_loader?
433
443
  cached_fetch(:placeholder_loader) do
434
444
  Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
435
- ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
445
+ ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
446
+ if self[:block]
447
+ ds = self[:block].call(ds)
448
+ end
449
+ ds
436
450
  end
437
451
  end
438
452
  end
@@ -626,9 +640,7 @@ module Sequel
626
640
  # given the hash passed to the eager loader.
627
641
  def eager_loading_dataset(eo=OPTS)
628
642
  ds = eo[:dataset] || associated_eager_dataset
629
- if id_map = eo[:id_map]
630
- ds = ds.where(eager_loading_predicate_condition(id_map.keys))
631
- end
643
+ ds = eager_loading_set_predicate_condition(ds, eo)
632
644
  if associations = eo[:associations]
633
645
  ds = ds.eager(associations)
634
646
  end
@@ -655,6 +667,15 @@ module Sequel
655
667
  self[:model].default_eager_limit_strategy || :ruby
656
668
  end
657
669
 
670
+ # Set the predicate condition for the eager loading dataset based on the id map
671
+ # in the eager loading options.
672
+ def eager_loading_set_predicate_condition(ds, eo)
673
+ if id_map = eo[:id_map]
674
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
675
+ end
676
+ ds
677
+ end
678
+
658
679
  # The predicate condition to use for the eager_loader.
659
680
  def eager_loading_predicate_condition(keys)
660
681
  {predicate_key=>keys}
@@ -796,7 +817,7 @@ module Sequel
796
817
 
797
818
  # Whether the placeholder loader can be used to load the association.
798
819
  def use_placeholder_loader?
799
- !self[:instance_specific] && !self[:eager_graph]
820
+ self[:use_placeholder_loader]
800
821
  end
801
822
  end
802
823
 
@@ -1244,7 +1265,9 @@ module Sequel
1244
1265
  else
1245
1266
  assoc_record.values.delete(left_key_alias)
1246
1267
  end
1247
- next unless objects = h[hash_key]
1268
+
1269
+ objects = h[hash_key]
1270
+
1248
1271
  if assign_singular
1249
1272
  objects.each do |object|
1250
1273
  object.associations[name] ||= assoc_record
@@ -1304,7 +1327,7 @@ module Sequel
1304
1327
 
1305
1328
  # many_to_many associations need to select a key in an associated table to eagerly load
1306
1329
  def eager_loading_use_associated_key?
1307
- true
1330
+ !separate_query_per_table?
1308
1331
  end
1309
1332
 
1310
1333
  # The source of the join table. This is the join table itself, unless it
@@ -1361,10 +1384,30 @@ module Sequel
1361
1384
  cached_fetch(:select){default_select}
1362
1385
  end
1363
1386
 
1387
+ # Whether a separate query should be used for the join table.
1388
+ def separate_query_per_table?
1389
+ self[:join_table_db]
1390
+ end
1391
+
1364
1392
  private
1365
1393
 
1394
+ # Join to the the join table, unless using a separate query per table.
1366
1395
  def _associated_dataset
1367
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1396
+ if separate_query_per_table?
1397
+ super
1398
+ else
1399
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1400
+ end
1401
+ end
1402
+
1403
+ # Use the right_keys from the eager loading options if
1404
+ # using a separate query per table.
1405
+ def eager_loading_set_predicate_condition(ds, eo)
1406
+ if separate_query_per_table?
1407
+ ds.where(right_primary_key=>eo[:right_keys])
1408
+ else
1409
+ super
1410
+ end
1368
1411
  end
1369
1412
 
1370
1413
  # The default selection for associations that require joins. These do not use the default
@@ -1581,6 +1624,7 @@ module Sequel
1581
1624
  # === Multiple Types
1582
1625
  # :adder :: Proc used to define the private _add_* method for doing the database work
1583
1626
  # to associate the given object to the current object (*_to_many assocations).
1627
+ # Set to nil to not define a add_* method for the association.
1584
1628
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1585
1629
  # after a new item is added to the association.
1586
1630
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1591,6 +1635,8 @@ module Sequel
1591
1635
  # after an item is set using the association setter method.
1592
1636
  # :allow_eager :: If set to false, you cannot load the association eagerly
1593
1637
  # via eager or eager_graph
1638
+ # :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
1639
+ # :allow_filtering_by :: If set to false, you cannot use the association when filtering
1594
1640
  # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
1595
1641
  # before a new item is added to the association.
1596
1642
  # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1609,6 +1655,7 @@ module Sequel
1609
1655
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1610
1656
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1611
1657
  # to remove all objects associated to the current object (*_to_many assocations).
1658
+ # Set to nil to not define a remove_all_* method for the association.
1612
1659
  # :clone :: Merge the current options and block into the options and block used in defining
1613
1660
  # the given association. Can be used to DRY up a bunch of similar associations that
1614
1661
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1663,7 +1710,7 @@ module Sequel
1663
1710
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1664
1711
  # the association via +eager_graph+, instead of the default conditions specified by the
1665
1712
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1666
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1713
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1667
1714
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1668
1715
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1669
1716
  # automatically qualified when using eager_graph.
@@ -1675,6 +1722,10 @@ module Sequel
1675
1722
  # limit (first element) and an offset (second element).
1676
1723
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1677
1724
  # to the module containing the model's columns.
1725
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1726
+ # method is never used.
1727
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1728
+ # method is never used.
1678
1729
  # :order :: the column(s) by which to order the association dataset. Can be a
1679
1730
  # singular column symbol or an array of column symbols.
1680
1731
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1687,6 +1738,7 @@ module Sequel
1687
1738
  # the current association's key(s). Set to nil to not use a reciprocal.
1688
1739
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1689
1740
  # to remove the association between the given object and the current object (*_to_many assocations).
1741
+ # Set to nil to not define a remove_* method for the association.
1690
1742
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1691
1743
  # that uses joins, which means it doesn't include the attributes from the
1692
1744
  # join table. If you want to include the join table attributes, you can
@@ -1695,6 +1747,7 @@ module Sequel
1695
1747
  # the same name in both the join table and the associated table.
1696
1748
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1697
1749
  # between the given object and the current object (*_to_one associations).
1750
+ # Set to nil to not define a setter method for the association.
1698
1751
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1699
1752
  # loading limited associations using the default :union strategy.
1700
1753
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1751,6 +1804,9 @@ module Sequel
1751
1804
  # underscored, sorted, and joined with '_'.
1752
1805
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1753
1806
  # methods. Should accept a dataset argument and return a modified dataset if present.
1807
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1808
+ # a join between to the join table and the associated table, use a separate query for the
1809
+ # join table using the given Database object.
1754
1810
  # :left_key :: foreign key in join table that points to current model's
1755
1811
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1756
1812
  # Can use an array of symbols for a composite key association.
@@ -1780,7 +1836,9 @@ module Sequel
1780
1836
 
1781
1837
  if opts[:clone]
1782
1838
  cloned_assoc = association_reflection(opts[:clone])
1839
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1783
1840
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1841
+ orig_opts.delete(:class_name) if remove_class_name
1784
1842
  end
1785
1843
 
1786
1844
  opts = Hash[default_association_options]
@@ -1791,11 +1849,12 @@ module Sequel
1791
1849
  opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1792
1850
 
1793
1851
  opts[:block] = block if block
1794
- if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1852
+ opts[:instance_specific] = true if orig_opts[:dataset]
1853
+ if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
1795
1854
  # It's possible the association is instance specific, in that it depends on
1796
1855
  # values other than the foreign key value. This needs to be checked for
1797
1856
  # in certain places to disable optimizations.
1798
- opts[:instance_specific] = true
1857
+ opts[:instance_specific] = _association_instance_specific_default(name)
1799
1858
  end
1800
1859
  opts = assoc_class.new.merge!(opts)
1801
1860
 
@@ -1803,6 +1862,7 @@ module Sequel
1803
1862
  raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
1804
1863
  end
1805
1864
 
1865
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1806
1866
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1807
1867
  opts[:graph_join_type] ||= :left_outer
1808
1868
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
@@ -1825,8 +1885,7 @@ module Sequel
1825
1885
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1826
1886
  opts.delete(:class) unless opts[:class]
1827
1887
 
1828
- send(:"def_#{type}", opts)
1829
- def_association_instance_methods(opts)
1888
+ def_association(opts)
1830
1889
 
1831
1890
  orig_opts.delete(:clone)
1832
1891
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1899,6 +1958,12 @@ module Sequel
1899
1958
  Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1900
1959
 
1901
1960
  private
1961
+
1962
+ # The default value for the instance_specific option, if the association
1963
+ # could be instance specific and the :instance_specific option is not specified.
1964
+ def _association_instance_specific_default(_)
1965
+ true
1966
+ end
1902
1967
 
1903
1968
  # The module to use for the association's methods. Defaults to
1904
1969
  # the overridable_methods_module.
@@ -1910,7 +1975,22 @@ module Sequel
1910
1975
  # can be easily overridden in the class itself while allowing for
1911
1976
  # super to be called.
1912
1977
  def association_module_def(name, opts=OPTS, &block)
1913
- association_module(opts).send(:define_method, name, &block)
1978
+ mod = association_module(opts)
1979
+ mod.send(:define_method, name, &block)
1980
+ mod.send(:alias_method, name, name)
1981
+ end
1982
+
1983
+ # Add a method to the module included in the class, so the method
1984
+ # can be easily overridden in the class itself while allowing for
1985
+ # super to be called. This method allows passing keywords through
1986
+ # the defined methods.
1987
+ def association_module_delegate_def(name, opts, &block)
1988
+ mod = association_module(opts)
1989
+ mod.send(:define_method, name, &block)
1990
+ # :nocov:
1991
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
1992
+ # :nocov:
1993
+ mod.send(:alias_method, name, name)
1914
1994
  end
1915
1995
 
1916
1996
  # Add a private method to the module included in the class.
@@ -1919,6 +1999,13 @@ module Sequel
1919
1999
  association_module(opts).send(:private, name)
1920
2000
  end
1921
2001
 
2002
+ # Delegate to the type-specific association method to setup the
2003
+ # association, and define the association instance methods.
2004
+ def def_association(opts)
2005
+ send(:"def_#{opts[:type]}", opts)
2006
+ def_association_instance_methods(opts)
2007
+ end
2008
+
1922
2009
  # Adds the association method to the association methods module.
1923
2010
  def def_association_method(opts)
1924
2011
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1944,15 +2031,13 @@ module Sequel
1944
2031
  opts[:setter_method] = :"#{opts[:name]}="
1945
2032
  end
1946
2033
 
1947
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
2034
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1948
2035
  if opts[:block]
1949
2036
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1950
2037
  end
1951
- if opts[:dataset]
1952
- opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1953
- opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1954
- end
1955
- def_association_method(opts)
2038
+ opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
2039
+ opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
2040
+ def_association_method(opts) unless opts[:no_association_method]
1956
2041
 
1957
2042
  return if opts[:read_only]
1958
2043
 
@@ -1964,17 +2049,17 @@ module Sequel
1964
2049
 
1965
2050
  if adder = opts[:adder]
1966
2051
  association_module_private_def(opts[:_add_method], opts, &adder)
1967
- association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
2052
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1968
2053
  end
1969
2054
 
1970
2055
  if remover = opts[:remover]
1971
2056
  association_module_private_def(opts[:_remove_method], opts, &remover)
1972
- association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
2057
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1973
2058
  end
1974
2059
 
1975
2060
  if clearer = opts[:clearer]
1976
2061
  association_module_private_def(opts[:_remove_all_method], opts, &clearer)
1977
- association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
2062
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1978
2063
  end
1979
2064
  end
1980
2065
 
@@ -1996,7 +2081,7 @@ module Sequel
1996
2081
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
1997
2082
  end
1998
2083
  opts[:uses_left_composite_keys] = lcks.length > 1
1999
- opts[:uses_right_composite_keys] = rcks.length > 1
2084
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
2000
2085
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
2001
2086
  join_table = (opts[:join_table] ||= opts.default_join_table)
2002
2087
  opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -2005,8 +2090,75 @@ module Sequel
2005
2090
  opts[:after_load] ||= []
2006
2091
  opts[:after_load].unshift(:array_uniq!)
2007
2092
  end
2008
- opts[:dataset] ||= opts.association_dataset_proc
2009
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2093
+ if join_table_db = opts[:join_table_db]
2094
+ opts[:use_placeholder_loader] = false
2095
+ opts[:allow_eager_graph] = false
2096
+ opts[:allow_filtering_by] = false
2097
+ opts[:eager_limit_strategy] = nil
2098
+ join_table_ds = join_table_db.from(join_table)
2099
+ opts[:dataset] ||= proc do |r|
2100
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2101
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2102
+ if uses_rcks
2103
+ vals.delete_if{|v| v.any?(&:nil?)}
2104
+ else
2105
+ vals.delete(nil)
2106
+ end
2107
+ ds = ds.clone(:no_results=>true) if vals.empty?
2108
+ ds
2109
+ end
2110
+ opts[:eager_loader] ||= proc do |eo|
2111
+ h = eo[:id_map]
2112
+ assign_singular = opts.assign_singular?
2113
+ rpk = opts.right_primary_key
2114
+ name = opts[:name]
2115
+
2116
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2117
+
2118
+ if uses_rcks
2119
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2120
+ else
2121
+ join_map.delete(nil)
2122
+ end
2123
+
2124
+ eo = Hash[eo]
2125
+
2126
+ if join_map.empty?
2127
+ eo[:no_results] = true
2128
+ else
2129
+ join_map.each_value do |vs|
2130
+ vs.replace(vs.flat_map{|v| h[v]})
2131
+ vs.uniq!
2132
+ end
2133
+
2134
+ eo[:loader] = false
2135
+ eo[:right_keys] = join_map.keys
2136
+ end
2137
+
2138
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2139
+ rpkv = if uses_rcks
2140
+ assoc_record.values.values_at(*rpk)
2141
+ else
2142
+ assoc_record.values[rpk]
2143
+ end
2144
+
2145
+ objects = join_map[rpkv]
2146
+
2147
+ if assign_singular
2148
+ objects.each do |object|
2149
+ object.associations[name] ||= assoc_record
2150
+ end
2151
+ else
2152
+ objects.each do |object|
2153
+ object.associations[name].push(assoc_record)
2154
+ end
2155
+ end
2156
+ end
2157
+ end
2158
+ else
2159
+ opts[:dataset] ||= opts.association_dataset_proc
2160
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2161
+ end
2010
2162
 
2011
2163
  join_type = opts[:graph_join_type]
2012
2164
  select = opts[:graph_select]
@@ -2040,50 +2192,60 @@ module Sequel
2040
2192
  return if opts[:read_only]
2041
2193
 
2042
2194
  if one_through_one
2043
- opts[:setter] ||= proc do |o|
2044
- h = {}
2045
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2046
- jtds = _join_table_dataset(opts).where(lh)
2195
+ unless opts.has_key?(:setter)
2196
+ opts[:setter] = proc do |o|
2197
+ h = {}
2198
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2199
+ jtds = _join_table_dataset(opts).where(lh)
2047
2200
 
2048
- checked_transaction do
2049
- current = jtds.first
2201
+ checked_transaction do
2202
+ current = jtds.first
2050
2203
 
2051
- if o
2052
- new_values = []
2053
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2054
- end
2055
-
2056
- if current
2057
- current_values = rcks.map{|k| current[k]}
2058
- jtds = jtds.where(rcks.zip(current_values))
2059
2204
  if o
2060
- if current_values != new_values
2061
- jtds.update(h)
2205
+ new_values = []
2206
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2207
+ end
2208
+
2209
+ if current
2210
+ current_values = rcks.map{|k| current[k]}
2211
+ jtds = jtds.where(rcks.zip(current_values))
2212
+ if o
2213
+ if current_values != new_values
2214
+ jtds.update(h)
2215
+ end
2216
+ else
2217
+ jtds.delete
2062
2218
  end
2063
- else
2064
- jtds.delete
2219
+ elsif o
2220
+ lh.each{|k,v| h[k] = v}
2221
+ jtds.insert(h)
2065
2222
  end
2066
- elsif o
2067
- lh.each{|k,v| h[k] = v}
2068
- jtds.insert(h)
2069
2223
  end
2070
2224
  end
2071
2225
  end
2072
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2226
+ if opts.fetch(:setter, true)
2227
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2228
+ end
2073
2229
  else
2074
- opts[:adder] ||= proc do |o|
2075
- h = {}
2076
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2077
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2078
- _join_table_dataset(opts).insert(h)
2230
+ unless opts.has_key?(:adder)
2231
+ opts[:adder] = proc do |o|
2232
+ h = {}
2233
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2234
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2235
+ _join_table_dataset(opts).insert(h)
2236
+ end
2079
2237
  end
2080
2238
 
2081
- opts[:remover] ||= proc do |o|
2082
- _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
2239
+ unless opts.has_key?(:remover)
2240
+ opts[:remover] = proc do |o|
2241
+ _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
2242
+ end
2083
2243
  end
2084
2244
 
2085
- opts[:clearer] ||= proc do
2086
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2245
+ unless opts.has_key?(:clearer)
2246
+ opts[:clearer] = proc do
2247
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2248
+ end
2087
2249
  end
2088
2250
  end
2089
2251
  end
@@ -2122,9 +2284,7 @@ module Sequel
2122
2284
 
2123
2285
  eager_load_results(opts, eo) do |assoc_record|
2124
2286
  hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
2125
- if objects = h[hash_key]
2126
- objects.each{|object| object.associations[name] = assoc_record}
2127
- end
2287
+ h[hash_key].each{|object| object.associations[name] = assoc_record}
2128
2288
  end
2129
2289
  end
2130
2290
 
@@ -2142,8 +2302,12 @@ module Sequel
2142
2302
 
2143
2303
  return if opts[:read_only]
2144
2304
 
2145
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2146
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2305
+ unless opts.has_key?(:setter)
2306
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2307
+ end
2308
+ if opts.fetch(:setter, true)
2309
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2310
+ end
2147
2311
  end
2148
2312
 
2149
2313
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2171,7 +2335,7 @@ module Sequel
2171
2335
  eager_load_results(opts, eo) do |assoc_record|
2172
2336
  assoc_record.values.delete(delete_rn) if delete_rn
2173
2337
  hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
2174
- next unless objects = h[hash_key]
2338
+ objects = h[hash_key]
2175
2339
  if assign_singular
2176
2340
  objects.each do |object|
2177
2341
  unless object.associations[name]
@@ -2210,49 +2374,59 @@ module Sequel
2210
2374
  cks.each{|k| ck_nil_hash[k] = nil}
2211
2375
 
2212
2376
  if one_to_one
2213
- opts[:setter] ||= proc do |o|
2214
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2377
+ unless opts.has_key?(:setter)
2378
+ opts[:setter] = proc do |o|
2379
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2215
2380
 
2216
- 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)))
2217
- if old = up_ds.first
2218
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2381
+ 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)))
2382
+ if old = up_ds.first
2383
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2384
+ end
2385
+ save_old = true
2219
2386
  end
2220
- save_old = true
2221
- end
2222
2387
 
2223
- if o
2224
- if !o.new? && !save_old
2225
- up_ds = up_ds.exclude(o.pk_hash)
2388
+ if o
2389
+ if !o.new? && !save_old
2390
+ up_ds = up_ds.exclude(o.pk_hash)
2391
+ end
2392
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2226
2393
  end
2227
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2228
- end
2229
2394
 
2230
- checked_transaction do
2231
- if save_old
2232
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2233
- else
2234
- up_ds.skip_limit_check.update(ck_nil_hash)
2235
- end
2395
+ checked_transaction do
2396
+ if save_old
2397
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2398
+ else
2399
+ up_ds.skip_limit_check.update(ck_nil_hash)
2400
+ end
2236
2401
 
2237
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2402
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2403
+ end
2238
2404
  end
2239
2405
  end
2240
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2406
+ if opts.fetch(:setter, true)
2407
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2408
+ end
2241
2409
  else
2242
2410
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2243
2411
 
2244
- opts[:adder] ||= proc do |o|
2245
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2246
- o.save(save_opts)
2412
+ unless opts.has_key?(:adder)
2413
+ opts[:adder] = proc do |o|
2414
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2415
+ o.save(save_opts)
2416
+ end
2247
2417
  end
2248
2418
 
2249
- opts[:remover] ||= proc do |o|
2250
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2251
- o.save(save_opts)
2419
+ unless opts.has_key?(:remover)
2420
+ opts[:remover] = proc do |o|
2421
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2422
+ o.save(save_opts)
2423
+ end
2252
2424
  end
2253
2425
 
2254
- opts[:clearer] ||= proc do
2255
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2426
+ unless opts.has_key?(:clearer)
2427
+ opts[:clearer] = proc do
2428
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2429
+ end
2256
2430
  end
2257
2431
  end
2258
2432
  end
@@ -2346,7 +2520,7 @@ module Sequel
2346
2520
 
2347
2521
  # Dataset for the join table of the given many to many association reflection
2348
2522
  def _join_table_dataset(opts)
2349
- ds = model.db.from(opts.join_table_source)
2523
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2350
2524
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2351
2525
  end
2352
2526
 
@@ -2367,7 +2541,12 @@ module Sequel
2367
2541
  if loader = _associated_object_loader(opts, dynamic_opts)
2368
2542
  loader.all(*opts.predicate_key_values(self))
2369
2543
  else
2370
- _associated_dataset(opts, dynamic_opts).all
2544
+ ds = _associated_dataset(opts, dynamic_opts)
2545
+ if ds.opts[:no_results]
2546
+ []
2547
+ else
2548
+ ds.all
2549
+ end
2371
2550
  end
2372
2551
  end
2373
2552
 
@@ -2408,6 +2587,9 @@ module Sequel
2408
2587
  run_association_callbacks(opts, :after_add, o)
2409
2588
  o
2410
2589
  end
2590
+ # :nocov:
2591
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2592
+ # :nocov:
2411
2593
 
2412
2594
  # Add/Set the current object to/as the given object's reciprocal association.
2413
2595
  def add_reciprocal_object(opts, o)
@@ -2550,6 +2732,9 @@ module Sequel
2550
2732
  associations[opts[:name]] = []
2551
2733
  ret
2552
2734
  end
2735
+ # :nocov:
2736
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2737
+ # :nocov:
2553
2738
 
2554
2739
  # Remove the given associated object from the given association
2555
2740
  def remove_associated_object(opts, o, *args)
@@ -2571,6 +2756,9 @@ module Sequel
2571
2756
  run_association_callbacks(opts, :after_remove, o)
2572
2757
  o
2573
2758
  end
2759
+ # :nocov:
2760
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2761
+ # :nocov:
2574
2762
 
2575
2763
  # Check that the object from the associated table specified by the primary key
2576
2764
  # is currently associated to the receiver. If it is associated, return the object, otherwise
@@ -2819,6 +3007,8 @@ module Sequel
2819
3007
  (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)})))))
2820
3008
  l = args[0]
2821
3009
  if ar = model.association_reflections[l]
3010
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3011
+
2822
3012
  if multiple
2823
3013
  klass = ar.associated_class
2824
3014
  if is_ds
@@ -2940,7 +3130,7 @@ module Sequel
2940
3130
 
2941
3131
  # The secondary eager loading method. Loads all associations in a single query. This
2942
3132
  # method should only be used if you need to filter or order based on columns in associated tables,
2943
- # or if you have done comparative benchmarking it and determined it is faster.
3133
+ # or if you have done comparative benchmarking and determined it is faster.
2944
3134
  #
2945
3135
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2946
3136
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2969,6 +3159,8 @@ module Sequel
2969
3159
  # You can specify an custom alias and/or join type on a per-association basis by providing an
2970
3160
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2971
3161
  #
3162
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3163
+ #
2972
3164
  # Examples:
2973
3165
  #
2974
3166
  # # For each album, eager_graph load the artist
@@ -3064,6 +3256,8 @@ module Sequel
3064
3256
  # significantly slower in some cases (perhaps even the majority of cases), so you should
3065
3257
  # only use this if you have benchmarked that it is faster for your use cases.
3066
3258
  def eager_graph_with_options(associations, opts=OPTS)
3259
+ return self if associations.empty?
3260
+
3067
3261
  opts = opts.dup unless opts.frozen?
3068
3262
  associations = [associations] unless associations.is_a?(Array)
3069
3263
  ds = if eg = @opts[:eager_graph]
@@ -3190,7 +3384,6 @@ module Sequel
3190
3384
  # requirements :: an array, used as a stack for requirements
3191
3385
  # *associations :: the associations to add to the graph
3192
3386
  def eager_graph_associations(ds, model, ta, requirements, *associations)
3193
- return ds if associations.empty?
3194
3387
  associations.flatten.each do |association|
3195
3388
  ds = case association
3196
3389
  when Symbol, SQL::AliasedExpression
@@ -3311,7 +3504,7 @@ module Sequel
3311
3504
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
3312
3505
  # per-call determining of the alias base.
3313
3506
  def eager_graph_check_association(model, association)
3314
- if association.is_a?(SQL::AliasedExpression)
3507
+ reflection = if association.is_a?(SQL::AliasedExpression)
3315
3508
  expr = association.expression
3316
3509
  if expr.is_a?(SQL::Identifier)
3317
3510
  expr = expr.value
@@ -3320,10 +3513,17 @@ module Sequel
3320
3513
  end
3321
3514
  end
3322
3515
 
3323
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3516
+ check_reflection = check_association(model, expr)
3517
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
3324
3518
  else
3325
- check_association(model, association)
3519
+ check_reflection = check_association(model, association)
3326
3520
  end
3521
+
3522
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3523
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
3524
+ end
3525
+
3526
+ reflection
3327
3527
  end
3328
3528
 
3329
3529
  # The EagerGraphLoader instance used for converting eager_graph results.
@@ -3334,15 +3534,30 @@ module Sequel
3334
3534
  egl.dup
3335
3535
  end
3336
3536
 
3337
- # Eagerly load all specified associations
3537
+ # Eagerly load all specified associations.
3338
3538
  def eager_load(a, eager_assoc=@opts[:eager])
3339
3539
  return if a.empty?
3540
+
3541
+ # Reflections for all associations to eager load
3542
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3543
+
3544
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3545
+
3546
+ reflections.each do |r|
3547
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3548
+ end
3549
+
3550
+ nil
3551
+ end
3552
+
3553
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3554
+ def prepare_eager_load(a, reflections, eager_assoc)
3555
+ eager_load_data = {}
3556
+
3340
3557
  # Key is foreign/primary key name symbol.
3341
3558
  # Value is hash with keys being foreign/primary key values (generally integers)
3342
3559
  # and values being an array of current model objects with that specific foreign/primary key
3343
3560
  key_hash = {}
3344
- # Reflections for all associations to eager load
3345
- reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3346
3561
 
3347
3562
  # Populate the key_hash entry for each association being eagerly loaded
3348
3563
  reflections.each do |r|
@@ -3373,7 +3588,6 @@ module Sequel
3373
3588
  id_map = nil
3374
3589
  end
3375
3590
 
3376
- loader = r[:eager_loader]
3377
3591
  associations = eager_assoc[r[:name]]
3378
3592
  if associations.respond_to?(:call)
3379
3593
  eager_block = associations
@@ -3381,9 +3595,23 @@ module Sequel
3381
3595
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3382
3596
  eager_block, associations = pr_assoc
3383
3597
  end
3384
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
3385
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3386
- end
3598
+
3599
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3600
+ end
3601
+
3602
+ eager_load_data
3603
+ end
3604
+
3605
+ # Using the hash of loaders and eager options, perform the eager loading.
3606
+ def perform_eager_loads(eager_load_data)
3607
+ eager_load_data.map do |loader, eo|
3608
+ perform_eager_load(loader, eo)
3609
+ end
3610
+ end
3611
+
3612
+ # Perform eager loading for a single association using the loader and eager options.
3613
+ def perform_eager_load(loader, eo)
3614
+ loader.call(eo)
3387
3615
  end
3388
3616
 
3389
3617
  # Return a subquery expression for filering by a many_to_many association