sequel 5.33.0 → 5.58.0

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