sequel 3.21.0 → 3.39.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 +1061 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +103 -63
- data/Rakefile +59 -27
- data/bin/sequel +50 -26
- data/doc/active_record.rdoc +67 -51
- data/doc/advanced_associations.rdoc +189 -75
- data/doc/association_basics.rdoc +327 -79
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +10 -10
- data/doc/dataset_filtering.rdoc +54 -42
- data/doc/mass_assignment.rdoc +56 -0
- data/doc/migration.rdoc +89 -557
- data/doc/model_hooks.rdoc +80 -29
- data/doc/object_model.rdoc +547 -0
- data/doc/opening_databases.rdoc +114 -53
- data/doc/prepared_statements.rdoc +41 -15
- data/doc/querying.rdoc +137 -56
- data/doc/reflection.rdoc +36 -10
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/release_notes/3.31.0.txt +146 -0
- data/doc/release_notes/3.32.0.txt +202 -0
- data/doc/release_notes/3.33.0.txt +157 -0
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/release_notes/3.36.0.txt +245 -0
- data/doc/release_notes/3.37.0.txt +338 -0
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/schema_modification.rdoc +585 -0
- data/doc/sharding.rdoc +99 -8
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +169 -0
- data/doc/thread_safety.rdoc +17 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +78 -43
- data/lib/sequel/adapters/ado/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 +2 -61
- 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 +56 -0
- data/lib/sequel/adapters/jdbc/derby.rb +325 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
- data/lib/sequel/adapters/jdbc/h2.rb +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 +40 -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 +77 -52
- data/lib/sequel/adapters/jdbc/progress.rb +21 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
- data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +279 -97
- data/lib/sequel/adapters/mock.rb +372 -0
- data/lib/sequel/adapters/mysql.rb +125 -222
- data/lib/sequel/adapters/mysql2.rb +65 -56
- data/lib/sequel/adapters/odbc/mssql.rb +17 -9
- 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 +368 -138
- data/lib/sequel/adapters/shared/access.rb +32 -9
- data/lib/sequel/adapters/shared/db2.rb +343 -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 +401 -116
- data/lib/sequel/adapters/shared/mysql.rb +424 -89
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
- data/lib/sequel/adapters/shared/oracle.rb +257 -45
- data/lib/sequel/adapters/shared/postgres.rb +675 -279
- data/lib/sequel/adapters/shared/progress.rb +5 -8
- data/lib/sequel/adapters/shared/sqlite.rb +285 -96
- data/lib/sequel/adapters/sqlite.rb +101 -39
- data/lib/sequel/adapters/swift/mysql.rb +10 -12
- data/lib/sequel/adapters/swift/postgres.rb +13 -79
- data/lib/sequel/adapters/swift/sqlite.rb +9 -6
- data/lib/sequel/adapters/swift.rb +28 -19
- data/lib/sequel/adapters/tinytds.rb +174 -22
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
- data/lib/sequel/adapters/utils/pg_types.rb +81 -0
- data/lib/sequel/adapters/utils/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 +33 -2
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +43 -14
- data/lib/sequel/connection_pool.rb +10 -2
- data/lib/sequel/core.rb +143 -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 +190 -46
- data/lib/sequel/database/query.rb +272 -110
- data/lib/sequel/database/schema_generator.rb +103 -36
- data/lib/sequel/database/schema_methods.rb +300 -67
- data/lib/sequel/dataset/actions.rb +344 -90
- data/lib/sequel/dataset/features.rb +101 -8
- data/lib/sequel/dataset/graph.rb +42 -15
- data/lib/sequel/dataset/misc.rb +52 -25
- data/lib/sequel/dataset/mutation.rb +12 -7
- data/lib/sequel/dataset/prepared_statements.rb +68 -30
- data/lib/sequel/dataset/query.rb +305 -54
- data/lib/sequel/dataset/sql.rb +720 -292
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/_pretty_table.rb +83 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +72 -0
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
- data/lib/sequel/extensions/eval_inspect.rb +173 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +100 -11
- data/lib/sequel/extensions/named_timezones.rb +9 -0
- data/lib/sequel/extensions/null_dataset.rb +94 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +537 -0
- data/lib/sequel/extensions/pg_array_ops.rb +263 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
- data/lib/sequel/extensions/pg_hstore.rb +313 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
- data/lib/sequel/extensions/pg_inet.rb +113 -0
- data/lib/sequel/extensions/pg_interval.rb +191 -0
- data/lib/sequel/extensions/pg_json.rb +208 -0
- data/lib/sequel/extensions/pg_range.rb +512 -0
- data/lib/sequel/extensions/pg_range_ops.rb +150 -0
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +182 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
- data/lib/sequel/extensions/pretty_table.rb +17 -72
- data/lib/sequel/extensions/query.rb +8 -4
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +282 -76
- data/lib/sequel/extensions/select_remove.rb +39 -0
- data/lib/sequel/extensions/server_block.rb +140 -0
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +8 -110
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
- data/lib/sequel/extensions/to_dot.rb +99 -83
- data/lib/sequel/model/associations.rb +1263 -409
- data/lib/sequel/model/base.rb +624 -171
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +24 -2
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model.rb +30 -11
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/active_model.rb +13 -1
- data/lib/sequel/plugins/association_pks.rb +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/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +72 -0
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/eager_each.rb +59 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +134 -15
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +58 -6
- data/lib/sequel/plugins/list.rb +13 -2
- data/lib/sequel/plugins/many_through_many.rb +103 -51
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +150 -66
- data/lib/sequel/plugins/optimistic_locking.rb +8 -0
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/prepared_statements.rb +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 +31 -17
- data/lib/sequel/plugins/schema.rb +8 -3
- data/lib/sequel/plugins/serialization.rb +98 -49
- data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
- data/lib/sequel/plugins/sharding.rb +21 -54
- data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/subclasses.rb +29 -3
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/tree.rb +3 -3
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +55 -4
- data/lib/sequel/plugins/xml_serializer.rb +15 -4
- data/lib/sequel/sql.rb +649 -122
- data/lib/sequel/timezones.rb +67 -40
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +194 -66
- data/spec/adapters/mysql_spec.rb +475 -306
- data/spec/adapters/oracle_spec.rb +92 -117
- data/spec/adapters/postgres_spec.rb +1982 -325
- data/spec/adapters/spec_helper.rb +9 -6
- data/spec/adapters/sqlite_spec.rb +203 -75
- data/spec/core/connection_pool_spec.rb +218 -93
- data/spec/core/database_spec.rb +956 -522
- data/spec/core/dataset_spec.rb +1631 -1242
- data/spec/core/expression_filters_spec.rb +690 -334
- data/spec/core/mock_adapter_spec.rb +453 -0
- data/spec/core/object_graph_spec.rb +88 -129
- data/spec/core/schema_generator_spec.rb +4 -4
- data/spec/core/schema_spec.rb +556 -53
- data/spec/core/spec_helper.rb +7 -48
- data/spec/core_extensions_spec.rb +626 -0
- data/spec/extensions/active_model_spec.rb +13 -0
- data/spec/extensions/arbitrary_servers_spec.rb +110 -0
- data/spec/extensions/association_autoreloading_spec.rb +18 -10
- data/spec/extensions/association_dependencies_spec.rb +15 -25
- data/spec/extensions/association_pks_spec.rb +66 -32
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/boolean_readers_spec.rb +25 -25
- data/spec/extensions/caching_spec.rb +47 -51
- data/spec/extensions/class_table_inheritance_spec.rb +31 -83
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +88 -0
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/eager_each_spec.rb +34 -0
- data/spec/extensions/eval_inspect_spec.rb +67 -0
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +97 -128
- data/spec/extensions/identity_map_spec.rb +142 -24
- data/spec/extensions/inflector_spec.rb +0 -4
- data/spec/extensions/instance_filters_spec.rb +11 -21
- data/spec/extensions/instance_hooks_spec.rb +72 -0
- data/spec/extensions/json_serializer_spec.rb +49 -12
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +49 -40
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +368 -254
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +140 -35
- data/spec/extensions/named_timezones_spec.rb +29 -11
- data/spec/extensions/nested_attributes_spec.rb +268 -89
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +24 -21
- data/spec/extensions/pg_array_ops_spec.rb +112 -0
- data/spec/extensions/pg_array_spec.rb +320 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
- data/spec/extensions/pg_hstore_spec.rb +194 -0
- data/spec/extensions/pg_inet_spec.rb +47 -0
- data/spec/extensions/pg_interval_spec.rb +72 -0
- data/spec/extensions/pg_json_spec.rb +99 -0
- data/spec/extensions/pg_range_ops_spec.rb +56 -0
- data/spec/extensions/pg_range_spec.rb +395 -0
- data/spec/extensions/pg_row_ops_spec.rb +58 -0
- data/spec/extensions/pg_row_plugin_spec.rb +49 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_statement_cache_spec.rb +208 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
- data/spec/extensions/prepared_statements_spec.rb +87 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
- data/spec/extensions/pretty_table_spec.rb +7 -6
- data/spec/extensions/query_literals_spec.rb +167 -0
- data/spec/extensions/query_spec.rb +9 -3
- data/spec/extensions/rcte_tree_spec.rb +50 -43
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +434 -49
- data/spec/extensions/schema_spec.rb +30 -49
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
- data/spec/extensions/serialization_spec.rb +86 -48
- data/spec/extensions/server_block_spec.rb +90 -0
- data/spec/extensions/sharding_spec.rb +69 -143
- data/spec/extensions/single_table_inheritance_spec.rb +23 -29
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +34 -67
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/extensions/sql_expr_spec.rb +29 -60
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/subclasses_spec.rb +14 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +8 -11
- data/spec/extensions/touch_spec.rb +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 +58 -67
- data/spec/extensions/validation_helpers_spec.rb +31 -37
- data/spec/extensions/xml_serializer_spec.rb +29 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
- data/spec/integration/associations_test.rb +659 -21
- data/spec/integration/database_test.rb +11 -5
- data/spec/integration/dataset_test.rb +717 -129
- data/spec/integration/eager_loader_test.rb +38 -54
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +81 -2
- data/spec/integration/plugin_test.rb +604 -147
- data/spec/integration/prepared_statement_test.rb +272 -128
- data/spec/integration/schema_test.rb +367 -57
- data/spec/integration/spec_helper.rb +42 -32
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +183 -8
- data/spec/integration/type_test.rb +31 -8
- data/spec/model/association_reflection_spec.rb +207 -8
- data/spec/model/associations_spec.rb +1206 -693
- data/spec/model/base_spec.rb +334 -100
- data/spec/model/dataset_methods_spec.rb +45 -27
- data/spec/model/eager_loading_spec.rb +828 -692
- data/spec/model/hooks_spec.rb +355 -67
- data/spec/model/model_spec.rb +251 -179
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +506 -232
- data/spec/model/spec_helper.rb +16 -77
- data/spec/model/validations_spec.rb +38 -2
- data/spec/rcov.opts +2 -0
- metadata +209 -80
- data/spec/core/core_sql_spec.rb +0 -451
|
@@ -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)
|
|
@@ -28,24 +83,37 @@ module Sequel
|
|
|
28
83
|
|
|
29
84
|
# Allow nested attributes to be set for the given associations. Options:
|
|
30
85
|
# * :destroy - Allow destruction of nested records.
|
|
31
|
-
# * :fields - If provided, should be an Array.
|
|
32
|
-
#
|
|
86
|
+
# * :fields - If provided, should be an Array or proc. If it is an array,
|
|
87
|
+
# restricts the fields allowed to be modified through the
|
|
88
|
+
# association_attributes= method to the specific fields given. If it is
|
|
89
|
+
# a proc, it will be called with the associated object and should return an
|
|
90
|
+
# array of the allowable fields.
|
|
33
91
|
# * :limit - For *_to_many associations, a limit on the number of records
|
|
34
92
|
# that will be processed, to prevent denial of service attacks.
|
|
93
|
+
# * :reject_if - A proc that is given each attribute hash before it is
|
|
94
|
+
# passed to its associated object. If the proc returns a truthy
|
|
95
|
+
# value, the attribute hash is ignored.
|
|
35
96
|
# * :remove - Allow disassociation of nested records (can remove the associated
|
|
36
97
|
# object from the parent object, but not destroy the associated object).
|
|
37
|
-
# * :strict -
|
|
38
|
-
#
|
|
39
|
-
#
|
|
98
|
+
# * :strict - Kept for backward compatibility. Setting it to false is
|
|
99
|
+
# equivalent to setting :unmatched_pk to :ignore.
|
|
100
|
+
# * :transform - A proc to transform attribute hashes before they are
|
|
101
|
+
# passed to associated object. Takes two arguments, the parent object and
|
|
102
|
+
# the attribute hash. Uses the return value as the new attribute hash.
|
|
103
|
+
# * :unmatched_pk - Specify the action to be taken if a primary key is
|
|
104
|
+
# provided in a record, but it doesn't match an existing associated
|
|
105
|
+
# object. Set to :create to create a new object with that primary
|
|
106
|
+
# key, :ignore to ignore the record, or :raise to raise an error.
|
|
107
|
+
# The default is :raise.
|
|
40
108
|
#
|
|
41
|
-
# If a block is provided, it is
|
|
42
|
-
# the hash should be ignored, the block should return anything except false or nil.
|
|
109
|
+
# If a block is provided, it is used to set the :reject_if option.
|
|
43
110
|
def nested_attributes(*associations, &block)
|
|
44
111
|
include(self.nested_attributes_module ||= Module.new) unless nested_attributes_module
|
|
45
112
|
opts = associations.last.is_a?(Hash) ? associations.pop : {}
|
|
46
113
|
reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
|
|
47
114
|
reflections.each do |r|
|
|
48
115
|
r[:nested_attributes] = opts
|
|
116
|
+
r[:nested_attributes][:unmatched_pk] ||= opts.delete(:strict) == false ? :ignore : :raise
|
|
49
117
|
r[:nested_attributes][:reject_if] ||= block
|
|
50
118
|
def_nested_attribute_method(r)
|
|
51
119
|
end
|
|
@@ -78,7 +146,9 @@ module Sequel
|
|
|
78
146
|
def nested_attributes_check_key_modifications(reflection, obj)
|
|
79
147
|
keys = reflection.associated_object_keys.map{|x| obj.send(x)}
|
|
80
148
|
yield
|
|
81
|
-
|
|
149
|
+
unless keys == reflection.associated_object_keys.map{|x| obj.send(x)}
|
|
150
|
+
raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed")
|
|
151
|
+
end
|
|
82
152
|
end
|
|
83
153
|
|
|
84
154
|
# Create a new associated object with the given attributes, validate
|
|
@@ -93,19 +163,19 @@ module Sequel
|
|
|
93
163
|
after_save_hook{send(reflection.add_method, obj)}
|
|
94
164
|
else
|
|
95
165
|
associations[reflection[:name]] = obj
|
|
166
|
+
|
|
167
|
+
# Because we are modifying the associations cache manually before the
|
|
168
|
+
# setter is called, we still want to run the setter code even though
|
|
169
|
+
# the cached value will be the same as the given value.
|
|
170
|
+
@set_associated_object_if_same = true
|
|
171
|
+
|
|
96
172
|
# Don't need to validate the object twice if :validate association option is not false
|
|
97
173
|
# and don't want to validate it at all if it is false.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
174
|
+
if reflection[:type] == :many_to_one
|
|
175
|
+
before_save_hook{send(reflection.setter_method, obj.save(:validate=>false))}
|
|
176
|
+
else
|
|
177
|
+
after_save_hook{send(reflection.setter_method, obj)}
|
|
178
|
+
end
|
|
109
179
|
end
|
|
110
180
|
obj
|
|
111
181
|
end
|
|
@@ -122,72 +192,86 @@ module Sequel
|
|
|
122
192
|
attributes_list.each{|a| nested_attributes_setter(reflection, a)}
|
|
123
193
|
end
|
|
124
194
|
|
|
125
|
-
# Remove the
|
|
126
|
-
#
|
|
195
|
+
# Remove the given associated object from the current object. If the
|
|
196
|
+
# :destroy option is given, destroy the object after disassociating it
|
|
127
197
|
# (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
|
|
198
|
+
# Returns the object removed.
|
|
199
|
+
def nested_attributes_remove(reflection, obj, opts={})
|
|
200
|
+
if !opts[:destroy] || reflection.remove_before_destroy?
|
|
201
|
+
before_save_hook do
|
|
202
|
+
if reflection.returns_array?
|
|
203
|
+
send(reflection.remove_method, obj)
|
|
204
|
+
else
|
|
205
|
+
send(reflection.setter_method, nil)
|
|
138
206
|
end
|
|
139
207
|
end
|
|
140
|
-
after_save_hook{obj.destroy} if opts[:destroy]
|
|
141
|
-
obj
|
|
142
208
|
end
|
|
209
|
+
after_save_hook{obj.destroy} if opts[:destroy]
|
|
210
|
+
obj
|
|
143
211
|
end
|
|
144
212
|
|
|
145
213
|
# Set the fields in the obj based on the association, only allowing
|
|
146
214
|
# specific :fields if configured.
|
|
147
215
|
def nested_attributes_set_attributes(reflection, obj, attributes)
|
|
148
216
|
if fields = reflection[:nested_attributes][:fields]
|
|
217
|
+
fields = fields.call(obj) if fields.respond_to?(:call)
|
|
149
218
|
obj.set_only(attributes, fields)
|
|
150
219
|
else
|
|
151
220
|
obj.set(attributes)
|
|
152
221
|
end
|
|
153
222
|
end
|
|
154
223
|
|
|
155
|
-
# Modify the associated object based on the contents of the
|
|
224
|
+
# Modify the associated object based on the contents of the attributes hash:
|
|
225
|
+
# * If a :transform block was given to nested_attributes, use it to modify the attribute hash.
|
|
156
226
|
# * If a block was given to nested_attributes, call it with the attributes and return immediately if the block returns true.
|
|
227
|
+
# * If a primary key exists in the attributes hash and it matches an associated object:
|
|
228
|
+
# ** If _delete is a key in the hash and the :destroy option is used, destroy the matching associated object.
|
|
229
|
+
# ** If _remove is a key in the hash and the :remove option is used, disassociated the matching associated object.
|
|
230
|
+
# ** Otherwise, update the matching associated object with the contents of the hash.
|
|
231
|
+
# * If a primary key exists in the attributes hash but it does not match an associated object,
|
|
232
|
+
# either raise an error, create a new object or ignore the hash, depending on the :unmatched_pk option.
|
|
157
233
|
# * 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
234
|
def nested_attributes_setter(reflection, attributes)
|
|
235
|
+
if a = reflection[:nested_attributes][:transform]
|
|
236
|
+
attributes = a.call(self, attributes)
|
|
237
|
+
end
|
|
162
238
|
return if (b = reflection[:nested_attributes][:reject_if]) && b.call(attributes)
|
|
163
239
|
modified!
|
|
164
240
|
klass = reflection.associated_class
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
241
|
+
sym_keys = Array(klass.primary_key)
|
|
242
|
+
str_keys = sym_keys.map{|k| k.to_s}
|
|
243
|
+
if (pk = attributes.values_at(*sym_keys)).all? || (pk = attributes.values_at(*str_keys)).all?
|
|
244
|
+
pk = pk.map{|k| k.to_s}
|
|
245
|
+
obj = Array(send(reflection[:name])).find{|x| Array(x.pk).map{|k| k.to_s} == pk}
|
|
246
|
+
end
|
|
247
|
+
if obj
|
|
248
|
+
attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
|
|
249
|
+
if reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
|
|
250
|
+
nested_attributes_remove(reflection, obj, :destroy=>true)
|
|
251
|
+
elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
|
|
252
|
+
nested_attributes_remove(reflection, obj)
|
|
170
253
|
else
|
|
171
|
-
nested_attributes_update(reflection,
|
|
254
|
+
nested_attributes_update(reflection, obj, attributes)
|
|
255
|
+
end
|
|
256
|
+
elsif pk.all? && reflection[:nested_attributes][:unmatched_pk] != :create
|
|
257
|
+
if reflection[:nested_attributes][:unmatched_pk] == :raise
|
|
258
|
+
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})")
|
|
172
259
|
end
|
|
173
260
|
else
|
|
174
261
|
nested_attributes_create(reflection, attributes)
|
|
175
262
|
end
|
|
176
263
|
end
|
|
177
264
|
|
|
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
|
|
265
|
+
# Update the given object with the attributes, validating it when the
|
|
266
|
+
# parent object is validated and saving it when the parent is saved.
|
|
267
|
+
# Returns the object updated.
|
|
268
|
+
def nested_attributes_update(reflection, obj, attributes)
|
|
269
|
+
nested_attributes_update_attributes(reflection, obj, attributes)
|
|
270
|
+
after_validation_hook{validate_associated_object(reflection, obj)}
|
|
271
|
+
# Don't need to validate the object twice if :validate association option is not false
|
|
272
|
+
# and don't want to validate it at all if it is false.
|
|
273
|
+
after_save_hook{obj.save_changes(:validate=>false)}
|
|
274
|
+
obj
|
|
191
275
|
end
|
|
192
276
|
|
|
193
277
|
# 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.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The pg_row plugin allows you to use Sequel::Model classes as composite type
|
|
4
|
+
# classes, via the pg_row extension. So if you have an address table:
|
|
5
|
+
#
|
|
6
|
+
# DB.create_table(:address) do
|
|
7
|
+
# String :street
|
|
8
|
+
# String :city
|
|
9
|
+
# String :zip
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# and a company table with an address:
|
|
13
|
+
#
|
|
14
|
+
# DB.create_table(:company) do
|
|
15
|
+
# String :name
|
|
16
|
+
# address :address
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# You can create a Sequel::Model for the address table, and load the plugin,
|
|
20
|
+
# which registers the row type:
|
|
21
|
+
#
|
|
22
|
+
# class Address < Sequel::Model(:address)
|
|
23
|
+
# plugin :pg_row
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# Then when you select from the company table (even using a plain dataset),
|
|
27
|
+
# it will return address values as instances of Address:
|
|
28
|
+
#
|
|
29
|
+
# DB[:company].first
|
|
30
|
+
# # => {:name=>'MS', :address=>
|
|
31
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345')}
|
|
32
|
+
#
|
|
33
|
+
# If you want a lot of your models to be used as row types, you can load the
|
|
34
|
+
# plugin into Sequel::Model itself:
|
|
35
|
+
#
|
|
36
|
+
# Sequel::Model.plugin :pg_row
|
|
37
|
+
#
|
|
38
|
+
# And then call register_row_type in the class
|
|
39
|
+
#
|
|
40
|
+
# Address.register_row_type
|
|
41
|
+
#
|
|
42
|
+
# Note that automatic conversion only works with the native postgres adapter.
|
|
43
|
+
# For other adapters that connect to PostgreSQL, you need to call the conversion
|
|
44
|
+
# proc manually.
|
|
45
|
+
#
|
|
46
|
+
# In addition to returning row-valued/composite types as instances of Sequel::Model,
|
|
47
|
+
# this also lets you use model instances in datasets when inserting, updating, and
|
|
48
|
+
# filtering:
|
|
49
|
+
#
|
|
50
|
+
# DB[:company].insert(:name=>'MS', :address=>
|
|
51
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345'))
|
|
52
|
+
module PgRow
|
|
53
|
+
# When loading the extension, make sure the database has the pg_row extension
|
|
54
|
+
# loaded, load the custom database extensions, and automatically register the
|
|
55
|
+
# row type if the model has a dataset.
|
|
56
|
+
def self.configure(model)
|
|
57
|
+
model.db.extension(:pg_row)
|
|
58
|
+
model.db.extend(DatabaseMethods)
|
|
59
|
+
model.register_row_type if model.instance_variable_get(:@dataset)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module DatabaseMethods
|
|
63
|
+
ESCAPE_RE = /("|\\)/.freeze
|
|
64
|
+
ESCAPE_REPLACEMENT = '\\\\\1'.freeze
|
|
65
|
+
COMMA = ','
|
|
66
|
+
|
|
67
|
+
# Handle Sequel::Model instances in bound variables.
|
|
68
|
+
def bound_variable_arg(arg, conn)
|
|
69
|
+
case arg
|
|
70
|
+
when Sequel::Model
|
|
71
|
+
"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
|
|
72
|
+
else
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# If a Sequel::Model instance is given, return it as-is
|
|
78
|
+
# instead of attempting to convert it.
|
|
79
|
+
def row_type(db_type, v)
|
|
80
|
+
if v.is_a?(Sequel::Model)
|
|
81
|
+
v
|
|
82
|
+
else
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Handle Sequel::Model instances in bound variable arrays.
|
|
90
|
+
def bound_variable_array(arg)
|
|
91
|
+
case arg
|
|
92
|
+
when Sequel::Model
|
|
93
|
+
"\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
|
94
|
+
else
|
|
95
|
+
super
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
module ClassMethods
|
|
101
|
+
# Register the model's row type with the database.
|
|
102
|
+
def register_row_type
|
|
103
|
+
db.register_row_type(model.table_name, :converter=>self, :typecaster=>method(:new))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module InstanceMethods
|
|
108
|
+
ROW = 'ROW'.freeze
|
|
109
|
+
CAST = '::'.freeze
|
|
110
|
+
|
|
111
|
+
# Literalize the model instance and append it to the sql.
|
|
112
|
+
def sql_literal_append(ds, sql)
|
|
113
|
+
sql << ROW
|
|
114
|
+
ds.literal_append(sql, values.values_at(*columns))
|
|
115
|
+
sql << CAST
|
|
116
|
+
ds.quote_schema_table_append(sql, model.table_name)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|
|
4
|
+
# using the do, swift, or jdbc adapter, Sequel doesn't have complete
|
|
5
|
+
# control over typecasting, and may return columns as strings instead of how
|
|
6
|
+
# the native postgres adapter would typecast them. This is mostly needed for
|
|
7
|
+
# the additional support that the pg_* extensions add for advanced PostgreSQL
|
|
8
|
+
# types such as arrays.
|
|
9
|
+
#
|
|
10
|
+
# This plugin modifies Model#set_values to do the same conversion that the
|
|
11
|
+
# native postgres adapter would do for all columns given. You can either
|
|
12
|
+
# specify the columns to typecast on load in the plugin call itself, or
|
|
13
|
+
# afterwards using add_pg_typecast_on_load_columns:
|
|
14
|
+
#
|
|
15
|
+
# # aliases => text[] column
|
|
16
|
+
# # config => hstore column
|
|
17
|
+
#
|
|
18
|
+
# Album.plugin :pg_typecast_on_load, :aliases, :config
|
|
19
|
+
# # or:
|
|
20
|
+
# Album.plugin :pg_typecast_on_load
|
|
21
|
+
# Album.add_pg_typecast_on_load_columns :aliases, :config
|
|
22
|
+
#
|
|
23
|
+
# This plugin only handles values that the adapter returns as strings. If
|
|
24
|
+
# the adapter returns a value other than a string for a column, that value
|
|
25
|
+
# will be used directly without typecasting.
|
|
26
|
+
module PgTypecastOnLoad
|
|
27
|
+
# Call add_pg_typecast_on_load_columns on the passed column arguments.
|
|
28
|
+
def self.configure(model, *columns)
|
|
29
|
+
model.instance_eval do
|
|
30
|
+
@pg_typecast_on_load_columns ||= []
|
|
31
|
+
add_pg_typecast_on_load_columns(*columns)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
# The columns to typecast on load for this model.
|
|
37
|
+
attr_reader :pg_typecast_on_load_columns
|
|
38
|
+
|
|
39
|
+
# Add additional columns to typecast on load for this model.
|
|
40
|
+
def add_pg_typecast_on_load_columns(*columns)
|
|
41
|
+
@pg_typecast_on_load_columns.concat(columns)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Give the subclass a copy of the columns to typecast on load.
|
|
45
|
+
def inherited(subclass)
|
|
46
|
+
super
|
|
47
|
+
subclass.instance_variable_set(:@pg_typecast_on_load_columns, pg_typecast_on_load_columns.dup)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module InstanceMethods
|
|
52
|
+
# Lookup the conversion proc for the column's oid in the Database
|
|
53
|
+
# object, and use it to convert the value.
|
|
54
|
+
def set_values(values)
|
|
55
|
+
model.pg_typecast_on_load_columns.each do |c|
|
|
56
|
+
if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid])
|
|
57
|
+
values[c] = db.conversion_procs[oid].call(v)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The prepared_statements plugin modifies the model to use prepared statements for
|
|
4
|
+
# instance level deletes and saves, as well as class level lookups by
|
|
5
|
+
# primary key.
|
|
6
|
+
#
|
|
7
|
+
# Note that this plugin is unsafe in some circumstances, as it can allow up to
|
|
8
|
+
# 2^N prepared statements to be created for each type of insert and update query, where
|
|
9
|
+
# N is the number of colums in the table. It is recommended that you use the
|
|
10
|
+
# +prepared_statements_safe+ plugin in addition to this plugin to reduce the number
|
|
11
|
+
# of prepared statements that can be created, unless you tightly control how your
|
|
12
|
+
# model instances are saved.
|
|
13
|
+
#
|
|
14
|
+
# This plugin probably does not work correctly with the instance filters plugin.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
#
|
|
18
|
+
# # Make all model subclasses use prepared statements (called before loading subclasses)
|
|
19
|
+
# Sequel::Model.plugin :prepared_statements
|
|
20
|
+
#
|
|
21
|
+
# # Make the Album class use prepared statements
|
|
22
|
+
# Album.plugin :prepared_statements
|
|
23
|
+
module PreparedStatements
|
|
24
|
+
# Synchronize access to the integer sequence so that no two calls get the same integer.
|
|
25
|
+
MUTEX = Mutex.new
|
|
26
|
+
|
|
27
|
+
i = 0
|
|
28
|
+
# This plugin names prepared statements uniquely using an integer sequence, this
|
|
29
|
+
# lambda returns the next integer to use.
|
|
30
|
+
NEXT = lambda{MUTEX.synchronize{i += 1}}
|
|
31
|
+
|
|
32
|
+
# Setup the datastructure used to hold the prepared statements in the model.
|
|
33
|
+
def self.apply(model)
|
|
34
|
+
model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module ClassMethods
|
|
38
|
+
# Setup the datastructure used to hold the prepared statements in the subclass.
|
|
39
|
+
def inherited(subclass)
|
|
40
|
+
super
|
|
41
|
+
subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Create a prepared statement based on the given dataset with a unique name for the given
|
|
47
|
+
# type of query and values.
|
|
48
|
+
def prepare_statement(ds, type, vals={})
|
|
49
|
+
ps = ds.prepare(type, :"smpsp_#{NEXT.call}", vals)
|
|
50
|
+
ps.log_sql = true
|
|
51
|
+
ps
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Return a sorted array of columns for use as a hash key.
|
|
55
|
+
def prepared_columns(cols)
|
|
56
|
+
RUBY_VERSION >= '1.9' ? cols.sort : cols.sort_by{|c| c.to_s}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return a prepared statement that can be used to delete a row from this model's dataset.
|
|
60
|
+
def prepared_delete
|
|
61
|
+
cached_prepared_statement(:fixed, :delete){prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return a prepared statement that can be used to insert a row using the given columns.
|
|
65
|
+
def prepared_insert(cols)
|
|
66
|
+
cached_prepared_statement(:insert, prepared_columns(cols)){prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Return a prepared statement that can be used to insert a row using the given columns
|
|
70
|
+
# and return that column values for the row created.
|
|
71
|
+
def prepared_insert_select(cols)
|
|
72
|
+
if dataset.supports_insert_select?
|
|
73
|
+
cached_prepared_statement(:insert_select, prepared_columns(cols)){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Return a prepared statement that can be used to lookup a row solely based on the primary key.
|
|
78
|
+
def prepared_lookup
|
|
79
|
+
cached_prepared_statement(:fixed, :lookup){prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Return a prepared statement that can be used to refresh a row to get new column values after insertion.
|
|
83
|
+
def prepared_refresh
|
|
84
|
+
cached_prepared_statement(:fixed, :refresh){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Return an array of two element arrays with the column symbol as the first entry and the
|
|
88
|
+
# placeholder symbol as the second entry.
|
|
89
|
+
def prepared_statement_key_array(keys)
|
|
90
|
+
if dataset.requires_placeholder_type_specifiers?
|
|
91
|
+
sch = db_schema
|
|
92
|
+
Array(keys).map do |k|
|
|
93
|
+
if (s = sch[k]) && (t = s[:type])
|
|
94
|
+
[k, :"$#{k}__#{t}"]
|
|
95
|
+
else
|
|
96
|
+
[k, :"$#{k}"]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
Array(keys).map{|k| [k, :"$#{k}"]}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Return a hash mapping column symbols to placeholder symbols.
|
|
105
|
+
def prepared_statement_key_hash(keys)
|
|
106
|
+
Hash[*(prepared_statement_key_array(keys).flatten)]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Return a prepared statement that can be used to update row using the given columns.
|
|
110
|
+
def prepared_update(cols)
|
|
111
|
+
cached_prepared_statement(:update, prepared_columns(cols)){prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Use a prepared statement to query the database for the row matching the given primary key.
|
|
115
|
+
def primary_key_lookup(pk)
|
|
116
|
+
prepared_lookup.call(primary_key_hash(pk))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# If a prepared statement has already been cached for the given type and subtype,
|
|
122
|
+
# return it. Otherwise, yield to the block to get the prepared statement, and cache it.
|
|
123
|
+
def cached_prepared_statement(type, subtype)
|
|
124
|
+
h = @prepared_statements[type]
|
|
125
|
+
Sequel.synchronize do
|
|
126
|
+
if v = h[subtype]
|
|
127
|
+
return v end
|
|
128
|
+
end
|
|
129
|
+
ps = yield
|
|
130
|
+
Sequel.synchronize{h[subtype] = ps}
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
module InstanceMethods
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# Use a prepared statement to delete the row.
|
|
138
|
+
def _delete_without_checking
|
|
139
|
+
model.send(:prepared_delete).call(pk_hash)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Use a prepared statement to insert the values into the model's dataset.
|
|
143
|
+
def _insert_raw(ds)
|
|
144
|
+
model.send(:prepared_insert, @values.keys).call(@values)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Use a prepared statement to insert the values into the model's dataset
|
|
148
|
+
# and return the new column values.
|
|
149
|
+
def _insert_select_raw(ds)
|
|
150
|
+
if ps = model.send(:prepared_insert_select, @values.keys)
|
|
151
|
+
ps.call(@values)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Use a prepared statement to refresh this model's column values.
|
|
156
|
+
def _refresh_get(ds)
|
|
157
|
+
model.send(:prepared_refresh).call(pk_hash)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Use a prepared statement to update this model's columns in the database.
|
|
161
|
+
def _update_without_checking(columns)
|
|
162
|
+
model.send(:prepared_update, columns.keys).call(columns.merge(pk_hash))
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|