sequel 4.26.0 → 5.37.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 +5 -5
- data/CHANGELOG +405 -5656
- data/MIT-LICENSE +1 -1
- data/README.rdoc +232 -157
- data/bin/sequel +32 -9
- data/doc/advanced_associations.rdoc +252 -188
- data/doc/association_basics.rdoc +231 -273
- data/doc/bin_sequel.rdoc +5 -3
- data/doc/cheat_sheet.rdoc +75 -48
- data/doc/code_order.rdoc +28 -10
- data/doc/core_extensions.rdoc +104 -63
- data/doc/dataset_basics.rdoc +12 -21
- data/doc/dataset_filtering.rdoc +99 -86
- data/doc/extensions.rdoc +3 -10
- data/doc/mass_assignment.rdoc +74 -31
- data/doc/migration.rdoc +72 -46
- data/doc/model_dataset_method_design.rdoc +129 -0
- data/doc/model_hooks.rdoc +15 -25
- data/doc/model_plugins.rdoc +12 -12
- data/doc/mssql_stored_procedures.rdoc +3 -3
- data/doc/object_model.rdoc +59 -69
- data/doc/opening_databases.rdoc +84 -94
- data/doc/postgresql.rdoc +268 -38
- data/doc/prepared_statements.rdoc +29 -24
- data/doc/querying.rdoc +184 -164
- data/doc/reflection.rdoc +5 -6
- data/doc/release_notes/5.0.0.txt +159 -0
- data/doc/release_notes/5.1.0.txt +31 -0
- data/doc/release_notes/5.10.0.txt +84 -0
- data/doc/release_notes/5.11.0.txt +83 -0
- data/doc/release_notes/5.12.0.txt +141 -0
- data/doc/release_notes/5.13.0.txt +27 -0
- data/doc/release_notes/5.14.0.txt +63 -0
- data/doc/release_notes/5.15.0.txt +39 -0
- data/doc/release_notes/5.16.0.txt +110 -0
- data/doc/release_notes/5.17.0.txt +31 -0
- data/doc/release_notes/5.18.0.txt +69 -0
- data/doc/release_notes/5.19.0.txt +28 -0
- data/doc/release_notes/5.2.0.txt +33 -0
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/release_notes/5.27.0.txt +21 -0
- data/doc/release_notes/5.28.0.txt +16 -0
- data/doc/release_notes/5.29.0.txt +22 -0
- data/doc/release_notes/5.3.0.txt +121 -0
- data/doc/release_notes/5.30.0.txt +20 -0
- data/doc/release_notes/5.31.0.txt +148 -0
- data/doc/release_notes/5.32.0.txt +46 -0
- data/doc/release_notes/5.33.0.txt +24 -0
- 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.4.0.txt +80 -0
- data/doc/release_notes/5.5.0.txt +61 -0
- data/doc/release_notes/5.6.0.txt +31 -0
- data/doc/release_notes/5.7.0.txt +108 -0
- data/doc/release_notes/5.8.0.txt +170 -0
- data/doc/release_notes/5.9.0.txt +99 -0
- data/doc/schema_modification.rdoc +102 -77
- data/doc/security.rdoc +160 -87
- data/doc/sharding.rdoc +74 -47
- data/doc/sql.rdoc +135 -122
- data/doc/testing.rdoc +34 -18
- data/doc/thread_safety.rdoc +2 -4
- data/doc/transactions.rdoc +101 -19
- data/doc/validations.rdoc +64 -51
- data/doc/virtual_rows.rdoc +90 -109
- data/lib/sequel.rb +3 -1
- data/lib/sequel/adapters/ado.rb +154 -22
- data/lib/sequel/adapters/ado/access.rb +21 -21
- data/lib/sequel/adapters/ado/mssql.rb +8 -15
- data/lib/sequel/adapters/amalgalite.rb +17 -25
- data/lib/sequel/adapters/ibmdb.rb +52 -58
- data/lib/sequel/adapters/jdbc.rb +149 -127
- data/lib/sequel/adapters/jdbc/db2.rb +32 -40
- data/lib/sequel/adapters/jdbc/derby.rb +56 -58
- data/lib/sequel/adapters/jdbc/h2.rb +40 -30
- data/lib/sequel/adapters/jdbc/hsqldb.rb +22 -33
- data/lib/sequel/adapters/jdbc/jtds.rb +4 -10
- data/lib/sequel/adapters/jdbc/mssql.rb +6 -12
- data/lib/sequel/adapters/jdbc/mysql.rb +17 -18
- data/lib/sequel/adapters/jdbc/oracle.rb +25 -19
- data/lib/sequel/adapters/jdbc/postgresql.rb +90 -69
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +14 -24
- data/lib/sequel/adapters/jdbc/sqlite.rb +50 -12
- data/lib/sequel/adapters/jdbc/sqlserver.rb +36 -9
- data/lib/sequel/adapters/jdbc/transactions.rb +25 -39
- data/lib/sequel/adapters/mock.rb +104 -113
- data/lib/sequel/adapters/mysql.rb +42 -61
- data/lib/sequel/adapters/mysql2.rb +126 -35
- data/lib/sequel/adapters/odbc.rb +21 -28
- data/lib/sequel/adapters/odbc/db2.rb +3 -1
- data/lib/sequel/adapters/odbc/mssql.rb +11 -15
- data/lib/sequel/adapters/odbc/oracle.rb +11 -0
- data/lib/sequel/adapters/oracle.rb +62 -68
- data/lib/sequel/adapters/postgres.rb +257 -311
- data/lib/sequel/adapters/postgresql.rb +3 -1
- data/lib/sequel/adapters/shared/access.rb +75 -79
- data/lib/sequel/adapters/shared/db2.rb +96 -74
- data/lib/sequel/adapters/shared/mssql.rb +258 -213
- data/lib/sequel/adapters/shared/mysql.rb +284 -216
- data/lib/sequel/adapters/shared/oracle.rb +175 -60
- data/lib/sequel/adapters/shared/postgres.rb +829 -383
- data/lib/sequel/adapters/shared/sqlanywhere.rb +105 -127
- data/lib/sequel/adapters/shared/sqlite.rb +382 -159
- data/lib/sequel/adapters/sqlanywhere.rb +53 -38
- data/lib/sequel/adapters/sqlite.rb +111 -105
- data/lib/sequel/adapters/tinytds.rb +38 -46
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -9
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -5
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +87 -0
- data/lib/sequel/adapters/utils/mysql_prepared_statements.rb +56 -0
- data/lib/sequel/adapters/utils/replace.rb +3 -4
- data/lib/sequel/adapters/utils/split_alter_table.rb +2 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +9 -22
- data/lib/sequel/adapters/utils/unmodified_identifiers.rb +28 -0
- data/lib/sequel/ast_transformer.rb +13 -89
- data/lib/sequel/connection_pool.rb +54 -26
- data/lib/sequel/connection_pool/sharded_single.rb +19 -12
- data/lib/sequel/connection_pool/sharded_threaded.rb +160 -111
- data/lib/sequel/connection_pool/single.rb +21 -12
- data/lib/sequel/connection_pool/threaded.rb +137 -119
- data/lib/sequel/core.rb +352 -320
- data/lib/sequel/database.rb +19 -2
- data/lib/sequel/database/connecting.rb +70 -55
- data/lib/sequel/database/dataset.rb +15 -5
- data/lib/sequel/database/dataset_defaults.rb +20 -102
- data/lib/sequel/database/features.rb +20 -4
- data/lib/sequel/database/logging.rb +25 -7
- data/lib/sequel/database/misc.rb +132 -118
- data/lib/sequel/database/query.rb +51 -28
- data/lib/sequel/database/schema_generator.rb +188 -75
- data/lib/sequel/database/schema_methods.rb +161 -92
- data/lib/sequel/database/transactions.rb +260 -58
- data/lib/sequel/dataset.rb +28 -12
- data/lib/sequel/dataset/actions.rb +354 -170
- data/lib/sequel/dataset/dataset_module.rb +46 -0
- data/lib/sequel/dataset/features.rb +81 -34
- data/lib/sequel/dataset/graph.rb +82 -58
- data/lib/sequel/dataset/misc.rb +139 -47
- data/lib/sequel/dataset/placeholder_literalizer.rb +66 -26
- data/lib/sequel/dataset/prepared_statements.rb +188 -85
- data/lib/sequel/dataset/query.rb +428 -214
- data/lib/sequel/dataset/sql.rb +446 -339
- data/lib/sequel/deprecated.rb +14 -2
- data/lib/sequel/exceptions.rb +48 -16
- data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
- data/lib/sequel/extensions/_model_pg_row.rb +43 -0
- data/lib/sequel/extensions/_pretty_table.rb +10 -9
- data/lib/sequel/extensions/any_not_empty.rb +45 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +15 -11
- data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
- data/lib/sequel/extensions/blank.rb +2 -0
- data/lib/sequel/extensions/caller_logging.rb +79 -0
- data/lib/sequel/extensions/columns_introspection.rb +9 -4
- data/lib/sequel/extensions/connection_expiration.rb +99 -0
- data/lib/sequel/extensions/connection_validator.rb +26 -13
- data/lib/sequel/extensions/constant_sql_override.rb +65 -0
- data/lib/sequel/extensions/constraint_validations.rb +93 -38
- data/lib/sequel/extensions/core_extensions.rb +45 -53
- data/lib/sequel/extensions/core_refinements.rb +44 -46
- data/lib/sequel/extensions/current_datetime_timestamp.rb +5 -4
- data/lib/sequel/extensions/dataset_source_alias.rb +4 -0
- data/lib/sequel/extensions/date_arithmetic.rb +42 -16
- data/lib/sequel/extensions/datetime_parse_to_time.rb +37 -0
- data/lib/sequel/extensions/duplicate_columns_handler.rb +94 -0
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -3
- data/lib/sequel/extensions/error_sql.rb +7 -3
- data/lib/sequel/extensions/escaped_like.rb +100 -0
- data/lib/sequel/extensions/eval_inspect.rb +14 -15
- data/lib/sequel/extensions/exclude_or_null.rb +68 -0
- data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
- data/lib/sequel/extensions/freeze_datasets.rb +3 -0
- data/lib/sequel/extensions/from_block.rb +2 -31
- data/lib/sequel/extensions/graph_each.rb +19 -6
- data/lib/sequel/extensions/identifier_mangling.rb +180 -0
- data/lib/sequel/extensions/implicit_subquery.rb +48 -0
- data/lib/sequel/extensions/index_caching.rb +109 -0
- data/lib/sequel/extensions/inflector.rb +8 -4
- data/lib/sequel/extensions/integer64.rb +32 -0
- data/lib/sequel/extensions/looser_typecasting.rb +19 -9
- data/lib/sequel/extensions/migration.rb +132 -80
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +4 -0
- data/lib/sequel/extensions/named_timezones.rb +88 -23
- data/lib/sequel/extensions/no_auto_literal_strings.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +12 -8
- data/lib/sequel/extensions/pagination.rb +35 -28
- data/lib/sequel/extensions/pg_array.rb +227 -316
- data/lib/sequel/extensions/pg_array_ops.rb +19 -7
- data/lib/sequel/extensions/pg_enum.rb +69 -24
- data/lib/sequel/extensions/pg_extended_date_support.rb +250 -0
- data/lib/sequel/extensions/pg_hstore.rb +50 -59
- data/lib/sequel/extensions/pg_hstore_ops.rb +9 -3
- data/lib/sequel/extensions/pg_inet.rb +34 -15
- data/lib/sequel/extensions/pg_inet_ops.rb +5 -1
- data/lib/sequel/extensions/pg_interval.rb +26 -26
- data/lib/sequel/extensions/pg_json.rb +422 -141
- data/lib/sequel/extensions/pg_json_ops.rb +248 -9
- data/lib/sequel/extensions/pg_loose_count.rb +5 -1
- data/lib/sequel/extensions/pg_range.rb +162 -146
- data/lib/sequel/extensions/pg_range_ops.rb +10 -5
- data/lib/sequel/extensions/pg_row.rb +53 -87
- data/lib/sequel/extensions/pg_row_ops.rb +36 -13
- data/lib/sequel/extensions/pg_static_cache_updater.rb +6 -2
- data/lib/sequel/extensions/pg_timestamptz.rb +28 -0
- data/lib/sequel/extensions/pretty_table.rb +4 -0
- data/lib/sequel/extensions/query.rb +12 -7
- data/lib/sequel/extensions/round_timestamps.rb +6 -9
- data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
- data/lib/sequel/extensions/s.rb +59 -0
- data/lib/sequel/extensions/schema_caching.rb +14 -1
- data/lib/sequel/extensions/schema_dumper.rb +83 -55
- data/lib/sequel/extensions/select_remove.rb +8 -4
- data/lib/sequel/extensions/sequel_4_dataset_methods.rb +85 -0
- data/lib/sequel/extensions/server_block.rb +50 -17
- data/lib/sequel/extensions/server_logging.rb +61 -0
- data/lib/sequel/extensions/split_array_nil.rb +8 -4
- data/lib/sequel/extensions/sql_comments.rb +96 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -1
- data/lib/sequel/extensions/string_agg.rb +181 -0
- data/lib/sequel/extensions/string_date_time.rb +2 -0
- data/lib/sequel/extensions/symbol_aref.rb +53 -0
- data/lib/sequel/extensions/symbol_aref_refinement.rb +43 -0
- data/lib/sequel/extensions/symbol_as.rb +23 -0
- data/lib/sequel/extensions/symbol_as_refinement.rb +37 -0
- data/lib/sequel/extensions/synchronize_sql.rb +45 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +15 -5
- data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
- data/lib/sequel/model.rb +36 -126
- data/lib/sequel/model/associations.rb +850 -257
- data/lib/sequel/model/base.rb +652 -764
- data/lib/sequel/model/dataset_module.rb +13 -10
- data/lib/sequel/model/default_inflections.rb +3 -1
- data/lib/sequel/model/errors.rb +3 -3
- data/lib/sequel/model/exceptions.rb +12 -12
- data/lib/sequel/model/inflections.rb +8 -19
- data/lib/sequel/model/plugins.rb +111 -0
- data/lib/sequel/plugins/accessed_columns.rb +2 -0
- data/lib/sequel/plugins/active_model.rb +32 -7
- data/lib/sequel/plugins/after_initialize.rb +3 -1
- data/lib/sequel/plugins/association_dependencies.rb +27 -18
- data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
- data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
- data/lib/sequel/plugins/association_pks.rb +181 -83
- data/lib/sequel/plugins/association_proxies.rb +33 -9
- data/lib/sequel/plugins/auto_validations.rb +58 -23
- data/lib/sequel/plugins/before_after_save.rb +8 -0
- data/lib/sequel/plugins/blacklist_security.rb +23 -12
- data/lib/sequel/plugins/boolean_readers.rb +9 -6
- data/lib/sequel/plugins/boolean_subsets.rb +64 -0
- data/lib/sequel/plugins/caching.rb +27 -16
- data/lib/sequel/plugins/class_table_inheritance.rb +192 -94
- data/lib/sequel/plugins/column_conflicts.rb +18 -3
- data/lib/sequel/plugins/column_select.rb +9 -5
- data/lib/sequel/plugins/columns_updated.rb +42 -0
- data/lib/sequel/plugins/composition.rb +36 -24
- data/lib/sequel/plugins/constraint_validations.rb +37 -16
- data/lib/sequel/plugins/csv_serializer.rb +58 -35
- data/lib/sequel/plugins/dataset_associations.rb +60 -18
- data/lib/sequel/plugins/def_dataset_method.rb +90 -0
- data/lib/sequel/plugins/defaults_setter.rb +74 -13
- data/lib/sequel/plugins/delay_add_association.rb +4 -1
- data/lib/sequel/plugins/dirty.rb +65 -24
- data/lib/sequel/plugins/eager_each.rb +27 -3
- data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
- data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
- data/lib/sequel/plugins/error_splitter.rb +19 -12
- data/lib/sequel/plugins/finder.rb +246 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
- data/lib/sequel/plugins/force_encoding.rb +9 -12
- data/lib/sequel/plugins/hook_class_methods.rb +39 -54
- data/lib/sequel/plugins/input_transformer.rb +20 -10
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/insert_returning_select.rb +4 -2
- data/lib/sequel/plugins/instance_filters.rb +12 -8
- data/lib/sequel/plugins/instance_hooks.rb +36 -17
- data/lib/sequel/plugins/instance_specific_default.rb +113 -0
- data/lib/sequel/plugins/inverted_subsets.rb +24 -13
- data/lib/sequel/plugins/json_serializer.rb +123 -47
- data/lib/sequel/plugins/lazy_attributes.rb +20 -14
- data/lib/sequel/plugins/list.rb +40 -26
- data/lib/sequel/plugins/many_through_many.rb +28 -12
- data/lib/sequel/plugins/modification_detection.rb +17 -5
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -5
- data/lib/sequel/plugins/nested_attributes.rb +55 -28
- data/lib/sequel/plugins/optimistic_locking.rb +5 -3
- data/lib/sequel/plugins/pg_array_associations.rb +52 -18
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +348 -0
- data/lib/sequel/plugins/pg_row.rb +7 -51
- data/lib/sequel/plugins/prepared_statements.rb +53 -72
- data/lib/sequel/plugins/prepared_statements_safe.rb +13 -5
- data/lib/sequel/plugins/rcte_tree.rb +43 -63
- data/lib/sequel/plugins/serialization.rb +37 -44
- data/lib/sequel/plugins/serialization_modification_detection.rb +3 -1
- data/lib/sequel/plugins/sharding.rb +17 -10
- data/lib/sequel/plugins/single_table_inheritance.rb +62 -28
- data/lib/sequel/plugins/singular_table_names.rb +2 -0
- data/lib/sequel/plugins/skip_create_refresh.rb +5 -3
- data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
- data/lib/sequel/plugins/split_values.rb +13 -6
- data/lib/sequel/plugins/static_cache.rb +79 -53
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/string_stripper.rb +5 -3
- data/lib/sequel/plugins/subclasses.rb +20 -2
- data/lib/sequel/plugins/subset_conditions.rb +48 -0
- data/lib/sequel/plugins/table_select.rb +4 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +120 -6
- data/lib/sequel/plugins/throw_failures.rb +110 -0
- data/lib/sequel/plugins/timestamps.rb +22 -8
- data/lib/sequel/plugins/touch.rb +21 -8
- data/lib/sequel/plugins/tree.rb +57 -30
- data/lib/sequel/plugins/typecast_on_load.rb +14 -4
- data/lib/sequel/plugins/unlimited_update.rb +3 -7
- data/lib/sequel/plugins/update_or_create.rb +6 -4
- data/lib/sequel/plugins/update_primary_key.rb +3 -1
- data/lib/sequel/plugins/update_refresh.rb +28 -15
- data/lib/sequel/plugins/uuid.rb +70 -0
- data/lib/sequel/plugins/validate_associated.rb +20 -0
- data/lib/sequel/plugins/validation_class_methods.rb +40 -19
- data/lib/sequel/plugins/validation_contexts.rb +49 -0
- data/lib/sequel/plugins/validation_helpers.rb +49 -31
- data/lib/sequel/plugins/whitelist_security.rb +122 -0
- data/lib/sequel/plugins/xml_serializer.rb +31 -30
- data/lib/sequel/sql.rb +479 -329
- data/lib/sequel/timezones.rb +62 -32
- data/lib/sequel/version.rb +10 -3
- metadata +177 -477
- data/Rakefile +0 -165
- data/doc/active_record.rdoc +0 -912
- data/doc/release_notes/1.0.txt +0 -38
- data/doc/release_notes/1.1.txt +0 -143
- data/doc/release_notes/1.3.txt +0 -101
- data/doc/release_notes/1.4.0.txt +0 -53
- data/doc/release_notes/1.5.0.txt +0 -155
- data/doc/release_notes/2.0.0.txt +0 -298
- data/doc/release_notes/2.1.0.txt +0 -271
- data/doc/release_notes/2.10.0.txt +0 -328
- data/doc/release_notes/2.11.0.txt +0 -215
- data/doc/release_notes/2.12.0.txt +0 -534
- data/doc/release_notes/2.2.0.txt +0 -253
- data/doc/release_notes/2.3.0.txt +0 -88
- data/doc/release_notes/2.4.0.txt +0 -106
- data/doc/release_notes/2.5.0.txt +0 -137
- data/doc/release_notes/2.6.0.txt +0 -157
- data/doc/release_notes/2.7.0.txt +0 -166
- data/doc/release_notes/2.8.0.txt +0 -171
- data/doc/release_notes/2.9.0.txt +0 -97
- data/doc/release_notes/3.0.0.txt +0 -221
- data/doc/release_notes/3.1.0.txt +0 -406
- data/doc/release_notes/3.10.0.txt +0 -286
- data/doc/release_notes/3.11.0.txt +0 -254
- data/doc/release_notes/3.12.0.txt +0 -304
- data/doc/release_notes/3.13.0.txt +0 -210
- data/doc/release_notes/3.14.0.txt +0 -118
- data/doc/release_notes/3.15.0.txt +0 -78
- data/doc/release_notes/3.16.0.txt +0 -45
- data/doc/release_notes/3.17.0.txt +0 -58
- data/doc/release_notes/3.18.0.txt +0 -120
- data/doc/release_notes/3.19.0.txt +0 -67
- data/doc/release_notes/3.2.0.txt +0 -268
- data/doc/release_notes/3.20.0.txt +0 -41
- data/doc/release_notes/3.21.0.txt +0 -87
- data/doc/release_notes/3.22.0.txt +0 -39
- data/doc/release_notes/3.23.0.txt +0 -172
- data/doc/release_notes/3.24.0.txt +0 -420
- data/doc/release_notes/3.25.0.txt +0 -88
- data/doc/release_notes/3.26.0.txt +0 -88
- data/doc/release_notes/3.27.0.txt +0 -82
- data/doc/release_notes/3.28.0.txt +0 -304
- data/doc/release_notes/3.29.0.txt +0 -459
- data/doc/release_notes/3.3.0.txt +0 -192
- data/doc/release_notes/3.30.0.txt +0 -135
- data/doc/release_notes/3.31.0.txt +0 -146
- data/doc/release_notes/3.32.0.txt +0 -202
- data/doc/release_notes/3.33.0.txt +0 -157
- data/doc/release_notes/3.34.0.txt +0 -671
- data/doc/release_notes/3.35.0.txt +0 -144
- data/doc/release_notes/3.36.0.txt +0 -245
- data/doc/release_notes/3.37.0.txt +0 -338
- data/doc/release_notes/3.38.0.txt +0 -234
- data/doc/release_notes/3.39.0.txt +0 -237
- data/doc/release_notes/3.4.0.txt +0 -325
- data/doc/release_notes/3.40.0.txt +0 -73
- data/doc/release_notes/3.41.0.txt +0 -155
- data/doc/release_notes/3.42.0.txt +0 -74
- data/doc/release_notes/3.43.0.txt +0 -105
- data/doc/release_notes/3.44.0.txt +0 -152
- data/doc/release_notes/3.45.0.txt +0 -179
- data/doc/release_notes/3.46.0.txt +0 -122
- data/doc/release_notes/3.47.0.txt +0 -270
- data/doc/release_notes/3.48.0.txt +0 -477
- data/doc/release_notes/3.5.0.txt +0 -510
- data/doc/release_notes/3.6.0.txt +0 -366
- data/doc/release_notes/3.7.0.txt +0 -179
- data/doc/release_notes/3.8.0.txt +0 -151
- data/doc/release_notes/3.9.0.txt +0 -233
- data/doc/release_notes/4.0.0.txt +0 -262
- data/doc/release_notes/4.1.0.txt +0 -85
- data/doc/release_notes/4.10.0.txt +0 -226
- data/doc/release_notes/4.11.0.txt +0 -147
- data/doc/release_notes/4.12.0.txt +0 -105
- data/doc/release_notes/4.13.0.txt +0 -169
- data/doc/release_notes/4.14.0.txt +0 -68
- data/doc/release_notes/4.15.0.txt +0 -56
- data/doc/release_notes/4.16.0.txt +0 -36
- data/doc/release_notes/4.17.0.txt +0 -38
- data/doc/release_notes/4.18.0.txt +0 -36
- data/doc/release_notes/4.19.0.txt +0 -45
- data/doc/release_notes/4.2.0.txt +0 -129
- data/doc/release_notes/4.20.0.txt +0 -79
- data/doc/release_notes/4.21.0.txt +0 -94
- data/doc/release_notes/4.22.0.txt +0 -72
- data/doc/release_notes/4.23.0.txt +0 -65
- data/doc/release_notes/4.24.0.txt +0 -99
- data/doc/release_notes/4.25.0.txt +0 -181
- data/doc/release_notes/4.26.0.txt +0 -44
- data/doc/release_notes/4.3.0.txt +0 -40
- data/doc/release_notes/4.4.0.txt +0 -92
- data/doc/release_notes/4.5.0.txt +0 -34
- data/doc/release_notes/4.6.0.txt +0 -30
- data/doc/release_notes/4.7.0.txt +0 -103
- data/doc/release_notes/4.8.0.txt +0 -175
- data/doc/release_notes/4.9.0.txt +0 -190
- data/lib/sequel/adapters/cubrid.rb +0 -142
- data/lib/sequel/adapters/do.rb +0 -156
- data/lib/sequel/adapters/do/mysql.rb +0 -64
- data/lib/sequel/adapters/do/postgres.rb +0 -42
- data/lib/sequel/adapters/do/sqlite3.rb +0 -40
- data/lib/sequel/adapters/jdbc/as400.rb +0 -82
- data/lib/sequel/adapters/jdbc/cubrid.rb +0 -62
- data/lib/sequel/adapters/jdbc/firebirdsql.rb +0 -34
- data/lib/sequel/adapters/jdbc/informix-sqli.rb +0 -31
- data/lib/sequel/adapters/jdbc/jdbcprogress.rb +0 -31
- data/lib/sequel/adapters/odbc/progress.rb +0 -8
- data/lib/sequel/adapters/shared/cubrid.rb +0 -243
- data/lib/sequel/adapters/shared/firebird.rb +0 -245
- data/lib/sequel/adapters/shared/informix.rb +0 -52
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +0 -150
- data/lib/sequel/adapters/shared/progress.rb +0 -38
- data/lib/sequel/adapters/swift.rb +0 -158
- data/lib/sequel/adapters/swift/mysql.rb +0 -47
- data/lib/sequel/adapters/swift/postgres.rb +0 -45
- data/lib/sequel/adapters/swift/sqlite.rb +0 -47
- data/lib/sequel/adapters/utils/pg_types.rb +0 -68
- data/lib/sequel/dataset/mutation.rb +0 -109
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +0 -3
- data/lib/sequel/extensions/filter_having.rb +0 -59
- data/lib/sequel/extensions/hash_aliases.rb +0 -45
- data/lib/sequel/extensions/meta_def.rb +0 -31
- data/lib/sequel/extensions/query_literals.rb +0 -80
- data/lib/sequel/extensions/ruby18_symbol_extensions.rb +0 -22
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +0 -118
- data/lib/sequel/extensions/set_overrides.rb +0 -72
- data/lib/sequel/no_core_ext.rb +0 -1
- data/lib/sequel/plugins/association_autoreloading.rb +0 -7
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +0 -7
- data/lib/sequel/plugins/pg_typecast_on_load.rb +0 -78
- data/lib/sequel/plugins/prepared_statements_associations.rb +0 -117
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +0 -59
- data/lib/sequel/plugins/schema.rb +0 -80
- data/lib/sequel/plugins/scissors.rb +0 -33
- data/spec/adapters/db2_spec.rb +0 -160
- data/spec/adapters/firebird_spec.rb +0 -411
- data/spec/adapters/informix_spec.rb +0 -100
- data/spec/adapters/mssql_spec.rb +0 -706
- data/spec/adapters/mysql_spec.rb +0 -1287
- data/spec/adapters/oracle_spec.rb +0 -313
- data/spec/adapters/postgres_spec.rb +0 -3725
- data/spec/adapters/spec_helper.rb +0 -43
- data/spec/adapters/sqlanywhere_spec.rb +0 -170
- data/spec/adapters/sqlite_spec.rb +0 -653
- data/spec/bin_spec.rb +0 -254
- data/spec/core/connection_pool_spec.rb +0 -1016
- data/spec/core/database_spec.rb +0 -2531
- data/spec/core/dataset_spec.rb +0 -5098
- data/spec/core/deprecated_spec.rb +0 -70
- data/spec/core/expression_filters_spec.rb +0 -1243
- data/spec/core/mock_adapter_spec.rb +0 -462
- data/spec/core/object_graph_spec.rb +0 -303
- data/spec/core/placeholder_literalizer_spec.rb +0 -163
- data/spec/core/schema_generator_spec.rb +0 -179
- data/spec/core/schema_spec.rb +0 -1659
- data/spec/core/spec_helper.rb +0 -34
- data/spec/core/version_spec.rb +0 -7
- data/spec/core_extensions_spec.rb +0 -699
- data/spec/extensions/accessed_columns_spec.rb +0 -51
- data/spec/extensions/active_model_spec.rb +0 -123
- data/spec/extensions/after_initialize_spec.rb +0 -24
- data/spec/extensions/arbitrary_servers_spec.rb +0 -109
- data/spec/extensions/association_dependencies_spec.rb +0 -117
- data/spec/extensions/association_pks_spec.rb +0 -365
- data/spec/extensions/association_proxies_spec.rb +0 -86
- data/spec/extensions/auto_validations_spec.rb +0 -192
- data/spec/extensions/blacklist_security_spec.rb +0 -88
- data/spec/extensions/blank_spec.rb +0 -69
- data/spec/extensions/boolean_readers_spec.rb +0 -93
- data/spec/extensions/caching_spec.rb +0 -270
- data/spec/extensions/class_table_inheritance_spec.rb +0 -420
- data/spec/extensions/column_conflicts_spec.rb +0 -60
- data/spec/extensions/column_select_spec.rb +0 -108
- data/spec/extensions/columns_introspection_spec.rb +0 -91
- data/spec/extensions/composition_spec.rb +0 -242
- data/spec/extensions/connection_validator_spec.rb +0 -120
- data/spec/extensions/constraint_validations_plugin_spec.rb +0 -274
- data/spec/extensions/constraint_validations_spec.rb +0 -325
- data/spec/extensions/core_refinements_spec.rb +0 -519
- data/spec/extensions/csv_serializer_spec.rb +0 -173
- data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
- data/spec/extensions/dataset_associations_spec.rb +0 -311
- data/spec/extensions/dataset_source_alias_spec.rb +0 -51
- data/spec/extensions/date_arithmetic_spec.rb +0 -150
- data/spec/extensions/defaults_setter_spec.rb +0 -101
- data/spec/extensions/delay_add_association_spec.rb +0 -52
- data/spec/extensions/dirty_spec.rb +0 -180
- data/spec/extensions/eager_each_spec.rb +0 -42
- data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
- data/spec/extensions/error_splitter_spec.rb +0 -18
- data/spec/extensions/error_sql_spec.rb +0 -20
- data/spec/extensions/eval_inspect_spec.rb +0 -73
- data/spec/extensions/filter_having_spec.rb +0 -40
- data/spec/extensions/force_encoding_spec.rb +0 -114
- data/spec/extensions/from_block_spec.rb +0 -21
- data/spec/extensions/graph_each_spec.rb +0 -109
- data/spec/extensions/hash_aliases_spec.rb +0 -24
- data/spec/extensions/hook_class_methods_spec.rb +0 -429
- data/spec/extensions/inflector_spec.rb +0 -183
- data/spec/extensions/input_transformer_spec.rb +0 -54
- data/spec/extensions/insert_returning_select_spec.rb +0 -46
- data/spec/extensions/instance_filters_spec.rb +0 -79
- data/spec/extensions/instance_hooks_spec.rb +0 -276
- data/spec/extensions/inverted_subsets_spec.rb +0 -33
- data/spec/extensions/json_serializer_spec.rb +0 -291
- data/spec/extensions/lazy_attributes_spec.rb +0 -170
- data/spec/extensions/list_spec.rb +0 -267
- data/spec/extensions/looser_typecasting_spec.rb +0 -43
- data/spec/extensions/many_through_many_spec.rb +0 -2172
- data/spec/extensions/meta_def_spec.rb +0 -21
- data/spec/extensions/migration_spec.rb +0 -712
- data/spec/extensions/modification_detection_spec.rb +0 -80
- data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -91
- data/spec/extensions/named_timezones_spec.rb +0 -108
- data/spec/extensions/nested_attributes_spec.rb +0 -697
- data/spec/extensions/null_dataset_spec.rb +0 -85
- data/spec/extensions/optimistic_locking_spec.rb +0 -128
- data/spec/extensions/pagination_spec.rb +0 -118
- data/spec/extensions/pg_array_associations_spec.rb +0 -736
- data/spec/extensions/pg_array_ops_spec.rb +0 -143
- data/spec/extensions/pg_array_spec.rb +0 -395
- data/spec/extensions/pg_enum_spec.rb +0 -92
- data/spec/extensions/pg_hstore_ops_spec.rb +0 -236
- data/spec/extensions/pg_hstore_spec.rb +0 -206
- data/spec/extensions/pg_inet_ops_spec.rb +0 -101
- data/spec/extensions/pg_inet_spec.rb +0 -52
- data/spec/extensions/pg_interval_spec.rb +0 -76
- data/spec/extensions/pg_json_ops_spec.rb +0 -229
- data/spec/extensions/pg_json_spec.rb +0 -218
- data/spec/extensions/pg_loose_count_spec.rb +0 -17
- data/spec/extensions/pg_range_ops_spec.rb +0 -58
- data/spec/extensions/pg_range_spec.rb +0 -404
- data/spec/extensions/pg_row_ops_spec.rb +0 -60
- data/spec/extensions/pg_row_plugin_spec.rb +0 -62
- data/spec/extensions/pg_row_spec.rb +0 -360
- data/spec/extensions/pg_static_cache_updater_spec.rb +0 -92
- data/spec/extensions/pg_typecast_on_load_spec.rb +0 -63
- data/spec/extensions/prepared_statements_associations_spec.rb +0 -159
- data/spec/extensions/prepared_statements_safe_spec.rb +0 -61
- data/spec/extensions/prepared_statements_spec.rb +0 -103
- data/spec/extensions/prepared_statements_with_pk_spec.rb +0 -31
- data/spec/extensions/pretty_table_spec.rb +0 -92
- data/spec/extensions/query_literals_spec.rb +0 -183
- data/spec/extensions/query_spec.rb +0 -102
- data/spec/extensions/rcte_tree_spec.rb +0 -392
- data/spec/extensions/round_timestamps_spec.rb +0 -43
- data/spec/extensions/schema_caching_spec.rb +0 -41
- data/spec/extensions/schema_dumper_spec.rb +0 -789
- data/spec/extensions/schema_spec.rb +0 -117
- data/spec/extensions/scissors_spec.rb +0 -26
- data/spec/extensions/select_remove_spec.rb +0 -38
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -101
- data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
- data/spec/extensions/serialization_spec.rb +0 -362
- data/spec/extensions/server_block_spec.rb +0 -90
- data/spec/extensions/set_overrides_spec.rb +0 -61
- data/spec/extensions/sharding_spec.rb +0 -198
- data/spec/extensions/shared_caching_spec.rb +0 -175
- data/spec/extensions/single_table_inheritance_spec.rb +0 -297
- data/spec/extensions/singular_table_names_spec.rb +0 -22
- data/spec/extensions/skip_create_refresh_spec.rb +0 -17
- data/spec/extensions/spec_helper.rb +0 -71
- data/spec/extensions/split_array_nil_spec.rb +0 -24
- data/spec/extensions/split_values_spec.rb +0 -22
- data/spec/extensions/sql_expr_spec.rb +0 -60
- data/spec/extensions/static_cache_spec.rb +0 -361
- data/spec/extensions/string_date_time_spec.rb +0 -95
- data/spec/extensions/string_stripper_spec.rb +0 -68
- data/spec/extensions/subclasses_spec.rb +0 -66
- data/spec/extensions/table_select_spec.rb +0 -71
- data/spec/extensions/tactical_eager_loading_spec.rb +0 -82
- data/spec/extensions/thread_local_timezones_spec.rb +0 -67
- data/spec/extensions/timestamps_spec.rb +0 -175
- data/spec/extensions/to_dot_spec.rb +0 -154
- data/spec/extensions/touch_spec.rb +0 -203
- data/spec/extensions/tree_spec.rb +0 -274
- data/spec/extensions/typecast_on_load_spec.rb +0 -80
- data/spec/extensions/unlimited_update_spec.rb +0 -20
- data/spec/extensions/update_or_create_spec.rb +0 -87
- data/spec/extensions/update_primary_key_spec.rb +0 -100
- data/spec/extensions/update_refresh_spec.rb +0 -53
- data/spec/extensions/validate_associated_spec.rb +0 -52
- data/spec/extensions/validation_class_methods_spec.rb +0 -1027
- data/spec/extensions/validation_helpers_spec.rb +0 -541
- data/spec/extensions/xml_serializer_spec.rb +0 -207
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
- data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
- data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
- data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
- data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
- data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
- data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/reversible_migrations/001_reversible.rb +0 -5
- data/spec/files/reversible_migrations/002_reversible.rb +0 -5
- data/spec/files/reversible_migrations/003_reversible.rb +0 -5
- data/spec/files/reversible_migrations/004_reversible.rb +0 -5
- data/spec/files/reversible_migrations/005_reversible.rb +0 -10
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
- data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
- data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
- data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
- data/spec/guards_helper.rb +0 -55
- data/spec/integration/associations_test.rb +0 -2454
- data/spec/integration/database_test.rb +0 -113
- data/spec/integration/dataset_test.rb +0 -1808
- data/spec/integration/eager_loader_test.rb +0 -687
- data/spec/integration/migrator_test.rb +0 -240
- data/spec/integration/model_test.rb +0 -226
- data/spec/integration/plugin_test.rb +0 -2240
- data/spec/integration/prepared_statement_test.rb +0 -467
- data/spec/integration/schema_test.rb +0 -817
- data/spec/integration/spec_helper.rb +0 -48
- data/spec/integration/timezone_test.rb +0 -86
- data/spec/integration/transaction_test.rb +0 -374
- data/spec/integration/type_test.rb +0 -133
- data/spec/model/association_reflection_spec.rb +0 -525
- data/spec/model/associations_spec.rb +0 -4426
- data/spec/model/base_spec.rb +0 -759
- data/spec/model/class_dataset_methods_spec.rb +0 -146
- data/spec/model/dataset_methods_spec.rb +0 -149
- data/spec/model/eager_loading_spec.rb +0 -2137
- data/spec/model/hooks_spec.rb +0 -604
- data/spec/model/inflector_spec.rb +0 -26
- data/spec/model/model_spec.rb +0 -982
- data/spec/model/plugins_spec.rb +0 -299
- data/spec/model/record_spec.rb +0 -2147
- data/spec/model/spec_helper.rb +0 -46
- data/spec/model/validations_spec.rb +0 -193
- data/spec/sequel_coverage.rb +0 -15
- data/spec/spec_config.rb +0 -10
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# These modifies virtual row blocks so that you can pass a block
|
|
4
|
+
# when calling a method to change the behavior. It only exists
|
|
5
|
+
# for backwards compatibility with previous Sequel versions, and
|
|
6
|
+
# is not recommended for new applications.
|
|
7
|
+
#
|
|
8
|
+
# To load the extension:
|
|
9
|
+
#
|
|
10
|
+
# Sequel.extension :virtual_row_method_block
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
module Sequel
|
|
14
|
+
module SQL
|
|
15
|
+
class VirtualRow < BasicObject
|
|
16
|
+
include(Module.new do
|
|
17
|
+
# Handle blocks passed to methods and change the behavior.
|
|
18
|
+
def method_missing(m, *args, &block)
|
|
19
|
+
if block
|
|
20
|
+
if args.empty?
|
|
21
|
+
Function.new(m)
|
|
22
|
+
else
|
|
23
|
+
case args.shift
|
|
24
|
+
when :*
|
|
25
|
+
Function.new(m, *args).*
|
|
26
|
+
when :distinct
|
|
27
|
+
Function.new(m, *args).distinct
|
|
28
|
+
when :over
|
|
29
|
+
opts = args.shift || OPTS
|
|
30
|
+
f = Function.new(m, *::Kernel.Array(opts[:args]))
|
|
31
|
+
f = f.* if opts[:*]
|
|
32
|
+
f.over(opts)
|
|
33
|
+
else
|
|
34
|
+
Kernel.raise(Error, 'unsupported VirtualRow method argument used with block')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/sequel/model.rb
CHANGED
|
@@ -1,67 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# Lets you create a Model subclass with its dataset already set.
|
|
5
|
-
# +source+ should be an instance of one of the following classes:
|
|
6
|
-
#
|
|
7
|
-
# Database :: Sets the database for this model to +source+.
|
|
8
|
-
# Generally only useful when subclassing directly
|
|
9
|
-
# from the returned class, where the name of the
|
|
10
|
-
# subclass sets the table name (which is combined
|
|
11
|
-
# with the +Database+ in +source+ to create the
|
|
12
|
-
# dataset to use)
|
|
13
|
-
# Dataset :: Sets the dataset for this model to +source+.
|
|
14
|
-
# other :: Sets the table name for this model to +source+. The
|
|
15
|
-
# class will use the default database for model
|
|
16
|
-
# classes in order to create the dataset.
|
|
17
|
-
#
|
|
18
|
-
# The purpose of this method is to set the dataset/database automatically
|
|
19
|
-
# for a model class, if the table name doesn't match the implicit
|
|
20
|
-
# name. This is neater than using set_dataset inside the class,
|
|
21
|
-
# doesn't require a bogus query for the schema.
|
|
22
|
-
#
|
|
23
|
-
# # Using a symbol
|
|
24
|
-
# class Comment < Sequel::Model(:something)
|
|
25
|
-
# table_name # => :something
|
|
26
|
-
# end
|
|
27
|
-
#
|
|
28
|
-
# # Using a dataset
|
|
29
|
-
# class Comment < Sequel::Model(DB1[:something])
|
|
30
|
-
# dataset # => DB1[:something]
|
|
31
|
-
# end
|
|
32
|
-
#
|
|
33
|
-
# # Using a database
|
|
34
|
-
# class Comment < Sequel::Model(DB1)
|
|
35
|
-
# dataset # => DB1[:comments]
|
|
36
|
-
# end
|
|
37
|
-
def self.Model(source)
|
|
38
|
-
if cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
|
|
39
|
-
return klass
|
|
40
|
-
end
|
|
41
|
-
klass = if source.is_a?(Database)
|
|
42
|
-
c = Class.new(Model)
|
|
43
|
-
c.db = source
|
|
44
|
-
c
|
|
45
|
-
else
|
|
46
|
-
Class.new(Model).set_dataset(source)
|
|
47
|
-
end
|
|
48
|
-
Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if cache_anonymous_models
|
|
49
|
-
klass
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
@cache_anonymous_models = true
|
|
53
|
-
|
|
54
|
-
class << self
|
|
55
|
-
# Whether to cache the anonymous models created by Sequel::Model(). This is
|
|
56
|
-
# required for reloading them correctly (avoiding the superclass mismatch). True
|
|
57
|
-
# by default for backwards compatibility.
|
|
58
|
-
attr_accessor :cache_anonymous_models
|
|
59
|
-
end
|
|
3
|
+
require_relative 'core'
|
|
60
4
|
|
|
5
|
+
module Sequel
|
|
61
6
|
# <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
|
|
62
7
|
# model class is backed by a dataset instance, and many dataset methods can be
|
|
63
8
|
# called directly on the class. Model datasets return rows as model instances,
|
|
64
|
-
# which
|
|
9
|
+
# which are wrappers around the underlying hash that allow easily updating or
|
|
10
|
+
# deleting the individual row.
|
|
65
11
|
#
|
|
66
12
|
# <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
|
|
67
13
|
# instance, or dataset method defined by a previous plugin and call super to get the default
|
|
@@ -74,75 +20,30 @@ module Sequel
|
|
|
74
20
|
class Model
|
|
75
21
|
OPTS = Sequel::OPTS
|
|
76
22
|
|
|
77
|
-
#
|
|
78
|
-
# of classes when dealing with code reloading.
|
|
79
|
-
ANONYMOUS_MODEL_CLASSES = {}
|
|
80
|
-
|
|
81
|
-
# Mutex protecting access to ANONYMOUS_MODEL_CLASSES
|
|
82
|
-
ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new
|
|
83
|
-
|
|
84
|
-
# Class methods added to model that call the method of the same name on the dataset
|
|
85
|
-
DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
|
|
86
|
-
[:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
|
|
87
|
-
|
|
88
|
-
# Boolean settings that can be modified at the global, class, or instance level.
|
|
89
|
-
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
|
|
90
|
-
:raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
|
|
91
|
-
|
|
92
|
-
# Hooks that are called before an action. Can return false to not do the action. When
|
|
93
|
-
# overriding these, it is recommended to call +super+ as the last line of your method,
|
|
94
|
-
# so later hooks are called before earlier hooks.
|
|
95
|
-
BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
|
|
96
|
-
|
|
97
|
-
# Hooks that are called after an action. When overriding these, it is recommended to call
|
|
98
|
-
# +super+ on the first line of your method, so later hooks are called after earlier hooks.
|
|
99
|
-
AFTER_HOOKS = [:after_create, :after_update, :after_save, :after_destroy,
|
|
100
|
-
:after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
|
|
101
|
-
|
|
102
|
-
# Hooks that are called around an action. If overridden, these methods must call super
|
|
103
|
-
# exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
|
|
104
|
-
# raised by the code they wrap or ensure that some behavior is executed no matter what.
|
|
105
|
-
AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
|
|
106
|
-
|
|
107
|
-
# Empty instance methods to create that the user can override to get hook/callback behavior.
|
|
23
|
+
# Empty instance methods to create that the user can override.
|
|
108
24
|
# Just like any other method defined by Sequel, if you override one of these, you should
|
|
109
|
-
# call +super+ to get the default behavior (while empty by default, they
|
|
25
|
+
# call +super+ to get the default behavior (while empty by default, they are often overridden
|
|
110
26
|
# by plugins). See the {"Model Hooks" guide}[rdoc-ref:doc/model_hooks.rdoc] for
|
|
111
27
|
# more detail on hooks.
|
|
112
|
-
HOOKS =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
:@db=>nil, :@default_set_fields_options=>:dup}
|
|
127
|
-
|
|
128
|
-
# Regular expression that determines if a method name is normal in the sense that
|
|
129
|
-
# it could be used literally in ruby code without using send. Used to
|
|
130
|
-
# avoid problems when using eval with a string to define methods.
|
|
131
|
-
NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
|
132
|
-
|
|
133
|
-
# Regular expression that determines if the method is a valid setter name
|
|
134
|
-
# (i.e. it ends with =).
|
|
135
|
-
SETTER_METHOD_REGEXP = /=\z/
|
|
136
|
-
|
|
137
|
-
@allowed_columns = nil
|
|
28
|
+
HOOKS = [
|
|
29
|
+
:after_create,
|
|
30
|
+
:after_destroy,
|
|
31
|
+
:after_save,
|
|
32
|
+
:after_update,
|
|
33
|
+
:after_validation,
|
|
34
|
+
:before_create,
|
|
35
|
+
:before_destroy,
|
|
36
|
+
:before_save,
|
|
37
|
+
:before_update,
|
|
38
|
+
:before_validation
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
41
|
+
@cache_anonymous_models = true
|
|
138
42
|
@db = nil
|
|
139
43
|
@db_schema = nil
|
|
140
44
|
@dataset = nil
|
|
141
45
|
@dataset_method_modules = []
|
|
142
|
-
@default_eager_limit_strategy = true
|
|
143
46
|
@default_set_fields_options = {}
|
|
144
|
-
@finders = {}
|
|
145
|
-
@finder_loaders = {}
|
|
146
47
|
@overridable_methods_module = nil
|
|
147
48
|
@fast_pk_lookup_sql = nil
|
|
148
49
|
@fast_instance_delete_sql = nil
|
|
@@ -151,25 +52,34 @@ module Sequel
|
|
|
151
52
|
@raise_on_save_failure = true
|
|
152
53
|
@raise_on_typecast_failure = false
|
|
153
54
|
@require_modification = nil
|
|
55
|
+
@require_valid_table = true
|
|
154
56
|
@restrict_primary_key = true
|
|
155
|
-
@restricted_columns = nil
|
|
156
57
|
@setter_methods = nil
|
|
157
58
|
@simple_pk = nil
|
|
158
59
|
@simple_table = nil
|
|
159
60
|
@strict_param_setting = true
|
|
160
61
|
@typecast_empty_string_to_nil = true
|
|
161
62
|
@typecast_on_assignment = true
|
|
162
|
-
@use_after_commit_rollback = true
|
|
163
63
|
@use_transactions = true
|
|
164
64
|
|
|
165
|
-
|
|
65
|
+
require_relative "model/default_inflections"
|
|
66
|
+
require_relative "model/inflections"
|
|
67
|
+
require_relative "model/plugins"
|
|
68
|
+
require_relative "model/dataset_module"
|
|
69
|
+
require_relative "model/base"
|
|
70
|
+
require_relative "model/exceptions"
|
|
71
|
+
require_relative "model/errors"
|
|
72
|
+
# :nocov:
|
|
166
73
|
if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
|
|
167
|
-
|
|
74
|
+
# :nocov:
|
|
75
|
+
require_relative 'model/associations'
|
|
168
76
|
plugin Model::Associations
|
|
169
77
|
end
|
|
170
78
|
|
|
79
|
+
def_Model(::Sequel)
|
|
80
|
+
|
|
171
81
|
# The setter methods (methods ending with =) that are never allowed
|
|
172
|
-
# to be called automatically via +set+/+update+/+new+/etc
|
|
173
|
-
RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).
|
|
82
|
+
# to be called automatically via +set+/+update+/+new+/etc.
|
|
83
|
+
RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
|
|
174
84
|
end
|
|
175
85
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
1
3
|
module Sequel
|
|
2
4
|
class Model
|
|
3
5
|
# Associations are used in order to specify relationships between model classes
|
|
@@ -5,45 +7,54 @@ module Sequel
|
|
|
5
7
|
module Associations
|
|
6
8
|
# Map of association type symbols to association reflection classes.
|
|
7
9
|
ASSOCIATION_TYPES = {}
|
|
8
|
-
|
|
10
|
+
|
|
9
11
|
# Set an empty association reflection hash in the model
|
|
10
12
|
def self.apply(model)
|
|
11
|
-
model.
|
|
13
|
+
model.instance_exec do
|
|
12
14
|
@association_reflections = {}
|
|
13
15
|
@autoreloading_associations = {}
|
|
14
16
|
@cache_associations = true
|
|
17
|
+
@default_eager_limit_strategy = true
|
|
18
|
+
@default_association_options = {}
|
|
19
|
+
@default_association_type_options = {}
|
|
20
|
+
@dataset_module_class = DatasetModule
|
|
15
21
|
end
|
|
16
22
|
end
|
|
17
23
|
|
|
24
|
+
# The dataset module to use for classes using the associations plugin.
|
|
25
|
+
class DatasetModule < Model::DatasetModule
|
|
26
|
+
def_dataset_caching_method(self, :eager)
|
|
27
|
+
end
|
|
28
|
+
|
|
18
29
|
# AssociationReflection is a Hash subclass that keeps information on Sequel::Model associations. It
|
|
19
30
|
# provides methods to reduce internal code duplication. It should not
|
|
20
31
|
# be instantiated by the user.
|
|
21
32
|
class AssociationReflection < Hash
|
|
22
33
|
include Sequel::Inflections
|
|
23
|
-
|
|
34
|
+
|
|
24
35
|
# Name symbol for the _add internal association method
|
|
25
36
|
def _add_method
|
|
26
|
-
|
|
37
|
+
self[:_add_method]
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
# Name symbol for the _remove_all internal association method
|
|
30
41
|
def _remove_all_method
|
|
31
|
-
|
|
42
|
+
self[:_remove_all_method]
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
# Name symbol for the _remove internal association method
|
|
35
46
|
def _remove_method
|
|
36
|
-
|
|
47
|
+
self[:_remove_method]
|
|
37
48
|
end
|
|
38
49
|
|
|
39
50
|
# Name symbol for the _setter association method
|
|
40
51
|
def _setter_method
|
|
41
|
-
|
|
52
|
+
self[:_setter_method]
|
|
42
53
|
end
|
|
43
54
|
|
|
44
55
|
# Name symbol for the add association method
|
|
45
56
|
def add_method
|
|
46
|
-
|
|
57
|
+
self[:add_method]
|
|
47
58
|
end
|
|
48
59
|
|
|
49
60
|
# Name symbol for association method, the same as the name of the association.
|
|
@@ -53,7 +64,13 @@ module Sequel
|
|
|
53
64
|
|
|
54
65
|
# The class associated to the current model class via this association
|
|
55
66
|
def associated_class
|
|
56
|
-
cached_fetch(:class)
|
|
67
|
+
cached_fetch(:class) do
|
|
68
|
+
begin
|
|
69
|
+
constantize(self[:class_name])
|
|
70
|
+
rescue NameError => e
|
|
71
|
+
raise NameError, "#{e.message} (this happened when attempting to find the associated class for #{inspect})", e.backtrace
|
|
72
|
+
end
|
|
73
|
+
end
|
|
57
74
|
end
|
|
58
75
|
|
|
59
76
|
# The dataset associated via this association, with the non-instance specific
|
|
@@ -65,16 +82,17 @@ module Sequel
|
|
|
65
82
|
|
|
66
83
|
# Apply all non-instance specific changes to the given dataset and return it.
|
|
67
84
|
def apply_dataset_changes(ds)
|
|
68
|
-
ds.
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
ds = ds.with_extend(AssociationDatasetMethods).clone(:association_reflection => self)
|
|
86
|
+
if exts = self[:reverse_extend]
|
|
87
|
+
ds = ds.with_extend(*exts)
|
|
88
|
+
end
|
|
71
89
|
ds = ds.select(*select) if select
|
|
72
90
|
if c = self[:conditions]
|
|
73
91
|
ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
|
|
74
92
|
end
|
|
75
93
|
ds = ds.order(*self[:order]) if self[:order]
|
|
76
94
|
ds = ds.limit(*self[:limit]) if self[:limit]
|
|
77
|
-
ds = ds.limit(1) if limit_to_single_row?
|
|
95
|
+
ds = ds.limit(1).skip_limit_check if limit_to_single_row?
|
|
78
96
|
ds = ds.eager(self[:eager]) if self[:eager]
|
|
79
97
|
ds = ds.distinct if self[:distinct]
|
|
80
98
|
ds
|
|
@@ -105,12 +123,12 @@ module Sequel
|
|
|
105
123
|
|
|
106
124
|
# Apply an eager limit strategy to the dataset, or return the dataset
|
|
107
125
|
# unmodified if it doesn't need an eager limit strategy.
|
|
108
|
-
def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy)
|
|
126
|
+
def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
|
|
109
127
|
case strategy
|
|
110
128
|
when :distinct_on
|
|
111
129
|
apply_distinct_on_eager_limit_strategy(ds)
|
|
112
130
|
when :window_function
|
|
113
|
-
apply_window_function_eager_limit_strategy(ds)
|
|
131
|
+
apply_window_function_eager_limit_strategy(ds, limit_and_offset)
|
|
114
132
|
else
|
|
115
133
|
ds
|
|
116
134
|
end
|
|
@@ -123,10 +141,11 @@ module Sequel
|
|
|
123
141
|
end
|
|
124
142
|
|
|
125
143
|
# Use a window function to limit the results of the eager loading dataset.
|
|
126
|
-
def apply_window_function_eager_limit_strategy(ds)
|
|
144
|
+
def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
|
|
127
145
|
rn = ds.row_number_column
|
|
128
146
|
limit, offset = limit_and_offset
|
|
129
|
-
ds = ds.unordered.select_append{|o| o.row_number
|
|
147
|
+
ds = ds.unordered.select_append{|o| o.row_number.function.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
|
|
148
|
+
ds = ds.order(rn) if ds.db.database_type == :mysql
|
|
130
149
|
ds = if !returns_array?
|
|
131
150
|
ds.where(rn => offset ? offset+1 : 1)
|
|
132
151
|
elsif offset
|
|
@@ -143,13 +162,13 @@ module Sequel
|
|
|
143
162
|
|
|
144
163
|
# If the ruby eager limit strategy is being used, slice the array using the slice
|
|
145
164
|
# range to return the object(s) at the correct offset/limit.
|
|
146
|
-
def apply_ruby_eager_limit_strategy(rows)
|
|
165
|
+
def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
|
|
147
166
|
name = self[:name]
|
|
167
|
+
return unless range = slice_range(limit_and_offset)
|
|
148
168
|
if returns_array?
|
|
149
|
-
range = slice_range
|
|
150
169
|
rows.each{|o| o.associations[name] = o.associations[name][range] || []}
|
|
151
|
-
|
|
152
|
-
offset =
|
|
170
|
+
else
|
|
171
|
+
offset = range.begin
|
|
153
172
|
rows.each{|o| o.associations[name] = o.associations[name][offset]}
|
|
154
173
|
end
|
|
155
174
|
end
|
|
@@ -175,7 +194,7 @@ module Sequel
|
|
|
175
194
|
|
|
176
195
|
# Name symbol for the dataset association method
|
|
177
196
|
def dataset_method
|
|
178
|
-
|
|
197
|
+
self[:dataset_method]
|
|
179
198
|
end
|
|
180
199
|
|
|
181
200
|
# Whether the dataset needs a primary key to function, true by default.
|
|
@@ -194,7 +213,13 @@ module Sequel
|
|
|
194
213
|
# Return an dataset that will load the appropriate associated objects for
|
|
195
214
|
# the given object using this association.
|
|
196
215
|
def association_dataset_for(object)
|
|
197
|
-
|
|
216
|
+
condition = if can_have_associated_objects?(object)
|
|
217
|
+
predicate_keys.zip(predicate_key_values(object))
|
|
218
|
+
else
|
|
219
|
+
false
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
associated_dataset.where(condition)
|
|
198
223
|
end
|
|
199
224
|
|
|
200
225
|
ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
|
|
@@ -245,12 +270,31 @@ module Sequel
|
|
|
245
270
|
end
|
|
246
271
|
strategy = eager_limit_strategy
|
|
247
272
|
cascade = eo[:associations]
|
|
273
|
+
eager_limit = nil
|
|
248
274
|
|
|
249
275
|
if eo[:eager_block] || eo[:loader] == false
|
|
276
|
+
ds = eager_loading_dataset(eo)
|
|
277
|
+
|
|
278
|
+
strategy = ds.opts[:eager_limit_strategy] || strategy
|
|
279
|
+
|
|
280
|
+
eager_limit =
|
|
281
|
+
if el = ds.opts[:eager_limit]
|
|
282
|
+
raise Error, "The :eager_limit dataset option is not supported for associations returning a single record" unless returns_array?
|
|
283
|
+
strategy ||= true_eager_graph_limit_strategy
|
|
284
|
+
if el.is_a?(Array)
|
|
285
|
+
el
|
|
286
|
+
else
|
|
287
|
+
[el, nil]
|
|
288
|
+
end
|
|
289
|
+
else
|
|
290
|
+
limit_and_offset
|
|
291
|
+
end
|
|
292
|
+
|
|
250
293
|
strategy = true_eager_graph_limit_strategy if strategy == :union
|
|
251
294
|
# Correlated subqueries are not supported for regular eager loading
|
|
252
295
|
strategy = :ruby if strategy == :correlated_subquery
|
|
253
|
-
|
|
296
|
+
strategy = nil if strategy == :ruby && assign_singular?
|
|
297
|
+
objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
|
|
254
298
|
elsif strategy == :union
|
|
255
299
|
objects = []
|
|
256
300
|
ds = associated_dataset
|
|
@@ -269,7 +313,7 @@ module Sequel
|
|
|
269
313
|
|
|
270
314
|
objects.each(&block)
|
|
271
315
|
if strategy == :ruby
|
|
272
|
-
apply_ruby_eager_limit_strategy(rows)
|
|
316
|
+
apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
|
|
273
317
|
end
|
|
274
318
|
end
|
|
275
319
|
|
|
@@ -284,11 +328,6 @@ module Sequel
|
|
|
284
328
|
false
|
|
285
329
|
end
|
|
286
330
|
|
|
287
|
-
# Alias of predicate_key, only for backwards compatibility.
|
|
288
|
-
def eager_loading_predicate_key
|
|
289
|
-
predicate_key
|
|
290
|
-
end
|
|
291
|
-
|
|
292
331
|
# Whether to eagerly graph a lazy dataset, true by default. If this
|
|
293
332
|
# is false, the association won't respect the :eager_graph option
|
|
294
333
|
# when loading the association for a single record.
|
|
@@ -311,6 +350,44 @@ module Sequel
|
|
|
311
350
|
{filter_by_associations_conditions_key=>ds}
|
|
312
351
|
end
|
|
313
352
|
|
|
353
|
+
# Finalize the association by first attempting to populate the thread-safe cache,
|
|
354
|
+
# and then transfering the thread-safe cache value to the association itself,
|
|
355
|
+
# so that a mutex is not needed to get the value.
|
|
356
|
+
def finalize
|
|
357
|
+
return unless cache = self[:cache]
|
|
358
|
+
|
|
359
|
+
finalizer = proc do |meth, key|
|
|
360
|
+
next if has_key?(key)
|
|
361
|
+
|
|
362
|
+
# Allow calling private methods to make sure caching is done appropriately
|
|
363
|
+
send(meth)
|
|
364
|
+
self[key] = cache.delete(key) if cache.has_key?(key)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
finalize_settings.each(&finalizer)
|
|
368
|
+
|
|
369
|
+
unless self[:instance_specific]
|
|
370
|
+
finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
|
|
371
|
+
finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Map of methods to cache keys used for finalizing associations.
|
|
378
|
+
FINALIZE_SETTINGS = {
|
|
379
|
+
:associated_class=>:class,
|
|
380
|
+
:associated_dataset=>:_dataset,
|
|
381
|
+
:eager_limit_strategy=>:_eager_limit_strategy,
|
|
382
|
+
:placeholder_loader=>:placeholder_loader,
|
|
383
|
+
:predicate_key=>:predicate_key,
|
|
384
|
+
:predicate_keys=>:predicate_keys,
|
|
385
|
+
:reciprocal=>:reciprocal,
|
|
386
|
+
}.freeze
|
|
387
|
+
def finalize_settings
|
|
388
|
+
FINALIZE_SETTINGS
|
|
389
|
+
end
|
|
390
|
+
|
|
314
391
|
# Whether to handle silent modification failure when adding/removing
|
|
315
392
|
# associated records, false by default.
|
|
316
393
|
def handle_silent_modification_failure?
|
|
@@ -327,6 +404,18 @@ module Sequel
|
|
|
327
404
|
end
|
|
328
405
|
end
|
|
329
406
|
|
|
407
|
+
# Show which type of reflection this is, and a guess at what code was used to create the
|
|
408
|
+
# association.
|
|
409
|
+
def inspect
|
|
410
|
+
o = self[:orig_opts].dup
|
|
411
|
+
o.delete(:class)
|
|
412
|
+
o.delete(:class_name)
|
|
413
|
+
o.delete(:block) unless o[:block]
|
|
414
|
+
o[:class] = self[:orig_class] if self[:orig_class]
|
|
415
|
+
|
|
416
|
+
"#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
|
|
417
|
+
end
|
|
418
|
+
|
|
330
419
|
# The limit and offset for this association (returned as a two element array).
|
|
331
420
|
def limit_and_offset
|
|
332
421
|
if (v = self[:limit]).is_a?(Array)
|
|
@@ -348,7 +437,11 @@ module Sequel
|
|
|
348
437
|
if use_placeholder_loader?
|
|
349
438
|
cached_fetch(:placeholder_loader) do
|
|
350
439
|
Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
|
|
351
|
-
ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)})
|
|
440
|
+
ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
|
|
441
|
+
if self[:block]
|
|
442
|
+
ds = self[:block].call(ds)
|
|
443
|
+
end
|
|
444
|
+
ds
|
|
352
445
|
end
|
|
353
446
|
end
|
|
354
447
|
end
|
|
@@ -364,16 +457,14 @@ module Sequel
|
|
|
364
457
|
predicate_key_methods.map{|k| object.get_column_value(k)}
|
|
365
458
|
end
|
|
366
459
|
|
|
367
|
-
# Qualify +col+ with the given table name.
|
|
368
|
-
# return an array of qualified columns. Only qualifies Symbols and SQL::Identifier
|
|
369
|
-
# values, other values are not modified.
|
|
460
|
+
# Qualify +col+ with the given table name.
|
|
370
461
|
def qualify(table, col)
|
|
371
462
|
transform(col) do |k|
|
|
372
463
|
case k
|
|
373
464
|
when Symbol, SQL::Identifier
|
|
374
465
|
SQL::QualifiedIdentifier.new(table, k)
|
|
375
466
|
else
|
|
376
|
-
Sequel::Qualifier.new(
|
|
467
|
+
Sequel::Qualifier.new(table).transform(k)
|
|
377
468
|
end
|
|
378
469
|
end
|
|
379
470
|
end
|
|
@@ -419,7 +510,7 @@ module Sequel
|
|
|
419
510
|
|
|
420
511
|
# Name symbol for the remove_all_ association method
|
|
421
512
|
def remove_all_method
|
|
422
|
-
|
|
513
|
+
self[:remove_all_method]
|
|
423
514
|
end
|
|
424
515
|
|
|
425
516
|
# Whether associated objects need to be removed from the association before
|
|
@@ -430,7 +521,7 @@ module Sequel
|
|
|
430
521
|
|
|
431
522
|
# Name symbol for the remove_ association method
|
|
432
523
|
def remove_method
|
|
433
|
-
|
|
524
|
+
self[:remove_method]
|
|
434
525
|
end
|
|
435
526
|
|
|
436
527
|
# Whether to check that an object to be disassociated is already associated to this object, false by default.
|
|
@@ -457,11 +548,11 @@ module Sequel
|
|
|
457
548
|
|
|
458
549
|
# Name symbol for the setter association method
|
|
459
550
|
def setter_method
|
|
460
|
-
|
|
551
|
+
self[:setter_method]
|
|
461
552
|
end
|
|
462
553
|
|
|
463
554
|
# The range used for slicing when using the :ruby eager limit strategy.
|
|
464
|
-
def slice_range
|
|
555
|
+
def slice_range(limit_and_offset = limit_and_offset())
|
|
465
556
|
limit, offset = limit_and_offset
|
|
466
557
|
if limit || offset
|
|
467
558
|
(offset||0)..(limit ? (offset||0)+limit-1 : -1)
|
|
@@ -470,49 +561,32 @@ module Sequel
|
|
|
470
561
|
|
|
471
562
|
private
|
|
472
563
|
|
|
473
|
-
|
|
474
|
-
#
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
Sequel.synchronize{h[key] = value}
|
|
483
|
-
end
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
# Cache the value at the given key, synchronizing access.
|
|
487
|
-
def cached_set(key, value)
|
|
488
|
-
return unless h = self[:cache]
|
|
564
|
+
# If the key exists in the reflection hash, return it.
|
|
565
|
+
# If the key doesn't exist and association reflections are uncached, then yield to get the value.
|
|
566
|
+
# If the key doesn't exist and association reflection are cached, check the cache and return
|
|
567
|
+
# the value if present, or yield to get the value, cache the value, and return it.
|
|
568
|
+
def cached_fetch(key)
|
|
569
|
+
fetch(key) do
|
|
570
|
+
return yield unless h = self[:cache]
|
|
571
|
+
Sequel.synchronize{return h[key] if h.has_key?(key)}
|
|
572
|
+
value = yield
|
|
489
573
|
Sequel.synchronize{h[key] = value}
|
|
490
574
|
end
|
|
491
|
-
|
|
492
|
-
else
|
|
493
|
-
# On MRI, use a plain fetch, since the GVL will synchronize access.
|
|
494
|
-
def cached_fetch(key)
|
|
495
|
-
fetch(key) do
|
|
496
|
-
return yield unless h = self[:cache]
|
|
497
|
-
h.fetch(key){h[key] = yield}
|
|
498
|
-
end
|
|
499
|
-
end
|
|
575
|
+
end
|
|
500
576
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
h[key] = value
|
|
506
|
-
end
|
|
577
|
+
# Cache the value at the given key if caching.
|
|
578
|
+
def cached_set(key, value)
|
|
579
|
+
return unless h = self[:cache]
|
|
580
|
+
Sequel.synchronize{h[key] = value}
|
|
507
581
|
end
|
|
508
582
|
|
|
509
583
|
# The base dataset used for the association, before any order/conditions
|
|
510
584
|
# options have been applied.
|
|
511
585
|
def _associated_dataset
|
|
512
|
-
associated_class.dataset
|
|
586
|
+
associated_class.dataset
|
|
513
587
|
end
|
|
514
588
|
|
|
515
|
-
# Whether for the reciprocal type for the given association
|
|
589
|
+
# Whether for the reciprocal type for the given association cannot be
|
|
516
590
|
# known in advantage, false by default.
|
|
517
591
|
def ambiguous_reciprocal_type?
|
|
518
592
|
false
|
|
@@ -568,10 +642,15 @@ module Sequel
|
|
|
568
642
|
ds = ds.eager(associations)
|
|
569
643
|
end
|
|
570
644
|
if block = eo[:eager_block]
|
|
645
|
+
orig_ds = ds
|
|
571
646
|
ds = block.call(ds)
|
|
572
647
|
end
|
|
573
648
|
if eager_loading_use_associated_key?
|
|
574
|
-
ds = ds.
|
|
649
|
+
ds = if ds.opts[:eager_graph] && !orig_ds.opts[:eager_graph]
|
|
650
|
+
block.call(orig_ds.select_append(*associated_key_array))
|
|
651
|
+
else
|
|
652
|
+
ds.select_append(*associated_key_array)
|
|
653
|
+
end
|
|
575
654
|
end
|
|
576
655
|
if self[:eager_graph]
|
|
577
656
|
raise(Error, "cannot eagerly load a #{self[:type]} association that uses :eager_graph") if eager_loading_use_associated_key?
|
|
@@ -629,14 +708,12 @@ module Sequel
|
|
|
629
708
|
v = fetch(:filter_limit_strategy, self[:eager_limit_strategy])
|
|
630
709
|
if v || self[:limit] || !returns_array?
|
|
631
710
|
case v ||= self[:model].default_eager_limit_strategy
|
|
632
|
-
when :union, :ruby
|
|
711
|
+
when true, :union, :ruby
|
|
633
712
|
# Can't use a union or ruby-based strategy for filtering by associations, switch to default eager graph limit
|
|
634
713
|
# strategy.
|
|
635
714
|
true_eager_graph_limit_strategy
|
|
636
715
|
when Symbol
|
|
637
716
|
v
|
|
638
|
-
when true
|
|
639
|
-
true_eager_graph_limit_strategy
|
|
640
717
|
end
|
|
641
718
|
end
|
|
642
719
|
end
|
|
@@ -684,8 +761,8 @@ module Sequel
|
|
|
684
761
|
|
|
685
762
|
# If +s+ is an array, map +s+ over the block. Otherwise, just call the
|
|
686
763
|
# block with +s+.
|
|
687
|
-
def transform(s)
|
|
688
|
-
s.is_a?(Array) ? s.map(&
|
|
764
|
+
def transform(s, &block)
|
|
765
|
+
s.is_a?(Array) ? s.map(&block) : (yield s)
|
|
689
766
|
end
|
|
690
767
|
|
|
691
768
|
# What eager limit strategy should be used when true is given as the value,
|
|
@@ -728,7 +805,7 @@ module Sequel
|
|
|
728
805
|
|
|
729
806
|
# Whether the placeholder loader can be used to load the association.
|
|
730
807
|
def use_placeholder_loader?
|
|
731
|
-
|
|
808
|
+
self[:use_placeholder_loader]
|
|
732
809
|
end
|
|
733
810
|
end
|
|
734
811
|
|
|
@@ -773,6 +850,18 @@ module Sequel
|
|
|
773
850
|
nil
|
|
774
851
|
end
|
|
775
852
|
|
|
853
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
|
854
|
+
:primary_key=>:primary_key,
|
|
855
|
+
:primary_keys=>:primary_keys,
|
|
856
|
+
:primary_key_method=>:primary_key_method,
|
|
857
|
+
:primary_key_methods=>:primary_key_methods,
|
|
858
|
+
:qualified_primary_key=>:qualified_primary_key,
|
|
859
|
+
:reciprocal_type=>:reciprocal_type
|
|
860
|
+
).freeze
|
|
861
|
+
def finalize_settings
|
|
862
|
+
FINALIZE_SETTINGS
|
|
863
|
+
end
|
|
864
|
+
|
|
776
865
|
# The expression to use on the left hand side of the IN lookup when eager loading
|
|
777
866
|
def predicate_key
|
|
778
867
|
cached_fetch(:predicate_key){qualified_primary_key}
|
|
@@ -917,6 +1006,13 @@ module Sequel
|
|
|
917
1006
|
:"#{underscore(demodulize(self[:model].name))}_id"
|
|
918
1007
|
end
|
|
919
1008
|
|
|
1009
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
|
1010
|
+
:qualified_primary_key=>:qualified_primary_key
|
|
1011
|
+
).freeze
|
|
1012
|
+
def finalize_settings
|
|
1013
|
+
FINALIZE_SETTINGS
|
|
1014
|
+
end
|
|
1015
|
+
|
|
920
1016
|
# Handle silent failure of add/remove methods if raise_on_save_failure is false.
|
|
921
1017
|
def handle_silent_modification_failure?
|
|
922
1018
|
self[:raise_on_save_failure] == false
|
|
@@ -1022,7 +1118,7 @@ module Sequel
|
|
|
1022
1118
|
end
|
|
1023
1119
|
|
|
1024
1120
|
# Support automatic use of correlated subqueries if :ruby option is best available option,
|
|
1025
|
-
#
|
|
1121
|
+
# the database supports them, and either the associated class has a non-composite primary key
|
|
1026
1122
|
# or the database supports multiple columns in IN.
|
|
1027
1123
|
def true_eager_graph_limit_strategy
|
|
1028
1124
|
r = super
|
|
@@ -1157,7 +1253,9 @@ module Sequel
|
|
|
1157
1253
|
else
|
|
1158
1254
|
assoc_record.values.delete(left_key_alias)
|
|
1159
1255
|
end
|
|
1160
|
-
|
|
1256
|
+
|
|
1257
|
+
objects = h[hash_key]
|
|
1258
|
+
|
|
1161
1259
|
if assign_singular
|
|
1162
1260
|
objects.each do |object|
|
|
1163
1261
|
object.associations[name] ||= assoc_record
|
|
@@ -1187,6 +1285,22 @@ module Sequel
|
|
|
1187
1285
|
:"#{singularize(self[:name])}_id"
|
|
1188
1286
|
end
|
|
1189
1287
|
|
|
1288
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
|
1289
|
+
:associated_key_array=>:associated_key_array,
|
|
1290
|
+
:qualified_right_key=>:qualified_right_key,
|
|
1291
|
+
:join_table_source=>:join_table_source,
|
|
1292
|
+
:join_table_alias=>:join_table_alias,
|
|
1293
|
+
:qualified_right_primary_key=>:qualified_right_primary_key,
|
|
1294
|
+
:right_primary_key=>:right_primary_key,
|
|
1295
|
+
:right_primary_keys=>:right_primary_keys,
|
|
1296
|
+
:right_primary_key_method=>:right_primary_key_method,
|
|
1297
|
+
:right_primary_key_methods=>:right_primary_key_methods,
|
|
1298
|
+
:select=>:select
|
|
1299
|
+
).freeze
|
|
1300
|
+
def finalize_settings
|
|
1301
|
+
FINALIZE_SETTINGS
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1190
1304
|
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
|
|
1191
1305
|
# The left key qualified by the join table.
|
|
1192
1306
|
def predicate_key
|
|
@@ -1352,10 +1466,20 @@ module Sequel
|
|
|
1352
1466
|
# This module contains methods added to all association datasets
|
|
1353
1467
|
module AssociationDatasetMethods
|
|
1354
1468
|
# The model object that created the association dataset
|
|
1355
|
-
|
|
1469
|
+
def model_object
|
|
1470
|
+
@opts[:model_object]
|
|
1471
|
+
end
|
|
1356
1472
|
|
|
1357
1473
|
# The association reflection related to the association dataset
|
|
1358
|
-
|
|
1474
|
+
def association_reflection
|
|
1475
|
+
@opts[:association_reflection]
|
|
1476
|
+
end
|
|
1477
|
+
|
|
1478
|
+
private
|
|
1479
|
+
|
|
1480
|
+
def non_sql_option?(key)
|
|
1481
|
+
super || key == :model_object || key == :association_reflection
|
|
1482
|
+
end
|
|
1359
1483
|
end
|
|
1360
1484
|
|
|
1361
1485
|
# Each kind of association adds a number of instance methods to the model class which
|
|
@@ -1396,7 +1520,7 @@ module Sequel
|
|
|
1396
1520
|
# Project.associations
|
|
1397
1521
|
# => [:portfolio, :milestones]
|
|
1398
1522
|
# Project.association_reflection(:portfolio)
|
|
1399
|
-
# =>
|
|
1523
|
+
# => #<Sequel::Model::Associations::ManyToOneAssociationReflection Project.many_to_one :portfolio>
|
|
1400
1524
|
#
|
|
1401
1525
|
# Associations should not have the same names as any of the columns in the
|
|
1402
1526
|
# model's current table they reference. If you are dealing with an existing schema that
|
|
@@ -1417,11 +1541,20 @@ module Sequel
|
|
|
1417
1541
|
attr_reader :autoreloading_associations
|
|
1418
1542
|
|
|
1419
1543
|
# Whether association metadata should be cached in the association reflection. If not cached, it will be computed
|
|
1420
|
-
# on demand. In general you only want to set this to
|
|
1421
|
-
# setting this will make sure that if an associated class is removed or modified, this class will not
|
|
1544
|
+
# on demand. In general you only want to set this to false when using code reloading. When using code reloading,
|
|
1545
|
+
# setting this will make sure that if an associated class is removed or modified, this class will not have a reference to
|
|
1422
1546
|
# the previous class.
|
|
1423
1547
|
attr_accessor :cache_associations
|
|
1424
1548
|
|
|
1549
|
+
# The default options to use for all associations. This hash is merged into the association reflection hash for
|
|
1550
|
+
# all association reflections.
|
|
1551
|
+
attr_accessor :default_association_options
|
|
1552
|
+
|
|
1553
|
+
# The default options to use for all associations of a given type. This is a hash keyed by association type
|
|
1554
|
+
# symbol. If there is a value for the association type symbol key, the resulting hash will be merged into the
|
|
1555
|
+
# association reflection hash for all association reflections of that type.
|
|
1556
|
+
attr_accessor :default_association_type_options
|
|
1557
|
+
|
|
1425
1558
|
# The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
|
|
1426
1559
|
# to use what it considers the most appropriate strategy).
|
|
1427
1560
|
attr_accessor :default_eager_limit_strategy
|
|
@@ -1482,16 +1615,23 @@ module Sequel
|
|
|
1482
1615
|
# :class :: The associated class or its name as a string or symbol. If not
|
|
1483
1616
|
# given, uses the association's name, which is camelized (and
|
|
1484
1617
|
# singularized unless the type is :many_to_one, :one_to_one, or one_through_one). If this is specified
|
|
1485
|
-
# as a string or symbol, you must specify the full class name (e.g. "SomeModule::MyModel").
|
|
1618
|
+
# as a string or symbol, you must specify the full class name (e.g. "::SomeModule::MyModel").
|
|
1619
|
+
# :class_namespace :: If :class is given as a string or symbol, sets the default namespace in which to look for
|
|
1620
|
+
# the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
|
|
1486
1621
|
# :clearer :: Proc used to define the private _remove_all_* method for doing the database work
|
|
1487
1622
|
# to remove all objects associated to the current object (*_to_many assocations).
|
|
1488
1623
|
# :clone :: Merge the current options and block into the options and block used in defining
|
|
1489
1624
|
# the given association. Can be used to DRY up a bunch of similar associations that
|
|
1490
1625
|
# all share the same options such as :class and :key, while changing the order and block used.
|
|
1491
1626
|
# :conditions :: The conditions to use to filter the association, can be any argument passed to where.
|
|
1492
|
-
#
|
|
1627
|
+
# This option is not respected when using eager_graph or association_join, unless it
|
|
1628
|
+
# is hash or array of two element arrays. Consider also specifying the :graph_block
|
|
1629
|
+
# option if the value for this option is not a hash or array of two element arrays
|
|
1630
|
+
# and you plan to use this association in eager_graph or association_join.
|
|
1631
|
+
# :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
|
|
1493
1632
|
# options are applied). If the proc accepts an argument, it is passed the related
|
|
1494
|
-
# association reflection.
|
|
1633
|
+
# association reflection. It is a best practice to always have the dataset accept an argument
|
|
1634
|
+
# and use the argument to return the appropriate dataset.
|
|
1495
1635
|
# :distinct :: Use the DISTINCT clause when selecting associating object, both when
|
|
1496
1636
|
# lazy loading and eager loading via .eager (but not when using .eager_graph).
|
|
1497
1637
|
# :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
|
|
@@ -1551,8 +1691,7 @@ module Sequel
|
|
|
1551
1691
|
# :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
|
|
1552
1692
|
# via +eager_graph+. Defaults to true, so set to false to disable.
|
|
1553
1693
|
# :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
|
|
1554
|
-
# or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
|
|
1555
|
-
# true for one_through_one associations.
|
|
1694
|
+
# or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
|
|
1556
1695
|
# :reciprocal :: the symbol name of the reciprocal association,
|
|
1557
1696
|
# if it exists. By default, Sequel will try to determine it by looking at the
|
|
1558
1697
|
# associated model's assocations for a association that matches
|
|
@@ -1583,7 +1722,7 @@ module Sequel
|
|
|
1583
1722
|
# array of symbols for a composite key association.
|
|
1584
1723
|
# :primary_key_method :: the method symbol or array of method symbols to call on the associated
|
|
1585
1724
|
# object to get the foreign key values. Defaults to :primary_key option.
|
|
1586
|
-
# :qualify :: Whether to use
|
|
1725
|
+
# :qualify :: Whether to use qualified primary keys when loading the association. The default
|
|
1587
1726
|
# is true, so you must set to false to not qualify. Qualification rarely causes
|
|
1588
1727
|
# problems, but it's necessary to disable in some cases, such as when you are doing
|
|
1589
1728
|
# a JOIN USING operation on the column on Oracle.
|
|
@@ -1644,7 +1783,7 @@ module Sequel
|
|
|
1644
1783
|
# Defaults to :right_primary_key option.
|
|
1645
1784
|
# :uniq :: Adds a after_load callback that makes the array of objects unique.
|
|
1646
1785
|
def associate(type, name, opts = OPTS, &block)
|
|
1647
|
-
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
|
1786
|
+
raise(Error, 'invalid association type') unless assoc_class = Sequel.synchronize{ASSOCIATION_TYPES[type]}
|
|
1648
1787
|
raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
|
|
1649
1788
|
|
|
1650
1789
|
# dup early so we don't modify opts
|
|
@@ -1655,13 +1794,20 @@ module Sequel
|
|
|
1655
1794
|
orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
|
|
1656
1795
|
end
|
|
1657
1796
|
|
|
1658
|
-
opts =
|
|
1797
|
+
opts = Hash[default_association_options]
|
|
1798
|
+
if type_options = default_association_type_options[type]
|
|
1799
|
+
opts.merge!(type_options)
|
|
1800
|
+
end
|
|
1801
|
+
opts.merge!(orig_opts)
|
|
1802
|
+
opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
|
|
1803
|
+
|
|
1659
1804
|
opts[:block] = block if block
|
|
1660
|
-
|
|
1805
|
+
opts[:instance_specific] = true if orig_opts[:dataset]
|
|
1806
|
+
if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
|
|
1661
1807
|
# It's possible the association is instance specific, in that it depends on
|
|
1662
1808
|
# values other than the foreign key value. This needs to be checked for
|
|
1663
1809
|
# in certain places to disable optimizations.
|
|
1664
|
-
opts[:instance_specific] =
|
|
1810
|
+
opts[:instance_specific] = _association_instance_specific_default(name)
|
|
1665
1811
|
end
|
|
1666
1812
|
opts = assoc_class.new.merge!(opts)
|
|
1667
1813
|
|
|
@@ -1669,10 +1815,8 @@ module Sequel
|
|
|
1669
1815
|
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]})")
|
|
1670
1816
|
end
|
|
1671
1817
|
|
|
1818
|
+
opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
|
|
1672
1819
|
opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
|
|
1673
|
-
if !opts.has_key?(:predicate_key) && opts.has_key?(:eager_loading_predicate_key)
|
|
1674
|
-
opts[:predicate_key] = opts[:eager_loading_predicate_key]
|
|
1675
|
-
end
|
|
1676
1820
|
opts[:graph_join_type] ||= :left_outer
|
|
1677
1821
|
opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
|
|
1678
1822
|
conds = opts[:conditions]
|
|
@@ -1680,9 +1824,15 @@ module Sequel
|
|
|
1680
1824
|
opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
|
|
1681
1825
|
opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
|
|
1682
1826
|
opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
|
|
1683
|
-
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set
|
|
1684
|
-
opts[cb_type] = Array(opts[cb_type])
|
|
1827
|
+
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set].each do |cb_type|
|
|
1828
|
+
opts[cb_type] = Array(opts[cb_type]) if opts[cb_type]
|
|
1685
1829
|
end
|
|
1830
|
+
|
|
1831
|
+
if opts[:extend]
|
|
1832
|
+
opts[:extend] = Array(opts[:extend])
|
|
1833
|
+
opts[:reverse_extend] = opts[:extend].reverse
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1686
1836
|
late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
|
|
1687
1837
|
|
|
1688
1838
|
# Remove :class entry if it exists and is nil, to work with cached_fetch
|
|
@@ -1692,6 +1842,7 @@ module Sequel
|
|
|
1692
1842
|
def_association_instance_methods(opts)
|
|
1693
1843
|
|
|
1694
1844
|
orig_opts.delete(:clone)
|
|
1845
|
+
opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
|
|
1695
1846
|
orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
|
|
1696
1847
|
opts[:orig_opts] = orig_opts
|
|
1697
1848
|
# don't add to association_reflections until we are sure there are no errors
|
|
@@ -1713,6 +1864,25 @@ module Sequel
|
|
|
1713
1864
|
opts.eager_load_results(eo, &block)
|
|
1714
1865
|
end
|
|
1715
1866
|
|
|
1867
|
+
# Freeze association related metadata when freezing model class.
|
|
1868
|
+
def freeze
|
|
1869
|
+
@association_reflections.freeze.each_value(&:freeze)
|
|
1870
|
+
@autoreloading_associations.freeze.each_value(&:freeze)
|
|
1871
|
+
@default_association_options.freeze
|
|
1872
|
+
@default_association_type_options.freeze
|
|
1873
|
+
@default_association_type_options.each_value(&:freeze)
|
|
1874
|
+
|
|
1875
|
+
super
|
|
1876
|
+
end
|
|
1877
|
+
|
|
1878
|
+
# Finalize all associations such that values that are looked up
|
|
1879
|
+
# dynamically in associated classes are set statically.
|
|
1880
|
+
# As this modifies the associations, it must be done before
|
|
1881
|
+
# calling freeze.
|
|
1882
|
+
def finalize_associations
|
|
1883
|
+
@association_reflections.each_value(&:finalize)
|
|
1884
|
+
end
|
|
1885
|
+
|
|
1716
1886
|
# Shortcut for adding a many_to_many association, see #associate
|
|
1717
1887
|
def many_to_many(name, opts=OPTS, &block)
|
|
1718
1888
|
associate(:many_to_many, name, opts, &block)
|
|
@@ -1723,7 +1893,7 @@ module Sequel
|
|
|
1723
1893
|
associate(:many_to_one, name, opts, &block)
|
|
1724
1894
|
end
|
|
1725
1895
|
|
|
1726
|
-
# Shortcut for adding a one_through_one association, see #associate
|
|
1896
|
+
# Shortcut for adding a one_through_one association, see #associate
|
|
1727
1897
|
def one_through_one(name, opts=OPTS, &block)
|
|
1728
1898
|
associate(:one_through_one, name, opts, &block)
|
|
1729
1899
|
end
|
|
@@ -1733,15 +1903,21 @@ module Sequel
|
|
|
1733
1903
|
associate(:one_to_many, name, opts, &block)
|
|
1734
1904
|
end
|
|
1735
1905
|
|
|
1736
|
-
# Shortcut for adding a one_to_one association, see #associate
|
|
1906
|
+
# Shortcut for adding a one_to_one association, see #associate
|
|
1737
1907
|
def one_to_one(name, opts=OPTS, &block)
|
|
1738
1908
|
associate(:one_to_one, name, opts, &block)
|
|
1739
1909
|
end
|
|
1740
1910
|
|
|
1741
|
-
Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
|
|
1911
|
+
Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_association_options=>:dup, :@default_association_type_options=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
|
|
1742
1912
|
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])
|
|
1743
1913
|
|
|
1744
1914
|
private
|
|
1915
|
+
|
|
1916
|
+
# The default value for the instance_specific option, if the association
|
|
1917
|
+
# could be instance specific and the :instance_specific option is not specified.
|
|
1918
|
+
def _association_instance_specific_default(_)
|
|
1919
|
+
true
|
|
1920
|
+
end
|
|
1745
1921
|
|
|
1746
1922
|
# The module to use for the association's methods. Defaults to
|
|
1747
1923
|
# the overridable_methods_module.
|
|
@@ -1753,7 +1929,7 @@ module Sequel
|
|
|
1753
1929
|
# can be easily overridden in the class itself while allowing for
|
|
1754
1930
|
# super to be called.
|
|
1755
1931
|
def association_module_def(name, opts=OPTS, &block)
|
|
1756
|
-
association_module(opts).
|
|
1932
|
+
association_module(opts).send(:define_method, name, &block)
|
|
1757
1933
|
end
|
|
1758
1934
|
|
|
1759
1935
|
# Add a private method to the module included in the class.
|
|
@@ -1764,42 +1940,64 @@ module Sequel
|
|
|
1764
1940
|
|
|
1765
1941
|
# Adds the association method to the association methods module.
|
|
1766
1942
|
def def_association_method(opts)
|
|
1767
|
-
association_module_def(opts.association_method, opts)
|
|
1943
|
+
association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
|
|
1944
|
+
load_associated_objects(opts, dynamic_opts, &block)
|
|
1945
|
+
end
|
|
1768
1946
|
end
|
|
1769
1947
|
|
|
1770
1948
|
# Define all of the association instance methods for this association.
|
|
1771
1949
|
def def_association_instance_methods(opts)
|
|
1950
|
+
# Always set the method names in the association reflection, even if they
|
|
1951
|
+
# are not used, for backwards compatibility.
|
|
1952
|
+
opts[:dataset_method] = :"#{opts[:name]}_dataset"
|
|
1953
|
+
if opts.returns_array?
|
|
1954
|
+
sname = singularize(opts[:name])
|
|
1955
|
+
opts[:_add_method] = :"_add_#{sname}"
|
|
1956
|
+
opts[:add_method] = :"add_#{sname}"
|
|
1957
|
+
opts[:_remove_method] = :"_remove_#{sname}"
|
|
1958
|
+
opts[:remove_method] = :"remove_#{sname}"
|
|
1959
|
+
opts[:_remove_all_method] = :"_remove_all_#{opts[:name]}"
|
|
1960
|
+
opts[:remove_all_method] = :"remove_all_#{opts[:name]}"
|
|
1961
|
+
else
|
|
1962
|
+
opts[:_setter_method] = :"_#{opts[:name]}="
|
|
1963
|
+
opts[:setter_method] = :"#{opts[:name]}="
|
|
1964
|
+
end
|
|
1965
|
+
|
|
1772
1966
|
association_module_def(opts.dataset_method, opts){_dataset(opts)}
|
|
1967
|
+
if opts[:block]
|
|
1968
|
+
opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
|
|
1969
|
+
end
|
|
1970
|
+
opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
|
|
1971
|
+
opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
|
|
1773
1972
|
def_association_method(opts)
|
|
1774
1973
|
|
|
1775
1974
|
return if opts[:read_only]
|
|
1776
1975
|
|
|
1777
1976
|
if opts[:setter] && opts[:_setter]
|
|
1778
1977
|
# This is backwards due to backwards compatibility
|
|
1779
|
-
association_module_private_def(opts
|
|
1780
|
-
association_module_def(opts
|
|
1978
|
+
association_module_private_def(opts[:_setter_method], opts, &opts[:setter])
|
|
1979
|
+
association_module_def(opts[:setter_method], opts, &opts[:_setter])
|
|
1781
1980
|
end
|
|
1782
1981
|
|
|
1783
1982
|
if adder = opts[:adder]
|
|
1784
|
-
association_module_private_def(opts
|
|
1785
|
-
association_module_def(opts
|
|
1983
|
+
association_module_private_def(opts[:_add_method], opts, &adder)
|
|
1984
|
+
association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
|
|
1786
1985
|
end
|
|
1787
1986
|
|
|
1788
1987
|
if remover = opts[:remover]
|
|
1789
|
-
association_module_private_def(opts
|
|
1790
|
-
association_module_def(opts
|
|
1988
|
+
association_module_private_def(opts[:_remove_method], opts, &remover)
|
|
1989
|
+
association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
|
|
1791
1990
|
end
|
|
1792
1991
|
|
|
1793
1992
|
if clearer = opts[:clearer]
|
|
1794
|
-
association_module_private_def(opts
|
|
1795
|
-
association_module_def(opts
|
|
1993
|
+
association_module_private_def(opts[:_remove_all_method], opts, &clearer)
|
|
1994
|
+
association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
|
|
1796
1995
|
end
|
|
1797
1996
|
end
|
|
1798
1997
|
|
|
1799
1998
|
# Configures many_to_many and one_through_one association reflection and adds the related association methods
|
|
1800
1999
|
def def_many_to_many(opts)
|
|
1801
2000
|
one_through_one = opts[:type] == :one_through_one
|
|
1802
|
-
opts[:read_only] = true if one_through_one
|
|
1803
2001
|
left = (opts[:left_key] ||= opts.default_left_key)
|
|
1804
2002
|
lcks = opts[:left_keys] = Array(left)
|
|
1805
2003
|
right = (opts[:right_key] ||= opts.default_right_key)
|
|
@@ -1820,7 +2018,10 @@ module Sequel
|
|
|
1820
2018
|
join_table = (opts[:join_table] ||= opts.default_join_table)
|
|
1821
2019
|
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
1822
2020
|
opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
|
|
1823
|
-
|
|
2021
|
+
if opts[:uniq]
|
|
2022
|
+
opts[:after_load] ||= []
|
|
2023
|
+
opts[:after_load].unshift(:array_uniq!)
|
|
2024
|
+
end
|
|
1824
2025
|
opts[:dataset] ||= opts.association_dataset_proc
|
|
1825
2026
|
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
|
1826
2027
|
|
|
@@ -1853,21 +2054,54 @@ module Sequel
|
|
|
1853
2054
|
end
|
|
1854
2055
|
end
|
|
1855
2056
|
|
|
1856
|
-
return if opts[:read_only]
|
|
2057
|
+
return if opts[:read_only]
|
|
1857
2058
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
end
|
|
2059
|
+
if one_through_one
|
|
2060
|
+
opts[:setter] ||= proc do |o|
|
|
2061
|
+
h = {}
|
|
2062
|
+
lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
|
|
2063
|
+
jtds = _join_table_dataset(opts).where(lh)
|
|
1864
2064
|
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2065
|
+
checked_transaction do
|
|
2066
|
+
current = jtds.first
|
|
2067
|
+
|
|
2068
|
+
if o
|
|
2069
|
+
new_values = []
|
|
2070
|
+
rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
|
|
2071
|
+
end
|
|
2072
|
+
|
|
2073
|
+
if current
|
|
2074
|
+
current_values = rcks.map{|k| current[k]}
|
|
2075
|
+
jtds = jtds.where(rcks.zip(current_values))
|
|
2076
|
+
if o
|
|
2077
|
+
if current_values != new_values
|
|
2078
|
+
jtds.update(h)
|
|
2079
|
+
end
|
|
2080
|
+
else
|
|
2081
|
+
jtds.delete
|
|
2082
|
+
end
|
|
2083
|
+
elsif o
|
|
2084
|
+
lh.each{|k,v| h[k] = v}
|
|
2085
|
+
jtds.insert(h)
|
|
2086
|
+
end
|
|
2087
|
+
end
|
|
2088
|
+
end
|
|
2089
|
+
opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
|
|
2090
|
+
else
|
|
2091
|
+
opts[:adder] ||= proc do |o|
|
|
2092
|
+
h = {}
|
|
2093
|
+
lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
|
|
2094
|
+
rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
|
|
2095
|
+
_join_table_dataset(opts).insert(h)
|
|
2096
|
+
end
|
|
2097
|
+
|
|
2098
|
+
opts[:remover] ||= proc do |o|
|
|
2099
|
+
_join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
|
|
2100
|
+
end
|
|
1868
2101
|
|
|
1869
|
-
|
|
1870
|
-
|
|
2102
|
+
opts[:clearer] ||= proc do
|
|
2103
|
+
_join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
|
|
2104
|
+
end
|
|
1871
2105
|
end
|
|
1872
2106
|
end
|
|
1873
2107
|
|
|
@@ -1905,9 +2139,7 @@ module Sequel
|
|
|
1905
2139
|
|
|
1906
2140
|
eager_load_results(opts, eo) do |assoc_record|
|
|
1907
2141
|
hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
|
|
1908
|
-
|
|
1909
|
-
objects.each{|object| object.associations[name] = assoc_record}
|
|
1910
|
-
end
|
|
2142
|
+
h[hash_key].each{|object| object.associations[name] = assoc_record}
|
|
1911
2143
|
end
|
|
1912
2144
|
end
|
|
1913
2145
|
|
|
@@ -1920,7 +2152,7 @@ module Sequel
|
|
|
1920
2152
|
graph_cks = opts[:graph_keys]
|
|
1921
2153
|
opts[:eager_grapher] ||= proc do |eo|
|
|
1922
2154
|
ds = eo[:self]
|
|
1923
|
-
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions,
|
|
2155
|
+
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
|
|
1924
2156
|
end
|
|
1925
2157
|
|
|
1926
2158
|
return if opts[:read_only]
|
|
@@ -1954,7 +2186,7 @@ module Sequel
|
|
|
1954
2186
|
eager_load_results(opts, eo) do |assoc_record|
|
|
1955
2187
|
assoc_record.values.delete(delete_rn) if delete_rn
|
|
1956
2188
|
hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
|
|
1957
|
-
|
|
2189
|
+
objects = h[hash_key]
|
|
1958
2190
|
if assign_singular
|
|
1959
2191
|
objects.each do |object|
|
|
1960
2192
|
unless object.associations[name]
|
|
@@ -1980,7 +2212,7 @@ module Sequel
|
|
|
1980
2212
|
graph_block = opts[:graph_block]
|
|
1981
2213
|
opts[:eager_grapher] ||= proc do |eo|
|
|
1982
2214
|
ds = eo[:self]
|
|
1983
|
-
ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions,
|
|
2215
|
+
ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
|
|
1984
2216
|
# We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
|
|
1985
2217
|
ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
|
|
1986
2218
|
ds
|
|
@@ -1995,12 +2227,28 @@ module Sequel
|
|
|
1995
2227
|
if one_to_one
|
|
1996
2228
|
opts[:setter] ||= proc do |o|
|
|
1997
2229
|
up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
|
|
2230
|
+
|
|
2231
|
+
if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
|
|
2232
|
+
if old = up_ds.first
|
|
2233
|
+
cks.each{|k| old.set_column_value(:"#{k}=", nil)}
|
|
2234
|
+
end
|
|
2235
|
+
save_old = true
|
|
2236
|
+
end
|
|
2237
|
+
|
|
1998
2238
|
if o
|
|
1999
|
-
|
|
2239
|
+
if !o.new? && !save_old
|
|
2240
|
+
up_ds = up_ds.exclude(o.pk_hash)
|
|
2241
|
+
end
|
|
2000
2242
|
cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
|
|
2001
2243
|
end
|
|
2244
|
+
|
|
2002
2245
|
checked_transaction do
|
|
2003
|
-
|
|
2246
|
+
if save_old
|
|
2247
|
+
old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
|
|
2248
|
+
else
|
|
2249
|
+
up_ds.skip_limit_check.update(ck_nil_hash)
|
|
2250
|
+
end
|
|
2251
|
+
|
|
2004
2252
|
o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
|
|
2005
2253
|
end
|
|
2006
2254
|
end
|
|
@@ -2063,8 +2311,10 @@ module Sequel
|
|
|
2063
2311
|
# retrieving associations after freezing will still work in most cases,
|
|
2064
2312
|
# but the associations will not be cached in the association cache.
|
|
2065
2313
|
def freeze
|
|
2066
|
-
associations
|
|
2314
|
+
associations
|
|
2067
2315
|
super
|
|
2316
|
+
associations.freeze
|
|
2317
|
+
self
|
|
2068
2318
|
end
|
|
2069
2319
|
|
|
2070
2320
|
private
|
|
@@ -2074,15 +2324,16 @@ module Sequel
|
|
|
2074
2324
|
unless ds.kind_of?(AssociationDatasetMethods)
|
|
2075
2325
|
ds = opts.apply_dataset_changes(ds)
|
|
2076
2326
|
end
|
|
2077
|
-
ds.model_object
|
|
2327
|
+
ds = ds.clone(:model_object => self)
|
|
2078
2328
|
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
|
|
2079
|
-
|
|
2329
|
+
# block method is private
|
|
2330
|
+
ds = send(opts[:block_method], ds) if opts[:block_method]
|
|
2080
2331
|
ds
|
|
2081
2332
|
end
|
|
2082
2333
|
|
|
2083
2334
|
# Return a dataset for the association after applying any dynamic callback.
|
|
2084
2335
|
def _associated_dataset(opts, dynamic_opts)
|
|
2085
|
-
ds =
|
|
2336
|
+
ds = public_send(opts.dataset_method)
|
|
2086
2337
|
if callback = dynamic_opts[:callback]
|
|
2087
2338
|
ds = callback.call(ds)
|
|
2088
2339
|
end
|
|
@@ -2099,10 +2350,11 @@ module Sequel
|
|
|
2099
2350
|
# Return an association dataset for the given association reflection
|
|
2100
2351
|
def _dataset(opts)
|
|
2101
2352
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
|
2102
|
-
ds = if opts[:
|
|
2103
|
-
|
|
2353
|
+
ds = if opts[:dataset_opt_arity] == 1
|
|
2354
|
+
# dataset_opt_method is private
|
|
2355
|
+
send(opts[:dataset_opt_method], opts)
|
|
2104
2356
|
else
|
|
2105
|
-
|
|
2357
|
+
send(opts[:dataset_opt_method])
|
|
2106
2358
|
end
|
|
2107
2359
|
_apply_association_options(opts, ds)
|
|
2108
2360
|
end
|
|
@@ -2158,18 +2410,12 @@ module Sequel
|
|
|
2158
2410
|
|
|
2159
2411
|
# Add the given associated object to the given association
|
|
2160
2412
|
def add_associated_object(opts, o, *args)
|
|
2161
|
-
|
|
2162
|
-
if o.is_a?(Hash)
|
|
2163
|
-
o = klass.new(o)
|
|
2164
|
-
elsif o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
|
|
2165
|
-
o = klass.with_pk!(o)
|
|
2166
|
-
elsif !o.is_a?(klass)
|
|
2167
|
-
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
|
2168
|
-
end
|
|
2413
|
+
o = make_add_associated_object(opts, o)
|
|
2169
2414
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
|
2170
2415
|
ensure_associated_primary_key(opts, o, *args)
|
|
2171
2416
|
return if run_association_callbacks(opts, :before_add, o) == false
|
|
2172
|
-
|
|
2417
|
+
# Allow calling private _add method
|
|
2418
|
+
return if !send(opts[:_add_method], o, *args) && opts.handle_silent_modification_failure?
|
|
2173
2419
|
if array = associations[opts[:name]] and !array.include?(o)
|
|
2174
2420
|
array.push(o)
|
|
2175
2421
|
end
|
|
@@ -2201,7 +2447,32 @@ module Sequel
|
|
|
2201
2447
|
# cached associations.
|
|
2202
2448
|
def change_column_value(column, value)
|
|
2203
2449
|
if assocs = model.autoreloading_associations[column]
|
|
2204
|
-
|
|
2450
|
+
vals = @values
|
|
2451
|
+
if new?
|
|
2452
|
+
# Do deeper checking for new objects, so that associations are
|
|
2453
|
+
# not deleted when values do not change. This code is run at
|
|
2454
|
+
# a higher level for existing objects.
|
|
2455
|
+
if value == (c = vals[column]) && value.class == c.class
|
|
2456
|
+
# If the value is the same, there is no reason to delete
|
|
2457
|
+
# the related associations, so exit early in that case.
|
|
2458
|
+
return super
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
only_delete_nil = c.nil?
|
|
2462
|
+
elsif vals[column].nil?
|
|
2463
|
+
only_delete_nil = true
|
|
2464
|
+
end
|
|
2465
|
+
|
|
2466
|
+
if only_delete_nil
|
|
2467
|
+
# If the current foreign key value is nil, but the association
|
|
2468
|
+
# is already present in the cache, it was probably added to the
|
|
2469
|
+
# cache for a reason, and we do not want to delete it in that case.
|
|
2470
|
+
# However, we still want to delete associations with nil values
|
|
2471
|
+
# to remove the cached false negative.
|
|
2472
|
+
assocs.each{|a| associations.delete(a) if associations[a].nil?}
|
|
2473
|
+
else
|
|
2474
|
+
assocs.each{|a| associations.delete(a)}
|
|
2475
|
+
end
|
|
2205
2476
|
end
|
|
2206
2477
|
super
|
|
2207
2478
|
end
|
|
@@ -2223,18 +2494,21 @@ module Sequel
|
|
|
2223
2494
|
self
|
|
2224
2495
|
end
|
|
2225
2496
|
|
|
2226
|
-
#
|
|
2227
|
-
def
|
|
2228
|
-
if
|
|
2229
|
-
dynamic_opts =
|
|
2230
|
-
|
|
2231
|
-
dynamic_opts = {:callback=>dynamic_opts}
|
|
2232
|
-
end
|
|
2233
|
-
if block_given?
|
|
2234
|
-
dynamic_opts = Hash[dynamic_opts].merge!(:callback=>Proc.new)
|
|
2497
|
+
# If a block is given, assign it as the :callback option in the hash, and return the hash.
|
|
2498
|
+
def load_association_objects_options(dynamic_opts, &block)
|
|
2499
|
+
if block
|
|
2500
|
+
dynamic_opts = Hash[dynamic_opts]
|
|
2501
|
+
dynamic_opts[:callback] = block
|
|
2235
2502
|
end
|
|
2503
|
+
|
|
2504
|
+
dynamic_opts
|
|
2505
|
+
end
|
|
2506
|
+
|
|
2507
|
+
# Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
|
|
2508
|
+
def load_associated_objects(opts, dynamic_opts, &block)
|
|
2509
|
+
dynamic_opts = load_association_objects_options(dynamic_opts, &block)
|
|
2236
2510
|
name = opts[:name]
|
|
2237
|
-
if associations.include?(name)
|
|
2511
|
+
if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
|
2238
2512
|
associations[name]
|
|
2239
2513
|
else
|
|
2240
2514
|
objs = _load_associated_objects(opts, dynamic_opts)
|
|
@@ -2263,10 +2537,30 @@ module Sequel
|
|
|
2263
2537
|
opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == opts.associated_class.primary_key}
|
|
2264
2538
|
end
|
|
2265
2539
|
|
|
2540
|
+
# Convert the input of the add_* association method into an associated object. For
|
|
2541
|
+
# hashes, this creates a new object using the hash. For integers, strings, and arrays,
|
|
2542
|
+
# assume the value specifies a primary key, and lookup an existing object with that primary key.
|
|
2543
|
+
# Otherwise, if the object is not already an instance of the class, raise an exception.
|
|
2544
|
+
def make_add_associated_object(opts, o)
|
|
2545
|
+
klass = opts.associated_class
|
|
2546
|
+
|
|
2547
|
+
case o
|
|
2548
|
+
when Hash
|
|
2549
|
+
klass.new(o)
|
|
2550
|
+
when Integer, String, Array
|
|
2551
|
+
klass.with_pk!(o)
|
|
2552
|
+
when klass
|
|
2553
|
+
o
|
|
2554
|
+
else
|
|
2555
|
+
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
|
2556
|
+
end
|
|
2557
|
+
end
|
|
2558
|
+
|
|
2266
2559
|
# Remove all associated objects from the given association
|
|
2267
2560
|
def remove_all_associated_objects(opts, *args)
|
|
2268
2561
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
|
2269
|
-
|
|
2562
|
+
# Allow calling private _remove_all method
|
|
2563
|
+
send(opts[:_remove_all_method], *args)
|
|
2270
2564
|
ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
|
|
2271
2565
|
associations[opts[:name]] = []
|
|
2272
2566
|
ret
|
|
@@ -2279,13 +2573,14 @@ module Sequel
|
|
|
2279
2573
|
o = remove_check_existing_object_from_pk(opts, o, *args)
|
|
2280
2574
|
elsif !o.is_a?(klass)
|
|
2281
2575
|
raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
|
|
2282
|
-
elsif opts.remove_should_check_existing? &&
|
|
2576
|
+
elsif opts.remove_should_check_existing? && public_send(opts.dataset_method).where(o.pk_hash).empty?
|
|
2283
2577
|
raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
|
|
2284
2578
|
end
|
|
2285
2579
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
|
2286
2580
|
raise(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
|
|
2287
2581
|
return if run_association_callbacks(opts, :before_remove, o) == false
|
|
2288
|
-
|
|
2582
|
+
# Allow calling private _remove method
|
|
2583
|
+
return if !send(opts[:_remove_method], o, *args) && opts.handle_silent_modification_failure?
|
|
2289
2584
|
associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
|
|
2290
2585
|
remove_reciprocal_object(opts, o)
|
|
2291
2586
|
run_association_callbacks(opts, :after_remove, o)
|
|
@@ -2298,7 +2593,7 @@ module Sequel
|
|
|
2298
2593
|
def remove_check_existing_object_from_pk(opts, o, *args)
|
|
2299
2594
|
key = o
|
|
2300
2595
|
pkh = opts.associated_class.qualified_primary_key_hash(key)
|
|
2301
|
-
raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o =
|
|
2596
|
+
raise(Sequel::Error, "no object with key(s) #{key.inspect} is currently associated to #{inspect}") unless o = public_send(opts.dataset_method).first(pkh)
|
|
2302
2597
|
o
|
|
2303
2598
|
end
|
|
2304
2599
|
|
|
@@ -2316,40 +2611,52 @@ module Sequel
|
|
|
2316
2611
|
|
|
2317
2612
|
# Run the callback for the association with the object.
|
|
2318
2613
|
def run_association_callbacks(reflection, callback_type, object)
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
raise Error, "callbacks should either be Procs or Symbols"
|
|
2333
|
-
end
|
|
2334
|
-
|
|
2335
|
-
if res == false and stop_on_false
|
|
2336
|
-
raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false")
|
|
2614
|
+
return unless cbs = reflection[callback_type]
|
|
2615
|
+
|
|
2616
|
+
begin
|
|
2617
|
+
cbs.each do |cb|
|
|
2618
|
+
case cb
|
|
2619
|
+
when Symbol
|
|
2620
|
+
# Allow calling private methods in association callbacks
|
|
2621
|
+
send(cb, object)
|
|
2622
|
+
when Proc
|
|
2623
|
+
cb.call(self, object)
|
|
2624
|
+
else
|
|
2625
|
+
raise Error, "callbacks should either be Procs or Symbols"
|
|
2626
|
+
end
|
|
2337
2627
|
end
|
|
2628
|
+
rescue HookFailed
|
|
2629
|
+
# The reason we automatically set raise_error for singular associations is that
|
|
2630
|
+
# assignment in ruby always returns the argument instead of the result of the
|
|
2631
|
+
# method, so we can't return nil to signal that the association callback prevented
|
|
2632
|
+
# the modification
|
|
2633
|
+
return false unless raise_on_save_failure || !reflection.returns_array?
|
|
2634
|
+
raise
|
|
2338
2635
|
end
|
|
2339
|
-
rescue HookFailed
|
|
2340
|
-
return false unless raise_error
|
|
2341
|
-
raise
|
|
2342
2636
|
end
|
|
2343
2637
|
|
|
2344
2638
|
# Set the given object as the associated object for the given *_to_one association reflection
|
|
2345
2639
|
def _set_associated_object(opts, o)
|
|
2346
2640
|
a = associations[opts[:name]]
|
|
2347
|
-
|
|
2641
|
+
reciprocal = opts.reciprocal
|
|
2642
|
+
if set_associated_object_if_same?
|
|
2643
|
+
if reciprocal
|
|
2644
|
+
remove_reciprocal = a && (a != o || a.associations[reciprocal] != self)
|
|
2645
|
+
add_reciprocal = o && o.associations[reciprocal] != self
|
|
2646
|
+
end
|
|
2647
|
+
else
|
|
2648
|
+
return if a && a == o
|
|
2649
|
+
if reciprocal
|
|
2650
|
+
remove_reciprocal = a
|
|
2651
|
+
add_reciprocal = o
|
|
2652
|
+
end
|
|
2653
|
+
end
|
|
2348
2654
|
run_association_callbacks(opts, :before_set, o)
|
|
2349
|
-
remove_reciprocal_object(opts, a) if
|
|
2350
|
-
|
|
2655
|
+
remove_reciprocal_object(opts, a) if remove_reciprocal
|
|
2656
|
+
# Allow calling private _setter method
|
|
2657
|
+
send(opts[:_setter_method], o)
|
|
2351
2658
|
associations[opts[:name]] = o
|
|
2352
|
-
add_reciprocal_object(opts, o) if
|
|
2659
|
+
add_reciprocal_object(opts, o) if add_reciprocal
|
|
2353
2660
|
run_association_callbacks(opts, :after_set, o)
|
|
2354
2661
|
o
|
|
2355
2662
|
end
|
|
@@ -2367,6 +2674,13 @@ module Sequel
|
|
|
2367
2674
|
_set_associated_object(opts, o)
|
|
2368
2675
|
end
|
|
2369
2676
|
|
|
2677
|
+
# Set the given object as the associated object for the given one_through_one association reflection
|
|
2678
|
+
def set_one_through_one_associated_object(opts, o)
|
|
2679
|
+
raise(Error, "object #{inspect} does not have a primary key") unless pk
|
|
2680
|
+
raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
|
|
2681
|
+
_set_associated_object(opts, o)
|
|
2682
|
+
end
|
|
2683
|
+
|
|
2370
2684
|
# Set the given object as the associated object for the given one_to_one association reflection
|
|
2371
2685
|
def set_one_to_one_associated_object(opts, o)
|
|
2372
2686
|
raise(Error, "object #{inspect} does not have a primary key") unless pk
|
|
@@ -2397,48 +2711,112 @@ module Sequel
|
|
|
2397
2711
|
# Album.eager(:artist, :genre).all
|
|
2398
2712
|
# Album.eager_graph(:artist, :genre).all
|
|
2399
2713
|
# Album.eager(:artist).eager(:genre).all
|
|
2400
|
-
# Album.eager_graph(:artist).
|
|
2401
|
-
# Artist.eager(:
|
|
2402
|
-
# Artist.eager_graph(:
|
|
2403
|
-
# Artist.eager(:
|
|
2404
|
-
# Artist.eager_graph(:
|
|
2714
|
+
# Album.eager_graph(:artist).eager_graph(:genre).all
|
|
2715
|
+
# Artist.eager(albums: :tracks).all
|
|
2716
|
+
# Artist.eager_graph(albums: :tracks).all
|
|
2717
|
+
# Artist.eager(albums: {tracks: :genre}).all
|
|
2718
|
+
# Artist.eager_graph(albums: {tracks: :genre}).all
|
|
2405
2719
|
#
|
|
2406
2720
|
# You can also pass a callback as a hash value in order to customize the dataset being
|
|
2407
2721
|
# eager loaded at query time, analogous to the way the :eager_block association option
|
|
2408
2722
|
# allows you to customize it at association definition time. For example,
|
|
2409
2723
|
# if you wanted artists with their albums since 1990:
|
|
2410
2724
|
#
|
|
2411
|
-
# Artist.eager(:
|
|
2725
|
+
# Artist.eager(albums: proc{|ds| ds.where{year > 1990}})
|
|
2412
2726
|
#
|
|
2413
2727
|
# Or if you needed albums and their artist's name only, using a single query:
|
|
2414
2728
|
#
|
|
2415
|
-
# Albums.eager_graph(:
|
|
2729
|
+
# Albums.eager_graph(artist: proc{|ds| ds.select(:name)})
|
|
2416
2730
|
#
|
|
2417
2731
|
# To cascade eager loading while using a callback, you substitute the cascaded
|
|
2418
2732
|
# associations with a single entry hash that has the proc callback as the key and
|
|
2419
2733
|
# the cascaded associations as the value. This will load artists with their albums
|
|
2420
2734
|
# since 1990, and also the tracks on those albums and the genre for those tracks:
|
|
2421
2735
|
#
|
|
2422
|
-
# Artist.eager(:
|
|
2736
|
+
# Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>{tracks: :genre}})
|
|
2423
2737
|
module DatasetMethods
|
|
2424
|
-
Sequel::Dataset.def_mutation_method(:eager, :eager_graph, :module=>self)
|
|
2425
|
-
|
|
2426
2738
|
%w'inner left right full'.each do |type|
|
|
2427
|
-
class_eval
|
|
2739
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
2428
2740
|
def association_#{type}_join(*associations)
|
|
2429
2741
|
_association_join(:#{type}, associations)
|
|
2430
2742
|
end
|
|
2431
|
-
END
|
|
2743
|
+
END
|
|
2432
2744
|
end
|
|
2433
2745
|
|
|
2434
2746
|
# Adds one or more INNER JOINs to the existing dataset using the keys and conditions
|
|
2435
|
-
# specified by the given association.
|
|
2436
|
-
#
|
|
2747
|
+
# specified by the given association(s). Take the same arguments as eager_graph, and
|
|
2748
|
+
# operates similarly, but only adds the joins as opposed to making the other changes
|
|
2749
|
+
# (such as adding selected columns and setting up eager loading).
|
|
2750
|
+
#
|
|
2751
|
+
# The following methods also exist for specifying a different type of JOIN:
|
|
2437
2752
|
#
|
|
2438
2753
|
# association_full_join :: FULL JOIN
|
|
2439
2754
|
# association_inner_join :: INNER JOIN
|
|
2440
2755
|
# association_left_join :: LEFT JOIN
|
|
2441
2756
|
# association_right_join :: RIGHT JOIN
|
|
2757
|
+
#
|
|
2758
|
+
# Examples:
|
|
2759
|
+
#
|
|
2760
|
+
# # For each album, association_join load the artist
|
|
2761
|
+
# Album.association_join(:artist).all
|
|
2762
|
+
# # SELECT *
|
|
2763
|
+
# # FROM albums
|
|
2764
|
+
# # INNER JOIN artists AS artist ON (artists.id = albums.artist_id)
|
|
2765
|
+
#
|
|
2766
|
+
# # For each album, association_join load the artist, using a specified alias
|
|
2767
|
+
# Album.association_join(Sequel[:artist].as(:a)).all
|
|
2768
|
+
# # SELECT *
|
|
2769
|
+
# # FROM albums
|
|
2770
|
+
# # INNER JOIN artists AS a ON (a.id = albums.artist_id)
|
|
2771
|
+
#
|
|
2772
|
+
# # For each album, association_join load the artist and genre
|
|
2773
|
+
# Album.association_join(:artist, :genre).all
|
|
2774
|
+
# Album.association_join(:artist).association_join(:genre).all
|
|
2775
|
+
# # SELECT *
|
|
2776
|
+
# # FROM albums
|
|
2777
|
+
# # INNER JOIN artists AS artist ON (artist.id = albums.artist_id)
|
|
2778
|
+
# # INNER JOIN genres AS genre ON (genre.id = albums.genre_id)
|
|
2779
|
+
#
|
|
2780
|
+
# # For each artist, association_join load albums and tracks for each album
|
|
2781
|
+
# Artist.association_join(albums: :tracks).all
|
|
2782
|
+
# # SELECT *
|
|
2783
|
+
# # FROM artists
|
|
2784
|
+
# # INNER JOIN albums ON (albums.artist_id = artists.id)
|
|
2785
|
+
# # INNER JOIN tracks ON (tracks.album_id = albums.id)
|
|
2786
|
+
#
|
|
2787
|
+
# # For each artist, association_join load albums, tracks for each album, and genre for each track
|
|
2788
|
+
# Artist.association_join(albums: {tracks: :genre}).all
|
|
2789
|
+
# # SELECT *
|
|
2790
|
+
# # FROM artists
|
|
2791
|
+
# # INNER JOIN albums ON (albums.artist_id = artists.id)
|
|
2792
|
+
# # INNER JOIN tracks ON (tracks.album_id = albums.id)
|
|
2793
|
+
# # INNER JOIN genres AS genre ON (genre.id = tracks.genre_id)
|
|
2794
|
+
#
|
|
2795
|
+
# # For each artist, association_join load albums with year > 1990
|
|
2796
|
+
# Artist.association_join(albums: proc{|ds| ds.where{year > 1990}}).all
|
|
2797
|
+
# # SELECT *
|
|
2798
|
+
# # FROM artists
|
|
2799
|
+
# # INNER JOIN (
|
|
2800
|
+
# # SELECT * FROM albums WHERE (year > 1990)
|
|
2801
|
+
# # ) AS albums ON (albums.artist_id = artists.id)
|
|
2802
|
+
#
|
|
2803
|
+
# # For each artist, association_join load albums and tracks 1-10 for each album
|
|
2804
|
+
# Artist.association_join(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
|
|
2805
|
+
# # SELECT *
|
|
2806
|
+
# # FROM artists
|
|
2807
|
+
# # INNER JOIN albums ON (albums.artist_id = artists.id)
|
|
2808
|
+
# # INNER JOIN (
|
|
2809
|
+
# # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
|
|
2810
|
+
# # ) AS tracks ON (tracks.albums_id = albums.id)
|
|
2811
|
+
#
|
|
2812
|
+
# # For each artist, association_join load albums with year > 1990, and tracks for those albums
|
|
2813
|
+
# Artist.association_join(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
|
|
2814
|
+
# # SELECT *
|
|
2815
|
+
# # FROM artists
|
|
2816
|
+
# # INNER JOIN (
|
|
2817
|
+
# # SELECT * FROM albums WHERE (year > 1990)
|
|
2818
|
+
# # ) AS albums ON (albums.artist_id = artists.id)
|
|
2819
|
+
# # INNER JOIN tracks ON (tracks.album_id = albums.id)
|
|
2442
2820
|
def association_join(*associations)
|
|
2443
2821
|
association_inner_join(*associations)
|
|
2444
2822
|
end
|
|
@@ -2451,10 +2829,10 @@ END
|
|
|
2451
2829
|
# types, this is a simple transformation, but for +many_to_many+ associations this
|
|
2452
2830
|
# creates a subquery to the join table.
|
|
2453
2831
|
def complex_expression_sql_append(sql, op, args)
|
|
2454
|
-
r = args
|
|
2455
|
-
if (((op == :'=' || op == :'!=')
|
|
2456
|
-
(multiple = ((op == :IN || op == :'NOT IN')
|
|
2457
|
-
l = args
|
|
2832
|
+
r = args[1]
|
|
2833
|
+
if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
|
|
2834
|
+
(multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
|
|
2835
|
+
l = args[0]
|
|
2458
2836
|
if ar = model.association_reflections[l]
|
|
2459
2837
|
if multiple
|
|
2460
2838
|
klass = ar.associated_class
|
|
@@ -2509,7 +2887,7 @@ END
|
|
|
2509
2887
|
# it avoids problems such as aliasing conflicts and creating cartesian product
|
|
2510
2888
|
# result sets if multiple one_to_many or many_to_many eager associations are requested.
|
|
2511
2889
|
#
|
|
2512
|
-
# One limitation of using this method is that you cannot filter the dataset
|
|
2890
|
+
# One limitation of using this method is that you cannot filter the current dataset
|
|
2513
2891
|
# based on values of columns in an associated table, since the associations are loaded
|
|
2514
2892
|
# in separate queries. To do that you need to load all associations in the
|
|
2515
2893
|
# same query, and extract an object graph from the results of that query. If you
|
|
@@ -2518,15 +2896,66 @@ END
|
|
|
2518
2896
|
#
|
|
2519
2897
|
# Each association's order, if defined, is respected.
|
|
2520
2898
|
# If the association uses a block or has an :eager_block argument, it is used.
|
|
2899
|
+
#
|
|
2900
|
+
# To modify the associated dataset that will be used for the eager load, you should use a
|
|
2901
|
+
# hash for the association, with the key being the association name symbol, and the value being
|
|
2902
|
+
# a callable object that is called with the associated dataset and should return a modified
|
|
2903
|
+
# dataset. If that association also has dependent associations, instead of a callable object,
|
|
2904
|
+
# use a hash with the callable object being the key, and the dependent association(s) as the value.
|
|
2905
|
+
#
|
|
2906
|
+
# Examples:
|
|
2907
|
+
#
|
|
2908
|
+
# # For each album, eager load the artist
|
|
2909
|
+
# Album.eager(:artist).all
|
|
2910
|
+
# # SELECT * FROM albums
|
|
2911
|
+
# # SELECT * FROM artists WHERE (id IN (...))
|
|
2912
|
+
#
|
|
2913
|
+
# # For each album, eager load the artist and genre
|
|
2914
|
+
# Album.eager(:artist, :genre).all
|
|
2915
|
+
# Album.eager(:artist).eager(:genre).all
|
|
2916
|
+
# # SELECT * FROM albums
|
|
2917
|
+
# # SELECT * FROM artists WHERE (id IN (...))
|
|
2918
|
+
# # SELECT * FROM genres WHERE (id IN (...))
|
|
2919
|
+
#
|
|
2920
|
+
# # For each artist, eager load albums and tracks for each album
|
|
2921
|
+
# Artist.eager(albums: :tracks).all
|
|
2922
|
+
# # SELECT * FROM artists
|
|
2923
|
+
# # SELECT * FROM albums WHERE (artist_id IN (...))
|
|
2924
|
+
# # SELECT * FROM tracks WHERE (album_id IN (...))
|
|
2925
|
+
#
|
|
2926
|
+
# # For each artist, eager load albums, tracks for each album, and genre for each track
|
|
2927
|
+
# Artist.eager(albums: {tracks: :genre}).all
|
|
2928
|
+
# # SELECT * FROM artists
|
|
2929
|
+
# # SELECT * FROM albums WHERE (artist_id IN (...))
|
|
2930
|
+
# # SELECT * FROM tracks WHERE (album_id IN (...))
|
|
2931
|
+
# # SELECT * FROM genre WHERE (id IN (...))
|
|
2932
|
+
#
|
|
2933
|
+
# # For each artist, eager load albums with year > 1990
|
|
2934
|
+
# Artist.eager(albums: proc{|ds| ds.where{year > 1990}}).all
|
|
2935
|
+
# # SELECT * FROM artists
|
|
2936
|
+
# # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
|
|
2937
|
+
#
|
|
2938
|
+
# # For each artist, eager load albums and tracks 1-10 for each album
|
|
2939
|
+
# Artist.eager(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
|
|
2940
|
+
# # SELECT * FROM artists
|
|
2941
|
+
# # SELECT * FROM albums WHERE (artist_id IN (...))
|
|
2942
|
+
# # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10) AND (album_id IN (...)))
|
|
2943
|
+
#
|
|
2944
|
+
# # For each artist, eager load albums with year > 1990, and tracks for those albums
|
|
2945
|
+
# Artist.eager(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
|
|
2946
|
+
# # SELECT * FROM artists
|
|
2947
|
+
# # SELECT * FROM albums WHERE ((year > 1990) AND (artist_id IN (...)))
|
|
2948
|
+
# # SELECT * FROM albums WHERE (artist_id IN (...))
|
|
2521
2949
|
def eager(*associations)
|
|
2522
2950
|
opts = @opts[:eager]
|
|
2523
2951
|
association_opts = eager_options_for_associations(associations)
|
|
2524
|
-
opts = opts ?
|
|
2525
|
-
clone(:eager=>opts)
|
|
2952
|
+
opts = opts ? opts.merge(association_opts) : association_opts
|
|
2953
|
+
clone(:eager=>opts.freeze)
|
|
2526
2954
|
end
|
|
2527
2955
|
|
|
2528
2956
|
# The secondary eager loading method. Loads all associations in a single query. This
|
|
2529
|
-
# method should only be used if you need to filter or order based on columns in associated tables
|
|
2957
|
+
# method should only be used if you need to filter or order based on columns in associated tables,
|
|
2958
|
+
# or if you have done comparative benchmarking it and determined it is faster.
|
|
2530
2959
|
#
|
|
2531
2960
|
# This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
|
|
2532
2961
|
# tables. Then it uses the graph's metadata to build the associations from the single hash, and
|
|
@@ -2534,9 +2963,9 @@ END
|
|
|
2534
2963
|
#
|
|
2535
2964
|
# Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
|
|
2536
2965
|
# create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
|
|
2537
|
-
# make sure your filters are narrow if
|
|
2966
|
+
# make sure your filters are narrow if the datasets are large.
|
|
2538
2967
|
#
|
|
2539
|
-
# Each association's order, if
|
|
2968
|
+
# Each association's order, if defined, is respected. +eager_graph+ probably
|
|
2540
2969
|
# won't work correctly on a limited dataset, unless you are
|
|
2541
2970
|
# only graphing many_to_one, one_to_one, and one_through_one associations.
|
|
2542
2971
|
#
|
|
@@ -2545,6 +2974,86 @@ END
|
|
|
2545
2974
|
#
|
|
2546
2975
|
# Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
|
|
2547
2976
|
# call +each+, it will yield plain hashes, each containing all columns from all the tables.
|
|
2977
|
+
#
|
|
2978
|
+
# To modify the associated dataset that will be joined to the current dataset, you should use a
|
|
2979
|
+
# hash for the association, with the key being the association name symbol, and the value being
|
|
2980
|
+
# a callable object that is called with the associated dataset and should return a modified
|
|
2981
|
+
# dataset. If that association also has dependent associations, instead of a callable object,
|
|
2982
|
+
# use a hash with the callable object being the key, and the dependent association(s) as the value.
|
|
2983
|
+
#
|
|
2984
|
+
# You can specify an custom alias and/or join type on a per-association basis by providing an
|
|
2985
|
+
# Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
|
|
2986
|
+
#
|
|
2987
|
+
# Examples:
|
|
2988
|
+
#
|
|
2989
|
+
# # For each album, eager_graph load the artist
|
|
2990
|
+
# Album.eager_graph(:artist).all
|
|
2991
|
+
# # SELECT ...
|
|
2992
|
+
# # FROM albums
|
|
2993
|
+
# # LEFT OUTER JOIN artists AS artist ON (artists.id = albums.artist_id)
|
|
2994
|
+
#
|
|
2995
|
+
# # For each album, eager_graph load the artist, using a specified alias
|
|
2996
|
+
# Album.eager_graph(Sequel[:artist].as(:a)).all
|
|
2997
|
+
# # SELECT ...
|
|
2998
|
+
# # FROM albums
|
|
2999
|
+
# # LEFT OUTER JOIN artists AS a ON (a.id = albums.artist_id)
|
|
3000
|
+
#
|
|
3001
|
+
# # For each album, eager_graph load the artist, using a specified alias
|
|
3002
|
+
# # and custom join type
|
|
3003
|
+
#
|
|
3004
|
+
# Album.eager_graph(Sequel[:artist].as(:a, join_type: :inner)).all
|
|
3005
|
+
# # SELECT ...
|
|
3006
|
+
# # FROM albums
|
|
3007
|
+
# # INNER JOIN artists AS a ON (a.id = albums.artist_id)
|
|
3008
|
+
#
|
|
3009
|
+
# # For each album, eager_graph load the artist and genre
|
|
3010
|
+
# Album.eager_graph(:artist, :genre).all
|
|
3011
|
+
# Album.eager_graph(:artist).eager_graph(:genre).all
|
|
3012
|
+
# # SELECT ...
|
|
3013
|
+
# # FROM albums
|
|
3014
|
+
# # LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id)
|
|
3015
|
+
# # LEFT OUTER JOIN genres AS genre ON (genre.id = albums.genre_id)
|
|
3016
|
+
#
|
|
3017
|
+
# # For each artist, eager_graph load albums and tracks for each album
|
|
3018
|
+
# Artist.eager_graph(albums: :tracks).all
|
|
3019
|
+
# # SELECT ...
|
|
3020
|
+
# # FROM artists
|
|
3021
|
+
# # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
|
|
3022
|
+
# # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
|
|
3023
|
+
#
|
|
3024
|
+
# # For each artist, eager_graph load albums, tracks for each album, and genre for each track
|
|
3025
|
+
# Artist.eager_graph(albums: {tracks: :genre}).all
|
|
3026
|
+
# # SELECT ...
|
|
3027
|
+
# # FROM artists
|
|
3028
|
+
# # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
|
|
3029
|
+
# # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
|
|
3030
|
+
# # LEFT OUTER JOIN genres AS genre ON (genre.id = tracks.genre_id)
|
|
3031
|
+
#
|
|
3032
|
+
# # For each artist, eager_graph load albums with year > 1990
|
|
3033
|
+
# Artist.eager_graph(albums: proc{|ds| ds.where{year > 1990}}).all
|
|
3034
|
+
# # SELECT ...
|
|
3035
|
+
# # FROM artists
|
|
3036
|
+
# # LEFT OUTER JOIN (
|
|
3037
|
+
# # SELECT * FROM albums WHERE (year > 1990)
|
|
3038
|
+
# # ) AS albums ON (albums.artist_id = artists.id)
|
|
3039
|
+
#
|
|
3040
|
+
# # For each artist, eager_graph load albums and tracks 1-10 for each album
|
|
3041
|
+
# Artist.eager_graph(albums: {tracks: proc{|ds| ds.where(number: 1..10)}}).all
|
|
3042
|
+
# # SELECT ...
|
|
3043
|
+
# # FROM artists
|
|
3044
|
+
# # LEFT OUTER JOIN albums ON (albums.artist_id = artists.id)
|
|
3045
|
+
# # LEFT OUTER JOIN (
|
|
3046
|
+
# # SELECT * FROM tracks WHERE ((number >= 1) AND (number <= 10))
|
|
3047
|
+
# # ) AS tracks ON (tracks.albums_id = albums.id)
|
|
3048
|
+
#
|
|
3049
|
+
# # For each artist, eager_graph load albums with year > 1990, and tracks for those albums
|
|
3050
|
+
# Artist.eager_graph(albums: {proc{|ds| ds.where{year > 1990}}=>:tracks}).all
|
|
3051
|
+
# # SELECT ...
|
|
3052
|
+
# # FROM artists
|
|
3053
|
+
# # LEFT OUTER JOIN (
|
|
3054
|
+
# # SELECT * FROM albums WHERE (year > 1990)
|
|
3055
|
+
# # ) AS albums ON (albums.artist_id = artists.id)
|
|
3056
|
+
# # LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)
|
|
2548
3057
|
def eager_graph(*associations)
|
|
2549
3058
|
eager_graph_with_options(associations)
|
|
2550
3059
|
end
|
|
@@ -2570,8 +3079,11 @@ END
|
|
|
2570
3079
|
# significantly slower in some cases (perhaps even the majority of cases), so you should
|
|
2571
3080
|
# only use this if you have benchmarked that it is faster for your use cases.
|
|
2572
3081
|
def eager_graph_with_options(associations, opts=OPTS)
|
|
3082
|
+
return self if associations.empty?
|
|
3083
|
+
|
|
3084
|
+
opts = opts.dup unless opts.frozen?
|
|
2573
3085
|
associations = [associations] unless associations.is_a?(Array)
|
|
2574
|
-
if eg = @opts[:eager_graph]
|
|
3086
|
+
ds = if eg = @opts[:eager_graph]
|
|
2575
3087
|
eg = eg.dup
|
|
2576
3088
|
[:requirements, :reflections, :reciprocals, :limits].each{|k| eg[k] = eg[k].dup}
|
|
2577
3089
|
eg[:local] = opts
|
|
@@ -2585,8 +3097,32 @@ END
|
|
|
2585
3097
|
# :limits :: Any limit/offset array slicing that need to be handled in ruby land after loading
|
|
2586
3098
|
opts = {:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :limits=>{}, :local=>opts, :cartesian_product_number=>0, :row_proc=>row_proc}
|
|
2587
3099
|
ds = clone(:eager_graph=>opts)
|
|
2588
|
-
ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
|
|
3100
|
+
ds = ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
|
|
2589
3101
|
end
|
|
3102
|
+
|
|
3103
|
+
ds.opts[:eager_graph].freeze
|
|
3104
|
+
ds.opts[:eager_graph].each_value{|v| v.freeze if v.is_a?(Hash)}
|
|
3105
|
+
ds
|
|
3106
|
+
end
|
|
3107
|
+
|
|
3108
|
+
# If the dataset is being eagerly loaded, default to calling all
|
|
3109
|
+
# instead of each.
|
|
3110
|
+
def as_hash(key_column=nil, value_column=nil, opts=OPTS)
|
|
3111
|
+
if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
|
|
3112
|
+
opts = Hash[opts]
|
|
3113
|
+
opts[:all] = true
|
|
3114
|
+
end
|
|
3115
|
+
super
|
|
3116
|
+
end
|
|
3117
|
+
|
|
3118
|
+
# If the dataset is being eagerly loaded, default to calling all
|
|
3119
|
+
# instead of each.
|
|
3120
|
+
def to_hash_groups(key_column, value_column=nil, opts=OPTS)
|
|
3121
|
+
if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
|
|
3122
|
+
opts = Hash[opts]
|
|
3123
|
+
opts[:all] = true
|
|
3124
|
+
end
|
|
3125
|
+
super
|
|
2590
3126
|
end
|
|
2591
3127
|
|
|
2592
3128
|
# Do not attempt to split the result set into associations,
|
|
@@ -2596,7 +3132,7 @@ END
|
|
|
2596
3132
|
def ungraphed
|
|
2597
3133
|
ds = super.clone(:eager_graph=>nil)
|
|
2598
3134
|
if (eg = @opts[:eager_graph]) && (rp = eg[:row_proc])
|
|
2599
|
-
ds
|
|
3135
|
+
ds = ds.with_row_proc(rp)
|
|
2600
3136
|
end
|
|
2601
3137
|
ds
|
|
2602
3138
|
end
|
|
@@ -2614,11 +3150,16 @@ END
|
|
|
2614
3150
|
# ta :: table_alias used for the parent association
|
|
2615
3151
|
# requirements :: an array, used as a stack for requirements
|
|
2616
3152
|
# r :: association reflection for the current association, or an SQL::AliasedExpression
|
|
2617
|
-
# with the reflection as the expression
|
|
3153
|
+
# with the reflection as the expression, the alias base as the alias (or nil to
|
|
3154
|
+
# use the default alias), and an optional hash with a :join_type entry as the columns
|
|
3155
|
+
# to use a custom join type.
|
|
2618
3156
|
# *associations :: any associations dependent on this one
|
|
2619
3157
|
def eager_graph_association(ds, model, ta, requirements, r, *associations)
|
|
2620
3158
|
if r.is_a?(SQL::AliasedExpression)
|
|
2621
3159
|
alias_base = r.alias
|
|
3160
|
+
if r.columns.is_a?(Hash)
|
|
3161
|
+
join_type = r.columns[:join_type]
|
|
3162
|
+
end
|
|
2622
3163
|
r = r.expression
|
|
2623
3164
|
else
|
|
2624
3165
|
alias_base = r[:graph_alias_base]
|
|
@@ -2636,9 +3177,14 @@ END
|
|
|
2636
3177
|
end
|
|
2637
3178
|
local_opts = ds.opts[:eager_graph][:local]
|
|
2638
3179
|
limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
|
|
2639
|
-
|
|
3180
|
+
|
|
3181
|
+
if r[:conditions] && !Sequel.condition_specifier?(r[:conditions]) && !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block)
|
|
3182
|
+
raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
|
|
3183
|
+
end
|
|
3184
|
+
|
|
3185
|
+
ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
|
|
2640
3186
|
if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
|
|
2641
|
-
ds = ds.
|
|
3187
|
+
ds = ds.order_append(*qualified_expression(order, assoc_table_alias))
|
|
2642
3188
|
end
|
|
2643
3189
|
eager_graph = ds.opts[:eager_graph]
|
|
2644
3190
|
eager_graph[:requirements][assoc_table_alias] = requirements.dup
|
|
@@ -2661,7 +3207,6 @@ END
|
|
|
2661
3207
|
# requirements :: an array, used as a stack for requirements
|
|
2662
3208
|
# *associations :: the associations to add to the graph
|
|
2663
3209
|
def eager_graph_associations(ds, model, ta, requirements, *associations)
|
|
2664
|
-
return ds if associations.empty?
|
|
2665
3210
|
associations.flatten.each do |association|
|
|
2666
3211
|
ds = case association
|
|
2667
3212
|
when Symbol, SQL::AliasedExpression
|
|
@@ -2681,7 +3226,7 @@ END
|
|
|
2681
3226
|
# Replace the array of plain hashes with an array of model objects will all eager_graphed
|
|
2682
3227
|
# associations set in the associations cache for each object.
|
|
2683
3228
|
def eager_graph_build_associations(hashes)
|
|
2684
|
-
hashes.replace(
|
|
3229
|
+
hashes.replace(_eager_graph_build_associations(hashes, eager_graph_loader))
|
|
2685
3230
|
end
|
|
2686
3231
|
|
|
2687
3232
|
private
|
|
@@ -2692,12 +3237,18 @@ END
|
|
|
2692
3237
|
clone(:join=>clone(:graph_from_self=>false).eager_graph_with_options(associations, :join_type=>type, :join_only=>true).opts[:join])
|
|
2693
3238
|
end
|
|
2694
3239
|
|
|
3240
|
+
# Process the array of hashes using the eager graph loader to return an array
|
|
3241
|
+
# of model objects with the associations set.
|
|
3242
|
+
def _eager_graph_build_associations(hashes, egl)
|
|
3243
|
+
egl.load(hashes)
|
|
3244
|
+
end
|
|
3245
|
+
|
|
2695
3246
|
# If the association has conditions itself, then it requires additional filters be
|
|
2696
3247
|
# added to the current dataset to ensure that the passed in object would also be
|
|
2697
3248
|
# included by the association's conditions.
|
|
2698
3249
|
def add_association_filter_conditions(ref, obj, expr)
|
|
2699
3250
|
if expr != SQL::Constants::FALSE && ref.filter_by_associations_add_conditions?
|
|
2700
|
-
Sequel
|
|
3251
|
+
Sequel[ref.filter_by_associations_conditions_expression(obj)]
|
|
2701
3252
|
else
|
|
2702
3253
|
expr
|
|
2703
3254
|
end
|
|
@@ -2725,6 +3276,7 @@ END
|
|
|
2725
3276
|
# Return an expression for filtering by the given association reflection and associated object.
|
|
2726
3277
|
def association_filter_expression(op, ref, obj)
|
|
2727
3278
|
meth = :"#{ref[:type]}_association_filter_expression"
|
|
3279
|
+
# Allow calling private association specific method to get filter expression
|
|
2728
3280
|
send(meth, op, ref, obj) if respond_to?(meth, true)
|
|
2729
3281
|
end
|
|
2730
3282
|
|
|
@@ -2776,22 +3328,37 @@ END
|
|
|
2776
3328
|
# per-call determining of the alias base.
|
|
2777
3329
|
def eager_graph_check_association(model, association)
|
|
2778
3330
|
if association.is_a?(SQL::AliasedExpression)
|
|
2779
|
-
|
|
3331
|
+
expr = association.expression
|
|
3332
|
+
if expr.is_a?(SQL::Identifier)
|
|
3333
|
+
expr = expr.value
|
|
3334
|
+
if expr.is_a?(String)
|
|
3335
|
+
expr = expr.to_sym
|
|
3336
|
+
end
|
|
3337
|
+
end
|
|
3338
|
+
|
|
3339
|
+
SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
|
|
2780
3340
|
else
|
|
2781
3341
|
check_association(model, association)
|
|
2782
3342
|
end
|
|
2783
3343
|
end
|
|
2784
3344
|
|
|
3345
|
+
# The EagerGraphLoader instance used for converting eager_graph results.
|
|
3346
|
+
def eager_graph_loader
|
|
3347
|
+
unless egl = cache_get(:_model_eager_graph_loader)
|
|
3348
|
+
egl = cache_set(:_model_eager_graph_loader, EagerGraphLoader.new(self))
|
|
3349
|
+
end
|
|
3350
|
+
egl.dup
|
|
3351
|
+
end
|
|
3352
|
+
|
|
2785
3353
|
# Eagerly load all specified associations
|
|
2786
3354
|
def eager_load(a, eager_assoc=@opts[:eager])
|
|
2787
3355
|
return if a.empty?
|
|
2788
|
-
# Key is foreign/primary key name symbol
|
|
3356
|
+
# Key is foreign/primary key name symbol.
|
|
2789
3357
|
# Value is hash with keys being foreign/primary key values (generally integers)
|
|
2790
|
-
#
|
|
2791
|
-
# specific foreign/primary key
|
|
3358
|
+
# and values being an array of current model objects with that specific foreign/primary key
|
|
2792
3359
|
key_hash = {}
|
|
2793
3360
|
# Reflections for all associations to eager load
|
|
2794
|
-
reflections = eager_assoc.keys.
|
|
3361
|
+
reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
|
|
2795
3362
|
|
|
2796
3363
|
# Populate the key_hash entry for each association being eagerly loaded
|
|
2797
3364
|
reflections.each do |r|
|
|
@@ -2826,12 +3393,12 @@ END
|
|
|
2826
3393
|
associations = eager_assoc[r[:name]]
|
|
2827
3394
|
if associations.respond_to?(:call)
|
|
2828
3395
|
eager_block = associations
|
|
2829
|
-
associations =
|
|
3396
|
+
associations = OPTS
|
|
2830
3397
|
elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
|
|
2831
3398
|
eager_block, associations = pr_assoc
|
|
2832
3399
|
end
|
|
2833
3400
|
loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
|
|
2834
|
-
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])}
|
|
3401
|
+
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
|
|
2835
3402
|
end
|
|
2836
3403
|
end
|
|
2837
3404
|
|
|
@@ -2887,6 +3454,10 @@ END
|
|
|
2887
3454
|
end
|
|
2888
3455
|
alias one_to_one_association_filter_expression one_to_many_association_filter_expression
|
|
2889
3456
|
|
|
3457
|
+
def non_sql_option?(key)
|
|
3458
|
+
super || key == :eager || key == :eager_graph
|
|
3459
|
+
end
|
|
3460
|
+
|
|
2890
3461
|
# Build associations from the graph if #eager_graph was used,
|
|
2891
3462
|
# and/or load other associations if #eager was used.
|
|
2892
3463
|
def post_load(all_records)
|
|
@@ -2916,7 +3487,6 @@ END
|
|
|
2916
3487
|
# Hash with table alias symbol keys and [limit, offset] values
|
|
2917
3488
|
attr_reader :limit_map
|
|
2918
3489
|
|
|
2919
|
-
# Hash with table alias symbol keys and callable values used to create model instances
|
|
2920
3490
|
# The table alias symbol for the primary model
|
|
2921
3491
|
attr_reader :master
|
|
2922
3492
|
|
|
@@ -2959,19 +3529,22 @@ END
|
|
|
2959
3529
|
after_load_map = @after_load_map = {}
|
|
2960
3530
|
reflection_map.each do |k, v|
|
|
2961
3531
|
alias_map[k] = v[:name]
|
|
2962
|
-
after_load_map[k] = v[:after_load]
|
|
3532
|
+
after_load_map[k] = v[:after_load] if v[:after_load]
|
|
2963
3533
|
type_map[k] = if v.returns_array?
|
|
2964
3534
|
true
|
|
2965
3535
|
elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
|
|
2966
3536
|
:offset
|
|
2967
3537
|
end
|
|
2968
3538
|
end
|
|
3539
|
+
after_load_map.freeze
|
|
3540
|
+
alias_map.freeze
|
|
3541
|
+
type_map.freeze
|
|
2969
3542
|
|
|
2970
3543
|
# Make dependency map hash out of requirements array for each association.
|
|
2971
3544
|
# This builds a tree of dependencies that will be used for recursion
|
|
2972
3545
|
# to ensure that all parts of the object graph are loaded into the
|
|
2973
3546
|
# appropriate subordinate association.
|
|
2974
|
-
@dependency_map = {}
|
|
3547
|
+
dependency_map = @dependency_map = {}
|
|
2975
3548
|
# Sort the associations by requirements length, so that
|
|
2976
3549
|
# requirements are added to the dependency hash before their
|
|
2977
3550
|
# dependencies.
|
|
@@ -2987,20 +3560,14 @@ END
|
|
|
2987
3560
|
hash[ta] = {}
|
|
2988
3561
|
end
|
|
2989
3562
|
end
|
|
3563
|
+
freezer = lambda do |h|
|
|
3564
|
+
h.freeze
|
|
3565
|
+
h.each_value(&freezer)
|
|
3566
|
+
end
|
|
3567
|
+
freezer.call(dependency_map)
|
|
2990
3568
|
|
|
2991
|
-
# This mapping is used to make sure that duplicate entries in the
|
|
2992
|
-
# result set are mapped to a single record. For example, using a
|
|
2993
|
-
# single one_to_many association with 10 associated records,
|
|
2994
|
-
# the main object column values appear in the object graph 10 times.
|
|
2995
|
-
# We map by primary key, if available, or by the object's entire values,
|
|
2996
|
-
# if not. The mapping must be per table, so create sub maps for each table
|
|
2997
|
-
# alias.
|
|
2998
|
-
records_map = {@master=>{}}
|
|
2999
|
-
alias_map.keys.each{|ta| records_map[ta] = {}}
|
|
3000
|
-
@records_map = records_map
|
|
3001
|
-
|
|
3002
3569
|
datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
|
|
3003
|
-
column_aliases = opts[:
|
|
3570
|
+
column_aliases = opts[:graph][:column_aliases]
|
|
3004
3571
|
primary_keys = {}
|
|
3005
3572
|
column_maps = {}
|
|
3006
3573
|
models = {}
|
|
@@ -3024,9 +3591,9 @@ END
|
|
|
3024
3591
|
h.select{|ca, c| primary_keys[ta] = ca if pk == c}
|
|
3025
3592
|
end
|
|
3026
3593
|
end
|
|
3027
|
-
@column_maps = column_maps
|
|
3028
|
-
@primary_keys = primary_keys
|
|
3029
|
-
@row_procs = row_procs
|
|
3594
|
+
@column_maps = column_maps.freeze
|
|
3595
|
+
@primary_keys = primary_keys.freeze
|
|
3596
|
+
@row_procs = row_procs.freeze
|
|
3030
3597
|
|
|
3031
3598
|
# For performance, create two special maps for the master table,
|
|
3032
3599
|
# so you can skip a hash lookup.
|
|
@@ -3038,22 +3605,35 @@ END
|
|
|
3038
3605
|
# used for performance, to get all values in one hash lookup instead of
|
|
3039
3606
|
# separate hash lookups for each data structure.
|
|
3040
3607
|
ta_map = {}
|
|
3041
|
-
alias_map.
|
|
3042
|
-
ta_map[ta] = [
|
|
3608
|
+
alias_map.each_key do |ta|
|
|
3609
|
+
ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
|
|
3043
3610
|
end
|
|
3044
|
-
@ta_map = ta_map
|
|
3611
|
+
@ta_map = ta_map.freeze
|
|
3612
|
+
freeze
|
|
3045
3613
|
end
|
|
3046
3614
|
|
|
3047
3615
|
# Return an array of primary model instances with the associations cache prepopulated
|
|
3048
3616
|
# for all model objects (both primary and associated).
|
|
3049
3617
|
def load(hashes)
|
|
3618
|
+
# This mapping is used to make sure that duplicate entries in the
|
|
3619
|
+
# result set are mapped to a single record. For example, using a
|
|
3620
|
+
# single one_to_many association with 10 associated records,
|
|
3621
|
+
# the main object column values appear in the object graph 10 times.
|
|
3622
|
+
# We map by primary key, if available, or by the object's entire values,
|
|
3623
|
+
# if not. The mapping must be per table, so create sub maps for each table
|
|
3624
|
+
# alias.
|
|
3625
|
+
@records_map = records_map = {}
|
|
3626
|
+
alias_map.keys.each{|ta| records_map[ta] = {}}
|
|
3627
|
+
|
|
3050
3628
|
master = master()
|
|
3051
3629
|
|
|
3052
3630
|
# Assign to local variables for speed increase
|
|
3053
3631
|
rp = row_procs[master]
|
|
3054
|
-
rm = records_map[master]
|
|
3632
|
+
rm = records_map[master] = {}
|
|
3055
3633
|
dm = dependency_map
|
|
3056
3634
|
|
|
3635
|
+
records_map.freeze
|
|
3636
|
+
|
|
3057
3637
|
# This will hold the final record set that we will be replacing the object graph with.
|
|
3058
3638
|
records = []
|
|
3059
3639
|
|
|
@@ -3074,6 +3654,9 @@ END
|
|
|
3074
3654
|
# Run after_load procs if there are any
|
|
3075
3655
|
post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
|
|
3076
3656
|
|
|
3657
|
+
records_map.each_value(&:freeze)
|
|
3658
|
+
freeze
|
|
3659
|
+
|
|
3077
3660
|
records
|
|
3078
3661
|
end
|
|
3079
3662
|
|
|
@@ -3093,7 +3676,17 @@ END
|
|
|
3093
3676
|
end
|
|
3094
3677
|
key = hkey(ta_h)
|
|
3095
3678
|
end
|
|
3096
|
-
|
|
3679
|
+
rp, assoc_name, tm, rcm = @ta_map[ta]
|
|
3680
|
+
rm = records_map[ta]
|
|
3681
|
+
|
|
3682
|
+
# Check type map for all dependencies, and use a unique
|
|
3683
|
+
# object if any are dependencies for multiple objects,
|
|
3684
|
+
# to prevent duplicate objects from showing up in the case
|
|
3685
|
+
# the normal duplicate removal code is not being used.
|
|
3686
|
+
if !@unique && !deps.empty? && deps.any?{|dep_key,_| @ta_map[dep_key][2]}
|
|
3687
|
+
key = [current.object_id, key]
|
|
3688
|
+
end
|
|
3689
|
+
|
|
3097
3690
|
unless rec = rm[key]
|
|
3098
3691
|
rec = rm[key] = rp.call(hfor(ta, h))
|
|
3099
3692
|
end
|
|
@@ -3122,7 +3715,7 @@ END
|
|
|
3122
3715
|
# Return a suitable hash key for any subhash +h+, which is an array of values by column order.
|
|
3123
3716
|
# This is only used if the primary key cannot be used.
|
|
3124
3717
|
def hkey(h)
|
|
3125
|
-
h.sort_by{|x| x[0]
|
|
3718
|
+
h.sort_by{|x| x[0]}
|
|
3126
3719
|
end
|
|
3127
3720
|
|
|
3128
3721
|
# Return the subhash for the master table by parsing the values out of the main hash +h+
|
|
@@ -3167,7 +3760,7 @@ END
|
|
|
3167
3760
|
records.each do |record|
|
|
3168
3761
|
dependency_map.each do |ta, deps|
|
|
3169
3762
|
assoc_name = alias_map[ta]
|
|
3170
|
-
list = record.
|
|
3763
|
+
list = record.public_send(assoc_name)
|
|
3171
3764
|
rec_list = if type_map[ta]
|
|
3172
3765
|
list.uniq!
|
|
3173
3766
|
if lo = limit_map[ta]
|