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.
- checksums.yaml +4 -4
- data/CHANGELOG +408 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +59 -27
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +13 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +26 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +28 -16
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +90 -9
- data/lib/sequel/adapters/shared/mysql.rb +47 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -1
- data/lib/sequel/adapters/shared/postgres.rb +496 -178
- data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
- data/lib/sequel/adapters/shared/sqlite.rb +116 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +55 -31
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +46 -53
- data/lib/sequel/database/schema_methods.rb +18 -2
- data/lib/sequel/dataset/actions.rb +108 -14
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +171 -44
- data/lib/sequel/dataset/sql.rb +182 -47
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +439 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +71 -31
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +11 -2
- data/lib/sequel/extensions/named_timezones.rb +26 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +32 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -3
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +11 -24
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +21 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +45 -11
- data/lib/sequel/extensions/server_block.rb +10 -13
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +345 -101
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +10 -6
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +12 -7
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +12 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +39 -1
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +46 -12
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- 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
|
-
|
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[:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
735
|
-
apply_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
|
-
|
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
|
-
|
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
|
-
|
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 ::
|
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
|
-
|
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)
|
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
|
-
|
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
|
-
|
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
|
-
|
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[:
|
2026
|
-
|
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
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
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
|
-
|
2066
|
-
|
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
|
-
|
2078
|
-
|
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
|
-
|
2081
|
-
|
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
|
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
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
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
|
2099
|
-
|
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
|
2103
|
-
|
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
|
-
|
2161
|
-
|
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
|
2229
|
-
|
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
|
-
|
2232
|
-
|
2233
|
-
|
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
|
-
|
2239
|
-
|
2240
|
-
|
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
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
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
|
-
|
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
|
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
|
2260
|
-
|
2261
|
-
|
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
|
2265
|
-
|
2266
|
-
|
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
|
2270
|
-
|
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)
|
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
|
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
|
-
|
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
|
-
|
3401
|
-
|
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
|