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