sequel 5.39.0 → 5.72.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -263,7 +263,9 @@ module Sequel
263
263
  # yielding each row to the block.
264
264
  def eager_load_results(eo, &block)
265
265
  rows = eo[:rows]
266
- initialize_association_cache(rows) unless eo[:initialize_rows] == false
266
+ unless eo[:initialize_rows] == false
267
+ Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
268
+ end
267
269
  if eo[:id_map]
268
270
  ids = eo[:id_map].keys
269
271
  return ids if ids.empty?
@@ -272,7 +274,9 @@ module Sequel
272
274
  cascade = eo[:associations]
273
275
  eager_limit = nil
274
276
 
275
- if eo[:eager_block] || eo[:loader] == false
277
+ if eo[:no_results]
278
+ no_results = true
279
+ elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
276
280
  ds = eager_loading_dataset(eo)
277
281
 
278
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -295,13 +299,28 @@ module Sequel
295
299
  strategy = :ruby if strategy == :correlated_subquery
296
300
  strategy = nil if strategy == :ruby && assign_singular?
297
301
  objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
302
+
303
+ if strategy == :window_function
304
+ delete_rn = ds.row_number_column
305
+ objects.each{|obj| obj.values.delete(delete_rn)}
306
+ end
298
307
  elsif strategy == :union
299
308
  objects = []
300
309
  ds = associated_dataset
301
310
  loader = union_eager_loader
302
311
  joiner = " UNION ALL "
303
312
  ids.each_slice(subqueries_per_union).each do |slice|
304
- objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
313
+ sql = loader.send(:sql_origin)
314
+ join = false
315
+ slice.each do |k|
316
+ if join
317
+ sql << joiner
318
+ else
319
+ join = true
320
+ end
321
+ loader.append_sql(sql, *k)
322
+ end
323
+ objects.concat(ds.with_sql(sql).to_a)
305
324
  end
306
325
  ds = ds.eager(cascade) if cascade
307
326
  ds.send(:post_load, objects)
@@ -311,7 +330,8 @@ module Sequel
311
330
  objects = loader.all(ids)
312
331
  end
313
332
 
314
- objects.each(&block)
333
+ Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
334
+
315
335
  if strategy == :ruby
316
336
  apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
317
337
  end
@@ -436,7 +456,7 @@ module Sequel
436
456
  def placeholder_loader
437
457
  if use_placeholder_loader?
438
458
  cached_fetch(:placeholder_loader) do
439
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
459
+ associated_dataset.placeholder_literalizer_loader do |pl, ds|
440
460
  ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
441
461
  if self[:block]
442
462
  ds = self[:block].call(ds)
@@ -635,9 +655,7 @@ module Sequel
635
655
  # given the hash passed to the eager loader.
636
656
  def eager_loading_dataset(eo=OPTS)
637
657
  ds = eo[:dataset] || associated_eager_dataset
638
- if id_map = eo[:id_map]
639
- ds = ds.where(eager_loading_predicate_condition(id_map.keys))
640
- end
658
+ ds = eager_loading_set_predicate_condition(ds, eo)
641
659
  if associations = eo[:associations]
642
660
  ds = ds.eager(associations)
643
661
  end
@@ -664,6 +682,15 @@ module Sequel
664
682
  self[:model].default_eager_limit_strategy || :ruby
665
683
  end
666
684
 
685
+ # Set the predicate condition for the eager loading dataset based on the id map
686
+ # in the eager loading options.
687
+ def eager_loading_set_predicate_condition(ds, eo)
688
+ if id_map = eo[:id_map]
689
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
690
+ end
691
+ ds
692
+ end
693
+
667
694
  # The predicate condition to use for the eager_loader.
668
695
  def eager_loading_predicate_condition(keys)
669
696
  {predicate_key=>keys}
@@ -731,8 +758,8 @@ module Sequel
731
758
  # A placeholder literalizer used to speed up eager loading.
732
759
  def placeholder_eager_loader
733
760
  cached_fetch(:placeholder_eager_loader) do
734
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
735
- apply_eager_limit_strategy(eager_loading_dataset.where(predicate_key=>pl.arg), eager_limit_strategy)
761
+ eager_loading_dataset.placeholder_literalizer_loader do |pl, ds|
762
+ apply_eager_limit_strategy(ds.where(predicate_key=>pl.arg), eager_limit_strategy)
736
763
  end
737
764
  end
738
765
  end
@@ -791,7 +818,7 @@ module Sequel
791
818
  # loading a limited association.
792
819
  def union_eager_loader
793
820
  cached_fetch(:union_eager_loader) do
794
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
821
+ associated_dataset.placeholder_literalizer_loader do |pl, ds|
795
822
  ds = self[:eager_block].call(ds) if self[:eager_block]
796
823
  keys = predicate_keys
797
824
  ds = ds.where(keys.map{pl.arg}.zip(keys))
@@ -805,7 +832,7 @@ module Sequel
805
832
 
806
833
  # Whether the placeholder loader can be used to load the association.
807
834
  def use_placeholder_loader?
808
- self[:use_placeholder_loader]
835
+ self[:use_placeholder_loader] && _associated_dataset.supports_placeholder_literalizer?
809
836
  end
810
837
  end
811
838
 
@@ -1315,7 +1342,7 @@ module Sequel
1315
1342
 
1316
1343
  # many_to_many associations need to select a key in an associated table to eagerly load
1317
1344
  def eager_loading_use_associated_key?
1318
- true
1345
+ !separate_query_per_table?
1319
1346
  end
1320
1347
 
1321
1348
  # The source of the join table. This is the join table itself, unless it
@@ -1372,10 +1399,30 @@ module Sequel
1372
1399
  cached_fetch(:select){default_select}
1373
1400
  end
1374
1401
 
1402
+ # Whether a separate query should be used for the join table.
1403
+ def separate_query_per_table?
1404
+ self[:join_table_db]
1405
+ end
1406
+
1375
1407
  private
1376
1408
 
1409
+ # Join to the the join table, unless using a separate query per table.
1377
1410
  def _associated_dataset
1378
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1411
+ if separate_query_per_table?
1412
+ super
1413
+ else
1414
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1415
+ end
1416
+ end
1417
+
1418
+ # Use the right_keys from the eager loading options if
1419
+ # using a separate query per table.
1420
+ def eager_loading_set_predicate_condition(ds, eo)
1421
+ if separate_query_per_table?
1422
+ ds.where(right_primary_key=>eo[:right_keys])
1423
+ else
1424
+ super
1425
+ end
1379
1426
  end
1380
1427
 
1381
1428
  # The default selection for associations that require joins. These do not use the default
@@ -1592,6 +1639,7 @@ module Sequel
1592
1639
  # === Multiple Types
1593
1640
  # :adder :: Proc used to define the private _add_* method for doing the database work
1594
1641
  # to associate the given object to the current object (*_to_many assocations).
1642
+ # Set to nil to not define a add_* method for the association.
1595
1643
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1596
1644
  # after a new item is added to the association.
1597
1645
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1602,6 +1650,8 @@ module Sequel
1602
1650
  # after an item is set using the association setter method.
1603
1651
  # :allow_eager :: If set to false, you cannot load the association eagerly
1604
1652
  # via eager or eager_graph
1653
+ # :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
1654
+ # :allow_filtering_by :: If set to false, you cannot use the association when filtering
1605
1655
  # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
1606
1656
  # before a new item is added to the association.
1607
1657
  # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1620,6 +1670,7 @@ module Sequel
1620
1670
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1621
1671
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1622
1672
  # to remove all objects associated to the current object (*_to_many assocations).
1673
+ # Set to nil to not define a remove_all_* method for the association.
1623
1674
  # :clone :: Merge the current options and block into the options and block used in defining
1624
1675
  # the given association. Can be used to DRY up a bunch of similar associations that
1625
1676
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1674,18 +1725,26 @@ module Sequel
1674
1725
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1675
1726
  # the association via +eager_graph+, instead of the default conditions specified by the
1676
1727
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1677
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1728
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1678
1729
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1679
1730
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1680
1731
  # automatically qualified when using eager_graph.
1681
1732
  # :graph_select :: A column or array of columns to select from the associated table
1682
1733
  # when eagerly loading the association via +eager_graph+. Defaults to all
1683
1734
  # columns in the associated table.
1735
+ # :graph_use_association_block :: Makes eager_graph consider the association block. Without this, eager_graph
1736
+ # ignores the bock and only use the :graph_* options.
1737
+ # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1738
+ # uses instance specific state, or transient state (accessing current date/time, etc.).
1684
1739
  # :limit :: Limit the number of records to the provided value. Use
1685
1740
  # an array with two elements for the value to specify a
1686
1741
  # limit (first element) and an offset (second element).
1687
1742
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1688
1743
  # to the module containing the model's columns.
1744
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1745
+ # method is never used.
1746
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1747
+ # method is never used.
1689
1748
  # :order :: the column(s) by which to order the association dataset. Can be a
1690
1749
  # singular column symbol or an array of column symbols.
1691
1750
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1698,6 +1757,7 @@ module Sequel
1698
1757
  # the current association's key(s). Set to nil to not use a reciprocal.
1699
1758
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1700
1759
  # to remove the association between the given object and the current object (*_to_many assocations).
1760
+ # Set to nil to not define a remove_* method for the association.
1701
1761
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1702
1762
  # that uses joins, which means it doesn't include the attributes from the
1703
1763
  # join table. If you want to include the join table attributes, you can
@@ -1706,6 +1766,7 @@ module Sequel
1706
1766
  # the same name in both the join table and the associated table.
1707
1767
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1708
1768
  # between the given object and the current object (*_to_one associations).
1769
+ # Set to nil to not define a setter method for the association.
1709
1770
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1710
1771
  # loading limited associations using the default :union strategy.
1711
1772
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1762,6 +1823,9 @@ module Sequel
1762
1823
  # underscored, sorted, and joined with '_'.
1763
1824
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1764
1825
  # methods. Should accept a dataset argument and return a modified dataset if present.
1826
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1827
+ # a join between to the join table and the associated table, use a separate query for the
1828
+ # join table using the given Database object.
1765
1829
  # :left_key :: foreign key in join table that points to current model's
1766
1830
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1767
1831
  # Can use an array of symbols for a composite key association.
@@ -1791,7 +1855,9 @@ module Sequel
1791
1855
 
1792
1856
  if opts[:clone]
1793
1857
  cloned_assoc = association_reflection(opts[:clone])
1858
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1794
1859
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1860
+ orig_opts.delete(:class_name) if remove_class_name
1795
1861
  end
1796
1862
 
1797
1863
  opts = Hash[default_association_options]
@@ -1809,6 +1875,16 @@ module Sequel
1809
1875
  # in certain places to disable optimizations.
1810
1876
  opts[:instance_specific] = _association_instance_specific_default(name)
1811
1877
  end
1878
+ if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
1879
+ # For associations explicitly marked as instance specific, or that use the
1880
+ # :dataset option, where :allow_eager is not set, and no :eager_loader is
1881
+ # provided, disallow eager loading. In these cases, eager loading is
1882
+ # unlikely to work. This is not done for implicit setting of :instance_specific,
1883
+ # because implicit use is done by default for all associations with blocks,
1884
+ # and the vast majority of associations with blocks use the block for filtering
1885
+ # in a manner compatible with eager loading.
1886
+ opts[:allow_eager] = false
1887
+ end
1812
1888
  opts = assoc_class.new.merge!(opts)
1813
1889
 
1814
1890
  if opts[:clone] && !opts.cloneable?(cloned_assoc)
@@ -1838,8 +1914,7 @@ module Sequel
1838
1914
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1839
1915
  opts.delete(:class) unless opts[:class]
1840
1916
 
1841
- send(:"def_#{type}", opts)
1842
- def_association_instance_methods(opts)
1917
+ def_association(opts)
1843
1918
 
1844
1919
  orig_opts.delete(:clone)
1845
1920
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1929,7 +2004,22 @@ module Sequel
1929
2004
  # can be easily overridden in the class itself while allowing for
1930
2005
  # super to be called.
1931
2006
  def association_module_def(name, opts=OPTS, &block)
1932
- association_module(opts).send(:define_method, name, &block)
2007
+ mod = association_module(opts)
2008
+ mod.send(:define_method, name, &block)
2009
+ mod.send(:alias_method, name, name)
2010
+ end
2011
+
2012
+ # Add a method to the module included in the class, so the method
2013
+ # can be easily overridden in the class itself while allowing for
2014
+ # super to be called. This method allows passing keywords through
2015
+ # the defined methods.
2016
+ def association_module_delegate_def(name, opts, &block)
2017
+ mod = association_module(opts)
2018
+ mod.send(:define_method, name, &block)
2019
+ # :nocov:
2020
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
2021
+ # :nocov:
2022
+ mod.send(:alias_method, name, name)
1933
2023
  end
1934
2024
 
1935
2025
  # Add a private method to the module included in the class.
@@ -1938,6 +2028,13 @@ module Sequel
1938
2028
  association_module(opts).send(:private, name)
1939
2029
  end
1940
2030
 
2031
+ # Delegate to the type-specific association method to setup the
2032
+ # association, and define the association instance methods.
2033
+ def def_association(opts)
2034
+ send(:"def_#{opts[:type]}", opts)
2035
+ def_association_instance_methods(opts)
2036
+ end
2037
+
1941
2038
  # Adds the association method to the association methods module.
1942
2039
  def def_association_method(opts)
1943
2040
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1963,13 +2060,13 @@ module Sequel
1963
2060
  opts[:setter_method] = :"#{opts[:name]}="
1964
2061
  end
1965
2062
 
1966
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
2063
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1967
2064
  if opts[:block]
1968
2065
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1969
2066
  end
1970
2067
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1971
2068
  opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1972
- def_association_method(opts)
2069
+ def_association_method(opts) unless opts[:no_association_method]
1973
2070
 
1974
2071
  return if opts[:read_only]
1975
2072
 
@@ -1981,17 +2078,17 @@ module Sequel
1981
2078
 
1982
2079
  if adder = opts[:adder]
1983
2080
  association_module_private_def(opts[:_add_method], opts, &adder)
1984
- association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
2081
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1985
2082
  end
1986
2083
 
1987
2084
  if remover = opts[:remover]
1988
2085
  association_module_private_def(opts[:_remove_method], opts, &remover)
1989
- association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
2086
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1990
2087
  end
1991
2088
 
1992
2089
  if clearer = opts[:clearer]
1993
2090
  association_module_private_def(opts[:_remove_all_method], opts, &clearer)
1994
- association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
2091
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1995
2092
  end
1996
2093
  end
1997
2094
 
@@ -2013,7 +2110,7 @@ module Sequel
2013
2110
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
2014
2111
  end
2015
2112
  opts[:uses_left_composite_keys] = lcks.length > 1
2016
- opts[:uses_right_composite_keys] = rcks.length > 1
2113
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
2017
2114
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
2018
2115
  join_table = (opts[:join_table] ||= opts.default_join_table)
2019
2116
  opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -2022,8 +2119,75 @@ module Sequel
2022
2119
  opts[:after_load] ||= []
2023
2120
  opts[:after_load].unshift(:array_uniq!)
2024
2121
  end
2025
- opts[:dataset] ||= opts.association_dataset_proc
2026
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2122
+ if join_table_db = opts[:join_table_db]
2123
+ opts[:use_placeholder_loader] = false
2124
+ opts[:allow_eager_graph] = false
2125
+ opts[:allow_filtering_by] = false
2126
+ opts[:eager_limit_strategy] = nil
2127
+ join_table_ds = join_table_db.from(join_table)
2128
+ opts[:dataset] ||= proc do |r|
2129
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2130
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2131
+ if uses_rcks
2132
+ vals.delete_if{|v| v.any?(&:nil?)}
2133
+ else
2134
+ vals.delete(nil)
2135
+ end
2136
+ ds = ds.clone(:no_results=>true) if vals.empty?
2137
+ ds
2138
+ end
2139
+ opts[:eager_loader] ||= proc do |eo|
2140
+ h = eo[:id_map]
2141
+ assign_singular = opts.assign_singular?
2142
+ rpk = opts.right_primary_key
2143
+ name = opts[:name]
2144
+
2145
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2146
+
2147
+ if uses_rcks
2148
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2149
+ else
2150
+ join_map.delete(nil)
2151
+ end
2152
+
2153
+ eo = Hash[eo]
2154
+
2155
+ if join_map.empty?
2156
+ eo[:no_results] = true
2157
+ else
2158
+ join_map.each_value do |vs|
2159
+ vs.replace(vs.flat_map{|v| h[v]})
2160
+ vs.uniq!
2161
+ end
2162
+
2163
+ eo[:loader] = false
2164
+ eo[:right_keys] = join_map.keys
2165
+ end
2166
+
2167
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2168
+ rpkv = if uses_rcks
2169
+ assoc_record.values.values_at(*rpk)
2170
+ else
2171
+ assoc_record.values[rpk]
2172
+ end
2173
+
2174
+ objects = join_map[rpkv]
2175
+
2176
+ if assign_singular
2177
+ objects.each do |object|
2178
+ object.associations[name] ||= assoc_record
2179
+ end
2180
+ else
2181
+ objects.each do |object|
2182
+ object.associations[name].push(assoc_record)
2183
+ end
2184
+ end
2185
+ end
2186
+ end
2187
+ else
2188
+ opts[:dataset] ||= opts.association_dataset_proc
2189
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2190
+ end
2027
2191
 
2028
2192
  join_type = opts[:graph_join_type]
2029
2193
  select = opts[:graph_select]
@@ -2057,50 +2221,60 @@ module Sequel
2057
2221
  return if opts[:read_only]
2058
2222
 
2059
2223
  if one_through_one
2060
- opts[:setter] ||= proc do |o|
2061
- h = {}
2062
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2063
- jtds = _join_table_dataset(opts).where(lh)
2224
+ unless opts.has_key?(:setter)
2225
+ opts[:setter] = proc do |o|
2226
+ h = {}
2227
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2228
+ jtds = _join_table_dataset(opts).where(lh)
2064
2229
 
2065
- checked_transaction do
2066
- current = jtds.first
2067
-
2068
- if o
2069
- new_values = []
2070
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2071
- end
2230
+ checked_transaction do
2231
+ current = jtds.first
2072
2232
 
2073
- if current
2074
- current_values = rcks.map{|k| current[k]}
2075
- jtds = jtds.where(rcks.zip(current_values))
2076
2233
  if o
2077
- if current_values != new_values
2078
- jtds.update(h)
2234
+ new_values = []
2235
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2236
+ end
2237
+
2238
+ if current
2239
+ current_values = rcks.map{|k| current[k]}
2240
+ jtds = jtds.where(rcks.zip(current_values))
2241
+ if o
2242
+ if current_values != new_values
2243
+ jtds.update(h)
2244
+ end
2245
+ else
2246
+ jtds.delete
2079
2247
  end
2080
- else
2081
- jtds.delete
2248
+ elsif o
2249
+ lh.each{|k,v| h[k] = v}
2250
+ jtds.insert(h)
2082
2251
  end
2083
- elsif o
2084
- lh.each{|k,v| h[k] = v}
2085
- jtds.insert(h)
2086
2252
  end
2087
2253
  end
2088
2254
  end
2089
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2255
+ if opts.fetch(:setter, true)
2256
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2257
+ end
2090
2258
  else
2091
- opts[:adder] ||= proc do |o|
2092
- h = {}
2093
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2094
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2095
- _join_table_dataset(opts).insert(h)
2259
+ unless opts.has_key?(:adder)
2260
+ opts[:adder] = proc do |o|
2261
+ h = {}
2262
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2263
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2264
+ _join_table_dataset(opts).insert(h)
2265
+ end
2096
2266
  end
2097
2267
 
2098
- opts[:remover] ||= proc do |o|
2099
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2268
+ unless opts.has_key?(:remover)
2269
+ opts[:remover] = proc do |o|
2270
+ _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
2271
+ end
2100
2272
  end
2101
2273
 
2102
- opts[:clearer] ||= proc do
2103
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2274
+ unless opts.has_key?(:clearer)
2275
+ opts[:clearer] = proc do
2276
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2277
+ end
2104
2278
  end
2105
2279
  end
2106
2280
  end
@@ -2157,8 +2331,12 @@ module Sequel
2157
2331
 
2158
2332
  return if opts[:read_only]
2159
2333
 
2160
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2161
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2334
+ unless opts.has_key?(:setter)
2335
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2336
+ end
2337
+ if opts.fetch(:setter, true)
2338
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2339
+ end
2162
2340
  end
2163
2341
 
2164
2342
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2225,49 +2403,59 @@ module Sequel
2225
2403
  cks.each{|k| ck_nil_hash[k] = nil}
2226
2404
 
2227
2405
  if one_to_one
2228
- opts[:setter] ||= proc do |o|
2229
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2406
+ unless opts.has_key?(:setter)
2407
+ opts[:setter] = proc do |o|
2408
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2230
2409
 
2231
- if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2232
- if old = up_ds.first
2233
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2410
+ 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)))
2411
+ if old = up_ds.first
2412
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2413
+ end
2414
+ save_old = true
2234
2415
  end
2235
- save_old = true
2236
- end
2237
2416
 
2238
- if o
2239
- if !o.new? && !save_old
2240
- up_ds = up_ds.exclude(o.pk_hash)
2417
+ if o
2418
+ if !o.new? && !save_old
2419
+ up_ds = up_ds.exclude(o.pk_hash)
2420
+ end
2421
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2241
2422
  end
2242
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2243
- end
2244
2423
 
2245
- checked_transaction do
2246
- if save_old
2247
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2248
- else
2249
- up_ds.skip_limit_check.update(ck_nil_hash)
2250
- end
2424
+ checked_transaction do
2425
+ if save_old
2426
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2427
+ else
2428
+ up_ds.skip_limit_check.update(ck_nil_hash)
2429
+ end
2251
2430
 
2252
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2431
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2432
+ end
2253
2433
  end
2254
2434
  end
2255
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2435
+ if opts.fetch(:setter, true)
2436
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2437
+ end
2256
2438
  else
2257
2439
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2258
2440
 
2259
- opts[:adder] ||= proc do |o|
2260
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2261
- o.save(save_opts)
2441
+ unless opts.has_key?(:adder)
2442
+ opts[:adder] = proc do |o|
2443
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2444
+ o.save(save_opts)
2445
+ end
2262
2446
  end
2263
2447
 
2264
- opts[:remover] ||= proc do |o|
2265
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2266
- o.save(save_opts)
2448
+ unless opts.has_key?(:remover)
2449
+ opts[:remover] = proc do |o|
2450
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2451
+ o.save(save_opts)
2452
+ end
2267
2453
  end
2268
2454
 
2269
- opts[:clearer] ||= proc do
2270
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2455
+ unless opts.has_key?(:clearer)
2456
+ opts[:clearer] = proc do
2457
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2458
+ end
2271
2459
  end
2272
2460
  end
2273
2461
  end
@@ -2285,6 +2473,9 @@ module Sequel
2285
2473
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2286
2474
  def eager_graph_dataset(opts, eager_options)
2287
2475
  ds = opts.associated_class.dataset
2476
+ if opts[:graph_use_association_block] && (b = opts[:block])
2477
+ ds = b.call(ds)
2478
+ end
2288
2479
  if cb = eager_options[:callback]
2289
2480
  ds = cb.call(ds)
2290
2481
  end
@@ -2361,7 +2552,7 @@ module Sequel
2361
2552
 
2362
2553
  # Dataset for the join table of the given many to many association reflection
2363
2554
  def _join_table_dataset(opts)
2364
- ds = model.db.from(opts.join_table_source)
2555
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2365
2556
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2366
2557
  end
2367
2558
 
@@ -2382,7 +2573,12 @@ module Sequel
2382
2573
  if loader = _associated_object_loader(opts, dynamic_opts)
2383
2574
  loader.all(*opts.predicate_key_values(self))
2384
2575
  else
2385
- _associated_dataset(opts, dynamic_opts).all
2576
+ ds = _associated_dataset(opts, dynamic_opts)
2577
+ if ds.opts[:no_results]
2578
+ []
2579
+ else
2580
+ ds.all
2581
+ end
2386
2582
  end
2387
2583
  end
2388
2584
 
@@ -2423,6 +2619,9 @@ module Sequel
2423
2619
  run_association_callbacks(opts, :after_add, o)
2424
2620
  o
2425
2621
  end
2622
+ # :nocov:
2623
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2624
+ # :nocov:
2426
2625
 
2427
2626
  # Add/Set the current object to/as the given object's reciprocal association.
2428
2627
  def add_reciprocal_object(opts, o)
@@ -2565,6 +2764,9 @@ module Sequel
2565
2764
  associations[opts[:name]] = []
2566
2765
  ret
2567
2766
  end
2767
+ # :nocov:
2768
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2769
+ # :nocov:
2568
2770
 
2569
2771
  # Remove the given associated object from the given association
2570
2772
  def remove_associated_object(opts, o, *args)
@@ -2586,6 +2788,9 @@ module Sequel
2586
2788
  run_association_callbacks(opts, :after_remove, o)
2587
2789
  o
2588
2790
  end
2791
+ # :nocov:
2792
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2793
+ # :nocov:
2589
2794
 
2590
2795
  # Check that the object from the associated table specified by the primary key
2591
2796
  # is currently associated to the receiver. If it is associated, return the object, otherwise
@@ -2834,6 +3039,8 @@ module Sequel
2834
3039
  (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
2835
3040
  l = args[0]
2836
3041
  if ar = model.association_reflections[l]
3042
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3043
+
2837
3044
  if multiple
2838
3045
  klass = ar.associated_class
2839
3046
  if is_ds
@@ -2955,7 +3162,7 @@ module Sequel
2955
3162
 
2956
3163
  # The secondary eager loading method. Loads all associations in a single query. This
2957
3164
  # method should only be used if you need to filter or order based on columns in associated tables,
2958
- # or if you have done comparative benchmarking it and determined it is faster.
3165
+ # or if you have done comparative benchmarking and determined it is faster.
2959
3166
  #
2960
3167
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
2961
3168
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -2984,6 +3191,8 @@ module Sequel
2984
3191
  # You can specify an custom alias and/or join type on a per-association basis by providing an
2985
3192
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
2986
3193
  #
3194
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3195
+ #
2987
3196
  # Examples:
2988
3197
  #
2989
3198
  # # For each album, eager_graph load the artist
@@ -3327,7 +3536,7 @@ module Sequel
3327
3536
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
3328
3537
  # per-call determining of the alias base.
3329
3538
  def eager_graph_check_association(model, association)
3330
- if association.is_a?(SQL::AliasedExpression)
3539
+ reflection = if association.is_a?(SQL::AliasedExpression)
3331
3540
  expr = association.expression
3332
3541
  if expr.is_a?(SQL::Identifier)
3333
3542
  expr = expr.value
@@ -3336,10 +3545,17 @@ module Sequel
3336
3545
  end
3337
3546
  end
3338
3547
 
3339
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3548
+ check_reflection = check_association(model, expr)
3549
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
3340
3550
  else
3341
- check_association(model, association)
3551
+ check_reflection = check_association(model, association)
3342
3552
  end
3553
+
3554
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3555
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
3556
+ end
3557
+
3558
+ reflection
3343
3559
  end
3344
3560
 
3345
3561
  # The EagerGraphLoader instance used for converting eager_graph results.
@@ -3350,15 +3566,30 @@ module Sequel
3350
3566
  egl.dup
3351
3567
  end
3352
3568
 
3353
- # Eagerly load all specified associations
3354
- def eager_load(a, eager_assoc=@opts[:eager])
3569
+ # Eagerly load all specified associations.
3570
+ def eager_load(a, eager_assoc=@opts[:eager], m=model)
3355
3571
  return if a.empty?
3572
+
3573
+ # Reflections for all associations to eager load
3574
+ reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3575
+
3576
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3577
+
3578
+ reflections.each do |r|
3579
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3580
+ end
3581
+
3582
+ nil
3583
+ end
3584
+
3585
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3586
+ def prepare_eager_load(a, reflections, eager_assoc)
3587
+ eager_load_data = {}
3588
+
3356
3589
  # Key is foreign/primary key name symbol.
3357
3590
  # Value is hash with keys being foreign/primary key values (generally integers)
3358
3591
  # and values being an array of current model objects with that specific foreign/primary key
3359
3592
  key_hash = {}
3360
- # Reflections for all associations to eager load
3361
- reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3362
3593
 
3363
3594
  # Populate the key_hash entry for each association being eagerly loaded
3364
3595
  reflections.each do |r|
@@ -3389,7 +3620,6 @@ module Sequel
3389
3620
  id_map = nil
3390
3621
  end
3391
3622
 
3392
- loader = r[:eager_loader]
3393
3623
  associations = eager_assoc[r[:name]]
3394
3624
  if associations.respond_to?(:call)
3395
3625
  eager_block = associations
@@ -3397,9 +3627,23 @@ module Sequel
3397
3627
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3398
3628
  eager_block, associations = pr_assoc
3399
3629
  end
3400
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
3401
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3402
- end
3630
+
3631
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3632
+ end
3633
+
3634
+ eager_load_data
3635
+ end
3636
+
3637
+ # Using the hash of loaders and eager options, perform the eager loading.
3638
+ def perform_eager_loads(eager_load_data)
3639
+ eager_load_data.map do |loader, eo|
3640
+ perform_eager_load(loader, eo)
3641
+ end
3642
+ end
3643
+
3644
+ # Perform eager loading for a single association using the loader and eager options.
3645
+ def perform_eager_load(loader, eo)
3646
+ loader.call(eo)
3403
3647
  end
3404
3648
 
3405
3649
  # Return a subquery expression for filering by a many_to_many association