sequel 3.21.0 → 3.41.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.
- data/CHANGELOG +1131 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +106 -65
- data/Rakefile +59 -27
- data/bin/sequel +50 -26
- data/doc/active_record.rdoc +68 -52
- data/doc/advanced_associations.rdoc +201 -75
- data/doc/association_basics.rdoc +327 -79
- data/doc/bin_sequel.rdoc +144 -0
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +10 -10
- data/doc/dataset_filtering.rdoc +54 -42
- data/doc/mass_assignment.rdoc +56 -0
- data/doc/migration.rdoc +89 -557
- data/doc/model_hooks.rdoc +80 -29
- data/doc/object_model.rdoc +576 -0
- data/doc/opening_databases.rdoc +121 -53
- data/doc/prepared_statements.rdoc +41 -15
- data/doc/querying.rdoc +137 -56
- data/doc/reflection.rdoc +36 -10
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/release_notes/3.31.0.txt +146 -0
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/release_notes/3.33.0.txt +157 -0
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/release_notes/3.37.0.txt +338 -0
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/release_notes/3.40.0.txt +73 -0
- data/doc/release_notes/3.41.0.txt +155 -0
- data/doc/schema_modification.rdoc +585 -0
- data/doc/sharding.rdoc +99 -8
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +169 -0
- data/doc/thread_safety.rdoc +17 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +78 -43
- data/lib/sequel/adapters/ado/access.rb +334 -0
- data/lib/sequel/adapters/ado/mssql.rb +18 -12
- data/lib/sequel/adapters/ado.rb +69 -21
- data/lib/sequel/adapters/amalgalite.rb +17 -14
- data/lib/sequel/adapters/cubrid.rb +143 -0
- data/lib/sequel/adapters/db2.rb +178 -85
- data/lib/sequel/adapters/dbi.rb +19 -21
- data/lib/sequel/adapters/do/mysql.rb +17 -11
- data/lib/sequel/adapters/do/postgres.rb +2 -61
- data/lib/sequel/adapters/do/sqlite.rb +0 -10
- data/lib/sequel/adapters/do.rb +20 -39
- data/lib/sequel/adapters/firebird.rb +33 -210
- data/lib/sequel/adapters/ibmdb.rb +452 -0
- data/lib/sequel/adapters/informix.rb +6 -29
- data/lib/sequel/adapters/jdbc/as400.rb +16 -34
- data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
- data/lib/sequel/adapters/jdbc/db2.rb +78 -0
- data/lib/sequel/adapters/jdbc/derby.rb +325 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
- data/lib/sequel/adapters/jdbc/h2.rb +68 -31
- data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
- data/lib/sequel/adapters/jdbc/informix.rb +21 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
- data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
- data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
- data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +308 -114
- data/lib/sequel/adapters/mock.rb +372 -0
- data/lib/sequel/adapters/mysql.rb +130 -230
- data/lib/sequel/adapters/mysql2.rb +64 -63
- data/lib/sequel/adapters/odbc/mssql.rb +17 -9
- data/lib/sequel/adapters/odbc.rb +29 -10
- data/lib/sequel/adapters/openbase.rb +7 -11
- data/lib/sequel/adapters/oracle.rb +364 -57
- data/lib/sequel/adapters/postgres.rb +379 -145
- data/lib/sequel/adapters/shared/access.rb +236 -11
- data/lib/sequel/adapters/shared/cubrid.rb +216 -0
- data/lib/sequel/adapters/shared/db2.rb +353 -0
- data/lib/sequel/adapters/shared/firebird.rb +231 -0
- data/lib/sequel/adapters/shared/informix.rb +53 -0
- data/lib/sequel/adapters/shared/mssql.rb +412 -116
- data/lib/sequel/adapters/shared/mysql.rb +396 -89
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
- data/lib/sequel/adapters/shared/oracle.rb +262 -45
- data/lib/sequel/adapters/shared/postgres.rb +688 -279
- data/lib/sequel/adapters/shared/progress.rb +5 -8
- data/lib/sequel/adapters/shared/sqlite.rb +317 -96
- data/lib/sequel/adapters/sqlite.rb +109 -46
- data/lib/sequel/adapters/swift/mysql.rb +10 -12
- data/lib/sequel/adapters/swift/postgres.rb +13 -79
- data/lib/sequel/adapters/swift/sqlite.rb +9 -6
- data/lib/sequel/adapters/swift.rb +31 -27
- data/lib/sequel/adapters/tinytds.rb +177 -25
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
- data/lib/sequel/adapters/utils/pg_types.rb +81 -0
- data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
- data/lib/sequel/ast_transformer.rb +194 -0
- data/lib/sequel/connection_pool/sharded_single.rb +23 -13
- data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
- data/lib/sequel/connection_pool/single.rb +11 -3
- data/lib/sequel/connection_pool/threaded.rb +69 -20
- data/lib/sequel/connection_pool.rb +20 -18
- data/lib/sequel/core.rb +143 -11
- data/lib/sequel/database/connecting.rb +66 -29
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +63 -2
- data/lib/sequel/database/logging.rb +7 -2
- data/lib/sequel/database/misc.rb +192 -51
- data/lib/sequel/database/query.rb +299 -114
- data/lib/sequel/database/schema_generator.rb +103 -36
- data/lib/sequel/database/schema_methods.rb +310 -69
- data/lib/sequel/dataset/actions.rb +344 -90
- data/lib/sequel/dataset/features.rb +101 -8
- data/lib/sequel/dataset/graph.rb +42 -15
- data/lib/sequel/dataset/misc.rb +52 -25
- data/lib/sequel/dataset/mutation.rb +12 -7
- data/lib/sequel/dataset/prepared_statements.rb +68 -30
- data/lib/sequel/dataset/query.rb +322 -68
- data/lib/sequel/dataset/sql.rb +716 -293
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/_pretty_table.rb +83 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +72 -0
- data/lib/sequel/extensions/connection_validator.rb +109 -0
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
- data/lib/sequel/extensions/eval_inspect.rb +173 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +100 -11
- data/lib/sequel/extensions/named_timezones.rb +9 -0
- data/lib/sequel/extensions/null_dataset.rb +94 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +539 -0
- data/lib/sequel/extensions/pg_array_ops.rb +263 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
- data/lib/sequel/extensions/pg_hstore.rb +334 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
- data/lib/sequel/extensions/pg_inet.rb +113 -0
- data/lib/sequel/extensions/pg_interval.rb +191 -0
- data/lib/sequel/extensions/pg_json.rb +212 -0
- data/lib/sequel/extensions/pg_range.rb +513 -0
- data/lib/sequel/extensions/pg_range_ops.rb +150 -0
- data/lib/sequel/extensions/pg_row.rb +576 -0
- data/lib/sequel/extensions/pg_row_ops.rb +182 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
- data/lib/sequel/extensions/pretty_table.rb +17 -72
- data/lib/sequel/extensions/query.rb +8 -4
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +282 -76
- data/lib/sequel/extensions/select_remove.rb +39 -0
- data/lib/sequel/extensions/server_block.rb +140 -0
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +8 -110
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
- data/lib/sequel/extensions/to_dot.rb +99 -83
- data/lib/sequel/model/associations.rb +1263 -409
- data/lib/sequel/model/base.rb +624 -171
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +24 -2
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model.rb +30 -11
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/association_pks.rb +137 -16
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/composition.rb +44 -12
- data/lib/sequel/plugins/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +72 -0
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +134 -15
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +77 -6
- data/lib/sequel/plugins/list.rb +13 -2
- data/lib/sequel/plugins/many_through_many.rb +103 -51
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +150 -66
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/prepared_statements.rb +168 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +31 -17
- data/lib/sequel/plugins/schema.rb +8 -3
- data/lib/sequel/plugins/serialization.rb +98 -49
- data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
- data/lib/sequel/plugins/sharding.rb +21 -54
- data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/subclasses.rb +29 -3
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/touch.rb +18 -6
- data/lib/sequel/plugins/tree.rb +3 -3
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +2 -1
- data/lib/sequel/plugins/validation_helpers.rb +57 -4
- data/lib/sequel/plugins/xml_serializer.rb +15 -4
- data/lib/sequel/sql.rb +721 -141
- data/lib/sequel/timezones.rb +67 -40
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/firebird_spec.rb +53 -39
- data/spec/adapters/mssql_spec.rb +261 -66
- data/spec/adapters/mysql_spec.rb +597 -352
- data/spec/adapters/oracle_spec.rb +92 -117
- data/spec/adapters/postgres_spec.rb +2103 -341
- data/spec/adapters/spec_helper.rb +16 -6
- data/spec/adapters/sqlite_spec.rb +237 -76
- data/spec/core/connection_pool_spec.rb +270 -141
- data/spec/core/database_spec.rb +980 -524
- data/spec/core/dataset_spec.rb +1658 -1243
- data/spec/core/expression_filters_spec.rb +741 -334
- data/spec/core/mock_adapter_spec.rb +453 -0
- data/spec/core/object_graph_spec.rb +88 -129
- data/spec/core/schema_generator_spec.rb +4 -4
- data/spec/core/schema_spec.rb +556 -53
- data/spec/core/spec_helper.rb +7 -48
- data/spec/core_extensions_spec.rb +626 -0
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/arbitrary_servers_spec.rb +110 -0
- data/spec/extensions/association_autoreloading_spec.rb +18 -10
- data/spec/extensions/association_dependencies_spec.rb +15 -25
- data/spec/extensions/association_pks_spec.rb +227 -33
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/boolean_readers_spec.rb +25 -25
- data/spec/extensions/caching_spec.rb +47 -51
- data/spec/extensions/class_table_inheritance_spec.rb +31 -83
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/extensions/connection_validator_spec.rb +118 -0
- data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +88 -0
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/eval_inspect_spec.rb +67 -0
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +97 -128
- data/spec/extensions/identity_map_spec.rb +142 -24
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/instance_filters_spec.rb +11 -21
- data/spec/extensions/instance_hooks_spec.rb +72 -0
- data/spec/extensions/json_serializer_spec.rb +49 -12
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +49 -40
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +368 -254
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +140 -35
- data/spec/extensions/named_timezones_spec.rb +29 -11
- data/spec/extensions/nested_attributes_spec.rb +268 -89
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +24 -21
- data/spec/extensions/pg_array_ops_spec.rb +112 -0
- data/spec/extensions/pg_array_spec.rb +324 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
- data/spec/extensions/pg_hstore_spec.rb +200 -0
- data/spec/extensions/pg_inet_spec.rb +47 -0
- data/spec/extensions/pg_interval_spec.rb +72 -0
- data/spec/extensions/pg_json_spec.rb +99 -0
- data/spec/extensions/pg_range_ops_spec.rb +56 -0
- data/spec/extensions/pg_range_spec.rb +395 -0
- data/spec/extensions/pg_row_ops_spec.rb +58 -0
- data/spec/extensions/pg_row_plugin_spec.rb +49 -0
- data/spec/extensions/pg_row_spec.rb +340 -0
- data/spec/extensions/pg_statement_cache_spec.rb +208 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
- data/spec/extensions/prepared_statements_spec.rb +87 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
- data/spec/extensions/pretty_table_spec.rb +7 -6
- data/spec/extensions/query_literals_spec.rb +167 -0
- data/spec/extensions/query_spec.rb +9 -3
- data/spec/extensions/rcte_tree_spec.rb +50 -43
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +434 -49
- data/spec/extensions/schema_spec.rb +30 -49
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
- data/spec/extensions/serialization_spec.rb +86 -48
- data/spec/extensions/server_block_spec.rb +90 -0
- data/spec/extensions/sharding_spec.rb +69 -143
- data/spec/extensions/single_table_inheritance_spec.rb +65 -29
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +34 -67
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/extensions/sql_expr_spec.rb +29 -60
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/subclasses_spec.rb +14 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +8 -11
- data/spec/extensions/touch_spec.rb +53 -14
- data/spec/extensions/tree_spec.rb +11 -26
- data/spec/extensions/typecast_on_load_spec.rb +9 -6
- data/spec/extensions/update_primary_key_spec.rb +30 -24
- data/spec/extensions/validation_class_methods_spec.rb +77 -68
- data/spec/extensions/validation_helpers_spec.rb +48 -37
- data/spec/extensions/xml_serializer_spec.rb +29 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
- data/spec/integration/associations_test.rb +659 -21
- data/spec/integration/database_test.rb +25 -5
- data/spec/integration/dataset_test.rb +729 -141
- data/spec/integration/eager_loader_test.rb +38 -54
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +81 -2
- data/spec/integration/plugin_test.rb +871 -160
- data/spec/integration/prepared_statement_test.rb +272 -128
- data/spec/integration/schema_test.rb +377 -57
- data/spec/integration/spec_helper.rb +49 -32
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +183 -8
- data/spec/integration/type_test.rb +40 -8
- data/spec/model/association_reflection_spec.rb +207 -8
- data/spec/model/associations_spec.rb +1206 -693
- data/spec/model/base_spec.rb +334 -100
- data/spec/model/dataset_methods_spec.rb +45 -27
- data/spec/model/eager_loading_spec.rb +828 -692
- data/spec/model/hooks_spec.rb +355 -67
- data/spec/model/model_spec.rb +251 -179
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +506 -232
- data/spec/model/spec_helper.rb +16 -77
- data/spec/model/validations_spec.rb +38 -2
- data/spec/rcov.opts +2 -0
- metadata +223 -81
- data/spec/core/core_sql_spec.rb +0 -451
|
@@ -22,10 +22,7 @@ module Sequel
|
|
|
22
22
|
# Identity maps are thread-local and only persist for the duration of the block,
|
|
23
23
|
# so they should only be considered as a possible performance enhancer.
|
|
24
24
|
#
|
|
25
|
-
# The identity_map plugin is not compatible with the
|
|
26
|
-
# many_to_many and many_through_many associations. If you want to use the identity_map plugin,
|
|
27
|
-
# you should use +eager_graph+ instead of +eager+ for those associations. It is also
|
|
28
|
-
# not compatible with the eager loading in the +rcte_tree+ plugin.
|
|
25
|
+
# The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
|
|
29
26
|
#
|
|
30
27
|
# Usage:
|
|
31
28
|
#
|
|
@@ -37,6 +34,116 @@ module Sequel
|
|
|
37
34
|
# # would need to do Album.with_identity_map{} to use the identity map
|
|
38
35
|
module IdentityMap
|
|
39
36
|
module ClassMethods
|
|
37
|
+
# Override the default :eager_loader option for many_*_many associations to work
|
|
38
|
+
# with an identity_map. If the :eager_graph association option is used, you'll probably have to use
|
|
39
|
+
# :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
|
|
40
|
+
# mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
|
|
41
|
+
# getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
|
|
42
|
+
# they'll probably be lost. Making that work correctly would require changing a lot of the core
|
|
43
|
+
# architecture, such as how graphing and eager graphing work.
|
|
44
|
+
def associate(type, name, opts = {}, &block)
|
|
45
|
+
if opts[:eager_loader]
|
|
46
|
+
super
|
|
47
|
+
elsif type == :many_to_many
|
|
48
|
+
opts = super
|
|
49
|
+
el = opts[:eager_loader]
|
|
50
|
+
model = self
|
|
51
|
+
left_pk = opts[:left_primary_key]
|
|
52
|
+
uses_lcks = opts[:uses_left_composite_keys]
|
|
53
|
+
uses_rcks = opts[:uses_right_composite_keys]
|
|
54
|
+
right = opts[:right_key]
|
|
55
|
+
join_table = opts[:join_table]
|
|
56
|
+
left = opts[:left_key]
|
|
57
|
+
lcks = opts[:left_keys]
|
|
58
|
+
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
59
|
+
opts[:eager_loader] = lambda do |eo|
|
|
60
|
+
return el.call(eo) unless model.identity_map
|
|
61
|
+
h = eo[:id_map]
|
|
62
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
|
63
|
+
r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
|
|
64
|
+
l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
|
|
65
|
+
|
|
66
|
+
# Replace the row proc to remove the left key alias before calling the previous row proc.
|
|
67
|
+
# Associate the value of the left key alias with the associated object (through its object_id).
|
|
68
|
+
# When loading the associated objects, lookup the left key alias value and associate the
|
|
69
|
+
# associated objects to the main objects if the left key alias value matches the left primary key
|
|
70
|
+
# value of the main object.
|
|
71
|
+
#
|
|
72
|
+
# The deleting of the left key alias from the hash before calling the previous row proc
|
|
73
|
+
# is necessary when an identity map is used, otherwise if the same associated object is returned more than
|
|
74
|
+
# once for the association, it won't know which of current objects to associate it to.
|
|
75
|
+
ds = opts.associated_class.inner_join(join_table, r + l)
|
|
76
|
+
pr = ds.row_proc
|
|
77
|
+
h2 = {}
|
|
78
|
+
ds.row_proc = proc do |hash|
|
|
79
|
+
hash_key = if uses_lcks
|
|
80
|
+
left_key_alias.map{|k| hash.delete(k)}
|
|
81
|
+
else
|
|
82
|
+
hash.delete(left_key_alias)
|
|
83
|
+
end
|
|
84
|
+
obj = pr.call(hash)
|
|
85
|
+
(h2[obj.object_id] ||= []) << hash_key
|
|
86
|
+
obj
|
|
87
|
+
end
|
|
88
|
+
model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
|
|
89
|
+
if hash_keys = h2.delete(assoc_record.object_id)
|
|
90
|
+
hash_keys.each do |hash_key|
|
|
91
|
+
if objects = h[hash_key]
|
|
92
|
+
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
opts
|
|
99
|
+
elsif type == :many_through_many
|
|
100
|
+
opts = super
|
|
101
|
+
el = opts[:eager_loader]
|
|
102
|
+
model = self
|
|
103
|
+
left_pk = opts[:left_primary_key]
|
|
104
|
+
left_key = opts[:left_key]
|
|
105
|
+
uses_lcks = opts[:uses_left_composite_keys]
|
|
106
|
+
left_keys = Array(left_key)
|
|
107
|
+
left_key_alias = opts[:left_key_alias]
|
|
108
|
+
opts[:eager_loader] = lambda do |eo|
|
|
109
|
+
return el.call(eo) unless model.identity_map
|
|
110
|
+
h = eo[:id_map]
|
|
111
|
+
eo[:rows].each{|object| object.associations[name] = []}
|
|
112
|
+
ds = opts.associated_class
|
|
113
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
114
|
+
ft = opts.final_reverse_edge
|
|
115
|
+
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
|
|
116
|
+
|
|
117
|
+
# See above comment in many_to_many eager_loader
|
|
118
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
|
|
119
|
+
pr = ds.row_proc
|
|
120
|
+
h2 = {}
|
|
121
|
+
ds.row_proc = proc do |hash|
|
|
122
|
+
hash_key = if uses_lcks
|
|
123
|
+
left_key_alias.map{|k| hash.delete(k)}
|
|
124
|
+
else
|
|
125
|
+
hash.delete(left_key_alias)
|
|
126
|
+
end
|
|
127
|
+
obj = pr.call(hash)
|
|
128
|
+
(h2[obj.object_id] ||= []) << hash_key
|
|
129
|
+
obj
|
|
130
|
+
end
|
|
131
|
+
model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
|
|
132
|
+
if hash_keys = h2.delete(assoc_record.object_id)
|
|
133
|
+
hash_keys.each do |hash_key|
|
|
134
|
+
if objects = h[hash_key]
|
|
135
|
+
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
opts
|
|
142
|
+
else
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
40
147
|
# Returns the current thread-local identity map. Should be a hash if
|
|
41
148
|
# there is an active identity map, and nil otherwise.
|
|
42
149
|
def identity_map
|
|
@@ -46,7 +153,8 @@ module Sequel
|
|
|
46
153
|
# The identity map key for an object of the current class with the given pk.
|
|
47
154
|
# May not always be correct for a class which uses STI.
|
|
48
155
|
def identity_map_key(pk)
|
|
49
|
-
|
|
156
|
+
pk = Array(pk)
|
|
157
|
+
"#{self}:#{pk.join(',')}" unless pk.compact.empty?
|
|
50
158
|
end
|
|
51
159
|
|
|
52
160
|
# If the identity map is in use, check it for a current copy of the object.
|
|
@@ -56,13 +164,15 @@ module Sequel
|
|
|
56
164
|
# certain fields in an initial query, make modifications to some of those
|
|
57
165
|
# fields and request other, potentially overlapping fields in a new query,
|
|
58
166
|
# and not have the second query override fields you modified.
|
|
59
|
-
def
|
|
60
|
-
return super unless idm = identity_map
|
|
61
|
-
if
|
|
167
|
+
def call(row)
|
|
168
|
+
return super unless (idm = identity_map) && (pk = primary_key)
|
|
169
|
+
if (k = identity_map_key(Array(pk).map{|x| row[x]})) && (o = idm[k])
|
|
62
170
|
o.merge_db_update(row)
|
|
63
171
|
else
|
|
64
172
|
o = super
|
|
65
|
-
|
|
173
|
+
if (k = identity_map_key(o.pk))
|
|
174
|
+
idm[k] = o
|
|
175
|
+
end
|
|
66
176
|
end
|
|
67
177
|
o
|
|
68
178
|
end
|
|
@@ -89,7 +199,7 @@ module Sequel
|
|
|
89
199
|
# Check the current identity map if it exists for the object with
|
|
90
200
|
# the matching pk. If one is found, return it, otherwise call super.
|
|
91
201
|
def primary_key_lookup(pk)
|
|
92
|
-
(idm = identity_map
|
|
202
|
+
((idm = identity_map) && (k = identity_map_key(pk)) && (o = idm[k])) ? o : super
|
|
93
203
|
end
|
|
94
204
|
end
|
|
95
205
|
|
|
@@ -97,8 +207,8 @@ module Sequel
|
|
|
97
207
|
# Remove instances from the identity map cache if they are deleted.
|
|
98
208
|
def delete
|
|
99
209
|
super
|
|
100
|
-
if idm = model.identity_map
|
|
101
|
-
idm.delete(
|
|
210
|
+
if (idm = model.identity_map) && (k = model.identity_map_key(pk))
|
|
211
|
+
idm.delete(k)
|
|
102
212
|
end
|
|
103
213
|
self
|
|
104
214
|
end
|
|
@@ -121,10 +231,19 @@ module Sequel
|
|
|
121
231
|
# key option has a value and the association uses the primary key of
|
|
122
232
|
# the associated class as the :primary_key option, check the identity
|
|
123
233
|
# map for the associated object and return it if present.
|
|
124
|
-
def
|
|
234
|
+
def _load_associated_object(opts, dynamic_opts)
|
|
125
235
|
klass = opts.associated_class
|
|
126
|
-
|
|
127
|
-
|
|
236
|
+
cache_lookup = opts.fetch(:idm_cache_lookup) do
|
|
237
|
+
opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
|
|
238
|
+
opts[:type] == :many_to_one &&
|
|
239
|
+
opts[:key] &&
|
|
240
|
+
opts.primary_key == klass.primary_key
|
|
241
|
+
end
|
|
242
|
+
if cache_lookup &&
|
|
243
|
+
!dynamic_opts[:callback] &&
|
|
244
|
+
(idm = klass.identity_map) &&
|
|
245
|
+
(k = klass.identity_map_key(_associated_object_pk(opts[:key]))) &&
|
|
246
|
+
(o = idm[k])
|
|
128
247
|
o
|
|
129
248
|
else
|
|
130
249
|
super
|
|
@@ -68,6 +68,16 @@ module Sequel
|
|
|
68
68
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
|
+
# If there are any instance filters, make sure not to use the
|
|
72
|
+
# instance delete optimization.
|
|
73
|
+
def _delete_without_checking
|
|
74
|
+
if @instance_filters && !@instance_filters.empty?
|
|
75
|
+
_delete_dataset.delete
|
|
76
|
+
else
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
71
81
|
# Lazily initialize the instance filter array.
|
|
72
82
|
def instance_filters
|
|
73
83
|
@instance_filters ||= []
|
|
@@ -29,7 +29,7 @@ module Sequel
|
|
|
29
29
|
BEFORE_HOOKS = Sequel::Model::BEFORE_HOOKS
|
|
30
30
|
AFTER_HOOKS = Sequel::Model::AFTER_HOOKS - [:after_initialize]
|
|
31
31
|
HOOKS = BEFORE_HOOKS + AFTER_HOOKS
|
|
32
|
-
HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block) end", __FILE__, __LINE__)}
|
|
32
|
+
HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block); self end", __FILE__, __LINE__)}
|
|
33
33
|
|
|
34
34
|
BEFORE_HOOKS.each{|h| class_eval("def #{h}; run_before_instance_hooks(:#{h}) == false ? false : super end", __FILE__, __LINE__)}
|
|
35
35
|
AFTER_HOOKS.each{|h| class_eval(<<-END, __FILE__, __LINE__ + 1)}
|
|
@@ -56,6 +56,30 @@ module Sequel
|
|
|
56
56
|
# Album.to_json
|
|
57
57
|
# Album.filter(:artist_id=>1).to_json(:include=>:tags)
|
|
58
58
|
#
|
|
59
|
+
# If you have an existing array of model instances you want to convert to
|
|
60
|
+
# JSON, you can call the class to_json method with the :array option:
|
|
61
|
+
#
|
|
62
|
+
# Album.to_json(:array=>[Album[1], Album[2]])
|
|
63
|
+
#
|
|
64
|
+
# Note that active_support/json makes incompatible changes to the to_json API,
|
|
65
|
+
# and breaks some aspects of the json_serializer plugin. You can undo the damage
|
|
66
|
+
# done by active_support/json by doing:
|
|
67
|
+
#
|
|
68
|
+
# class Array
|
|
69
|
+
# def to_json(options = {})
|
|
70
|
+
# JSON.generate(self)
|
|
71
|
+
# end
|
|
72
|
+
# end
|
|
73
|
+
#
|
|
74
|
+
# class Hash
|
|
75
|
+
# def to_json(options = {})
|
|
76
|
+
# JSON.generate(self)
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# Note that this will probably cause active_support/json to no longer work
|
|
81
|
+
# correctly in some cases.
|
|
82
|
+
#
|
|
59
83
|
# Usage:
|
|
60
84
|
#
|
|
61
85
|
# # Add JSON output capability to all model subclass instances (called before loading subclasses)
|
|
@@ -134,8 +158,13 @@ module Sequel
|
|
|
134
158
|
module InstanceMethods
|
|
135
159
|
# Parse the provided JSON, which should return a hash,
|
|
136
160
|
# and call +set+ with that hash.
|
|
137
|
-
def from_json(json)
|
|
138
|
-
|
|
161
|
+
def from_json(json, opts={})
|
|
162
|
+
h = JSON.parse(json)
|
|
163
|
+
if fields = opts[:fields]
|
|
164
|
+
set_fields(h, fields, opts)
|
|
165
|
+
else
|
|
166
|
+
set(h)
|
|
167
|
+
end
|
|
139
168
|
end
|
|
140
169
|
|
|
141
170
|
# Return a string in JSON format. Accepts the following
|
|
@@ -149,7 +178,7 @@ module Sequel
|
|
|
149
178
|
# to include in the JSON output. Using a nested
|
|
150
179
|
# hash, you can pass options to associations
|
|
151
180
|
# to affect the JSON used for associated objects.
|
|
152
|
-
# :naked :: Not to add the JSON.create_id key to the JSON
|
|
181
|
+
# :naked :: Not to add the JSON.create_id (json_class) key to the JSON
|
|
153
182
|
# output hash, so when the JSON is parsed, it
|
|
154
183
|
# will yield a hash instead of a model object.
|
|
155
184
|
# :only :: Symbol or Array of Symbols of columns to only
|
|
@@ -195,7 +224,21 @@ module Sequel
|
|
|
195
224
|
module DatasetMethods
|
|
196
225
|
# Return a JSON string representing an array of all objects in
|
|
197
226
|
# this dataset. Takes the same options as the the instance
|
|
198
|
-
# method, and passes them to every instance.
|
|
227
|
+
# method, and passes them to every instance. Additionally,
|
|
228
|
+
# respects the following options:
|
|
229
|
+
#
|
|
230
|
+
# :array :: An array of instances. If this is not provided,
|
|
231
|
+
# calls #all on the receiver to get the array.
|
|
232
|
+
# :root :: If set to :collection, only wraps the collection
|
|
233
|
+
# in a root object. If set to :instance, only wraps
|
|
234
|
+
# the instances in a root object. If set to :both,
|
|
235
|
+
# wraps both the collection and instances in a root
|
|
236
|
+
# object. Unfortunately, for backwards compatibility,
|
|
237
|
+
# if this option is true and doesn't match one of those
|
|
238
|
+
# symbols, it defaults to both. That may change in a
|
|
239
|
+
# future version, so for forwards compatibility, you
|
|
240
|
+
# should pick a specific symbol for your desired
|
|
241
|
+
# behavior.
|
|
199
242
|
def to_json(*a)
|
|
200
243
|
if opts = a.first.is_a?(Hash)
|
|
201
244
|
opts = model.json_serializer_opts.merge(a.first)
|
|
@@ -203,8 +246,36 @@ module Sequel
|
|
|
203
246
|
else
|
|
204
247
|
opts = model.json_serializer_opts
|
|
205
248
|
end
|
|
206
|
-
|
|
207
|
-
opts[:root]
|
|
249
|
+
|
|
250
|
+
collection_root = case opts[:root]
|
|
251
|
+
when nil, false, :instance
|
|
252
|
+
false
|
|
253
|
+
when :collection
|
|
254
|
+
opts = opts.dup
|
|
255
|
+
opts.delete(:root)
|
|
256
|
+
opts[:naked] = true unless opts.has_key?(:naked)
|
|
257
|
+
true
|
|
258
|
+
else
|
|
259
|
+
true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
res = if row_proc
|
|
263
|
+
array = if opts[:array]
|
|
264
|
+
opts = opts.dup
|
|
265
|
+
opts.delete(:array)
|
|
266
|
+
else
|
|
267
|
+
all
|
|
268
|
+
end
|
|
269
|
+
array.map{|obj| Literal.new(obj.to_json(opts))}
|
|
270
|
+
else
|
|
271
|
+
all
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
if collection_root
|
|
275
|
+
{model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a)
|
|
276
|
+
else
|
|
277
|
+
res.to_json(*a)
|
|
278
|
+
end
|
|
208
279
|
end
|
|
209
280
|
end
|
|
210
281
|
end
|
data/lib/sequel/plugins/list.rb
CHANGED
|
@@ -39,9 +39,11 @@ module Sequel
|
|
|
39
39
|
# Item.plugin :list, :scope=>:user_id
|
|
40
40
|
#
|
|
41
41
|
# Note that using this plugin modifies the order of the model's dataset to
|
|
42
|
-
# sort by the position and scope fields.
|
|
42
|
+
# sort by the position and scope fields. Also note that this plugin is subject to
|
|
43
|
+
# race conditions, and is not safe when concurrent modifications are made
|
|
44
|
+
# to the same list.
|
|
43
45
|
#
|
|
44
|
-
#
|
|
46
|
+
# Additionally, note that unlike ruby arrays, the list plugin assumes that the
|
|
45
47
|
# first entry in the list has position 1, not position 0.
|
|
46
48
|
#
|
|
47
49
|
# Copyright (c) 2007-2010 Sharon Rosner, Wayne E. Seguin, Aman Gupta, Adrian Madrid, Jeremy Evans
|
|
@@ -90,6 +92,15 @@ module Sequel
|
|
|
90
92
|
list_dataset.first(position_field => p)
|
|
91
93
|
end
|
|
92
94
|
|
|
95
|
+
# Set the value of the position_field to the maximum value plus 1 unless the
|
|
96
|
+
# position field already has a value.
|
|
97
|
+
def before_create
|
|
98
|
+
unless send(position_field)
|
|
99
|
+
send("#{position_field}=", list_dataset.max(position_field).to_i+1)
|
|
100
|
+
end
|
|
101
|
+
super
|
|
102
|
+
end
|
|
103
|
+
|
|
93
104
|
# Find the last position in the list containing this instance.
|
|
94
105
|
def last_position
|
|
95
106
|
list_dataset.max(position_field).to_i
|
|
@@ -47,20 +47,18 @@ module Sequel
|
|
|
47
47
|
class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
|
|
48
48
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
|
|
49
49
|
|
|
50
|
-
# The table containing the column to use for the associated key when eagerly loading
|
|
51
|
-
def associated_key_table
|
|
52
|
-
self[:associated_key_table] = self[:final_reverse_edge][:alias]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
50
|
# The default associated key alias(es) to use when eager loading
|
|
56
51
|
# associations via eager.
|
|
57
52
|
def default_associated_key_alias
|
|
58
53
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
|
59
54
|
end
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
%w'associated_key_table predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
|
|
57
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
58
|
+
def #{meth}
|
|
59
|
+
cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
|
|
60
|
+
end
|
|
61
|
+
END
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
# Many through many associations don't have a reciprocal
|
|
@@ -68,11 +66,6 @@ module Sequel
|
|
|
68
66
|
nil
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
# The list of joins to use when lazy loading or eager loading
|
|
72
|
-
def reverse_edges
|
|
73
|
-
self[:reverse_edges] || calculate_edges || self[:reverse_edges]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
69
|
private
|
|
77
70
|
|
|
78
71
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
|
@@ -94,7 +87,7 @@ module Sequel
|
|
|
94
87
|
|
|
95
88
|
# Transform the :through option into a list of edges and reverse edges to use to join tables when loading the association.
|
|
96
89
|
def calculate_edges
|
|
97
|
-
es = [{:left_table=>self[:model].table_name, :left_key=>self[:
|
|
90
|
+
es = [{:left_table=>self[:model].table_name, :left_key=>self[:left_primary_key_column]}]
|
|
98
91
|
self[:through].each do |t|
|
|
99
92
|
es.last.merge!(:right_key=>t[:left], :right_table=>t[:table], :join_type=>t[:join_type]||self[:graph_join_type], :conditions=>(t[:conditions]||[]).to_a, :block=>t[:block])
|
|
100
93
|
es.last[:only_conditions] = t[:only_conditions] if t.include?(:only_conditions)
|
|
@@ -109,37 +102,38 @@ module Sequel
|
|
|
109
102
|
reverse_edges = es.reverse.map{|e| {:table=>e[:left_table], :left=>e[:left_key], :right=>e[:right_key]}}
|
|
110
103
|
reverse_edges.pop
|
|
111
104
|
calculate_reverse_edge_aliases(reverse_edges)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
final_reverse_edge = reverse_edges.pop
|
|
106
|
+
final_reverse_alias = final_reverse_edge[:alias]
|
|
107
|
+
|
|
108
|
+
h = {:final_edge=>edges.pop,
|
|
109
|
+
:final_reverse_edge=>final_reverse_edge,
|
|
110
|
+
:edges=>edges,
|
|
111
|
+
:reverse_edges=>reverse_edges,
|
|
112
|
+
:predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
|
|
113
|
+
:associated_key_table=>final_reverse_edge[:alias],
|
|
114
|
+
}
|
|
115
|
+
h.each{|k, v| cached_set(k, v)}
|
|
116
|
+
h
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
|
+
|
|
119
120
|
module ClassMethods
|
|
120
121
|
# Create a many_through_many association. Arguments:
|
|
121
122
|
# * name - Same as associate, the name of the association.
|
|
122
123
|
# * through - The tables and keys to join between the current table and the associated table.
|
|
123
124
|
# Must be an array, with elements that are either 3 element arrays, or hashes with keys :table, :left, and :right.
|
|
124
125
|
# The required entries in the array/hash are:
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
126
|
+
# :table (first array element) :: The name of the table to join.
|
|
127
|
+
# :left (middle array element) :: The key joining the table to the previous table. Can use an
|
|
128
|
+
# array of symbols for a composite key association.
|
|
129
|
+
# :right (last array element) :: The key joining the table to the next table. Can use an
|
|
130
|
+
# array of symbols for a composite key association.
|
|
130
131
|
# If a hash is provided, the following keys are respected when using eager_graph:
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# * opts - The options for the associaion. Takes the same options as
|
|
136
|
-
# * :left_primary_key - column in current table that the first :left option in
|
|
137
|
-
# through points to, as a symbol. Defaults to primary key of current table. Can use an
|
|
138
|
-
# array of symbols for a composite key association.
|
|
139
|
-
# * :right_primary_key - column in associated table that the final :right option in
|
|
140
|
-
# through points to, as a symbol. Defaults to primary key of the associated table. Can use an
|
|
141
|
-
# array of symbols for a composite key association.
|
|
142
|
-
# * :uniq - Adds a after_load callback that makes the array of objects unique.
|
|
132
|
+
# :block :: A proc to use as the block argument to join.
|
|
133
|
+
# :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
|
|
134
|
+
# :join_type :: The join type to use for the join, defaults to :left_outer.
|
|
135
|
+
# :only_conditions :: Conditions to use for the join instead of the ones specified by the keys.
|
|
136
|
+
# * opts - The options for the associaion. Takes the same options as many_to_many.
|
|
143
137
|
def many_through_many(name, through, opts={}, &block)
|
|
144
138
|
associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
|
|
145
139
|
end
|
|
@@ -170,24 +164,41 @@ module Sequel
|
|
|
170
164
|
uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
|
171
165
|
left_keys = Array(left_key)
|
|
172
166
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
|
167
|
+
opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
|
|
173
168
|
left_pks = opts[:left_primary_keys] = Array(left_pk)
|
|
169
|
+
lpkc = opts[:left_primary_key_column] ||= left_pk
|
|
170
|
+
lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
|
|
174
171
|
opts[:dataset] ||= lambda do
|
|
175
172
|
ds = opts.associated_class
|
|
176
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
177
|
-
ft = opts
|
|
178
|
-
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) +
|
|
173
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
|
174
|
+
ft = opts.final_reverse_edge
|
|
175
|
+
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
|
|
179
176
|
end
|
|
180
177
|
|
|
181
178
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
182
179
|
opts[:eager_loader] ||= lambda do |eo|
|
|
183
|
-
h = eo[:
|
|
184
|
-
|
|
180
|
+
h = eo[:id_map]
|
|
181
|
+
rows = eo[:rows]
|
|
182
|
+
rows.each{|object| object.associations[name] = []}
|
|
185
183
|
ds = opts.associated_class
|
|
186
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
187
|
-
ft = opts
|
|
188
|
-
|
|
189
|
-
ds =
|
|
190
|
-
|
|
184
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
|
185
|
+
ft = opts.final_reverse_edge
|
|
186
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
|
|
187
|
+
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
|
188
|
+
case opts.eager_limit_strategy
|
|
189
|
+
when :window_function
|
|
190
|
+
delete_rn = true
|
|
191
|
+
rn = ds.row_number_column
|
|
192
|
+
ds = apply_window_function_eager_limit_strategy(ds, opts)
|
|
193
|
+
when :correlated_subquery
|
|
194
|
+
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
|
195
|
+
dsa = ds.send(:dataset_alias, 2)
|
|
196
|
+
opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
197
|
+
xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa, :qualify=>:deep)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
ds.all do |assoc_record|
|
|
201
|
+
assoc_record.values.delete(rn) if delete_rn
|
|
191
202
|
hash_key = if uses_lcks
|
|
192
203
|
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
|
193
204
|
else
|
|
@@ -196,6 +207,10 @@ module Sequel
|
|
|
196
207
|
next unless objects = h[hash_key]
|
|
197
208
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
198
209
|
end
|
|
210
|
+
if opts.eager_limit_strategy == :ruby
|
|
211
|
+
limit, offset = opts.limit_and_offset
|
|
212
|
+
rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
|
|
213
|
+
end
|
|
199
214
|
end
|
|
200
215
|
|
|
201
216
|
join_type = opts[:graph_join_type]
|
|
@@ -204,19 +219,56 @@ module Sequel
|
|
|
204
219
|
only_conditions = opts[:graph_only_conditions]
|
|
205
220
|
use_only_conditions = opts.include?(:graph_only_conditions)
|
|
206
221
|
conditions = opts[:graph_conditions]
|
|
207
|
-
opts[:eager_grapher] ||= proc do |
|
|
208
|
-
|
|
222
|
+
opts[:eager_grapher] ||= proc do |eo|
|
|
223
|
+
ds = eo[:self]
|
|
224
|
+
iq = eo[:implicit_qualifier]
|
|
209
225
|
opts.edges.each do |t|
|
|
210
|
-
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
|
226
|
+
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :qualify=>:deep, :implicit_qualifier=>iq, &t[:block])
|
|
211
227
|
iq = nil
|
|
212
228
|
end
|
|
213
|
-
fe = opts
|
|
214
|
-
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>
|
|
229
|
+
fe = opts.final_edge
|
|
230
|
+
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
|
|
215
231
|
end
|
|
216
232
|
|
|
217
233
|
def_association_dataset_methods(opts)
|
|
218
234
|
end
|
|
219
235
|
end
|
|
236
|
+
|
|
237
|
+
module DatasetMethods
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
# Use a subquery to filter rows to those related to the given associated object
|
|
241
|
+
def many_through_many_association_filter_expression(op, ref, obj)
|
|
242
|
+
lpks = ref[:left_primary_key_columns]
|
|
243
|
+
lpks = lpks.first if lpks.length == 1
|
|
244
|
+
lpks = ref.qualify(model.table_name, lpks)
|
|
245
|
+
edges = ref.edges
|
|
246
|
+
first, rest = edges.first, edges[1..-1]
|
|
247
|
+
last = edges.last
|
|
248
|
+
ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
|
|
249
|
+
rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), :qualify=>:deep, &e[:block])}
|
|
250
|
+
last_alias = if rest.empty?
|
|
251
|
+
first[:table]
|
|
252
|
+
else
|
|
253
|
+
last_join = ds.opts[:join].last
|
|
254
|
+
last_join.table_alias || last_join.table
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
meths = if obj.is_a?(Sequel::Dataset)
|
|
258
|
+
ref.qualify(obj.model.table_name, ref.right_primary_keys)
|
|
259
|
+
else
|
|
260
|
+
ref.right_primary_key_methods
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
|
|
264
|
+
if exp == SQL::Constants::FALSE
|
|
265
|
+
association_filter_handle_inversion(op, exp, Array(lpks))
|
|
266
|
+
else
|
|
267
|
+
ds = ds.where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ds.opts[:select].zip([]), :OR))
|
|
268
|
+
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>ds), Array(lpks))
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
220
272
|
end
|
|
221
273
|
end
|
|
222
274
|
end
|