sequel 3.21.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +855 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -7
- data/Rakefile +42 -24
- data/bin/sequel +26 -8
- data/doc/active_record.rdoc +1 -0
- data/doc/advanced_associations.rdoc +171 -11
- data/doc/association_basics.rdoc +293 -43
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +8 -0
- data/doc/mass_assignment.rdoc +56 -0
- data/doc/migration.rdoc +85 -552
- data/doc/model_hooks.rdoc +78 -27
- data/doc/object_model.rdoc +541 -0
- data/doc/opening_databases.rdoc +97 -50
- data/doc/prepared_statements.rdoc +40 -14
- data/doc/querying.rdoc +28 -4
- data/doc/reflection.rdoc +30 -4
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/release_notes/3.31.0.txt +146 -0
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/release_notes/3.33.0.txt +157 -0
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/schema_modification.rdoc +572 -0
- data/doc/sharding.rdoc +95 -3
- data/doc/testing.rdoc +129 -0
- data/doc/thread_safety.rdoc +17 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +51 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +17 -9
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +17 -11
- data/lib/sequel/adapters/do/postgres.rb +1 -57
- data/lib/sequel/adapters/do/sqlite.rb +0 -10
- data/lib/sequel/adapters/do.rb +18 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +453 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +16 -34
- data/lib/sequel/adapters/jdbc/db2.rb +47 -0
- data/lib/sequel/adapters/jdbc/derby.rb +287 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
- data/lib/sequel/adapters/jdbc/h2.rb +73 -31
- data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
- data/lib/sequel/adapters/jdbc/informix.rb +21 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
- data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
- data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
- data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +265 -94
- data/lib/sequel/adapters/mock.rb +361 -0
- data/lib/sequel/adapters/mysql.rb +118 -222
- data/lib/sequel/adapters/mysql2.rb +58 -57
- data/lib/sequel/adapters/odbc/mssql.rb +15 -7
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +3 -5
- data/lib/sequel/adapters/oracle.rb +349 -53
- data/lib/sequel/adapters/postgres.rb +356 -107
- data/lib/sequel/adapters/shared/access.rb +32 -9
- data/lib/sequel/adapters/shared/db2.rb +342 -0
- data/lib/sequel/adapters/shared/firebird.rb +221 -0
- data/lib/sequel/adapters/shared/informix.rb +53 -0
- data/lib/sequel/adapters/shared/mssql.rb +335 -118
- data/lib/sequel/adapters/shared/mysql.rb +340 -76
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
- data/lib/sequel/adapters/shared/oracle.rb +243 -45
- data/lib/sequel/adapters/shared/postgres.rb +524 -279
- data/lib/sequel/adapters/shared/progress.rb +5 -8
- data/lib/sequel/adapters/shared/sqlite.rb +265 -89
- data/lib/sequel/adapters/sqlite.rb +101 -39
- data/lib/sequel/adapters/swift/mysql.rb +9 -12
- data/lib/sequel/adapters/swift/postgres.rb +9 -74
- data/lib/sequel/adapters/swift/sqlite.rb +8 -6
- data/lib/sequel/adapters/swift.rb +8 -7
- data/lib/sequel/adapters/tinytds.rb +161 -21
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
- data/lib/sequel/ast_transformer.rb +194 -0
- data/lib/sequel/connection_pool/sharded_single.rb +11 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +38 -14
- data/lib/sequel/connection_pool.rb +10 -2
- data/lib/sequel/core.rb +118 -11
- data/lib/sequel/database/connecting.rb +38 -28
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +63 -2
- data/lib/sequel/database/logging.rb +7 -2
- data/lib/sequel/database/misc.rb +150 -46
- data/lib/sequel/database/query.rb +215 -65
- data/lib/sequel/database/schema_generator.rb +64 -20
- data/lib/sequel/database/schema_methods.rb +212 -54
- data/lib/sequel/dataset/actions.rb +322 -85
- data/lib/sequel/dataset/features.rb +96 -8
- data/lib/sequel/dataset/graph.rb +38 -11
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +12 -7
- data/lib/sequel/dataset/prepared_statements.rb +68 -30
- data/lib/sequel/dataset/query.rb +237 -44
- data/lib/sequel/dataset/sql.rb +698 -278
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/_pretty_table.rb +79 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
- data/lib/sequel/extensions/migration.rb +97 -11
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/null_dataset.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +460 -0
- data/lib/sequel/extensions/pg_array_ops.rb +220 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
- data/lib/sequel/extensions/pg_hstore.rb +296 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
- data/lib/sequel/extensions/pg_inet.rb +89 -0
- data/lib/sequel/extensions/pg_json.rb +178 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
- data/lib/sequel/extensions/pretty_table.rb +5 -71
- data/lib/sequel/extensions/query.rb +1 -1
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +251 -37
- data/lib/sequel/extensions/select_remove.rb +35 -0
- data/lib/sequel/extensions/server_block.rb +139 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -110
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +1187 -393
- data/lib/sequel/model/base.rb +578 -170
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model.rb +30 -11
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/composition.rb +44 -12
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +123 -8
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +58 -6
- data/lib/sequel/plugins/list.rb +12 -2
- data/lib/sequel/plugins/many_through_many.rb +79 -28
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +139 -63
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/prepared_statements.rb +167 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/schema.rb +7 -2
- data/lib/sequel/plugins/serialization.rb +98 -49
- data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
- data/lib/sequel/plugins/sharding.rb +21 -54
- data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/subclasses.rb +10 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +15 -4
- data/lib/sequel/sql.rb +526 -59
- data/lib/sequel/timezones.rb +57 -38
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +142 -40
- data/spec/adapters/mysql_spec.rb +380 -234
- data/spec/adapters/oracle_spec.rb +67 -77
- data/spec/adapters/postgres_spec.rb +1050 -212
- data/spec/adapters/spec_helper.rb +8 -5
- data/spec/adapters/sqlite_spec.rb +139 -27
- data/spec/core/connection_pool_spec.rb +196 -93
- data/spec/core/database_spec.rb +879 -510
- data/spec/core/dataset_spec.rb +1415 -1086
- data/spec/core/expression_filters_spec.rb +554 -109
- data/spec/core/mock_adapter_spec.rb +449 -0
- data/spec/core/object_graph_spec.rb +86 -114
- data/spec/core/schema_generator_spec.rb +4 -4
- data/spec/core/schema_spec.rb +482 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/arbitrary_servers_spec.rb +110 -0
- data/spec/extensions/association_autoreloading_spec.rb +18 -10
- data/spec/extensions/association_dependencies_spec.rb +15 -25
- data/spec/extensions/association_pks_spec.rb +66 -32
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/caching_spec.rb +47 -51
- data/spec/extensions/class_table_inheritance_spec.rb +31 -83
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +69 -92
- data/spec/extensions/identity_map_spec.rb +131 -22
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/instance_filters_spec.rb +11 -21
- data/spec/extensions/instance_hooks_spec.rb +72 -0
- data/spec/extensions/json_serializer_spec.rb +49 -6
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +48 -39
- data/spec/extensions/many_through_many_spec.rb +283 -250
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +140 -35
- data/spec/extensions/named_timezones_spec.rb +29 -11
- data/spec/extensions/nested_attributes_spec.rb +251 -89
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +24 -21
- data/spec/extensions/pg_array_ops_spec.rb +105 -0
- data/spec/extensions/pg_array_spec.rb +196 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
- data/spec/extensions/pg_hstore_spec.rb +195 -0
- data/spec/extensions/pg_inet_spec.rb +44 -0
- data/spec/extensions/pg_json_spec.rb +101 -0
- data/spec/extensions/pg_statement_cache_spec.rb +209 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
- data/spec/extensions/prepared_statements_spec.rb +87 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
- data/spec/extensions/pretty_table_spec.rb +7 -6
- data/spec/extensions/query_literals_spec.rb +168 -0
- data/spec/extensions/query_spec.rb +6 -0
- data/spec/extensions/rcte_tree_spec.rb +50 -43
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +429 -22
- data/spec/extensions/schema_spec.rb +25 -21
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
- data/spec/extensions/serialization_spec.rb +85 -43
- data/spec/extensions/server_block_spec.rb +90 -0
- data/spec/extensions/sharding_spec.rb +69 -143
- data/spec/extensions/single_table_inheritance_spec.rb +23 -29
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +22 -61
- data/spec/extensions/sql_expr_spec.rb +29 -60
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +4 -7
- data/spec/extensions/touch_spec.rb +13 -14
- data/spec/extensions/tree_spec.rb +11 -26
- data/spec/extensions/typecast_on_load_spec.rb +9 -6
- data/spec/extensions/update_primary_key_spec.rb +30 -24
- data/spec/extensions/validation_class_methods_spec.rb +30 -51
- data/spec/extensions/validation_helpers_spec.rb +16 -35
- data/spec/extensions/xml_serializer_spec.rb +29 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
- data/spec/integration/associations_test.rb +659 -21
- data/spec/integration/database_test.rb +9 -3
- data/spec/integration/dataset_test.rb +605 -91
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +81 -2
- data/spec/integration/plugin_test.rb +454 -143
- data/spec/integration/prepared_statement_test.rb +263 -128
- data/spec/integration/schema_test.rb +309 -43
- data/spec/integration/spec_helper.rb +40 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +183 -8
- data/spec/integration/type_test.rb +18 -4
- data/spec/model/association_reflection_spec.rb +201 -7
- data/spec/model/associations_spec.rb +1067 -661
- data/spec/model/base_spec.rb +329 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +677 -676
- data/spec/model/hooks_spec.rb +349 -67
- data/spec/model/model_spec.rb +240 -168
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +490 -232
- data/spec/model/spec_helper.rb +14 -71
- data/spec/model/validations_spec.rb +11 -0
- data/spec/rcov.opts +2 -0
- metadata +178 -80
|
@@ -47,20 +47,18 @@ module Sequel
|
|
|
47
47
|
class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
|
|
48
48
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
|
|
49
49
|
|
|
50
|
-
# The table containing the column to use for the associated key when eagerly loading
|
|
51
|
-
def associated_key_table
|
|
52
|
-
self[:associated_key_table] = self[:final_reverse_edge][:alias]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
50
|
# The default associated key alias(es) to use when eager loading
|
|
56
51
|
# associations via eager.
|
|
57
52
|
def default_associated_key_alias
|
|
58
53
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
|
59
54
|
end
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
%w'associated_key_table eager_loading_predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
|
|
57
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
58
|
+
def #{meth}
|
|
59
|
+
cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
|
|
60
|
+
end
|
|
61
|
+
END
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
# Many through many associations don't have a reciprocal
|
|
@@ -68,11 +66,6 @@ module Sequel
|
|
|
68
66
|
nil
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
# The list of joins to use when lazy loading or eager loading
|
|
72
|
-
def reverse_edges
|
|
73
|
-
self[:reverse_edges] || calculate_edges || self[:reverse_edges]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
69
|
private
|
|
77
70
|
|
|
78
71
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
|
@@ -109,13 +102,21 @@ module Sequel
|
|
|
109
102
|
reverse_edges = es.reverse.map{|e| {:table=>e[:left_table], :left=>e[:left_key], :right=>e[:right_key]}}
|
|
110
103
|
reverse_edges.pop
|
|
111
104
|
calculate_reverse_edge_aliases(reverse_edges)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
final_reverse_edge = reverse_edges.pop
|
|
106
|
+
final_reverse_alias = final_reverse_edge[:alias]
|
|
107
|
+
|
|
108
|
+
h = {:final_edge=>edges.pop,
|
|
109
|
+
:final_reverse_edge=>final_reverse_edge,
|
|
110
|
+
:edges=>edges,
|
|
111
|
+
:reverse_edges=>reverse_edges,
|
|
112
|
+
:eager_loading_predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
|
|
113
|
+
:associated_key_table=>final_reverse_edge[:alias],
|
|
114
|
+
}
|
|
115
|
+
h.each{|k, v| cached_set(k, v)}
|
|
116
|
+
h
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
|
+
|
|
119
120
|
module ClassMethods
|
|
120
121
|
# Create a many_through_many association. Arguments:
|
|
121
122
|
# * name - Same as associate, the name of the association.
|
|
@@ -174,20 +175,34 @@ module Sequel
|
|
|
174
175
|
opts[:dataset] ||= lambda do
|
|
175
176
|
ds = opts.associated_class
|
|
176
177
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
177
|
-
ft = opts
|
|
178
|
+
ft = opts.final_reverse_edge
|
|
178
179
|
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
|
|
179
180
|
end
|
|
180
181
|
|
|
181
182
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
182
183
|
opts[:eager_loader] ||= lambda do |eo|
|
|
183
184
|
h = eo[:key_hash][left_pk]
|
|
184
|
-
|
|
185
|
+
rows = eo[:rows]
|
|
186
|
+
rows.each{|object| object.associations[name] = []}
|
|
185
187
|
ds = opts.associated_class
|
|
186
188
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
187
|
-
ft = opts
|
|
188
|
-
|
|
189
|
-
ds =
|
|
190
|
-
|
|
189
|
+
ft = opts.final_reverse_edge
|
|
190
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.eager_loading_predicate_key, h.keys]], :table_alias=>ft[:alias])
|
|
191
|
+
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
|
192
|
+
case opts.eager_limit_strategy
|
|
193
|
+
when :window_function
|
|
194
|
+
delete_rn = true
|
|
195
|
+
rn = ds.row_number_column
|
|
196
|
+
ds = apply_window_function_eager_limit_strategy(ds, opts)
|
|
197
|
+
when :correlated_subquery
|
|
198
|
+
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
|
199
|
+
dsa = ds.send(:dataset_alias, 2)
|
|
200
|
+
opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
201
|
+
xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
ds.all do |assoc_record|
|
|
205
|
+
assoc_record.values.delete(rn) if delete_rn
|
|
191
206
|
hash_key = if uses_lcks
|
|
192
207
|
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
|
193
208
|
else
|
|
@@ -196,6 +211,10 @@ module Sequel
|
|
|
196
211
|
next unless objects = h[hash_key]
|
|
197
212
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
198
213
|
end
|
|
214
|
+
if opts.eager_limit_strategy == :ruby
|
|
215
|
+
limit, offset = opts.limit_and_offset
|
|
216
|
+
rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
|
|
217
|
+
end
|
|
199
218
|
end
|
|
200
219
|
|
|
201
220
|
join_type = opts[:graph_join_type]
|
|
@@ -204,19 +223,51 @@ module Sequel
|
|
|
204
223
|
only_conditions = opts[:graph_only_conditions]
|
|
205
224
|
use_only_conditions = opts.include?(:graph_only_conditions)
|
|
206
225
|
conditions = opts[:graph_conditions]
|
|
207
|
-
opts[:eager_grapher] ||= proc do |
|
|
208
|
-
|
|
226
|
+
opts[:eager_grapher] ||= proc do |eo|
|
|
227
|
+
ds = eo[:self]
|
|
228
|
+
iq = eo[:implicit_qualifier]
|
|
209
229
|
opts.edges.each do |t|
|
|
210
230
|
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
|
211
231
|
iq = nil
|
|
212
232
|
end
|
|
213
|
-
fe = opts
|
|
214
|
-
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>
|
|
233
|
+
fe = opts.final_edge
|
|
234
|
+
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
|
|
215
235
|
end
|
|
216
236
|
|
|
217
237
|
def_association_dataset_methods(opts)
|
|
218
238
|
end
|
|
219
239
|
end
|
|
240
|
+
|
|
241
|
+
module DatasetMethods
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
# Use a subquery to filter rows to those related to the given associated object
|
|
245
|
+
def many_through_many_association_filter_expression(op, ref, obj)
|
|
246
|
+
lpks = ref[:left_primary_keys]
|
|
247
|
+
lpks = lpks.first if lpks.length == 1
|
|
248
|
+
lpks = ref.qualify(model.table_name, lpks)
|
|
249
|
+
edges = ref.edges
|
|
250
|
+
first, rest = edges.first, edges[1..-1]
|
|
251
|
+
last = edges.last
|
|
252
|
+
ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
|
|
253
|
+
rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), &e[:block])}
|
|
254
|
+
last_alias = if rest.empty?
|
|
255
|
+
first[:table]
|
|
256
|
+
else
|
|
257
|
+
last_join = ds.opts[:join].last
|
|
258
|
+
last_join.table_alias || last_join.table
|
|
259
|
+
end
|
|
260
|
+
meths = ref.right_primary_keys
|
|
261
|
+
meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
|
|
262
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
|
|
263
|
+
if exp == SQL::Constants::FALSE
|
|
264
|
+
association_filter_handle_inversion(op, exp, Array(lpks))
|
|
265
|
+
else
|
|
266
|
+
ds = ds.where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ds.opts[:select].zip([]), :OR))
|
|
267
|
+
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>ds), Array(lpks))
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
220
271
|
end
|
|
221
272
|
end
|
|
222
273
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# This is a fairly simple plugin that modifies the internal association loading logic
|
|
4
|
+
# for many_to_one associations to use a simple primary key lookup on the associated
|
|
5
|
+
# class, which is generally faster as it uses mostly static SQL. Additional, if the
|
|
6
|
+
# associated class is caching primary key lookups, you get the benefit of a cached
|
|
7
|
+
# lookup.
|
|
8
|
+
#
|
|
9
|
+
# This plugin is generally not as fast as the prepared_statements_associations plugin
|
|
10
|
+
# in the case where the model is not caching primary key lookups, however, it is
|
|
11
|
+
# probably significantly faster if the model is caching primary key lookups. If
|
|
12
|
+
# the prepared_statements_associations plugin has been loaded first, this
|
|
13
|
+
# plugin will only use the primary key lookup code if the associated model is
|
|
14
|
+
# caching primary key lookups.
|
|
15
|
+
#
|
|
16
|
+
# This plugin attempts to determine cases where the primary key lookup would have
|
|
17
|
+
# different results than the regular lookup, and use the regular lookup in that case,
|
|
18
|
+
# but it cannot handle all situations correctly, which is why it is not Sequel's
|
|
19
|
+
# default behavior.
|
|
20
|
+
#
|
|
21
|
+
# You can disable primary key lookups on a per association basis with this
|
|
22
|
+
# plugin using the :many_to_one_pk_lookup=>false association option.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
#
|
|
26
|
+
# # Make all model subclass instances use primary key lookups for many_to_one
|
|
27
|
+
# # association loading
|
|
28
|
+
# Sequel::Model.plugin :many_to_one_pk_lookup
|
|
29
|
+
#
|
|
30
|
+
# # Do so for just the album class.
|
|
31
|
+
# Album.plugin :many_to_one_pk_lookup
|
|
32
|
+
module ManyToOnePkLookup
|
|
33
|
+
module InstanceMethods
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# If the current association is a fairly simple many_to_one association, use
|
|
37
|
+
# a simple primary key lookup on the associated model, which can benefit from
|
|
38
|
+
# caching if the associated model is using caching.
|
|
39
|
+
def _load_associated_object(opts, dynamic_opts)
|
|
40
|
+
klass = opts.associated_class
|
|
41
|
+
cache_lookup = opts.send(:cached_fetch, :many_to_one_pk_lookup) do
|
|
42
|
+
opts[:type] == :many_to_one &&
|
|
43
|
+
opts[:key] &&
|
|
44
|
+
opts.primary_key == klass.primary_key
|
|
45
|
+
end
|
|
46
|
+
if cache_lookup &&
|
|
47
|
+
!dynamic_opts[:callback] &&
|
|
48
|
+
(o = klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk))))
|
|
49
|
+
o
|
|
50
|
+
else
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Deal with the situation where the prepared_statements_associations plugin is
|
|
56
|
+
# loaded first, by using a primary key lookup for many_to_one associations if
|
|
57
|
+
# the associated class is using caching, and using the default code otherwise.
|
|
58
|
+
# This is done because the prepared_statements_associations code is probably faster
|
|
59
|
+
# than the primary key lookup this plugin uses if the model is not caching lookups,
|
|
60
|
+
# but probably slower if the model is caching lookups.
|
|
61
|
+
def _load_associated_objects(opts, dynamic_opts={})
|
|
62
|
+
if opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one && opts.associated_class.respond_to?(:cache_get_pk)
|
|
63
|
+
_load_associated_object(opts, dynamic_opts)
|
|
64
|
+
else
|
|
65
|
+
super
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,20 +1,75 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module Plugins
|
|
3
|
-
# The nested_attributes plugin allows you to update
|
|
4
|
-
# objects directly
|
|
5
|
-
# Nested
|
|
6
|
-
#
|
|
7
|
-
# Nested attributes are created using the nested_attributes method:
|
|
3
|
+
# The nested_attributes plugin allows you to create, update, and delete
|
|
4
|
+
# associated objects directly by calling a method on the current object.
|
|
5
|
+
# Nested attributes are defined using the nested_attributes class method:
|
|
8
6
|
#
|
|
9
7
|
# Artist.one_to_many :albums
|
|
10
8
|
# Artist.plugin :nested_attributes
|
|
11
9
|
# Artist.nested_attributes :albums
|
|
12
|
-
# a = Artist.new(:name=>'YJM',
|
|
13
|
-
# :albums_attributes=>[{:name=>'RF'}, {:name=>'MO'}])
|
|
14
|
-
# # No database activity yet
|
|
15
10
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
11
|
+
# The nested_attributes call defines a single method, <tt><i>association</i>_attributes=</tt>,
|
|
12
|
+
# (e.g. <tt>albums_attributes=</tt>). So if you have an Artist instance:
|
|
13
|
+
#
|
|
14
|
+
# a = Artist.new(:name=>'YJM')
|
|
15
|
+
#
|
|
16
|
+
# You can create new album instances related to this artist:
|
|
17
|
+
#
|
|
18
|
+
# a.albums_attributes = [{:name=>'RF'}, {:name=>'MO'}]
|
|
19
|
+
#
|
|
20
|
+
# Note that this doesn't send any queries to the database yet. That doesn't happen till
|
|
21
|
+
# you save the object:
|
|
22
|
+
#
|
|
23
|
+
# a.save
|
|
24
|
+
#
|
|
25
|
+
# That will save the artist first, and then save both albums. If either the artist
|
|
26
|
+
# is invalid or one of the albums is invalid, none of the objects will be saved to the
|
|
27
|
+
# database, and all related validation errors will be available in the artist's validation
|
|
28
|
+
# errors.
|
|
29
|
+
#
|
|
30
|
+
# In addition to creating new associated objects, you can also update existing associated
|
|
31
|
+
# objects. You just need to make sure that the primary key field is filled in for the
|
|
32
|
+
# associated object:
|
|
33
|
+
#
|
|
34
|
+
# a.update(:albums_attributes => [{:id=>1, :name=>'T'}])
|
|
35
|
+
#
|
|
36
|
+
# Since the primary key field is filled in, the plugin will update the album with id 1 instead
|
|
37
|
+
# of creating a new album.
|
|
38
|
+
#
|
|
39
|
+
# If you would like to delete the associated object instead of updating it, you add a _delete
|
|
40
|
+
# entry to the hash:
|
|
41
|
+
#
|
|
42
|
+
# a.update(:albums_attributes => [{:id=>1, :_delete=>true}])
|
|
43
|
+
#
|
|
44
|
+
# This will delete the related associated object from the database. If you want to leave the
|
|
45
|
+
# associated object in the database, but just remove it from the association, add a _remove
|
|
46
|
+
# entry in the hash:
|
|
47
|
+
#
|
|
48
|
+
# a.update(:albums_attributes => [{:id=>1, :_remove=>true}])
|
|
49
|
+
#
|
|
50
|
+
# The above example was for a one_to_many association, but the plugin also works similarly
|
|
51
|
+
# for other association types. For one_to_one and many_to_one associations, you need to
|
|
52
|
+
# pass a single hash instead of an array of hashes.
|
|
53
|
+
#
|
|
54
|
+
# This plugin is mainly designed to make it easy to use on html forms, where a single form
|
|
55
|
+
# submission can contained nested attributes (and even nested attributes of those attributes).
|
|
56
|
+
# You just need to name your form inputs correctly:
|
|
57
|
+
#
|
|
58
|
+
# artist[name]
|
|
59
|
+
# artist[albums_attributes][0][:name]
|
|
60
|
+
# artist[albums_attributes][1][:id]
|
|
61
|
+
# artist[albums_attributes][1][:name]
|
|
62
|
+
#
|
|
63
|
+
# Your web stack will probably parse that into a nested hash similar to:
|
|
64
|
+
#
|
|
65
|
+
# {:artist=>{:name=>?, :albums_attributes=>{0=>{:name=>?}, 1=>{:id=>?, :name=>?}}}}
|
|
66
|
+
#
|
|
67
|
+
# Then you can do:
|
|
68
|
+
#
|
|
69
|
+
# artist.update(params[:artist])
|
|
70
|
+
#
|
|
71
|
+
# To save changes to the artist, create the first album and associate it to the artist,
|
|
72
|
+
# and update the other existing associated album.
|
|
18
73
|
module NestedAttributes
|
|
19
74
|
# Depend on the instance_hooks plugin.
|
|
20
75
|
def self.apply(model)
|
|
@@ -32,20 +87,30 @@ module Sequel
|
|
|
32
87
|
# modified through the association_attributes= method to the specific fields given.
|
|
33
88
|
# * :limit - For *_to_many associations, a limit on the number of records
|
|
34
89
|
# that will be processed, to prevent denial of service attacks.
|
|
90
|
+
# * :reject_if - A proc that is given each attribute hash before it is
|
|
91
|
+
# passed to its associated object. If the proc returns a truthy
|
|
92
|
+
# value, the attribute hash is ignored.
|
|
35
93
|
# * :remove - Allow disassociation of nested records (can remove the associated
|
|
36
94
|
# object from the parent object, but not destroy the associated object).
|
|
37
|
-
# * :strict -
|
|
38
|
-
#
|
|
39
|
-
#
|
|
95
|
+
# * :strict - Kept for backward compatibility. Setting it to false is
|
|
96
|
+
# equivalent to setting :unmatched_pk to :ignore.
|
|
97
|
+
# * :transform - A proc to transform attribute hashes before they are
|
|
98
|
+
# passed to associated object. Takes two arguments, the parent object and
|
|
99
|
+
# the attribute hash. Uses the return value as the new attribute hash.
|
|
100
|
+
# * :unmatched_pk - Specify the action to be taken if a primary key is
|
|
101
|
+
# provided in a record, but it doesn't match an existing associated
|
|
102
|
+
# object. Set to :create to create a new object with that primary
|
|
103
|
+
# key, :ignore to ignore the record, or :raise to raise an error.
|
|
104
|
+
# The default is :raise.
|
|
40
105
|
#
|
|
41
|
-
# If a block is provided, it is
|
|
42
|
-
# the hash should be ignored, the block should return anything except false or nil.
|
|
106
|
+
# If a block is provided, it is used to set the :reject_if option.
|
|
43
107
|
def nested_attributes(*associations, &block)
|
|
44
108
|
include(self.nested_attributes_module ||= Module.new) unless nested_attributes_module
|
|
45
109
|
opts = associations.last.is_a?(Hash) ? associations.pop : {}
|
|
46
110
|
reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
|
|
47
111
|
reflections.each do |r|
|
|
48
112
|
r[:nested_attributes] = opts
|
|
113
|
+
r[:nested_attributes][:unmatched_pk] ||= opts.delete(:strict) == false ? :ignore : :raise
|
|
49
114
|
r[:nested_attributes][:reject_if] ||= block
|
|
50
115
|
def_nested_attribute_method(r)
|
|
51
116
|
end
|
|
@@ -78,7 +143,9 @@ module Sequel
|
|
|
78
143
|
def nested_attributes_check_key_modifications(reflection, obj)
|
|
79
144
|
keys = reflection.associated_object_keys.map{|x| obj.send(x)}
|
|
80
145
|
yield
|
|
81
|
-
|
|
146
|
+
unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
|
|
147
|
+
raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed")
|
|
148
|
+
end
|
|
82
149
|
end
|
|
83
150
|
|
|
84
151
|
# Create a new associated object with the given attributes, validate
|
|
@@ -93,6 +160,12 @@ module Sequel
|
|
|
93
160
|
after_save_hook{send(reflection.add_method, obj)}
|
|
94
161
|
else
|
|
95
162
|
associations[reflection[:name]] = obj
|
|
163
|
+
|
|
164
|
+
# Because we are modifying the associations cache manually before the
|
|
165
|
+
# setter is called, we still want to run the setter code even though
|
|
166
|
+
# the cached value will be the same as the given value.
|
|
167
|
+
@set_associated_object_if_same = true
|
|
168
|
+
|
|
96
169
|
# Don't need to validate the object twice if :validate association option is not false
|
|
97
170
|
# and don't want to validate it at all if it is false.
|
|
98
171
|
send(reflection[:type] == :many_to_one ? :before_save_hook : :after_save_hook){send(reflection.setter_method, obj.save(:validate=>false))}
|
|
@@ -100,16 +173,6 @@ module Sequel
|
|
|
100
173
|
obj
|
|
101
174
|
end
|
|
102
175
|
|
|
103
|
-
# Find an associated object with the matching pk. If a matching option
|
|
104
|
-
# is not found and the :strict option is not false, raise an Error.
|
|
105
|
-
def nested_attributes_find(reflection, pk)
|
|
106
|
-
pk = pk.to_s
|
|
107
|
-
unless obj = Array(associated_objects = send(reflection[:name])).find{|x| x.pk.to_s == pk}
|
|
108
|
-
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})") unless reflection[:nested_attributes][:strict] == false
|
|
109
|
-
end
|
|
110
|
-
obj
|
|
111
|
-
end
|
|
112
|
-
|
|
113
176
|
# Take an array or hash of attribute hashes and set each one individually.
|
|
114
177
|
# If a hash is provided it, sort it by key and then use the values.
|
|
115
178
|
# If there is a limit on the nested attributes for this association,
|
|
@@ -122,24 +185,22 @@ module Sequel
|
|
|
122
185
|
attributes_list.each{|a| nested_attributes_setter(reflection, a)}
|
|
123
186
|
end
|
|
124
187
|
|
|
125
|
-
# Remove the
|
|
126
|
-
#
|
|
188
|
+
# Remove the given associated object from the current object. If the
|
|
189
|
+
# :destroy option is given, destroy the object after disassociating it
|
|
127
190
|
# (unless destroying the object would automatically disassociate it).
|
|
128
|
-
# Returns the object removed
|
|
129
|
-
def nested_attributes_remove(reflection,
|
|
130
|
-
if
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
send(reflection.setter_method, nil)
|
|
137
|
-
end
|
|
191
|
+
# Returns the object removed.
|
|
192
|
+
def nested_attributes_remove(reflection, obj, opts={})
|
|
193
|
+
if !opts[:destroy] || reflection.remove_before_destroy?
|
|
194
|
+
before_save_hook do
|
|
195
|
+
if reflection.returns_array?
|
|
196
|
+
send(reflection.remove_method, obj)
|
|
197
|
+
else
|
|
198
|
+
send(reflection.setter_method, nil)
|
|
138
199
|
end
|
|
139
200
|
end
|
|
140
|
-
after_save_hook{obj.destroy} if opts[:destroy]
|
|
141
|
-
obj
|
|
142
201
|
end
|
|
202
|
+
after_save_hook{obj.destroy} if opts[:destroy]
|
|
203
|
+
obj
|
|
143
204
|
end
|
|
144
205
|
|
|
145
206
|
# Set the fields in the obj based on the association, only allowing
|
|
@@ -152,42 +213,57 @@ module Sequel
|
|
|
152
213
|
end
|
|
153
214
|
end
|
|
154
215
|
|
|
155
|
-
# Modify the associated object based on the contents of the
|
|
216
|
+
# Modify the associated object based on the contents of the attributes hash:
|
|
217
|
+
# * If a :transform block was given to nested_attributes, use it to modify the attribute hash.
|
|
156
218
|
# * If a block was given to nested_attributes, call it with the attributes and return immediately if the block returns true.
|
|
219
|
+
# * If a primary key exists in the attributes hash and it matches an associated object:
|
|
220
|
+
# ** If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
|
|
221
|
+
# ** If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
|
|
222
|
+
# ** Otherwise, update the matching associated object with the contents of the hash.
|
|
223
|
+
# * If a primary key exists in the attributes hash but it does not match an associated object,
|
|
224
|
+
# either raise an error, create a new object or ignore the hash, depending on the :unmatched_pk option.
|
|
157
225
|
# * If no primary key exists in the attributes hash, create a new object.
|
|
158
|
-
# * If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
|
|
159
|
-
# * If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
|
|
160
|
-
# * Otherwise, update the matching associated object with the contents of the hash.
|
|
161
226
|
def nested_attributes_setter(reflection, attributes)
|
|
227
|
+
if a = reflection[:nested_attributes][:transform]
|
|
228
|
+
attributes = a.call(self, attributes)
|
|
229
|
+
end
|
|
162
230
|
return if (b = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
|
|
163
231
|
modified!
|
|
164
232
|
klass = reflection.associated_class
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
233
|
+
sym_keys = Array(klass.primary_key)
|
|
234
|
+
str_keys = sym_keys.map{|k| k.to_s}
|
|
235
|
+
if (pk = attributes.values_at(*sym_keys)).all? || (pk = attributes.values_at(*str_keys)).all?
|
|
236
|
+
pk = pk.map{|k| k.to_s}
|
|
237
|
+
obj = Array(send(reflection[:name])).find{|x| Array(x.pk).map{|k| k.to_s} == pk}
|
|
238
|
+
end
|
|
239
|
+
if obj
|
|
240
|
+
attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
|
|
241
|
+
if reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
|
|
242
|
+
nested_attributes_remove(reflection, obj, :destroy=>true)
|
|
243
|
+
elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
|
|
244
|
+
nested_attributes_remove(reflection, obj)
|
|
170
245
|
else
|
|
171
|
-
nested_attributes_update(reflection,
|
|
246
|
+
nested_attributes_update(reflection, obj, attributes)
|
|
247
|
+
end
|
|
248
|
+
elsif pk.all? && reflection[:nested_attributes][:unmatched_pk] != :create
|
|
249
|
+
if reflection[:nested_attributes][:unmatched_pk] == :raise
|
|
250
|
+
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})")
|
|
172
251
|
end
|
|
173
252
|
else
|
|
174
253
|
nested_attributes_create(reflection, attributes)
|
|
175
254
|
end
|
|
176
255
|
end
|
|
177
256
|
|
|
178
|
-
# Update the
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
after_save_hook{obj.save_changes(:validate=>false)}
|
|
189
|
-
obj
|
|
190
|
-
end
|
|
257
|
+
# Update the given object with the attributes, validating it when the
|
|
258
|
+
# parent object is validated and saving it when the parent is saved.
|
|
259
|
+
# Returns the object updated.
|
|
260
|
+
def nested_attributes_update(reflection, obj, attributes)
|
|
261
|
+
nested_attributes_update_attributes(reflection, obj, attributes)
|
|
262
|
+
after_validation_hook{validate_associated_object(reflection, obj)}
|
|
263
|
+
# Don't need to validate the object twice if :validate association option is not false
|
|
264
|
+
# and don't want to validate it at all if it is false.
|
|
265
|
+
after_save_hook{obj.save_changes(:validate=>false)}
|
|
266
|
+
obj
|
|
191
267
|
end
|
|
192
268
|
|
|
193
269
|
# Update the attributes for the given object related to the current object through the association.
|
|
@@ -65,6 +65,14 @@ module Sequel
|
|
|
65
65
|
lc = model.lock_column
|
|
66
66
|
instance_filter(lc=>send(lc))
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
# Clear the instance filters when refreshing, so that attempting to
|
|
70
|
+
# refresh after a failed save removes the previous lock column filter
|
|
71
|
+
# (the new one will be added before updating).
|
|
72
|
+
def _refresh(ds)
|
|
73
|
+
clear_instance_filters
|
|
74
|
+
super
|
|
75
|
+
end
|
|
68
76
|
|
|
69
77
|
# Only update the row if it has the same lock version, and increment the
|
|
70
78
|
# lock version.
|