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
data/lib/sequel/model/errors.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Sequel
|
|
|
40
40
|
def full_messages
|
|
41
41
|
inject([]) do |m, kv|
|
|
42
42
|
att, errors = *kv
|
|
43
|
-
errors.each {|e| m << "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}"}
|
|
43
|
+
errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}")}
|
|
44
44
|
m
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
# Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
|
|
3
|
-
|
|
3
|
+
# or an around hook doesn't call super or yield.
|
|
4
|
+
class HookFailed < Error
|
|
5
|
+
# The Sequel::Model instance related to this error.
|
|
6
|
+
attr_reader :model
|
|
7
|
+
|
|
8
|
+
def initialize(message, model=nil)
|
|
9
|
+
@model = model
|
|
10
|
+
super(message)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Deprecated alias for HookFailed, kept for backwards compatibility
|
|
15
|
+
BeforeHookFailed = HookFailed
|
|
4
16
|
|
|
5
17
|
# Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
|
|
6
18
|
# modify a single row.
|
|
@@ -11,7 +23,18 @@ module Sequel
|
|
|
11
23
|
|
|
12
24
|
# Exception class raised when +raise_on_save_failure+ is set and validation fails
|
|
13
25
|
class ValidationFailed < Error
|
|
26
|
+
# The Sequel::Model object related to this exception.
|
|
27
|
+
attr_reader :model
|
|
28
|
+
|
|
29
|
+
# The Sequel::Model::Errors object related to this exception.
|
|
30
|
+
attr_reader :errors
|
|
31
|
+
|
|
14
32
|
def initialize(errors)
|
|
33
|
+
if errors.is_a?(Sequel::Model)
|
|
34
|
+
@model = errors
|
|
35
|
+
errors = @model.errors
|
|
36
|
+
end
|
|
37
|
+
|
|
15
38
|
if errors.respond_to?(:full_messages)
|
|
16
39
|
@errors = errors
|
|
17
40
|
super(errors.full_messages.join(', '))
|
|
@@ -19,6 +42,5 @@ module Sequel
|
|
|
19
42
|
super
|
|
20
43
|
end
|
|
21
44
|
end
|
|
22
|
-
attr_reader :errors
|
|
23
45
|
end
|
|
24
46
|
end
|
|
@@ -121,7 +121,7 @@ module Sequel
|
|
|
121
121
|
def constantize(s)
|
|
122
122
|
s = s.to_s
|
|
123
123
|
return s.constantize if s.respond_to?(:constantize)
|
|
124
|
-
raise(NameError, "#{inspect} is not a valid constant name!") unless m = VALID_CONSTANT_NAME_REGEXP.match(s
|
|
124
|
+
raise(NameError, "#{s.inspect} is not a valid constant name!") unless m = VALID_CONSTANT_NAME_REGEXP.match(s)
|
|
125
125
|
Object.module_eval("::#{m[1]}", __FILE__, __LINE__)
|
|
126
126
|
end
|
|
127
127
|
|
data/lib/sequel/model.rb
CHANGED
|
@@ -11,9 +11,9 @@ module Sequel
|
|
|
11
11
|
# with the +Database+ in +source+ to create the
|
|
12
12
|
# dataset to use)
|
|
13
13
|
# Dataset :: Sets the dataset for this model to +source+.
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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
17
|
#
|
|
18
18
|
# The purpose of this method is to set the dataset/database automatically
|
|
19
19
|
# for a model class, if the table name doesn't match the implicit
|
|
@@ -35,13 +35,18 @@ module Sequel
|
|
|
35
35
|
# dataset # => DB1[:comments]
|
|
36
36
|
# end
|
|
37
37
|
def self.Model(source)
|
|
38
|
-
Model::ANONYMOUS_MODEL_CLASSES[source]
|
|
38
|
+
if Sequel::Model.cache_anonymous_models && (klass = Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
|
|
39
|
+
return klass
|
|
40
|
+
end
|
|
41
|
+
klass = if source.is_a?(Database)
|
|
39
42
|
c = Class.new(Model)
|
|
40
43
|
c.db = source
|
|
41
44
|
c
|
|
42
45
|
else
|
|
43
46
|
Class.new(Model).set_dataset(source)
|
|
44
47
|
end
|
|
48
|
+
Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if Sequel::Model.cache_anonymous_models
|
|
49
|
+
klass
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
# <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
|
|
@@ -49,14 +54,18 @@ module Sequel
|
|
|
49
54
|
# called directly on the class. Model datasets return rows as model instances,
|
|
50
55
|
# which have fairly standard ORM instance behavior.
|
|
51
56
|
#
|
|
52
|
-
# <tt>Sequel::Model</tt> is built completely out of plugins
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
57
|
+
# <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
|
|
58
|
+
# instance, or dataset method defined by a previous plugin and call super to get the default
|
|
59
|
+
# behavior. By default, <tt>Sequel::Model</tt> loads two plugins, <tt>Sequel::Model</tt>
|
|
60
|
+
# (which is itself a plugin) for the base support, and <tt>Sequel::Model::Associations</tt>
|
|
61
|
+
# for the associations support.
|
|
56
62
|
#
|
|
57
63
|
# You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
|
|
58
64
|
# make Sequel not load the associations plugin by default.
|
|
59
65
|
class Model
|
|
66
|
+
# Cache anonymous models created by Sequel::Model()
|
|
67
|
+
@cache_anonymous_models = true
|
|
68
|
+
|
|
60
69
|
# Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
|
|
61
70
|
# of classes when dealing with code reloading.
|
|
62
71
|
ANONYMOUS_MODEL_CLASSES = {}
|
|
@@ -69,7 +78,8 @@ module Sequel
|
|
|
69
78
|
EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
|
|
70
79
|
|
|
71
80
|
# Boolean settings that can be modified at the global, class, or instance level.
|
|
72
|
-
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting,
|
|
81
|
+
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
|
|
82
|
+
:raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
|
|
73
83
|
|
|
74
84
|
# Hooks that are called before an action. Can return false to not do the action. When
|
|
75
85
|
# overriding these, it is recommended to call +super+ as the last line of your method,
|
|
@@ -78,7 +88,13 @@ module Sequel
|
|
|
78
88
|
|
|
79
89
|
# Hooks that are called after an action. When overriding these, it is recommended to call
|
|
80
90
|
# +super+ on the first line of your method, so later hooks are called after earlier hooks.
|
|
81
|
-
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
|
91
|
+
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
|
92
|
+
:after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
|
|
93
|
+
|
|
94
|
+
# Hooks that are called around an action. If overridden, these methods must call super
|
|
95
|
+
# exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
|
|
96
|
+
# raised by the code they wrap or ensure that some behavior is executed no matter what.
|
|
97
|
+
AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
|
|
82
98
|
|
|
83
99
|
# Empty instance methods to create that the user can override to get hook/callback behavior.
|
|
84
100
|
# Just like any other method defined by Sequel, if you override one of these, you should
|
|
@@ -96,7 +112,9 @@ module Sequel
|
|
|
96
112
|
:@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
|
97
113
|
:@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
|
|
98
114
|
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
|
|
99
|
-
:@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil
|
|
115
|
+
:@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
|
|
116
|
+
:@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
|
|
117
|
+
:@fast_instance_delete_sql=>nil}
|
|
100
118
|
|
|
101
119
|
# Regular expression that determines if a method name is normal in the sense that
|
|
102
120
|
# it could be used literally in ruby code without using send. Used to
|
|
@@ -126,6 +144,7 @@ module Sequel
|
|
|
126
144
|
@strict_param_setting = true
|
|
127
145
|
@typecast_empty_string_to_nil = true
|
|
128
146
|
@typecast_on_assignment = true
|
|
147
|
+
@use_after_commit_rollback = true
|
|
129
148
|
@use_transactions = true
|
|
130
149
|
|
|
131
150
|
Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
|
|
@@ -16,7 +16,14 @@ module Sequel
|
|
|
16
16
|
# # Make the Album class active_model compliant
|
|
17
17
|
# Album.plugin :active_model
|
|
18
18
|
module ActiveModel
|
|
19
|
-
ClassMethods
|
|
19
|
+
module ClassMethods
|
|
20
|
+
include ::ActiveModel::Naming
|
|
21
|
+
|
|
22
|
+
# Class level cache for to_partial_path.
|
|
23
|
+
def _to_partial_path
|
|
24
|
+
@_to_partial_path ||= "#{underscore(pluralize(to_s))}/#{underscore(demodulize(to_s))}".freeze
|
|
25
|
+
end
|
|
26
|
+
end
|
|
20
27
|
|
|
21
28
|
module InstanceMethods
|
|
22
29
|
# The default string to join composite primary keys with in to_param.
|
|
@@ -56,6 +63,11 @@ module Sequel
|
|
|
56
63
|
k.join(to_param_joiner)
|
|
57
64
|
end
|
|
58
65
|
end
|
|
66
|
+
|
|
67
|
+
# Returns a string identifying the path associated with the object.
|
|
68
|
+
def to_partial_path
|
|
69
|
+
model._to_partial_path
|
|
70
|
+
end
|
|
59
71
|
|
|
60
72
|
private
|
|
61
73
|
|
|
@@ -14,7 +14,7 @@ module Sequel
|
|
|
14
14
|
#
|
|
15
15
|
# Note that it uses the singular form of the association name. Also note
|
|
16
16
|
# that the setter both associates to new primary keys not in the assocation
|
|
17
|
-
# and
|
|
17
|
+
# and disassociates from primary keys not provided to the method.
|
|
18
18
|
#
|
|
19
19
|
# This plugin makes modifications directly to the underlying tables,
|
|
20
20
|
# it does not create or return any model objects, and therefore does
|
|
@@ -56,6 +56,7 @@ module Sequel
|
|
|
56
56
|
_join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key])).select_map(opts[:right_key])
|
|
57
57
|
end
|
|
58
58
|
def_association_pks_setter(opts) do |pks|
|
|
59
|
+
pks = convert_pk_array(opts, pks)
|
|
59
60
|
checked_transaction do
|
|
60
61
|
ds = _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key]))
|
|
61
62
|
ds.exclude(opts[:right_key]=>pks).delete
|
|
@@ -71,17 +72,34 @@ module Sequel
|
|
|
71
72
|
super
|
|
72
73
|
return if opts[:type] == :one_to_one
|
|
73
74
|
def_association_pks_getter(opts) do
|
|
74
|
-
send(opts.dataset_method).select_map(opts
|
|
75
|
+
send(opts.dataset_method).select_map(opts.associated_class.primary_key)
|
|
75
76
|
end
|
|
76
77
|
def_association_pks_setter(opts) do |pks|
|
|
78
|
+
pks = convert_pk_array(opts, pks)
|
|
77
79
|
checked_transaction do
|
|
78
80
|
ds = send(opts.dataset_method)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
primary_key = opts.associated_class.primary_key
|
|
82
|
+
key = opts[:key]
|
|
83
|
+
ds.unfiltered.filter(primary_key=>pks).update(key=>pk)
|
|
84
|
+
ds.exclude(primary_key=>pks).update(key=>nil)
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
88
|
end
|
|
89
|
+
|
|
90
|
+
module InstanceMethods
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# If the associated class's primary key column type is integer,
|
|
94
|
+
# typecast all provided values to integer before using them.
|
|
95
|
+
def convert_pk_array(opts, pks)
|
|
96
|
+
if klass = opts.associated_class and sch = klass.db_schema and col = sch[klass.primary_key] and col[:type] == :integer
|
|
97
|
+
pks.map{|pk| model.db.typecast_value(:integer, pk)}
|
|
98
|
+
else
|
|
99
|
+
pks
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
85
103
|
end
|
|
86
104
|
end
|
|
87
105
|
end
|
|
@@ -53,9 +53,21 @@ module Sequel
|
|
|
53
53
|
# The time to live for the cache store, in seconds.
|
|
54
54
|
attr_reader :cache_ttl
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
def
|
|
58
|
-
|
|
56
|
+
# Delete the cached object with the given primary key.
|
|
57
|
+
def cache_delete_pk(pk)
|
|
58
|
+
cache_delete(cache_key(pk))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Return the cached object with the given primary key,
|
|
62
|
+
# or nil if no such object is in the cache.
|
|
63
|
+
def cache_get_pk(pk)
|
|
64
|
+
cache_get(cache_key(pk))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return a key string for the given primary key.
|
|
68
|
+
def cache_key(pk)
|
|
69
|
+
raise(Error, 'no primary key for this record') unless pk.is_a?(Array) ? pk.all? : pk
|
|
70
|
+
"#{self}:#{Array(pk).join(',')}"
|
|
59
71
|
end
|
|
60
72
|
|
|
61
73
|
# Copy the necessary class instance variables to the subclass.
|
|
@@ -69,8 +81,13 @@ module Sequel
|
|
|
69
81
|
@cache_ttl = ttl
|
|
70
82
|
@cache_ignore_exceptions = cache_ignore_exceptions
|
|
71
83
|
end
|
|
72
|
-
end
|
|
84
|
+
end
|
|
73
85
|
|
|
86
|
+
# Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
|
|
87
|
+
def set_cache_ttl(ttl)
|
|
88
|
+
@cache_ttl = ttl
|
|
89
|
+
end
|
|
90
|
+
|
|
74
91
|
private
|
|
75
92
|
|
|
76
93
|
# Delete the entry with the matching key from the cache
|
|
@@ -79,6 +96,8 @@ module Sequel
|
|
|
79
96
|
nil
|
|
80
97
|
end
|
|
81
98
|
|
|
99
|
+
# Returned the cached object, or nil if the object was not
|
|
100
|
+
# in the cached
|
|
82
101
|
def cache_get(ck)
|
|
83
102
|
if @cache_ignore_exceptions
|
|
84
103
|
@cache_store.get(ck) rescue nil
|
|
@@ -87,11 +106,6 @@ module Sequel
|
|
|
87
106
|
end
|
|
88
107
|
end
|
|
89
108
|
|
|
90
|
-
# Return a key string for the pk
|
|
91
|
-
def cache_key(pk)
|
|
92
|
-
"#{self}:#{Array(pk).join(',')}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
109
|
# Set the object in the cache_store with the given key for cache_ttl seconds.
|
|
96
110
|
def cache_set(ck, obj)
|
|
97
111
|
@cache_store.set(ck, obj, @cache_ttl)
|
|
@@ -120,14 +134,7 @@ module Sequel
|
|
|
120
134
|
# primary key value(s) for the object. If the model does not have a primary
|
|
121
135
|
# key, raise an Error.
|
|
122
136
|
def cache_key
|
|
123
|
-
|
|
124
|
-
pk = case key
|
|
125
|
-
when Array
|
|
126
|
-
key.collect{|k| @values[k]}
|
|
127
|
-
else
|
|
128
|
-
@values[key] || (raise Error, 'no primary key for this record')
|
|
129
|
-
end
|
|
130
|
-
model.send(:cache_key, pk)
|
|
137
|
+
model.cache_key(pk)
|
|
131
138
|
end
|
|
132
139
|
|
|
133
140
|
# Remove the object from the cache when deleting
|
|
@@ -140,7 +147,7 @@ module Sequel
|
|
|
140
147
|
|
|
141
148
|
# Delete this object from the cache
|
|
142
149
|
def cache_delete
|
|
143
|
-
model.
|
|
150
|
+
model.cache_delete_pk(pk)
|
|
144
151
|
end
|
|
145
152
|
end
|
|
146
153
|
end
|
|
@@ -90,9 +90,9 @@ module Sequel
|
|
|
90
90
|
@cti_columns = {table_name=>columns}
|
|
91
91
|
@cti_table_map = opts[:table_map] || {}
|
|
92
92
|
dataset.row_proc = if key
|
|
93
|
-
lambda{|r| (m.call(r[key]) rescue model).
|
|
93
|
+
lambda{|r| (m.call(r[key]) rescue model).call(r)}
|
|
94
94
|
else
|
|
95
|
-
|
|
95
|
+
model
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
end
|
|
@@ -152,9 +152,9 @@ module Sequel
|
|
|
152
152
|
subclass.instance_eval do
|
|
153
153
|
m = method(:constantize)
|
|
154
154
|
dataset.row_proc = if cti_key
|
|
155
|
-
lambda{|r| (m.call(r[ck]) rescue subclass).
|
|
155
|
+
lambda{|r| (m.call(r[ck]) rescue subclass).call(r)}
|
|
156
156
|
else
|
|
157
|
-
|
|
157
|
+
subclass
|
|
158
158
|
end
|
|
159
159
|
(columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
|
|
160
160
|
cti_tables.reverse.each do |table|
|
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module Plugins
|
|
3
|
-
# The composition plugin allows you to easily define
|
|
4
|
-
#
|
|
5
|
-
# is composed of other getters and decomposed to other setters.
|
|
3
|
+
# The composition plugin allows you to easily define a virtual
|
|
4
|
+
# attribute where the backing data is composed of other columns.
|
|
6
5
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# with
|
|
6
|
+
# There are two ways to use the plugin. One way is with the
|
|
7
|
+
# :mapping option. A simple example of this is when you have a
|
|
8
|
+
# database table with separate columns for year, month, and day,
|
|
9
|
+
# but where you want to deal with Date objects in your ruby code.
|
|
10
|
+
# This can be handled with:
|
|
11
11
|
#
|
|
12
12
|
# Album.plugin :composition
|
|
13
13
|
# Album.composition :date, :mapping=>[:year, :month, :day]
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# :
|
|
15
|
+
# With the :mapping option, you can provide a :class option
|
|
16
|
+
# that gives the class to use, but if that is not provided, it
|
|
17
|
+
# is inferred from the name of the composition (e.g. :date -> Date).
|
|
18
|
+
# When the <tt>date</tt> method is called, it will return a
|
|
19
|
+
# Date object by calling:
|
|
20
|
+
#
|
|
21
|
+
# Date.new(year, month, day)
|
|
22
|
+
#
|
|
23
|
+
# When saving the object, if the date composition has been used
|
|
24
|
+
# (by calling either the getter or setter method), it will
|
|
25
|
+
# populate the related columns of the object before saving:
|
|
26
|
+
#
|
|
27
|
+
# self.year = date.year
|
|
28
|
+
# self.month = date.month
|
|
29
|
+
# self.day = date.day
|
|
30
|
+
#
|
|
31
|
+
# The :mapping option is just a shortcut that works in particular
|
|
32
|
+
# cases. To handle any case, you can define a custom :composer
|
|
33
|
+
# and :decomposer procs. The :composer proc will be instance_evaled
|
|
34
|
+
# the first time the getter is called, and the :decomposer proc
|
|
35
|
+
# will be instance_evaled before saving. The above example could
|
|
36
|
+
# also be implemented as:
|
|
37
|
+
#
|
|
38
|
+
# Album.composition :date,
|
|
39
|
+
# :composer=>proc{Date.new(year, month, day) if year || month || day},
|
|
40
|
+
# :decomposer=>(proc do
|
|
41
|
+
# if d = compositions[:date]
|
|
42
|
+
# self.year = d.year
|
|
43
|
+
# self.month = d.month
|
|
44
|
+
# self.day = d.day
|
|
45
|
+
# else
|
|
46
|
+
# self.year = nil
|
|
47
|
+
# self.month = nil
|
|
48
|
+
# self.day = nil
|
|
49
|
+
# end
|
|
50
|
+
# end)
|
|
18
51
|
#
|
|
19
52
|
# Note that when using the composition object, you should not
|
|
20
53
|
# modify the underlying columns if you are also instantiating
|
|
@@ -117,9 +150,8 @@ module Sequel
|
|
|
117
150
|
module InstanceMethods
|
|
118
151
|
# Clear the cached compositions when refreshing.
|
|
119
152
|
def _refresh(ds)
|
|
120
|
-
|
|
153
|
+
super
|
|
121
154
|
compositions.clear
|
|
122
|
-
v
|
|
123
155
|
end
|
|
124
156
|
|
|
125
157
|
# For each composition, set the columns in the model class based
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The constraint_validations plugin is designed to be used with databases
|
|
4
|
+
# that used the constraint_validations extension when creating their
|
|
5
|
+
# tables. The extension adds validation metadata for constraints created,
|
|
6
|
+
# and this plugin reads that metadata and automatically creates validations
|
|
7
|
+
# for all of the constraints. For example, if you used the extension
|
|
8
|
+
# and created your albums table like this:
|
|
9
|
+
#
|
|
10
|
+
# DB.create_table(:albums) do
|
|
11
|
+
# primary_key :id
|
|
12
|
+
# String :name
|
|
13
|
+
# validate do
|
|
14
|
+
# min_length 5, :name
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# Then when you went to save an album that uses this plugin:
|
|
19
|
+
#
|
|
20
|
+
# Album.create(:name=>'abc')
|
|
21
|
+
# # raises Sequel::ValidationFailed: name is shorter than 5 characters
|
|
22
|
+
#
|
|
23
|
+
# Usage:
|
|
24
|
+
#
|
|
25
|
+
# # Make all model subclasses use constraint validations (called before loading subclasses)
|
|
26
|
+
# Sequel::Model.plugin :constraint_validations
|
|
27
|
+
#
|
|
28
|
+
# # Make the Album class use constraint validations
|
|
29
|
+
# Album.plugin :constraint_validations
|
|
30
|
+
module ConstraintValidations
|
|
31
|
+
# The default constraint validation metadata table name.
|
|
32
|
+
DEFAULT_CONSTRAINT_VALIDATIONS_TABLE = :sequel_constraint_validations
|
|
33
|
+
|
|
34
|
+
# Automatically load the validation_helpers plugin to run the actual validations.
|
|
35
|
+
def self.apply(model, opts={})
|
|
36
|
+
model.plugin :validation_helpers
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Parse the constraint validations metadata from the database. Options:
|
|
40
|
+
# :constraint_validations_table :: Override the name of the constraint validations
|
|
41
|
+
# metadata table. Should only be used if the table
|
|
42
|
+
# name was overridden when creating the constraint
|
|
43
|
+
# validations.
|
|
44
|
+
def self.configure(model, opts={})
|
|
45
|
+
model.instance_variable_set(:@constraint_validations_table, opts[:constraint_validations_table] || DEFAULT_CONSTRAINT_VALIDATIONS_TABLE)
|
|
46
|
+
model.send(:parse_constraint_validations)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module DatabaseMethods
|
|
50
|
+
# A hash of validation method call metadata for all tables in the database.
|
|
51
|
+
# The hash is keyed by table name string and contains arrays of validation
|
|
52
|
+
# method call arrays.
|
|
53
|
+
attr_accessor :constraint_validations
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module ClassMethods
|
|
57
|
+
# An array of validation method call arrays. Each array is an array that
|
|
58
|
+
# is splatted to send to perform a validation via validation_helpers.
|
|
59
|
+
attr_reader :constraint_validations
|
|
60
|
+
|
|
61
|
+
# The name of the table containing the constraint validations metadata.
|
|
62
|
+
attr_reader :constraint_validations_table
|
|
63
|
+
|
|
64
|
+
# Copy the name of the constraint validations metadata table into the subclass.
|
|
65
|
+
def inherited(subclass)
|
|
66
|
+
super
|
|
67
|
+
subclass.instance_variable_set(:@constraint_validations_table, @constraint_validations_table)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Parse the constraint validations from the database whenever the dataset
|
|
71
|
+
# changes.
|
|
72
|
+
def set_dataset(*)
|
|
73
|
+
r = super
|
|
74
|
+
parse_constraint_validations
|
|
75
|
+
r
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# If the database has not already parsed constraint validation
|
|
81
|
+
# metadata, then run a query to get the metadata data and transform it
|
|
82
|
+
# into arrays of validation method calls.
|
|
83
|
+
#
|
|
84
|
+
# If this model has associated dataset, use the model's table name
|
|
85
|
+
# to get the validations for just this model.
|
|
86
|
+
def parse_constraint_validations
|
|
87
|
+
db.extend(DatabaseMethods)
|
|
88
|
+
|
|
89
|
+
unless hash = Sequel.synchronize{db.constraint_validations}
|
|
90
|
+
hash = {}
|
|
91
|
+
db.from(constraint_validations_table).each do |r|
|
|
92
|
+
(hash[r[:table]] ||= []) << constraint_validation_array(r)
|
|
93
|
+
end
|
|
94
|
+
Sequel.synchronize{db.constraint_validations = hash}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if @dataset
|
|
98
|
+
ds = @dataset.clone
|
|
99
|
+
ds.quote_identifiers = false
|
|
100
|
+
table_name = ds.literal(model.table_name)
|
|
101
|
+
@constraint_validations = Sequel.synchronize{hash[table_name]} || []
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Given a specific database constraint validation metadata row hash, transform
|
|
106
|
+
# it in an validation method call array suitable for splatting to send.
|
|
107
|
+
def constraint_validation_array(r)
|
|
108
|
+
opts = {}
|
|
109
|
+
opts[:message] = r[:message] if r[:message]
|
|
110
|
+
opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
|
|
111
|
+
type = r[:validation_type].to_sym
|
|
112
|
+
arg = r[:argument]
|
|
113
|
+
column = r[:column]
|
|
114
|
+
|
|
115
|
+
case type
|
|
116
|
+
when :like, :ilike
|
|
117
|
+
arg = constraint_validation_like_to_regexp(arg, type == :ilike)
|
|
118
|
+
type = :format
|
|
119
|
+
when :exact_length, :min_length, :max_length
|
|
120
|
+
arg = arg.to_i
|
|
121
|
+
when :length_range
|
|
122
|
+
arg = constraint_validation_int_range(arg)
|
|
123
|
+
when :format
|
|
124
|
+
arg = Regexp.new(arg)
|
|
125
|
+
when :iformat
|
|
126
|
+
arg = Regexp.new(arg, Regexp::IGNORECASE)
|
|
127
|
+
type = :format
|
|
128
|
+
when :includes_str_array
|
|
129
|
+
arg = arg.split(',')
|
|
130
|
+
type = :includes
|
|
131
|
+
when :includes_int_array
|
|
132
|
+
arg = arg.split(',').map{|x| x.to_i}
|
|
133
|
+
type = :includes
|
|
134
|
+
when :includes_int_range
|
|
135
|
+
arg = constraint_validation_int_range(arg)
|
|
136
|
+
type = :includes
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
column = if type == :unique
|
|
140
|
+
column.split(',').map{|c| c.to_sym}
|
|
141
|
+
else
|
|
142
|
+
column.to_sym
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
a = [:"validates_#{type}"]
|
|
146
|
+
if arg
|
|
147
|
+
a << arg
|
|
148
|
+
end
|
|
149
|
+
a << column
|
|
150
|
+
unless opts.empty?
|
|
151
|
+
a << opts
|
|
152
|
+
end
|
|
153
|
+
a
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Return a range of integers assuming the argument is in
|
|
157
|
+
# 1..2 or 1...2 format.
|
|
158
|
+
def constraint_validation_int_range(arg)
|
|
159
|
+
arg =~ /(\d+)\.\.(\.)?(\d+)/
|
|
160
|
+
Range.new($1.to_i, $3.to_i, $2 == '.')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Transform the LIKE pattern string argument into a
|
|
164
|
+
# Regexp argument suitable for use with validates_format.
|
|
165
|
+
def constraint_validation_like_to_regexp(arg, case_insensitive)
|
|
166
|
+
arg = Regexp.escape(arg).gsub(/%%|%|_/) do |s|
|
|
167
|
+
case s
|
|
168
|
+
when '%%'
|
|
169
|
+
'%'
|
|
170
|
+
when '%'
|
|
171
|
+
'.*'
|
|
172
|
+
when '_'
|
|
173
|
+
'.'
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
arg = "\\A#{arg}\\z"
|
|
177
|
+
|
|
178
|
+
if case_insensitive
|
|
179
|
+
Regexp.new(arg, Regexp::IGNORECASE)
|
|
180
|
+
else
|
|
181
|
+
Regexp.new(arg)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
module InstanceMethods
|
|
187
|
+
# Run all of the constraint validations parsed from the database
|
|
188
|
+
# when validating the instance.
|
|
189
|
+
def validate
|
|
190
|
+
super
|
|
191
|
+
model.constraint_validations.each do |v|
|
|
192
|
+
send(*v)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|