sequel 5.39.0 → 5.66.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 +336 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/release_notes/5.64.0.txt +50 -0
  40. data/doc/release_notes/5.65.0.txt +21 -0
  41. data/doc/release_notes/5.66.0.txt +24 -0
  42. data/doc/schema_modification.rdoc +1 -1
  43. data/doc/security.rdoc +9 -9
  44. data/doc/sql.rdoc +28 -16
  45. data/doc/testing.rdoc +22 -11
  46. data/doc/transactions.rdoc +6 -6
  47. data/doc/virtual_rows.rdoc +2 -2
  48. data/lib/sequel/adapters/ado/access.rb +1 -1
  49. data/lib/sequel/adapters/ado.rb +17 -17
  50. data/lib/sequel/adapters/amalgalite.rb +3 -5
  51. data/lib/sequel/adapters/ibmdb.rb +2 -2
  52. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  53. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  54. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  56. data/lib/sequel/adapters/jdbc.rb +16 -18
  57. data/lib/sequel/adapters/mysql.rb +80 -67
  58. data/lib/sequel/adapters/mysql2.rb +54 -49
  59. data/lib/sequel/adapters/odbc.rb +6 -2
  60. data/lib/sequel/adapters/oracle.rb +4 -3
  61. data/lib/sequel/adapters/postgres.rb +83 -40
  62. data/lib/sequel/adapters/shared/access.rb +11 -1
  63. data/lib/sequel/adapters/shared/db2.rb +30 -0
  64. data/lib/sequel/adapters/shared/mssql.rb +83 -7
  65. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  66. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  67. data/lib/sequel/adapters/shared/postgres.rb +430 -174
  68. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  69. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  70. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  71. data/lib/sequel/adapters/sqlite.rb +60 -18
  72. data/lib/sequel/adapters/tinytds.rb +1 -1
  73. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  74. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  75. data/lib/sequel/ast_transformer.rb +6 -0
  76. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  77. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  78. data/lib/sequel/connection_pool/single.rb +6 -8
  79. data/lib/sequel/connection_pool/threaded.rb +8 -8
  80. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  81. data/lib/sequel/connection_pool.rb +47 -30
  82. data/lib/sequel/core.rb +28 -18
  83. data/lib/sequel/database/connecting.rb +26 -2
  84. data/lib/sequel/database/misc.rb +69 -14
  85. data/lib/sequel/database/query.rb +73 -2
  86. data/lib/sequel/database/schema_generator.rb +45 -52
  87. data/lib/sequel/database/schema_methods.rb +17 -1
  88. data/lib/sequel/dataset/actions.rb +108 -14
  89. data/lib/sequel/dataset/features.rb +20 -0
  90. data/lib/sequel/dataset/misc.rb +12 -2
  91. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  92. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  93. data/lib/sequel/dataset/query.rb +118 -16
  94. data/lib/sequel/dataset/sql.rb +182 -47
  95. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  96. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  97. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  98. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  99. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  100. data/lib/sequel/extensions/blank.rb +8 -0
  101. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  102. data/lib/sequel/extensions/core_refinements.rb +36 -11
  103. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  104. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  105. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  106. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  107. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  108. data/lib/sequel/extensions/inflector.rb +9 -1
  109. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  110. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  111. data/lib/sequel/extensions/migration.rb +7 -2
  112. data/lib/sequel/extensions/named_timezones.rb +26 -6
  113. data/lib/sequel/extensions/pagination.rb +1 -1
  114. data/lib/sequel/extensions/pg_array.rb +23 -3
  115. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  116. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  117. data/lib/sequel/extensions/pg_enum.rb +1 -1
  118. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  119. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  120. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  121. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  122. data/lib/sequel/extensions/pg_inet.rb +10 -11
  123. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  124. data/lib/sequel/extensions/pg_interval.rb +45 -19
  125. data/lib/sequel/extensions/pg_json.rb +13 -15
  126. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  127. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  128. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  129. data/lib/sequel/extensions/pg_range.rb +10 -23
  130. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  131. data/lib/sequel/extensions/pg_row.rb +19 -13
  132. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  133. data/lib/sequel/extensions/query.rb +2 -0
  134. data/lib/sequel/extensions/s.rb +2 -1
  135. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  136. data/lib/sequel/extensions/server_block.rb +8 -12
  137. data/lib/sequel/extensions/sql_comments.rb +110 -3
  138. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  139. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  140. data/lib/sequel/extensions/string_agg.rb +1 -1
  141. data/lib/sequel/extensions/string_date_time.rb +19 -23
  142. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  143. data/lib/sequel/model/associations.rb +345 -101
  144. data/lib/sequel/model/base.rb +51 -27
  145. data/lib/sequel/model/errors.rb +10 -1
  146. data/lib/sequel/model/inflections.rb +1 -1
  147. data/lib/sequel/model/plugins.rb +5 -0
  148. data/lib/sequel/plugins/association_proxies.rb +2 -0
  149. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  150. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  151. data/lib/sequel/plugins/auto_validations.rb +87 -15
  152. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  153. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  154. data/lib/sequel/plugins/column_encryption.rb +728 -0
  155. data/lib/sequel/plugins/composition.rb +10 -4
  156. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  157. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  158. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  159. data/lib/sequel/plugins/dirty.rb +1 -1
  160. data/lib/sequel/plugins/enum.rb +124 -0
  161. data/lib/sequel/plugins/finder.rb +4 -2
  162. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  163. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  164. data/lib/sequel/plugins/json_serializer.rb +39 -24
  165. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  166. data/lib/sequel/plugins/list.rb +3 -1
  167. data/lib/sequel/plugins/many_through_many.rb +109 -10
  168. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  169. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  170. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  171. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  172. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  173. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  174. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  175. data/lib/sequel/plugins/serialization.rb +9 -3
  176. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  177. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  178. data/lib/sequel/plugins/sql_comments.rb +189 -0
  179. data/lib/sequel/plugins/static_cache.rb +1 -1
  180. data/lib/sequel/plugins/subclasses.rb +28 -11
  181. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  182. data/lib/sequel/plugins/timestamps.rb +1 -1
  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/validate_associated.rb +22 -12
  186. data/lib/sequel/plugins/validation_helpers.rb +38 -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 +12 -14
  190. data/lib/sequel/version.rb +1 -1
  191. metadata +103 -43
@@ -263,7 +263,9 @@ module Sequel
263
263
  # yielding each row to the block.
264
264
  def eager_load_results(eo, &block)
265
265
  rows = eo[:rows]
266
- initialize_association_cache(rows) unless eo[:initialize_rows] == false
266
+ unless eo[:initialize_rows] == false
267
+ Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
268
+ end
267
269
  if eo[:id_map]
268
270
  ids = eo[:id_map].keys
269
271
  return ids if ids.empty?
@@ -272,7 +274,9 @@ module Sequel
272
274
  cascade = eo[:associations]
273
275
  eager_limit = nil
274
276
 
275
- if eo[:eager_block] || eo[:loader] == false
277
+ if eo[:no_results]
278
+ no_results = true
279
+ elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
276
280
  ds = eager_loading_dataset(eo)
277
281
 
278
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -295,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