sequel 2.11.0 → 2.12.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 +168 -0
- data/README.rdoc +77 -95
- data/Rakefile +100 -80
- data/bin/sequel +2 -1
- data/doc/advanced_associations.rdoc +23 -32
- data/doc/cheat_sheet.rdoc +23 -40
- data/doc/dataset_filtering.rdoc +6 -6
- data/doc/prepared_statements.rdoc +22 -22
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/schema.rdoc +3 -1
- data/doc/sharding.rdoc +8 -8
- data/doc/virtual_rows.rdoc +65 -0
- data/lib/sequel.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
- data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
- data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
- data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
- data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
- data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
- data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
- data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
- data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
- data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
- data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
- data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
- data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
- data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
- data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
- data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
- data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
- data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
- data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
- data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
- data/lib/sequel/core.rb +221 -0
- data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
- data/lib/{sequel_core → sequel}/database.rb +264 -149
- data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
- data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
- data/lib/sequel/database/schema_sql.rb +224 -0
- data/lib/{sequel_core → sequel}/dataset.rb +78 -236
- data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
- data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
- data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
- data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
- data/lib/sequel/deprecated.rb +593 -0
- data/lib/sequel/deprecated_migration.rb +91 -0
- data/lib/sequel/exceptions.rb +48 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
- data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
- data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
- data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
- data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +43 -0
- data/lib/sequel/model.rb +110 -0
- data/lib/sequel/model/associations.rb +1300 -0
- data/lib/sequel/model/base.rb +937 -0
- data/lib/sequel/model/deprecated.rb +204 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +388 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
- data/lib/sequel/model/inflections.rb +208 -0
- data/lib/sequel/model/plugins.rb +76 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/serialization.rb +117 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +384 -0
- data/lib/sequel/plugins/validation_helpers.rb +150 -0
- data/lib/{sequel_core → sequel}/sql.rb +125 -190
- data/lib/{sequel_core → sequel}/version.rb +2 -1
- data/lib/sequel_core.rb +1 -172
- data/lib/sequel_model.rb +1 -91
- data/spec/adapters/firebird_spec.rb +5 -5
- data/spec/adapters/informix_spec.rb +1 -1
- data/spec/adapters/mysql_spec.rb +128 -42
- data/spec/adapters/oracle_spec.rb +47 -19
- data/spec/adapters/postgres_spec.rb +64 -52
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +12 -17
- data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
- data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
- data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
- data/spec/{sequel_core → core}/database_spec.rb +135 -99
- data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
- data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
- data/spec/core/migration_spec.rb +263 -0
- data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
- data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
- data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
- data/spec/{sequel_core → core}/schema_spec.rb +8 -10
- data/spec/{sequel_core → core}/spec_helper.rb +29 -2
- data/spec/{sequel_core → core}/version_spec.rb +0 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
- data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
- data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
- data/spec/extensions/serialization_spec.rb +109 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
- data/spec/extensions/validation_helpers_spec.rb +291 -0
- data/spec/integration/dataset_test.rb +31 -0
- data/spec/integration/eager_loader_test.rb +17 -30
- data/spec/integration/schema_test.rb +8 -5
- data/spec/integration/spec_helper.rb +17 -0
- data/spec/integration/transaction_test.rb +68 -0
- data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
- data/spec/{sequel_model → model}/associations_spec.rb +23 -10
- data/spec/{sequel_model → model}/base_spec.rb +29 -20
- data/spec/{sequel_model → model}/caching_spec.rb +16 -14
- data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
- data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/{sequel_model → model}/model_spec.rb +25 -20
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/{sequel_model → model}/record_spec.rb +121 -62
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- metadata +136 -107
- data/lib/sequel_core/core_ext.rb +0 -217
- data/lib/sequel_core/dataset/callback.rb +0 -13
- data/lib/sequel_core/dataset/schema.rb +0 -15
- data/lib/sequel_core/deprecated.rb +0 -26
- data/lib/sequel_core/exceptions.rb +0 -44
- data/lib/sequel_core/schema.rb +0 -2
- data/lib/sequel_core/schema/sql.rb +0 -325
- data/lib/sequel_model/association_reflection.rb +0 -267
- data/lib/sequel_model/associations.rb +0 -499
- data/lib/sequel_model/base.rb +0 -539
- data/lib/sequel_model/caching.rb +0 -82
- data/lib/sequel_model/dataset_methods.rb +0 -26
- data/lib/sequel_model/eager_loading.rb +0 -370
- data/lib/sequel_model/hooks.rb +0 -101
- data/lib/sequel_model/plugins.rb +0 -62
- data/lib/sequel_model/record.rb +0 -568
- data/lib/sequel_model/schema.rb +0 -49
- data/lib/sequel_model/validations.rb +0 -429
- data/spec/sequel_model/plugins_spec.rb +0 -80
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
module DeprecatedValidationClassMethods
|
|
4
|
+
module ClassMethods
|
|
5
|
+
# The Generator class is used to generate validation definitions using
|
|
6
|
+
# the validates {} idiom.
|
|
7
|
+
class Generator
|
|
8
|
+
# Initializes a new generator.
|
|
9
|
+
def initialize(receiver ,&block)
|
|
10
|
+
@receiver = receiver
|
|
11
|
+
instance_eval(&block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Delegates method calls to the receiver by calling receiver.validates_xxx.
|
|
15
|
+
def method_missing(m, *args, &block)
|
|
16
|
+
@receiver.send(:"validates_#{m}", *args, &block)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true if validations are defined.
|
|
21
|
+
def has_validations?
|
|
22
|
+
!validations.empty?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Instructs the model to skip validations defined in superclasses
|
|
26
|
+
def skip_superclass_validations
|
|
27
|
+
@skip_superclass_validations = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Defines validations by converting a longhand block into a series of
|
|
31
|
+
# shorthand definitions. For example:
|
|
32
|
+
#
|
|
33
|
+
# class MyClass < Sequel::Model
|
|
34
|
+
# validates do
|
|
35
|
+
# length_of :name, :minimum => 6
|
|
36
|
+
# length_of :password, :minimum => 8
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# is equivalent to:
|
|
41
|
+
# class MyClass < Sequel::Model
|
|
42
|
+
# validates_length_of :name, :minimum => 6
|
|
43
|
+
# validates_length_of :password, :minimum => 8
|
|
44
|
+
# end
|
|
45
|
+
def validates(&block)
|
|
46
|
+
Deprecation.deprecate('Sequel::Model.validates', 'Use Model.plugin(:validation_class_methods) first')
|
|
47
|
+
Generator.new(self, &block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Validates the given instance.
|
|
51
|
+
def validate(o)
|
|
52
|
+
if superclass.respond_to?(:validate) && !@skip_superclass_validations
|
|
53
|
+
superclass.validate(o)
|
|
54
|
+
end
|
|
55
|
+
validations.each do |att, procs|
|
|
56
|
+
v = case att
|
|
57
|
+
when Array
|
|
58
|
+
att.collect{|a| o.send(a)}
|
|
59
|
+
else
|
|
60
|
+
o.send(att)
|
|
61
|
+
end
|
|
62
|
+
procs.each {|tag, p| p.call(o, att, v)}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Validates acceptance of an attribute. Just checks that the value
|
|
67
|
+
# is equal to the :accept option. This method is unique in that
|
|
68
|
+
# :allow_nil is assumed to be true instead of false.
|
|
69
|
+
#
|
|
70
|
+
# Possible Options:
|
|
71
|
+
# * :accept - The value required for the object to be valid (default: '1')
|
|
72
|
+
# * :message - The message to use (default: 'is not accepted')
|
|
73
|
+
def validates_acceptance_of(*atts)
|
|
74
|
+
Deprecation.deprecate('Sequel::Model.validates_acceptance_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
75
|
+
opts = {
|
|
76
|
+
:message => 'is not accepted',
|
|
77
|
+
:allow_nil => true,
|
|
78
|
+
:accept => '1',
|
|
79
|
+
:tag => :acceptance,
|
|
80
|
+
}.merge!(extract_options!(atts))
|
|
81
|
+
atts << opts
|
|
82
|
+
validates_each(*atts) do |o, a, v|
|
|
83
|
+
o.errors[a] << opts[:message] unless v == opts[:accept]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Validates confirmation of an attribute. Checks that the object has
|
|
88
|
+
# a _confirmation value matching the current value. For example:
|
|
89
|
+
#
|
|
90
|
+
# validates_confirmation_of :blah
|
|
91
|
+
#
|
|
92
|
+
# Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
|
|
93
|
+
# or email addresses on web forms.
|
|
94
|
+
#
|
|
95
|
+
# Possible Options:
|
|
96
|
+
# * :message - The message to use (default: 'is not confirmed')
|
|
97
|
+
def validates_confirmation_of(*atts)
|
|
98
|
+
Deprecation.deprecate('Sequel::Model.validates_confirmation_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
99
|
+
opts = {
|
|
100
|
+
:message => 'is not confirmed',
|
|
101
|
+
:tag => :confirmation,
|
|
102
|
+
}.merge!(extract_options!(atts))
|
|
103
|
+
atts << opts
|
|
104
|
+
validates_each(*atts) do |o, a, v|
|
|
105
|
+
o.errors[a] << opts[:message] unless v == o.send(:"#{a}_confirmation")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Adds a validation for each of the given attributes using the supplied
|
|
110
|
+
# block. The block must accept three arguments: instance, attribute and
|
|
111
|
+
# value, e.g.:
|
|
112
|
+
#
|
|
113
|
+
# validates_each :name, :password do |object, attribute, value|
|
|
114
|
+
# object.errors[attribute] << 'is not nice' unless value.nice?
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# Possible Options:
|
|
118
|
+
# * :allow_blank - Whether to skip the validation if the value is blank.
|
|
119
|
+
# * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
|
|
120
|
+
# values hash. This is different from allow_nil, because Sequel only sends the attributes
|
|
121
|
+
# in the values when doing an insert or update. If the attribute is not present, Sequel
|
|
122
|
+
# doesn't specify it, so the database will use the table's default value. This is different
|
|
123
|
+
# from having an attribute in values with a value of nil, which Sequel will send as NULL.
|
|
124
|
+
# If your database table has a non NULL default, this may be a good option to use. You
|
|
125
|
+
# don't want to use allow_nil, because if the attribute is in values but has a value nil,
|
|
126
|
+
# Sequel will attempt to insert a NULL value into the database, instead of using the
|
|
127
|
+
# database's default.
|
|
128
|
+
# * :allow_nil - Whether to skip the validation if the value is nil.
|
|
129
|
+
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
|
|
130
|
+
# skipping this validation if it returns nil or false.
|
|
131
|
+
# * :tag - The tag to use for this validation.
|
|
132
|
+
def validates_each(*atts, &block)
|
|
133
|
+
Deprecation.deprecate('Sequel::Model.validates_each', 'Use Model.plugin(:validation_class_methods) first')
|
|
134
|
+
opts = extract_options!(atts)
|
|
135
|
+
blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
|
|
136
|
+
proc do |o,a,v|
|
|
137
|
+
next if i && !validation_if_proc(o, i)
|
|
138
|
+
next if an && Array(v).all?{|x| x.nil?}
|
|
139
|
+
next if ab && Array(v).all?{|x| x.blank?}
|
|
140
|
+
next if am && Array(a).all?{|x| !o.values.has_key?(x)}
|
|
141
|
+
block.call(o,a,v)
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
block
|
|
145
|
+
end
|
|
146
|
+
tag = opts[:tag]
|
|
147
|
+
atts.each do |a|
|
|
148
|
+
a_vals = validations[a]
|
|
149
|
+
if tag && (old = a_vals.find{|x| x[0] == tag})
|
|
150
|
+
old[1] = blk
|
|
151
|
+
else
|
|
152
|
+
a_vals << [tag, blk]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Validates the format of an attribute, checking the string representation of the
|
|
158
|
+
# value against the regular expression provided by the :with option.
|
|
159
|
+
#
|
|
160
|
+
# Possible Options:
|
|
161
|
+
# * :message - The message to use (default: 'is invalid')
|
|
162
|
+
# * :with - The regular expression to validate the value with (required).
|
|
163
|
+
def validates_format_of(*atts)
|
|
164
|
+
Deprecation.deprecate('Sequel::Model.validates_format_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
165
|
+
opts = {
|
|
166
|
+
:message => 'is invalid',
|
|
167
|
+
:tag => :format,
|
|
168
|
+
}.merge!(extract_options!(atts))
|
|
169
|
+
|
|
170
|
+
unless opts[:with].is_a?(Regexp)
|
|
171
|
+
raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
atts << opts
|
|
175
|
+
validates_each(*atts) do |o, a, v|
|
|
176
|
+
o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Validates the length of an attribute.
|
|
181
|
+
#
|
|
182
|
+
# Possible Options:
|
|
183
|
+
# * :is - The exact size required for the value to be valid (no default)
|
|
184
|
+
# * :maximum - The maximum size allowed for the value (no default)
|
|
185
|
+
# * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
|
|
186
|
+
# options if present)
|
|
187
|
+
# * :minimum - The minimum size allowed for the value (no default)
|
|
188
|
+
# * :too_long - The message to use use if it the value is too long (default: 'is too long')
|
|
189
|
+
# * :too_short - The message to use use if it the value is too short (default: 'is too short')
|
|
190
|
+
# * :within - The array/range that must include the size of the value for it to be valid (no default)
|
|
191
|
+
# * :wrong_length - The message to use use if it the value is not valid (default: 'is the wrong length')
|
|
192
|
+
def validates_length_of(*atts)
|
|
193
|
+
Deprecation.deprecate('Sequel::Model.validates_length_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
194
|
+
opts = {
|
|
195
|
+
:too_long => 'is too long',
|
|
196
|
+
:too_short => 'is too short',
|
|
197
|
+
:wrong_length => 'is the wrong length'
|
|
198
|
+
}.merge!(extract_options!(atts))
|
|
199
|
+
|
|
200
|
+
opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
|
|
201
|
+
atts << opts
|
|
202
|
+
validates_each(*atts) do |o, a, v|
|
|
203
|
+
if m = opts[:maximum]
|
|
204
|
+
o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
|
|
205
|
+
end
|
|
206
|
+
if m = opts[:minimum]
|
|
207
|
+
o.errors[a] << (opts[:message] || opts[:too_short]) unless v && v.size >= m
|
|
208
|
+
end
|
|
209
|
+
if i = opts[:is]
|
|
210
|
+
o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && v.size == i
|
|
211
|
+
end
|
|
212
|
+
if w = opts[:within]
|
|
213
|
+
o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Validates whether an attribute is not a string. This is generally useful
|
|
219
|
+
# in conjunction with raise_on_typecast_failure = false, where you are
|
|
220
|
+
# passing in string values for non-string attributes (such as numbers and dates).
|
|
221
|
+
# If typecasting fails (invalid number or date), the value of the attribute will
|
|
222
|
+
# be a string in an invalid format, and if typecasting succeeds, the value will
|
|
223
|
+
# not be a string.
|
|
224
|
+
#
|
|
225
|
+
# Possible Options:
|
|
226
|
+
# * :message - The message to use (default: 'is a string' or 'is not a valid (integer|datetime|etc.)' if the type is known)
|
|
227
|
+
def validates_not_string(*atts)
|
|
228
|
+
Deprecation.deprecate('Sequel::Model.validates_not_string', 'Use Model.plugin(:validation_class_methods) first')
|
|
229
|
+
opts = {
|
|
230
|
+
:tag => :not_string,
|
|
231
|
+
}.merge!(extract_options!(atts))
|
|
232
|
+
atts << opts
|
|
233
|
+
validates_each(*atts) do |o, a, v|
|
|
234
|
+
if v.is_a?(String)
|
|
235
|
+
unless message = opts[:message]
|
|
236
|
+
message = if sch = o.db_schema[a] and typ = sch[:type]
|
|
237
|
+
"is not a valid #{typ}"
|
|
238
|
+
else
|
|
239
|
+
"is a string"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
o.errors[a] << message
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Validates whether an attribute is a number.
|
|
248
|
+
#
|
|
249
|
+
# Possible Options:
|
|
250
|
+
# * :message - The message to use (default: 'is not a number')
|
|
251
|
+
# * :only_integer - Whether only integers are valid values (default: false)
|
|
252
|
+
def validates_numericality_of(*atts)
|
|
253
|
+
Deprecation.deprecate('Sequel::Model.validates_numericality_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
254
|
+
opts = {
|
|
255
|
+
:message => 'is not a number',
|
|
256
|
+
:tag => :numericality,
|
|
257
|
+
}.merge!(extract_options!(atts))
|
|
258
|
+
atts << opts
|
|
259
|
+
validates_each(*atts) do |o, a, v|
|
|
260
|
+
begin
|
|
261
|
+
if opts[:only_integer]
|
|
262
|
+
Kernel.Integer(v.to_s)
|
|
263
|
+
else
|
|
264
|
+
Kernel.Float(v.to_s)
|
|
265
|
+
end
|
|
266
|
+
rescue
|
|
267
|
+
o.errors[a] << opts[:message]
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Validates the presence of an attribute. Requires the value not be blank,
|
|
273
|
+
# with false considered present instead of absent.
|
|
274
|
+
#
|
|
275
|
+
# Possible Options:
|
|
276
|
+
# * :message - The message to use (default: 'is not present')
|
|
277
|
+
def validates_presence_of(*atts)
|
|
278
|
+
Deprecation.deprecate('Sequel::Model.validates_presence_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
279
|
+
opts = {
|
|
280
|
+
:message => 'is not present',
|
|
281
|
+
:tag => :presence,
|
|
282
|
+
}.merge!(extract_options!(atts))
|
|
283
|
+
atts << opts
|
|
284
|
+
validates_each(*atts) do |o, a, v|
|
|
285
|
+
o.errors[a] << opts[:message] if v.blank? && v != false
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Validates that an attribute is within a specified range or set of values.
|
|
290
|
+
#
|
|
291
|
+
# Possible Options:
|
|
292
|
+
# * :in - An array or range of values to check for validity (required)
|
|
293
|
+
# * :message - The message to use (default: 'is not in range or set: <specified range>')
|
|
294
|
+
def validates_inclusion_of(*atts)
|
|
295
|
+
Deprecation.deprecate('Sequel::Model.validates_inclusion_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
296
|
+
opts = extract_options!(atts)
|
|
297
|
+
unless opts[:in] && opts[:in].respond_to?(:include?)
|
|
298
|
+
raise ArgumentError, "The :in parameter is required, and respond to include?"
|
|
299
|
+
end
|
|
300
|
+
opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
|
|
301
|
+
atts << opts
|
|
302
|
+
validates_each(*atts) do |o, a, v|
|
|
303
|
+
o.errors[a] << opts[:message] unless opts[:in].include?(v)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Validates only if the fields in the model (specified by atts) are
|
|
308
|
+
# unique in the database. Pass an array of fields instead of multiple
|
|
309
|
+
# fields to specify that the combination of fields must be unique,
|
|
310
|
+
# instead of that each field should have a unique value.
|
|
311
|
+
#
|
|
312
|
+
# This means that the code:
|
|
313
|
+
# validates_uniqueness_of([:column1, :column2])
|
|
314
|
+
# validates the grouping of column1 and column2 while
|
|
315
|
+
# validates_uniqueness_of(:column1, :column2)
|
|
316
|
+
# validates them separately.
|
|
317
|
+
#
|
|
318
|
+
# You should also add a unique index in the
|
|
319
|
+
# database, as this suffers from a fairly obvious race condition.
|
|
320
|
+
#
|
|
321
|
+
# Possible Options:
|
|
322
|
+
# * :message - The message to use (default: 'is already taken')
|
|
323
|
+
def validates_uniqueness_of(*atts)
|
|
324
|
+
Deprecation.deprecate('Sequel::Model.validates_uniqueness_of', 'Use Model.plugin(:validation_class_methods) first')
|
|
325
|
+
opts = {
|
|
326
|
+
:message => 'is already taken',
|
|
327
|
+
:tag => :uniqueness,
|
|
328
|
+
}.merge!(extract_options!(atts))
|
|
329
|
+
|
|
330
|
+
atts << opts
|
|
331
|
+
validates_each(*atts) do |o, a, v|
|
|
332
|
+
error_field = a
|
|
333
|
+
a = Array(a)
|
|
334
|
+
v = Array(v)
|
|
335
|
+
ds = o.class.filter(a.zip(v))
|
|
336
|
+
num_dups = ds.count
|
|
337
|
+
allow = if num_dups == 0
|
|
338
|
+
# No unique value in the database
|
|
339
|
+
true
|
|
340
|
+
elsif num_dups > 1
|
|
341
|
+
# Multiple "unique" values in the database!!
|
|
342
|
+
# Someone didn't add a unique index
|
|
343
|
+
false
|
|
344
|
+
elsif o.new?
|
|
345
|
+
# New record, but unique value already exists in the database
|
|
346
|
+
false
|
|
347
|
+
elsif ds.first === o
|
|
348
|
+
# Unique value exists in database, but for the same record, so the update won't cause a duplicate record
|
|
349
|
+
true
|
|
350
|
+
else
|
|
351
|
+
false
|
|
352
|
+
end
|
|
353
|
+
o.errors[error_field] << opts[:message] unless allow
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Returns the validations hash for the class.
|
|
358
|
+
def validations
|
|
359
|
+
@validations ||= Hash.new {|h, k| h[k] = []}
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
private
|
|
363
|
+
|
|
364
|
+
def extract_options!(array)
|
|
365
|
+
array.last.is_a?(Hash) ? array.pop : {}
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def validation_if_proc(o, i)
|
|
369
|
+
case i
|
|
370
|
+
when Symbol then o.send(i)
|
|
371
|
+
when Proc then o.instance_eval(&i)
|
|
372
|
+
when nil then true
|
|
373
|
+
else raise(::Sequel::Error, "invalid value for :if validation option")
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
module InstanceMethods
|
|
379
|
+
# Validates the object.
|
|
380
|
+
def validate
|
|
381
|
+
model.validate(self)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
Model.plugin :deprecated_validation_class_methods
|
|
388
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Model
|
|
3
|
+
# Errors represents validation errors, a simple hash subclass
|
|
4
|
+
# with a few convenience methods.
|
|
5
|
+
class Errors < ::Hash
|
|
6
|
+
ATTRIBUTE_JOINER = ' and '
|
|
7
|
+
|
|
8
|
+
# Assign an array of messages for each attribute on access
|
|
9
|
+
def initialize
|
|
10
|
+
super{|h,k| h[k] = []}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Adds an error for the given attribute.
|
|
14
|
+
def add(att, msg)
|
|
15
|
+
self[att] << msg
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Return the total number of error messages.
|
|
19
|
+
def count
|
|
20
|
+
values.inject(0){|m, v| m + v.length}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns an array of fully-formatted error messages.
|
|
24
|
+
def full_messages
|
|
25
|
+
inject([]) do |m, kv|
|
|
26
|
+
att, errors = *kv
|
|
27
|
+
errors.each {|e| m << "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}"}
|
|
28
|
+
m
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the array of errors for the given attribute, or nil
|
|
33
|
+
# if there are no errors for the attribute.
|
|
34
|
+
def on(att)
|
|
35
|
+
self[att] if include?(att)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Sequel
|
|
2
|
-
# This exception will be raised when raise_on_save_failure is set and
|
|
3
|
-
class ValidationFailed < Error;end
|
|
2
|
+
# This exception will be raised when raise_on_save_failure is set and validation fails
|
|
3
|
+
class ValidationFailed < Error; end
|
|
4
4
|
|
|
5
|
-
# This exception will be raised when raise_on_save_failure is set and a before hook
|
|
6
|
-
class BeforeHookFailed < Error;end
|
|
5
|
+
# This exception will be raised when raise_on_save_failure is set and a before hook returns false
|
|
6
|
+
class BeforeHookFailed < Error; end
|
|
7
7
|
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
# Yield the Inflections module if a block is given, and return
|
|
3
|
+
# the Inflections module.
|
|
4
|
+
def self.inflections
|
|
5
|
+
yield Inflections if block_given?
|
|
6
|
+
Inflections
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# This module acts as a singleton returned/yielded by Sequel.inflections,
|
|
10
|
+
# which is used to override or specify additional inflection rules
|
|
11
|
+
# for Sequel. Examples:
|
|
12
|
+
#
|
|
13
|
+
# Sequel.inflections do |inflect|
|
|
14
|
+
# inflect.plural /^(ox)$/i, '\1\2en'
|
|
15
|
+
# inflect.singular /^(ox)en/i, '\1'
|
|
16
|
+
#
|
|
17
|
+
# inflect.irregular 'octopus', 'octopi'
|
|
18
|
+
#
|
|
19
|
+
# inflect.uncountable "equipment"
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
|
|
23
|
+
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
|
|
24
|
+
# already have been loaded.
|
|
25
|
+
module Inflections
|
|
26
|
+
CAMELIZE_CONVERT_REGEXP = /(^|_)(.)/.freeze
|
|
27
|
+
CAMELIZE_MODULE_REGEXP = /\/(.?)/.freeze
|
|
28
|
+
DASH = '-'.freeze
|
|
29
|
+
DEMODULIZE_CONVERT_REGEXP = /^.*::/.freeze
|
|
30
|
+
EMPTY_STRING= ''.freeze
|
|
31
|
+
SLASH = '/'.freeze
|
|
32
|
+
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
|
|
33
|
+
UNDERSCORE = '_'.freeze
|
|
34
|
+
UNDERSCORE_CONVERT_REGEXP1 = /([A-Z]+)([A-Z][a-z])/.freeze
|
|
35
|
+
UNDERSCORE_CONVERT_REGEXP2 = /([a-z\d])([A-Z])/.freeze
|
|
36
|
+
UNDERSCORE_CONVERT_REPLACE = '\1_\2'.freeze
|
|
37
|
+
UNDERSCORE_MODULE_REGEXP = /::/.freeze
|
|
38
|
+
|
|
39
|
+
@plurals, @singulars, @uncountables = [], [], []
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
attr_reader :plurals, :singulars, :uncountables
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
|
|
46
|
+
# the options are: :plurals, :singulars, :uncountables
|
|
47
|
+
#
|
|
48
|
+
# Examples:
|
|
49
|
+
# clear :all
|
|
50
|
+
# clear :plurals
|
|
51
|
+
def self.clear(scope = :all)
|
|
52
|
+
case scope
|
|
53
|
+
when :all
|
|
54
|
+
@plurals, @singulars, @uncountables = [], [], []
|
|
55
|
+
else
|
|
56
|
+
instance_variable_set("@#{scope}", [])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
|
|
61
|
+
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
|
|
62
|
+
#
|
|
63
|
+
# Examples:
|
|
64
|
+
# irregular 'octopus', 'octopi'
|
|
65
|
+
# irregular 'person', 'people'
|
|
66
|
+
def self.irregular(singular, plural)
|
|
67
|
+
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
|
|
68
|
+
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
|
|
72
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
|
73
|
+
#
|
|
74
|
+
# Example:
|
|
75
|
+
# plural(/(x|ch|ss|sh)$/i, '\1es')
|
|
76
|
+
def self.plural(rule, replacement)
|
|
77
|
+
@plurals.insert(0, [rule, replacement])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
|
|
81
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
|
82
|
+
#
|
|
83
|
+
# Example:
|
|
84
|
+
# singular(/([^aeiouy]|qu)ies$/i, '\1y')
|
|
85
|
+
def self.singular(rule, replacement)
|
|
86
|
+
@singulars.insert(0, [rule, replacement])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Add uncountable words that shouldn't be attempted inflected.
|
|
90
|
+
#
|
|
91
|
+
# Examples:
|
|
92
|
+
# uncountable "money"
|
|
93
|
+
# uncountable "money", "information"
|
|
94
|
+
# uncountable %w( money information rice )
|
|
95
|
+
def self.uncountable(*words)
|
|
96
|
+
(@uncountables << words).flatten!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Setup the default inflections
|
|
100
|
+
# Commented out until the deprecated inflector is removed
|
|
101
|
+
=begin
|
|
102
|
+
plural(/$/, 's')
|
|
103
|
+
plural(/s$/i, 's')
|
|
104
|
+
plural(/(ax|test)is$/i, '\1es')
|
|
105
|
+
plural(/(octop|vir)us$/i, '\1i')
|
|
106
|
+
plural(/(alias|status)$/i, '\1es')
|
|
107
|
+
plural(/(bu)s$/i, '\1ses')
|
|
108
|
+
plural(/(buffal|tomat)o$/i, '\1oes')
|
|
109
|
+
plural(/([ti])um$/i, '\1a')
|
|
110
|
+
plural(/sis$/i, 'ses')
|
|
111
|
+
plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
|
|
112
|
+
plural(/(hive)$/i, '\1s')
|
|
113
|
+
plural(/([^aeiouy]|qu)y$/i, '\1ies')
|
|
114
|
+
plural(/(x|ch|ss|sh)$/i, '\1es')
|
|
115
|
+
plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
|
|
116
|
+
plural(/([m|l])ouse$/i, '\1ice')
|
|
117
|
+
plural(/^(ox)$/i, '\1en')
|
|
118
|
+
plural(/(quiz)$/i, '\1zes')
|
|
119
|
+
|
|
120
|
+
singular(/s$/i, '')
|
|
121
|
+
singular(/(n)ews$/i, '\1ews')
|
|
122
|
+
singular(/([ti])a$/i, '\1um')
|
|
123
|
+
singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
|
|
124
|
+
singular(/(^analy)ses$/i, '\1sis')
|
|
125
|
+
singular(/([^f])ves$/i, '\1fe')
|
|
126
|
+
singular(/(hive)s$/i, '\1')
|
|
127
|
+
singular(/(tive)s$/i, '\1')
|
|
128
|
+
singular(/([lr])ves$/i, '\1f')
|
|
129
|
+
singular(/([^aeiouy]|qu)ies$/i, '\1y')
|
|
130
|
+
singular(/(s)eries$/i, '\1eries')
|
|
131
|
+
singular(/(m)ovies$/i, '\1ovie')
|
|
132
|
+
singular(/(x|ch|ss|sh)es$/i, '\1')
|
|
133
|
+
singular(/([m|l])ice$/i, '\1ouse')
|
|
134
|
+
singular(/(bus)es$/i, '\1')
|
|
135
|
+
singular(/(o)es$/i, '\1')
|
|
136
|
+
singular(/(shoe)s$/i, '\1')
|
|
137
|
+
singular(/(cris|ax|test)es$/i, '\1is')
|
|
138
|
+
singular(/(octop|vir)i$/i, '\1us')
|
|
139
|
+
singular(/(alias|status)es$/i, '\1')
|
|
140
|
+
singular(/^(ox)en/i, '\1')
|
|
141
|
+
singular(/(vert|ind)ices$/i, '\1ex')
|
|
142
|
+
singular(/(matr)ices$/i, '\1ix')
|
|
143
|
+
singular(/(quiz)zes$/i, '\1')
|
|
144
|
+
|
|
145
|
+
irregular('person', 'people')
|
|
146
|
+
irregular('man', 'men')
|
|
147
|
+
irregular('child', 'children')
|
|
148
|
+
irregular('sex', 'sexes')
|
|
149
|
+
irregular('move', 'moves')
|
|
150
|
+
|
|
151
|
+
uncountable(%w(equipment information rice money species series fish sheep))
|
|
152
|
+
=end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
# Convert the given string to CamelCase. Will also convert '/' to '::' which is useful for converting paths to namespaces.
|
|
157
|
+
def camelize(s)
|
|
158
|
+
s = s.to_s
|
|
159
|
+
# return s.camelize if s.respond_to?(:camelize)
|
|
160
|
+
s = s.gsub(CAMELIZE_MODULE_REGEXP){|x| "::#{x[-1..-1].upcase unless x == SLASH}"}.gsub(CAMELIZE_CONVERT_REGEXP){|x| x[-1..-1].upcase}
|
|
161
|
+
s
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Tries to find a declared constant with the name specified
|
|
165
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
|
166
|
+
# or is not initialized.
|
|
167
|
+
def constantize(s)
|
|
168
|
+
s = s.to_s
|
|
169
|
+
# return s.constantize if s.respond_to?(:constantize)
|
|
170
|
+
raise(NameError, "#{inspect} is not a valid constant name!") unless m = VALID_CONSTANT_NAME_REGEXP.match(s.to_s)
|
|
171
|
+
Object.module_eval("::#{m[1]}", __FILE__, __LINE__)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Removes the module part from the expression in the string
|
|
175
|
+
def demodulize(s)
|
|
176
|
+
s = s.to_s
|
|
177
|
+
# return s.demodulize if s.respond_to?(:demodulize)
|
|
178
|
+
s.gsub(DEMODULIZE_CONVERT_REGEXP, EMPTY_STRING)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Returns the plural form of the word in the string.
|
|
182
|
+
def pluralize(s)
|
|
183
|
+
s = s.to_s
|
|
184
|
+
# return s.pluralize if s.respond_to?(:pluralize)
|
|
185
|
+
result = s.dup
|
|
186
|
+
Inflections.plurals.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
|
|
187
|
+
result
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The reverse of pluralize, returns the singular form of a word in a string.
|
|
191
|
+
def singularize(s)
|
|
192
|
+
s = s.to_s
|
|
193
|
+
# return s.singularize if s.respond_to?(:singularize)
|
|
194
|
+
result = s.dup
|
|
195
|
+
Inflections.singulars.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase)
|
|
196
|
+
result
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# The reverse of camelize. Makes an underscored form from the expression in the string.
|
|
200
|
+
# Also changes '::' to '/' to convert namespaces to paths.
|
|
201
|
+
def underscore(s)
|
|
202
|
+
s = s.to_s
|
|
203
|
+
# return s.underscore if s.respond_to?(:underscore)
|
|
204
|
+
s.gsub(UNDERSCORE_MODULE_REGEXP, SLASH).gsub(UNDERSCORE_CONVERT_REGEXP1, UNDERSCORE_CONVERT_REPLACE).
|
|
205
|
+
gsub(UNDERSCORE_CONVERT_REGEXP2, UNDERSCORE_CONVERT_REPLACE).tr(DASH, UNDERSCORE).downcase
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|