sequel 3.47.0 → 3.48.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +230 -0
- data/README.rdoc +31 -40
- data/Rakefile +1 -14
- data/doc/active_record.rdoc +29 -29
- data/doc/association_basics.rdoc +4 -13
- data/doc/cheat_sheet.rdoc +8 -6
- data/doc/code_order.rdoc +89 -0
- data/doc/core_extensions.rdoc +3 -3
- data/doc/dataset_basics.rdoc +7 -8
- data/doc/dataset_filtering.rdoc +7 -2
- data/doc/mass_assignment.rdoc +2 -3
- data/doc/migration.rdoc +8 -8
- data/doc/model_hooks.rdoc +11 -7
- data/doc/object_model.rdoc +2 -2
- data/doc/opening_databases.rdoc +5 -14
- data/doc/prepared_statements.rdoc +5 -9
- data/doc/querying.rdoc +23 -28
- data/doc/reflection.rdoc +11 -0
- data/doc/release_notes/3.48.0.txt +477 -0
- data/doc/schema_modification.rdoc +12 -5
- data/doc/security.rdoc +2 -2
- data/doc/sharding.rdoc +1 -2
- data/doc/sql.rdoc +10 -13
- data/doc/testing.rdoc +8 -4
- data/doc/transactions.rdoc +2 -2
- data/doc/validations.rdoc +40 -17
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +25 -20
- data/lib/sequel/adapters/ado/access.rb +1 -0
- data/lib/sequel/adapters/ado/mssql.rb +1 -0
- data/lib/sequel/adapters/db2.rb +9 -7
- data/lib/sequel/adapters/dbi.rb +16 -16
- data/lib/sequel/adapters/do.rb +17 -18
- data/lib/sequel/adapters/do/mysql.rb +1 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -0
- data/lib/sequel/adapters/do/sqlite.rb +1 -0
- data/lib/sequel/adapters/firebird.rb +5 -7
- data/lib/sequel/adapters/ibmdb.rb +23 -20
- data/lib/sequel/adapters/informix.rb +8 -2
- data/lib/sequel/adapters/jdbc.rb +39 -35
- data/lib/sequel/adapters/jdbc/as400.rb +1 -0
- data/lib/sequel/adapters/jdbc/cubrid.rb +1 -0
- data/lib/sequel/adapters/jdbc/db2.rb +1 -0
- data/lib/sequel/adapters/jdbc/derby.rb +1 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +1 -0
- data/lib/sequel/adapters/jdbc/h2.rb +1 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -0
- data/lib/sequel/adapters/jdbc/informix.rb +1 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +1 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +1 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +2 -0
- data/lib/sequel/adapters/jdbc/progress.rb +1 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +1 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -0
- data/lib/sequel/adapters/mock.rb +30 -31
- data/lib/sequel/adapters/mysql.rb +6 -7
- data/lib/sequel/adapters/mysql2.rb +5 -6
- data/lib/sequel/adapters/odbc.rb +22 -20
- data/lib/sequel/adapters/odbc/mssql.rb +1 -0
- data/lib/sequel/adapters/openbase.rb +4 -1
- data/lib/sequel/adapters/oracle.rb +10 -8
- data/lib/sequel/adapters/postgres.rb +12 -10
- data/lib/sequel/adapters/shared/access.rb +6 -0
- data/lib/sequel/adapters/shared/cubrid.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +2 -0
- data/lib/sequel/adapters/shared/firebird.rb +2 -0
- data/lib/sequel/adapters/shared/informix.rb +2 -0
- data/lib/sequel/adapters/shared/mssql.rb +14 -8
- data/lib/sequel/adapters/shared/mysql.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +14 -4
- data/lib/sequel/adapters/shared/progress.rb +1 -0
- data/lib/sequel/adapters/shared/sqlite.rb +4 -3
- data/lib/sequel/adapters/sqlite.rb +6 -7
- data/lib/sequel/adapters/swift.rb +20 -21
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +2 -0
- data/lib/sequel/adapters/swift/sqlite.rb +1 -0
- data/lib/sequel/adapters/tinytds.rb +5 -6
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +68 -0
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +57 -50
- data/lib/sequel/database/connecting.rb +9 -10
- data/lib/sequel/database/dataset.rb +11 -6
- data/lib/sequel/database/dataset_defaults.rb +61 -69
- data/lib/sequel/database/features.rb +21 -0
- data/lib/sequel/database/misc.rb +23 -3
- data/lib/sequel/database/query.rb +13 -7
- data/lib/sequel/database/schema_methods.rb +6 -6
- data/lib/sequel/database/transactions.rb +1 -0
- data/lib/sequel/dataset/actions.rb +51 -38
- data/lib/sequel/dataset/features.rb +1 -0
- data/lib/sequel/dataset/graph.rb +9 -33
- data/lib/sequel/dataset/misc.rb +30 -5
- data/lib/sequel/dataset/mutation.rb +2 -3
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/query.rb +91 -27
- data/lib/sequel/dataset/sql.rb +40 -6
- data/lib/sequel/deprecated.rb +74 -0
- data/lib/sequel/deprecated_core_extensions.rb +135 -0
- data/lib/sequel/extensions/columns_introspection.rb +1 -5
- data/lib/sequel/extensions/core_extensions.rb +10 -3
- data/lib/sequel/extensions/date_arithmetic.rb +1 -0
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +33 -0
- data/lib/sequel/extensions/filter_having.rb +58 -0
- data/lib/sequel/extensions/graph_each.rb +63 -0
- data/lib/sequel/extensions/hash_aliases.rb +44 -0
- data/lib/sequel/extensions/looser_typecasting.rb +14 -3
- data/lib/sequel/extensions/migration.rb +2 -3
- data/lib/sequel/extensions/named_timezones.rb +14 -1
- data/lib/sequel/extensions/null_dataset.rb +7 -1
- data/lib/sequel/extensions/pagination.rb +15 -5
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +48 -14
- data/lib/sequel/extensions/pg_json.rb +7 -7
- data/lib/sequel/extensions/pg_range_ops.rb +8 -2
- data/lib/sequel/extensions/pg_statement_cache.rb +1 -0
- data/lib/sequel/extensions/pretty_table.rb +13 -4
- data/lib/sequel/extensions/query.rb +21 -4
- data/lib/sequel/extensions/ruby18_symbol_extensions.rb +22 -0
- data/lib/sequel/extensions/schema_caching.rb +10 -7
- data/lib/sequel/extensions/schema_dumper.rb +35 -48
- data/lib/sequel/extensions/select_remove.rb +13 -4
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +117 -0
- data/lib/sequel/extensions/set_overrides.rb +43 -0
- data/lib/sequel/extensions/to_dot.rb +6 -0
- data/lib/sequel/model.rb +12 -6
- data/lib/sequel/model/associations.rb +80 -38
- data/lib/sequel/model/base.rb +137 -52
- data/lib/sequel/model/errors.rb +7 -2
- data/lib/sequel/plugins/active_model.rb +13 -0
- data/lib/sequel/plugins/after_initialize.rb +43 -0
- data/lib/sequel/plugins/association_proxies.rb +63 -7
- data/lib/sequel/plugins/auto_validations.rb +56 -16
- data/lib/sequel/plugins/blacklist_security.rb +63 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +9 -0
- data/lib/sequel/plugins/constraint_validations.rb +50 -8
- data/lib/sequel/plugins/dataset_associations.rb +2 -0
- data/lib/sequel/plugins/hook_class_methods.rb +7 -1
- data/lib/sequel/plugins/identity_map.rb +4 -0
- data/lib/sequel/plugins/json_serializer.rb +32 -13
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +4 -4
- data/lib/sequel/plugins/scissors.rb +33 -0
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +6 -0
- data/lib/sequel/plugins/tree.rb +5 -1
- data/lib/sequel/plugins/validation_class_methods.rb +2 -1
- data/lib/sequel/plugins/validation_helpers.rb +15 -11
- data/lib/sequel/plugins/xml_serializer.rb +12 -3
- data/lib/sequel/sql.rb +12 -2
- data/lib/sequel/timezones.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/mssql_spec.rb +24 -57
- data/spec/adapters/postgres_spec.rb +27 -55
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +251 -0
- data/spec/core/database_spec.rb +46 -32
- data/spec/core/dataset_spec.rb +233 -181
- data/spec/core/deprecated_spec.rb +78 -0
- data/spec/core/expression_filters_spec.rb +3 -4
- data/spec/core/mock_adapter_spec.rb +9 -9
- data/spec/core/object_graph_spec.rb +9 -19
- data/spec/core/schema_spec.rb +3 -1
- data/spec/core/spec_helper.rb +19 -0
- data/spec/core_extensions_spec.rb +80 -30
- data/spec/extensions/after_initialize_spec.rb +24 -0
- data/spec/extensions/association_proxies_spec.rb +37 -1
- data/spec/extensions/auto_validations_spec.rb +20 -4
- data/spec/extensions/blacklist_security_spec.rb +87 -0
- data/spec/extensions/boolean_readers_spec.rb +2 -1
- data/spec/extensions/class_table_inheritance_spec.rb +7 -0
- data/spec/extensions/columns_introspection_spec.rb +3 -3
- data/spec/extensions/constraint_validations_plugin_spec.rb +83 -5
- data/spec/extensions/core_refinements_spec.rb +7 -7
- data/spec/extensions/dataset_associations_spec.rb +2 -2
- data/spec/extensions/date_arithmetic_spec.rb +1 -1
- data/spec/extensions/defaults_setter_spec.rb +2 -1
- data/spec/extensions/empty_array_ignore_nulls_spec.rb +24 -0
- data/spec/extensions/filter_having_spec.rb +40 -0
- data/spec/extensions/graph_each_spec.rb +109 -0
- data/spec/extensions/hash_aliases_spec.rb +16 -0
- data/spec/extensions/hook_class_methods_spec.rb +2 -2
- data/spec/extensions/identity_map_spec.rb +3 -3
- data/spec/extensions/json_serializer_spec.rb +19 -19
- data/spec/extensions/lazy_attributes_spec.rb +1 -0
- data/spec/extensions/list_spec.rb +13 -13
- data/spec/extensions/looser_typecasting_spec.rb +10 -3
- data/spec/extensions/many_through_many_spec.rb +1 -1
- data/spec/extensions/migration_spec.rb +7 -7
- data/spec/extensions/named_timezones_spec.rb +6 -0
- data/spec/extensions/nested_attributes_spec.rb +2 -2
- data/spec/extensions/null_dataset_spec.rb +1 -1
- data/spec/extensions/pagination_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +75 -0
- data/spec/extensions/pg_range_ops_spec.rb +4 -2
- data/spec/extensions/pg_row_plugin_spec.rb +1 -1
- data/spec/extensions/pretty_table_spec.rb +1 -1
- data/spec/extensions/query_literals_spec.rb +1 -1
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_caching_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +27 -2
- data/spec/extensions/schema_spec.rb +2 -2
- data/spec/extensions/scissors_spec.rb +26 -0
- data/spec/extensions/select_remove_spec.rb +1 -1
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +102 -0
- data/spec/extensions/set_overrides_spec.rb +45 -0
- data/spec/extensions/single_table_inheritance_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +24 -1
- data/spec/extensions/static_cache_spec.rb +1 -1
- data/spec/extensions/string_stripper_spec.rb +2 -1
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/typecast_on_load_spec.rb +3 -2
- data/spec/extensions/update_primary_key_spec.rb +2 -2
- data/spec/extensions/validation_class_methods_spec.rb +19 -19
- data/spec/extensions/validation_helpers_spec.rb +30 -21
- data/spec/extensions/xml_serializer_spec.rb +5 -5
- data/spec/integration/associations_test.rb +10 -30
- data/spec/integration/dataset_test.rb +20 -24
- data/spec/integration/eager_loader_test.rb +5 -5
- data/spec/integration/model_test.rb +3 -3
- data/spec/integration/plugin_test.rb +7 -39
- data/spec/integration/schema_test.rb +4 -38
- data/spec/integration/spec_helper.rb +2 -1
- data/spec/model/association_reflection_spec.rb +70 -5
- data/spec/model/associations_spec.rb +11 -11
- data/spec/model/base_spec.rb +25 -8
- data/spec/model/class_dataset_methods_spec.rb +143 -0
- data/spec/model/dataset_methods_spec.rb +1 -1
- data/spec/model/eager_loading_spec.rb +25 -25
- data/spec/model/hooks_spec.rb +1 -1
- data/spec/model/model_spec.rb +22 -7
- data/spec/model/plugins_spec.rb +1 -6
- data/spec/model/record_spec.rb +37 -29
- data/spec/model/spec_helper.rb +23 -1
- data/spec/model/validations_spec.rb +15 -17
- metadata +32 -3
data/lib/sequel/model/errors.rb
CHANGED
@@ -10,14 +10,19 @@ module Sequel
|
|
10
10
|
# to add new error messages, and +on+ to check existing
|
11
11
|
# error messages.
|
12
12
|
def [](k)
|
13
|
-
has_key?(k)
|
13
|
+
if has_key?(k)
|
14
|
+
super
|
15
|
+
else
|
16
|
+
Sequel::Deprecation.deprecate('Model::Errors#[] autovivification', 'Please switch to Model::Errors#add to add errors, and Model::Errors#on to get errors')
|
17
|
+
self[k] = []
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
21
|
# Adds an error for the given attribute.
|
17
22
|
#
|
18
23
|
# errors.add(:name, 'is not valid') if name == 'invalid'
|
19
24
|
def add(att, msg)
|
20
|
-
self[att] << msg
|
25
|
+
fetch(att){self[att] = []} << msg
|
21
26
|
end
|
22
27
|
|
23
28
|
# Return the total number of error messages.
|
@@ -16,6 +16,14 @@ module Sequel
|
|
16
16
|
# # Make the Album class active_model compliant
|
17
17
|
# Album.plugin :active_model
|
18
18
|
module ActiveModel
|
19
|
+
# ActiveModel compliant error class
|
20
|
+
class Errors < Sequel::Model::Errors
|
21
|
+
# Add autovivification so that #[] always returns an array.
|
22
|
+
def [](k)
|
23
|
+
fetch(k){self[k] = []}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
module ClassMethods
|
20
28
|
include ::ActiveModel::Naming
|
21
29
|
|
@@ -70,6 +78,11 @@ module Sequel
|
|
70
78
|
end
|
71
79
|
|
72
80
|
private
|
81
|
+
|
82
|
+
# Use ActiveModel compliant errors class.
|
83
|
+
def errors_class
|
84
|
+
Errors
|
85
|
+
end
|
73
86
|
|
74
87
|
# The string to use to join composite primary key param strings.
|
75
88
|
def to_param_joiner
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# Adds an after_initialize hook to models, called after initializing
|
4
|
+
# both new objects and ones loaded from the database.
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
#
|
8
|
+
# # Make all model subclasses support the after_initialize hook
|
9
|
+
# Sequel::Model.plugin :after_initialize
|
10
|
+
#
|
11
|
+
# # Make the Album class support the after_initialize hook
|
12
|
+
# Album.plugin :after_initialize
|
13
|
+
module AfterInitialize
|
14
|
+
module ClassMethods
|
15
|
+
# Call after_initialize for model objects loaded from the database.
|
16
|
+
#def call
|
17
|
+
# v = super
|
18
|
+
# v.after_initialize
|
19
|
+
# v
|
20
|
+
#end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# REMOVE40
|
25
|
+
def check_deprecated_after_initialize(meths)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
# Call after_initialize for new model objects.
|
31
|
+
#def initialize(h={})
|
32
|
+
# super
|
33
|
+
# after_initialize
|
34
|
+
#end
|
35
|
+
|
36
|
+
# An empty after_initialize hook, so that plugins that use this
|
37
|
+
# can always call super to get the default behavior.
|
38
|
+
#def after_initialize
|
39
|
+
#end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -6,6 +6,40 @@ module Sequel
|
|
6
6
|
# that will load the association and call a method on the association array if sent
|
7
7
|
# an array method, and otherwise send the method to the association's dataset.
|
8
8
|
#
|
9
|
+
# You can override which methods to forward to the dataset by passing a block to the plugin:
|
10
|
+
#
|
11
|
+
# plugin :association_proxies do |opts|
|
12
|
+
# [:find, :where, :create].include?(opts[:method])
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# If the block returns false or nil, the method is sent to the array of associated
|
16
|
+
# objects. Otherwise, the method is sent to the association dataset. Here are the entries
|
17
|
+
# in the hash passed to the block:
|
18
|
+
#
|
19
|
+
# :method :: The name of the method
|
20
|
+
# :arguments :: The arguments to the method
|
21
|
+
# :block :: The block given to the method
|
22
|
+
# :instance :: The model instance related to the call
|
23
|
+
# :reflection :: The reflection for the association related to the call
|
24
|
+
# :proxy_argument :: The argument given to the association method call
|
25
|
+
# :proxy_block :: The block given to the association method call
|
26
|
+
#
|
27
|
+
# For example, in a call like:
|
28
|
+
#
|
29
|
+
# artist.albums(1){|ds| ds}.foo(2){|x| 3}
|
30
|
+
#
|
31
|
+
# The opts passed to the block would be:
|
32
|
+
#
|
33
|
+
# {
|
34
|
+
# :method => :foo,
|
35
|
+
# :arguments => [2],
|
36
|
+
# :block => {|x| 3},
|
37
|
+
# :instance => artist,
|
38
|
+
# :reflection => {:name=>:albums, ...},
|
39
|
+
# :proxy_argument => 1,
|
40
|
+
# :proxy_block => {|ds| ds}
|
41
|
+
# }
|
42
|
+
#
|
9
43
|
# Usage:
|
10
44
|
#
|
11
45
|
# # Use association proxies in all model subclasses (called before loading subclasses)
|
@@ -14,34 +48,56 @@ module Sequel
|
|
14
48
|
# # Use association proxies in a specific model subclass
|
15
49
|
# Album.plugin :association_proxies
|
16
50
|
module AssociationProxies
|
51
|
+
def self.configure(model, &block)
|
52
|
+
model.instance_eval do
|
53
|
+
@association_proxy_to_dataset = block if block
|
54
|
+
@association_proxy_to_dataset ||= AssociationProxy::DEFAULT_PROXY_TO_DATASET
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
17
58
|
# A proxy for the association. Calling an array method will load the
|
18
59
|
# associated objects and call the method on the associated object array.
|
19
60
|
# Calling any other method will call that method on the association's dataset.
|
20
61
|
class AssociationProxy < BasicObject
|
21
|
-
|
22
|
-
|
62
|
+
array = []
|
63
|
+
|
64
|
+
# Default proc used to determine whether to sent the method to the dataset.
|
65
|
+
# If the array would respond to it, sends it to the array instead of the dataset.
|
66
|
+
DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
|
23
67
|
|
24
68
|
# Set the association reflection to use, and whether the association should be
|
25
69
|
# reloaded if an array method is called.
|
26
|
-
def initialize(instance, reflection,
|
70
|
+
def initialize(instance, reflection, proxy_argument, &proxy_block)
|
27
71
|
@instance = instance
|
28
72
|
@reflection = reflection
|
29
|
-
@
|
73
|
+
@proxy_argument = proxy_argument
|
74
|
+
@proxy_block = proxy_block
|
30
75
|
end
|
31
76
|
|
32
77
|
# Call the method given on the array of associated objects if the method
|
33
78
|
# is an array method, otherwise call the method on the association's dataset.
|
34
79
|
def method_missing(meth, *args, &block)
|
35
|
-
|
36
|
-
send(
|
80
|
+
v = if @instance.model.association_proxy_to_dataset.call(:method=>meth, :arguments=>args, :block=>block, :instance=>@instance, :reflection=>@reflection, :proxy_argument=>@proxy_argument, :proxy_block=>@proxy_block)
|
81
|
+
@instance.send(@reflection.dataset_method)
|
82
|
+
else
|
83
|
+
@instance.send(:load_associated_objects, @reflection, @proxy_argument, &@proxy_block)
|
84
|
+
end
|
85
|
+
v.send(meth, *args, &block)
|
37
86
|
end
|
38
87
|
end
|
39
88
|
|
40
89
|
module ClassMethods
|
90
|
+
# Proc that accepts a method name, array of arguments, and block and
|
91
|
+
# should return a truthy value to send the method to the dataset instead of the
|
92
|
+
# array of associated objects.
|
93
|
+
attr_reader :association_proxy_to_dataset
|
94
|
+
|
95
|
+
Plugins.inherited_instance_variables(self, :@association_proxy_to_dataset=>nil)
|
96
|
+
|
41
97
|
# Changes the association method to return a proxy instead of the associated objects
|
42
98
|
# directly.
|
43
99
|
def def_association_method(opts)
|
44
|
-
opts.returns_array? ? association_module_def(opts.association_method, opts){|*r| AssociationProxy.new(self, opts, r[0])} : super
|
100
|
+
opts.returns_array? ? association_module_def(opts.association_method, opts){|*r, &block| AssociationProxy.new(self, opts, r[0], &block)} : super
|
45
101
|
end
|
46
102
|
end
|
47
103
|
end
|
@@ -4,10 +4,10 @@ module Sequel
|
|
4
4
|
# for your model columns:
|
5
5
|
#
|
6
6
|
# 1. type validations for all columns
|
7
|
-
# 2.
|
7
|
+
# 2. not_null validations on NOT NULL columns (optionally, presence validations)
|
8
8
|
# 3. unique validations on columns or sets of columns with unique indexes
|
9
9
|
#
|
10
|
-
# To determine the columns to use for the
|
10
|
+
# To determine the columns to use for the not_null validations and the types for the type validations,
|
11
11
|
# the plugin looks at the database schema for the model's table. To determine
|
12
12
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
13
13
|
# for this plugin to be fully functional, the underlying database adapter needs
|
@@ -20,13 +20,21 @@ module Sequel
|
|
20
20
|
#
|
21
21
|
# You can skip certain types of validations from being automatically added via:
|
22
22
|
#
|
23
|
-
# Model.skip_auto_validations(:
|
23
|
+
# Model.skip_auto_validations(:not_null)
|
24
24
|
#
|
25
25
|
# If you want to skip all auto validations (only useful if loading the plugin
|
26
26
|
# in a superclass):
|
27
27
|
#
|
28
28
|
# Model.skip_auto_validations(:all)
|
29
29
|
#
|
30
|
+
# By default, the plugin uses a not_null validation for NOT NULL columns, but that
|
31
|
+
# can be changed to a presence validation using an option:
|
32
|
+
#
|
33
|
+
# Model.plugin :auto_validations, :not_null=>:presence
|
34
|
+
#
|
35
|
+
# This is useful if you want to enforce that NOT NULL string columns do not
|
36
|
+
# allow empty values.
|
37
|
+
#
|
30
38
|
# Usage:
|
31
39
|
#
|
32
40
|
# # Make all model subclass use auto validations (called before loading subclasses)
|
@@ -35,43 +43,61 @@ module Sequel
|
|
35
43
|
# # Make the Album class use auto validations
|
36
44
|
# Album.plugin :auto_validations
|
37
45
|
module AutoValidations
|
38
|
-
|
39
|
-
def self.apply(model)
|
46
|
+
def self.apply(model, opts={})
|
40
47
|
model.instance_eval do
|
41
48
|
plugin :validation_helpers
|
42
|
-
@
|
49
|
+
@auto_validate_presence = false
|
50
|
+
@auto_validate_not_null_columns = []
|
51
|
+
@auto_validate_explicit_not_null_columns = []
|
43
52
|
@auto_validate_unique_columns = []
|
44
53
|
@auto_validate_types = true
|
45
54
|
end
|
46
55
|
end
|
47
56
|
|
48
57
|
# Setup auto validations for the model if it has a dataset.
|
49
|
-
def self.configure(model)
|
58
|
+
def self.configure(model, opts={})
|
50
59
|
model.instance_eval do
|
51
60
|
setup_auto_validations if @dataset
|
61
|
+
if opts[:not_null] == :presence
|
62
|
+
@auto_validate_presence = true
|
63
|
+
end
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
module ClassMethods
|
56
|
-
# The columns with automatic
|
57
|
-
attr_reader :
|
68
|
+
# The columns with automatic not_null validations
|
69
|
+
attr_reader :auto_validate_not_null_columns
|
70
|
+
|
71
|
+
# The columns with automatic not_null validations for columns present in the values.
|
72
|
+
attr_reader :auto_validate_explicit_not_null_columns
|
58
73
|
|
59
74
|
# The columns or sets of columns with automatic unique validations
|
60
75
|
attr_reader :auto_validate_unique_columns
|
61
76
|
|
62
|
-
Plugins.inherited_instance_variables(self, :@auto_validate_types=>nil, :@
|
77
|
+
Plugins.inherited_instance_variables(self, :@auto_validate_presence=>nil, :@auto_validate_types=>nil, :@auto_validate_not_null_columns=>:dup, :@auto_validate_explicit_not_null_columns=>:dup, :@auto_validate_unique_columns=>:dup)
|
63
78
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
64
79
|
|
80
|
+
# REMOVE40
|
81
|
+
def auto_validate_presence_columns
|
82
|
+
Sequel::Deprecation.deprecate('Model.auto_validate_presence_columns', 'Please switch to auto_validate_not_null_columns')
|
83
|
+
auto_validate_not_null_columns
|
84
|
+
end
|
85
|
+
|
86
|
+
# Whether to use a presence validation for not null columns
|
87
|
+
def auto_validate_presence?
|
88
|
+
@auto_validate_presence
|
89
|
+
end
|
90
|
+
|
65
91
|
# Whether to automatically validate schema types for all columns
|
66
92
|
def auto_validate_types?
|
67
93
|
@auto_validate_types
|
68
94
|
end
|
69
95
|
|
70
|
-
# Skip automatic validations for the given validation type (:
|
96
|
+
# Skip automatic validations for the given validation type (:not_null, :types, :unique).
|
71
97
|
# If :all is given as the type, skip all auto validations.
|
72
98
|
def skip_auto_validations(type)
|
73
99
|
if type == :all
|
74
|
-
[:
|
100
|
+
[:not_null, :types, :unique].each{|v| skip_auto_validations(v)}
|
75
101
|
elsif type == :types
|
76
102
|
@auto_validate_types = false
|
77
103
|
else
|
@@ -83,8 +109,11 @@ module Sequel
|
|
83
109
|
|
84
110
|
# Parse the database schema and indexes and record the columns to automatically validate.
|
85
111
|
def setup_auto_validations
|
86
|
-
|
87
|
-
@
|
112
|
+
not_null_cols, explicit_not_null_cols = db_schema.select{|col, sch| sch[:allow_null] == false}.partition{|col, sch| sch[:ruby_default].nil?}.map{|cs| cs.map{|col, sch| col}}
|
113
|
+
@auto_validate_not_null_columns = not_null_cols - Array(primary_key)
|
114
|
+
explicit_not_null_cols += Array(primary_key)
|
115
|
+
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
116
|
+
@auto_validate_unique_columns = if db.supports_index_parsing?
|
88
117
|
db.indexes(dataset.first_source_table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
|
89
118
|
else
|
90
119
|
[]
|
@@ -96,8 +125,19 @@ module Sequel
|
|
96
125
|
# Validate the model's auto validations columns
|
97
126
|
def validate
|
98
127
|
super
|
99
|
-
|
100
|
-
|
128
|
+
unless (not_null_columns = model.auto_validate_not_null_columns).empty?
|
129
|
+
if model.auto_validate_presence?
|
130
|
+
validates_presence(not_null_columns)
|
131
|
+
else
|
132
|
+
validates_not_null(not_null_columns)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
unless (not_null_columns = model.auto_validate_explicit_not_null_columns).empty?
|
136
|
+
if model.auto_validate_presence?
|
137
|
+
validates_presence(not_null_columns, :allow_missing=>true)
|
138
|
+
else
|
139
|
+
validates_not_null(not_null_columns, :allow_missing=>true)
|
140
|
+
end
|
101
141
|
end
|
102
142
|
|
103
143
|
validates_schema_types if model.auto_validate_types?
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The blacklist_security plugin contains blacklist-based support for
|
4
|
+
# mass assignment, specifying which columns to not allow mass assignment for,
|
5
|
+
# implicitly allowing mass assignment for columns not listed. This is only
|
6
|
+
# for backwards compatibility, it should not be used by new code.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# # Make all model subclasses support the blacklist security features.
|
11
|
+
# Sequel::Model.plugin :blacklist_security
|
12
|
+
#
|
13
|
+
# # Make the Album class support the blacklist security features.
|
14
|
+
# Album.plugin :blacklist_security
|
15
|
+
module BlacklistSecurity
|
16
|
+
module ClassMethods
|
17
|
+
# Which columns are specifically restricted in a call to set/update/new/etc.
|
18
|
+
# (default: not set). Some columns are restricted regardless of
|
19
|
+
# this setting, such as the primary key column and columns in Model::RESTRICTED_SETTER_METHODS.
|
20
|
+
attr_reader :restricted_columns
|
21
|
+
|
22
|
+
# Set the columns to restrict when using mass assignment (e.g. +set+). Using this means that
|
23
|
+
# attempts to call setter methods for the columns listed here will cause an
|
24
|
+
# exception or be silently skipped (based on the +strict_param_setting+ setting).
|
25
|
+
# If you have any virtual setter methods (methods that end in =) that you
|
26
|
+
# want not to be used during mass assignment, they need to be listed here as well (without the =).
|
27
|
+
#
|
28
|
+
# It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
|
29
|
+
# approach such as set_allowed_columns or the instance level set_only or set_fields methods
|
30
|
+
# is usually a better choice. So use of this method is generally a bad idea.
|
31
|
+
#
|
32
|
+
# Artist.set_restricted_columns(:records_sold)
|
33
|
+
# Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
|
34
|
+
# Artist.set(:name=>'Bob', :records_sold=>30000) # Error
|
35
|
+
def set_restricted_columns(*cols)
|
36
|
+
clear_setter_methods_cache
|
37
|
+
@restricted_columns = cols
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
# Set all values using the entries in the hash, except for the keys
|
43
|
+
# given in except. You should probably use +set_fields+ or +set_only+
|
44
|
+
# instead of this method, as blacklist approaches to security are a bad idea.
|
45
|
+
#
|
46
|
+
# artist.set_except({:name=>'Jim'}, :hometown)
|
47
|
+
# artist.name # => 'Jim'
|
48
|
+
def set_except(hash, *except)
|
49
|
+
set_restricted(hash, false, except.flatten)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Update all values using the entries in the hash, except for the keys
|
53
|
+
# given in except. You should probably use +update_fields+ or +update_only+
|
54
|
+
# instead of this method, as blacklist approaches to security are a bad idea.
|
55
|
+
#
|
56
|
+
# artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
|
57
|
+
def update_except(hash, *except)
|
58
|
+
update_restricted(hash, false, except.flatten)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -175,6 +175,15 @@ module Sequel
|
|
175
175
|
def table_name
|
176
176
|
self == cti_base_model ? super : cti_tables.last
|
177
177
|
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# If calling set_dataset manually, make sure to set the dataset
|
182
|
+
# row proc to one that handles inheritance correctly.
|
183
|
+
def set_dataset_row_proc(ds)
|
184
|
+
ds.row_proc = @dataset.row_proc if @dataset
|
185
|
+
end
|
186
|
+
|
178
187
|
end
|
179
188
|
|
180
189
|
module InstanceMethods
|
@@ -33,7 +33,11 @@ module Sequel
|
|
33
33
|
|
34
34
|
# Automatically load the validation_helpers plugin to run the actual validations.
|
35
35
|
def self.apply(model, opts={})
|
36
|
-
model.
|
36
|
+
model.instance_eval do
|
37
|
+
plugin :validation_helpers
|
38
|
+
@constraint_validations_table = DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
|
39
|
+
@constraint_validation_options = {}
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
# Parse the constraint validations metadata from the database. Options:
|
@@ -41,9 +45,25 @@ module Sequel
|
|
41
45
|
# metadata table. Should only be used if the table
|
42
46
|
# name was overridden when creating the constraint
|
43
47
|
# validations.
|
48
|
+
# :validation_options :: Override/augment the options stored in the database with the
|
49
|
+
# given options. Keys should be validation type symbols (e.g.
|
50
|
+
# :presence) and values should be hashes of options specific
|
51
|
+
# to that validation type.
|
44
52
|
def self.configure(model, opts={})
|
45
|
-
model.
|
46
|
-
|
53
|
+
model.instance_eval do
|
54
|
+
if table = opts[:constraint_validations_table]
|
55
|
+
@constraint_validations_table = table
|
56
|
+
end
|
57
|
+
if vos = opts[:validation_options]
|
58
|
+
vos.each do |k, v|
|
59
|
+
if existing_options = @constraint_validation_options[k]
|
60
|
+
v = existing_options.merge(v)
|
61
|
+
end
|
62
|
+
@constraint_validation_options[k] = v
|
63
|
+
end
|
64
|
+
end
|
65
|
+
parse_constraint_validations
|
66
|
+
end
|
47
67
|
end
|
48
68
|
|
49
69
|
module DatabaseMethods
|
@@ -58,10 +78,17 @@ module Sequel
|
|
58
78
|
# is splatted to send to perform a validation via validation_helpers.
|
59
79
|
attr_reader :constraint_validations
|
60
80
|
|
81
|
+
# A hash of reflections of constraint validations. Keys are type name
|
82
|
+
# symbols. Each value is an array of pairs, with the first element being
|
83
|
+
# the validation type symbol (e.g. :presence) and the second element being
|
84
|
+
# options for the validation. If the validation takes an argument, it appears
|
85
|
+
# as the :argument entry in the validation option hash.
|
86
|
+
attr_reader :constraint_validation_reflections
|
87
|
+
|
61
88
|
# The name of the table containing the constraint validations metadata.
|
62
89
|
attr_reader :constraint_validations_table
|
63
90
|
|
64
|
-
Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil)
|
91
|
+
Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil, :@constraint_validation_options=>:hash_dup)
|
65
92
|
Plugins.after_set_dataset(self, :parse_constraint_validations)
|
66
93
|
|
67
94
|
private
|
@@ -78,7 +105,7 @@ module Sequel
|
|
78
105
|
unless hash = Sequel.synchronize{db.constraint_validations}
|
79
106
|
hash = {}
|
80
107
|
db.from(constraint_validations_table).each do |r|
|
81
|
-
(hash[r[:table]] ||= []) <<
|
108
|
+
(hash[r[:table]] ||= []) << r
|
82
109
|
end
|
83
110
|
Sequel.synchronize{db.constraint_validations = hash}
|
84
111
|
end
|
@@ -86,14 +113,16 @@ module Sequel
|
|
86
113
|
if @dataset
|
87
114
|
ds = @dataset.clone
|
88
115
|
ds.quote_identifiers = false
|
89
|
-
table_name = ds.literal(
|
90
|
-
|
116
|
+
table_name = ds.literal(ds.first_source_table)
|
117
|
+
reflections = {}
|
118
|
+
@constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections)}
|
119
|
+
@constraint_validation_reflections = reflections
|
91
120
|
end
|
92
121
|
end
|
93
122
|
|
94
123
|
# Given a specific database constraint validation metadata row hash, transform
|
95
124
|
# it in an validation method call array suitable for splatting to send.
|
96
|
-
def constraint_validation_array(r)
|
125
|
+
def constraint_validation_array(r, reflections)
|
97
126
|
opts = {}
|
98
127
|
opts[:message] = r[:message] if r[:message]
|
99
128
|
opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
|
@@ -131,14 +160,27 @@ module Sequel
|
|
131
160
|
column.to_sym
|
132
161
|
end
|
133
162
|
|
163
|
+
if type_opts = @constraint_validation_options[type]
|
164
|
+
opts = opts.merge(type_opts)
|
165
|
+
end
|
166
|
+
|
167
|
+
reflection_opts = opts
|
134
168
|
a = [:"validates_#{type}"]
|
169
|
+
|
135
170
|
if arg
|
136
171
|
a << arg
|
172
|
+
reflection_opts = reflection_opts.merge(:argument=>arg)
|
137
173
|
end
|
138
174
|
a << column
|
139
175
|
unless opts.empty?
|
140
176
|
a << opts
|
141
177
|
end
|
178
|
+
|
179
|
+
if column.is_a?(Array) && column.length == 1
|
180
|
+
column = column.first
|
181
|
+
end
|
182
|
+
(reflections[column] ||= []) << [type, reflection_opts]
|
183
|
+
|
142
184
|
a
|
143
185
|
end
|
144
186
|
|