sequel 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/CHANGELOG +168 -0
  2. data/README.rdoc +77 -95
  3. data/Rakefile +100 -80
  4. data/bin/sequel +2 -1
  5. data/doc/advanced_associations.rdoc +23 -32
  6. data/doc/cheat_sheet.rdoc +23 -40
  7. data/doc/dataset_filtering.rdoc +6 -6
  8. data/doc/prepared_statements.rdoc +22 -22
  9. data/doc/release_notes/2.12.0.txt +534 -0
  10. data/doc/schema.rdoc +3 -1
  11. data/doc/sharding.rdoc +8 -8
  12. data/doc/virtual_rows.rdoc +65 -0
  13. data/lib/sequel.rb +1 -1
  14. data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
  15. data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
  16. data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
  17. data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
  18. data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
  19. data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
  20. data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
  21. data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
  22. data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
  23. data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
  24. data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
  25. data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
  26. data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
  27. data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
  28. data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
  29. data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
  30. data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
  31. data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
  32. data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
  33. data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
  34. data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
  35. data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
  36. data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
  37. data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
  38. data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
  39. data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
  40. data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
  41. data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
  42. data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
  43. data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
  44. data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
  45. data/lib/sequel/core.rb +221 -0
  46. data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
  47. data/lib/{sequel_core → sequel}/database.rb +264 -149
  48. data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
  49. data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
  50. data/lib/sequel/database/schema_sql.rb +224 -0
  51. data/lib/{sequel_core → sequel}/dataset.rb +78 -236
  52. data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
  53. data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
  54. data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
  55. data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
  56. data/lib/sequel/deprecated.rb +593 -0
  57. data/lib/sequel/deprecated_migration.rb +91 -0
  58. data/lib/sequel/exceptions.rb +48 -0
  59. data/lib/sequel/extensions/blank.rb +42 -0
  60. data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
  61. data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
  62. data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
  63. data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
  64. data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
  65. data/lib/sequel/extensions/string_date_time.rb +47 -0
  66. data/lib/sequel/metaprogramming.rb +43 -0
  67. data/lib/sequel/model.rb +110 -0
  68. data/lib/sequel/model/associations.rb +1300 -0
  69. data/lib/sequel/model/base.rb +937 -0
  70. data/lib/sequel/model/deprecated.rb +204 -0
  71. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  72. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  73. data/lib/sequel/model/deprecated_validations.rb +388 -0
  74. data/lib/sequel/model/errors.rb +39 -0
  75. data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
  76. data/lib/sequel/model/inflections.rb +208 -0
  77. data/lib/sequel/model/plugins.rb +76 -0
  78. data/lib/sequel/plugins/caching.rb +122 -0
  79. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  80. data/lib/sequel/plugins/schema.rb +53 -0
  81. data/lib/sequel/plugins/serialization.rb +117 -0
  82. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  83. data/lib/sequel/plugins/validation_class_methods.rb +384 -0
  84. data/lib/sequel/plugins/validation_helpers.rb +150 -0
  85. data/lib/{sequel_core → sequel}/sql.rb +125 -190
  86. data/lib/{sequel_core → sequel}/version.rb +2 -1
  87. data/lib/sequel_core.rb +1 -172
  88. data/lib/sequel_model.rb +1 -91
  89. data/spec/adapters/firebird_spec.rb +5 -5
  90. data/spec/adapters/informix_spec.rb +1 -1
  91. data/spec/adapters/mysql_spec.rb +128 -42
  92. data/spec/adapters/oracle_spec.rb +47 -19
  93. data/spec/adapters/postgres_spec.rb +64 -52
  94. data/spec/adapters/spec_helper.rb +1 -1
  95. data/spec/adapters/sqlite_spec.rb +12 -17
  96. data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
  97. data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
  98. data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
  99. data/spec/{sequel_core → core}/database_spec.rb +135 -99
  100. data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
  101. data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
  102. data/spec/core/migration_spec.rb +263 -0
  103. data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
  104. data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
  105. data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
  106. data/spec/{sequel_core → core}/schema_spec.rb +8 -10
  107. data/spec/{sequel_core → core}/spec_helper.rb +29 -2
  108. data/spec/{sequel_core → core}/version_spec.rb +0 -0
  109. data/spec/extensions/blank_spec.rb +67 -0
  110. data/spec/extensions/caching_spec.rb +201 -0
  111. data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
  112. data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
  113. data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
  114. data/spec/extensions/pagination_spec.rb +99 -0
  115. data/spec/extensions/pretty_table_spec.rb +91 -0
  116. data/spec/extensions/query_spec.rb +85 -0
  117. data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
  118. data/spec/extensions/serialization_spec.rb +109 -0
  119. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  120. data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
  121. data/spec/extensions/string_date_time_spec.rb +93 -0
  122. data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
  123. data/spec/extensions/validation_helpers_spec.rb +291 -0
  124. data/spec/integration/dataset_test.rb +31 -0
  125. data/spec/integration/eager_loader_test.rb +17 -30
  126. data/spec/integration/schema_test.rb +8 -5
  127. data/spec/integration/spec_helper.rb +17 -0
  128. data/spec/integration/transaction_test.rb +68 -0
  129. data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
  130. data/spec/{sequel_model → model}/associations_spec.rb +23 -10
  131. data/spec/{sequel_model → model}/base_spec.rb +29 -20
  132. data/spec/{sequel_model → model}/caching_spec.rb +16 -14
  133. data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
  134. data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
  135. data/spec/model/hooks_spec.rb +472 -0
  136. data/spec/model/inflector_spec.rb +126 -0
  137. data/spec/{sequel_model → model}/model_spec.rb +25 -20
  138. data/spec/model/plugins_spec.rb +142 -0
  139. data/spec/{sequel_model → model}/record_spec.rb +121 -62
  140. data/spec/model/schema_spec.rb +92 -0
  141. data/spec/model/spec_helper.rb +124 -0
  142. data/spec/model/validations_spec.rb +1080 -0
  143. metadata +136 -107
  144. data/lib/sequel_core/core_ext.rb +0 -217
  145. data/lib/sequel_core/dataset/callback.rb +0 -13
  146. data/lib/sequel_core/dataset/schema.rb +0 -15
  147. data/lib/sequel_core/deprecated.rb +0 -26
  148. data/lib/sequel_core/exceptions.rb +0 -44
  149. data/lib/sequel_core/schema.rb +0 -2
  150. data/lib/sequel_core/schema/sql.rb +0 -325
  151. data/lib/sequel_model/association_reflection.rb +0 -267
  152. data/lib/sequel_model/associations.rb +0 -499
  153. data/lib/sequel_model/base.rb +0 -539
  154. data/lib/sequel_model/caching.rb +0 -82
  155. data/lib/sequel_model/dataset_methods.rb +0 -26
  156. data/lib/sequel_model/eager_loading.rb +0 -370
  157. data/lib/sequel_model/hooks.rb +0 -101
  158. data/lib/sequel_model/plugins.rb +0 -62
  159. data/lib/sequel_model/record.rb +0 -568
  160. data/lib/sequel_model/schema.rb +0 -49
  161. data/lib/sequel_model/validations.rb +0 -429
  162. data/spec/sequel_model/plugins_spec.rb +0 -80
@@ -0,0 +1,63 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in Single Table Inheritance plugin makes subclasses
4
+ # of this model only load rows where the given key field matches the
5
+ # subclass's name. If the key given has a NULL value or there are
6
+ # any problems looking up the class, uses the current class.
7
+ #
8
+ # You should only use this in the parent class, not in the subclasses.
9
+ #
10
+ # You shouldn't call set_dataset in the model after applying this
11
+ # plugin, otherwise subclasses might use the wrong dataset.
12
+ #
13
+ # The filters and row_proc that sti_key sets up in subclasses may not work correctly if
14
+ # those subclasses have further subclasses. For those middle subclasses,
15
+ # you may need to call set_dataset manually with the correct filter and
16
+ # row_proc.
17
+ module SingleTableInheritance
18
+ # Set the sti_key and sti_dataset for the model, and change the
19
+ # dataset's row_proc so that the dataset yields objects of varying classes,
20
+ # where the class used has the same name as the key field.
21
+ def self.apply(model, key)
22
+ m = model.method(:constantize)
23
+ model.instance_eval do
24
+ @sti_key = key
25
+ @sti_dataset = dataset
26
+ dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ # The base dataset for STI, to which filters are added to get
32
+ # only the models for the specific STI subclass.
33
+ attr_reader :sti_dataset
34
+
35
+ # The column name holding the STI key for this model
36
+ attr_reader :sti_key
37
+
38
+ # Copy the sti_key and sti_dataset to the subclasses, and filter the
39
+ # subclass's dataset so it is restricted to rows where the key column
40
+ # matches the subclass's name.
41
+ def inherited(subclass)
42
+ super
43
+ sk = sti_key
44
+ sd = sti_dataset
45
+ subclass.set_dataset(sd.filter(sk=>subclass.name.to_s), :inherited=>true)
46
+ subclass.instance_eval do
47
+ @sti_key = sk
48
+ @sti_dataset = sd
49
+ @simple_table = nil
50
+ end
51
+ end
52
+ end
53
+
54
+ module InstanceMethods
55
+ # Set the sti_key column to the name of the model.
56
+ def before_create
57
+ return false if super == false
58
+ send("#{model.sti_key}=", model.name.to_s)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,384 @@
1
+ module Sequel
2
+ require 'extensions/blank'
3
+
4
+ module Plugins
5
+ module ValidationClassMethods
6
+ module ClassMethods
7
+ # The Generator class is used to generate validation definitions using
8
+ # the validates {} idiom.
9
+ class Generator
10
+ # Initializes a new generator.
11
+ def initialize(receiver ,&block)
12
+ @receiver = receiver
13
+ instance_eval(&block)
14
+ end
15
+
16
+ # Delegates method calls to the receiver by calling receiver.validates_xxx.
17
+ def method_missing(m, *args, &block)
18
+ @receiver.send(:"validates_#{m}", *args, &block)
19
+ end
20
+ end
21
+
22
+ # Returns true if validations are defined.
23
+ def has_validations?
24
+ !validations.empty?
25
+ end
26
+
27
+ # Instructs the model to skip validations defined in superclasses
28
+ def skip_superclass_validations
29
+ @skip_superclass_validations = true
30
+ end
31
+
32
+ # Instructs the model to skip validations defined in superclasses
33
+ def skip_superclass_validations?
34
+ defined?(@skip_superclass_validations) && @skip_superclass_validations
35
+ end
36
+
37
+ # Defines validations by converting a longhand block into a series of
38
+ # shorthand definitions. For example:
39
+ #
40
+ # class MyClass < Sequel::Model
41
+ # validates do
42
+ # length_of :name, :minimum => 6
43
+ # length_of :password, :minimum => 8
44
+ # end
45
+ # end
46
+ #
47
+ # is equivalent to:
48
+ # class MyClass < Sequel::Model
49
+ # validates_length_of :name, :minimum => 6
50
+ # validates_length_of :password, :minimum => 8
51
+ # end
52
+ def validates(&block)
53
+ Generator.new(self, &block)
54
+ end
55
+
56
+ # Validates the given instance.
57
+ def validate(o)
58
+ superclass.validate(o) if superclass.respond_to?(:validate) && !skip_superclass_validations?
59
+ validations.each do |att, procs|
60
+ v = case att
61
+ when Array
62
+ att.collect{|a| o.send(a)}
63
+ else
64
+ o.send(att)
65
+ end
66
+ procs.each {|tag, p| p.call(o, att, v)}
67
+ end
68
+ end
69
+
70
+ # Validates acceptance of an attribute. Just checks that the value
71
+ # is equal to the :accept option. This method is unique in that
72
+ # :allow_nil is assumed to be true instead of false.
73
+ #
74
+ # Possible Options:
75
+ # * :accept - The value required for the object to be valid (default: '1')
76
+ # * :message - The message to use (default: 'is not accepted')
77
+ def validates_acceptance_of(*atts)
78
+ opts = {
79
+ :message => 'is not accepted',
80
+ :allow_nil => true,
81
+ :accept => '1',
82
+ :tag => :acceptance,
83
+ }.merge!(extract_options!(atts))
84
+ atts << opts
85
+ validates_each(*atts) do |o, a, v|
86
+ o.errors.add(a, opts[:message]) unless v == opts[:accept]
87
+ end
88
+ end
89
+
90
+ # Validates confirmation of an attribute. Checks that the object has
91
+ # a _confirmation value matching the current value. For example:
92
+ #
93
+ # validates_confirmation_of :blah
94
+ #
95
+ # Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
96
+ # or email addresses on web forms.
97
+ #
98
+ # Possible Options:
99
+ # * :message - The message to use (default: 'is not confirmed')
100
+ def validates_confirmation_of(*atts)
101
+ opts = {
102
+ :message => 'is not confirmed',
103
+ :tag => :confirmation,
104
+ }.merge!(extract_options!(atts))
105
+ atts << opts
106
+ validates_each(*atts) do |o, a, v|
107
+ o.errors.add(a, opts[:message]) unless v == o.send(:"#{a}_confirmation")
108
+ end
109
+ end
110
+
111
+ # Adds a validation for each of the given attributes using the supplied
112
+ # block. The block must accept three arguments: instance, attribute and
113
+ # value, e.g.:
114
+ #
115
+ # validates_each :name, :password do |object, attribute, value|
116
+ # object.errors.add(attribute, 'is not nice') unless value.nice?
117
+ # end
118
+ #
119
+ # Possible Options:
120
+ # * :allow_blank - Whether to skip the validation if the value is blank.
121
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
122
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
123
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
124
+ # doesn't specify it, so the database will use the table's default value. This is different
125
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
126
+ # If your database table has a non NULL default, this may be a good option to use. You
127
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
128
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
129
+ # database's default.
130
+ # * :allow_nil - Whether to skip the validation if the value is nil.
131
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
132
+ # skipping this validation if it returns nil or false.
133
+ # * :tag - The tag to use for this validation.
134
+ def validates_each(*atts, &block)
135
+ opts = extract_options!(atts)
136
+ blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
137
+ proc do |o,a,v|
138
+ next if i && !validation_if_proc(o, i)
139
+ next if an && Array(v).all?{|x| x.nil?}
140
+ next if ab && Array(v).all?{|x| x.blank?}
141
+ next if am && Array(a).all?{|x| !o.values.has_key?(x)}
142
+ block.call(o,a,v)
143
+ end
144
+ else
145
+ block
146
+ end
147
+ tag = opts[:tag]
148
+ atts.each do |a|
149
+ a_vals = validations[a]
150
+ if tag && (old = a_vals.find{|x| x[0] == tag})
151
+ old[1] = blk
152
+ else
153
+ a_vals << [tag, blk]
154
+ end
155
+ end
156
+ end
157
+
158
+ # Validates the format of an attribute, checking the string representation of the
159
+ # value against the regular expression provided by the :with option.
160
+ #
161
+ # Possible Options:
162
+ # * :message - The message to use (default: 'is invalid')
163
+ # * :with - The regular expression to validate the value with (required).
164
+ def validates_format_of(*atts)
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.add(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
+ opts = {
194
+ :too_long => 'is too long',
195
+ :too_short => 'is too short',
196
+ :wrong_length => 'is the wrong length'
197
+ }.merge!(extract_options!(atts))
198
+
199
+ opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
200
+ atts << opts
201
+ validates_each(*atts) do |o, a, v|
202
+ if m = opts[:maximum]
203
+ o.errors.add(a, opts[:message] || opts[:too_long]) unless v && v.size <= m
204
+ end
205
+ if m = opts[:minimum]
206
+ o.errors.add(a, opts[:message] || opts[:too_short]) unless v && v.size >= m
207
+ end
208
+ if i = opts[:is]
209
+ o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && v.size == i
210
+ end
211
+ if w = opts[:within]
212
+ o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
213
+ end
214
+ end
215
+ end
216
+
217
+ # Validates whether an attribute is not a string. This is generally useful
218
+ # in conjunction with raise_on_typecast_failure = false, where you are
219
+ # passing in string values for non-string attributes (such as numbers and dates).
220
+ # If typecasting fails (invalid number or date), the value of the attribute will
221
+ # be a string in an invalid format, and if typecasting succeeds, the value will
222
+ # not be a string.
223
+ #
224
+ # Possible Options:
225
+ # * :message - The message to use (default: 'is a string' or 'is not a valid (integer|datetime|etc.)' if the type is known)
226
+ def validates_not_string(*atts)
227
+ opts = {
228
+ :tag => :not_string,
229
+ }.merge!(extract_options!(atts))
230
+ atts << opts
231
+ validates_each(*atts) do |o, a, v|
232
+ if v.is_a?(String)
233
+ unless message = opts[:message]
234
+ message = if sch = o.db_schema[a] and typ = sch[:type]
235
+ "is not a valid #{typ}"
236
+ else
237
+ "is a string"
238
+ end
239
+ end
240
+ o.errors.add(a, message)
241
+ end
242
+ end
243
+ end
244
+
245
+ # Validates whether an attribute is a number.
246
+ #
247
+ # Possible Options:
248
+ # * :message - The message to use (default: 'is not a number')
249
+ # * :only_integer - Whether only integers are valid values (default: false)
250
+ def validates_numericality_of(*atts)
251
+ opts = {
252
+ :message => 'is not a number',
253
+ :tag => :numericality,
254
+ }.merge!(extract_options!(atts))
255
+ atts << opts
256
+ validates_each(*atts) do |o, a, v|
257
+ begin
258
+ if opts[:only_integer]
259
+ Kernel.Integer(v.to_s)
260
+ else
261
+ Kernel.Float(v.to_s)
262
+ end
263
+ rescue
264
+ o.errors.add(a, opts[:message])
265
+ end
266
+ end
267
+ end
268
+
269
+ # Validates the presence of an attribute. Requires the value not be blank,
270
+ # with false considered present instead of absent.
271
+ #
272
+ # Possible Options:
273
+ # * :message - The message to use (default: 'is not present')
274
+ def validates_presence_of(*atts)
275
+ opts = {
276
+ :message => 'is not present',
277
+ :tag => :presence,
278
+ }.merge!(extract_options!(atts))
279
+ atts << opts
280
+ validates_each(*atts) do |o, a, v|
281
+ o.errors.add(a, opts[:message]) if v.blank? && v != false
282
+ end
283
+ end
284
+
285
+ # Validates that an attribute is within a specified range or set of values.
286
+ #
287
+ # Possible Options:
288
+ # * :in - An array or range of values to check for validity (required)
289
+ # * :message - The message to use (default: 'is not in range or set: <specified range>')
290
+ def validates_inclusion_of(*atts)
291
+ opts = extract_options!(atts)
292
+ unless opts[:in] && opts[:in].respond_to?(:include?)
293
+ raise ArgumentError, "The :in parameter is required, and respond to include?"
294
+ end
295
+ opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
296
+ atts << opts
297
+ validates_each(*atts) do |o, a, v|
298
+ o.errors.add(a, opts[:message]) unless opts[:in].include?(v)
299
+ end
300
+ end
301
+
302
+ # Validates only if the fields in the model (specified by atts) are
303
+ # unique in the database. Pass an array of fields instead of multiple
304
+ # fields to specify that the combination of fields must be unique,
305
+ # instead of that each field should have a unique value.
306
+ #
307
+ # This means that the code:
308
+ # validates_uniqueness_of([:column1, :column2])
309
+ # validates the grouping of column1 and column2 while
310
+ # validates_uniqueness_of(:column1, :column2)
311
+ # validates them separately.
312
+ #
313
+ # You should also add a unique index in the
314
+ # database, as this suffers from a fairly obvious race condition.
315
+ #
316
+ # Possible Options:
317
+ # * :message - The message to use (default: 'is already taken')
318
+ def validates_uniqueness_of(*atts)
319
+ opts = {
320
+ :message => 'is already taken',
321
+ :tag => :uniqueness,
322
+ }.merge!(extract_options!(atts))
323
+
324
+ atts << opts
325
+ validates_each(*atts) do |o, a, v|
326
+ error_field = a
327
+ a = Array(a)
328
+ v = Array(v)
329
+ ds = o.class.filter(a.zip(v))
330
+ num_dups = ds.count
331
+ allow = if num_dups == 0
332
+ # No unique value in the database
333
+ true
334
+ elsif num_dups > 1
335
+ # Multiple "unique" values in the database!!
336
+ # Someone didn't add a unique index
337
+ false
338
+ elsif o.new?
339
+ # New record, but unique value already exists in the database
340
+ false
341
+ elsif ds.first === o
342
+ # Unique value exists in database, but for the same record, so the update won't cause a duplicate record
343
+ true
344
+ else
345
+ false
346
+ end
347
+ o.errors.add(error_field, opts[:message]) unless allow
348
+ end
349
+ end
350
+
351
+ # Returns the validations hash for the class.
352
+ def validations
353
+ @validations ||= Hash.new {|h, k| h[k] = []}
354
+ end
355
+
356
+ private
357
+
358
+ # Removes and returns the last member of the array if it is a hash. Otherwise,
359
+ # an empty hash is returned This method is useful when writing methods that
360
+ # take an options hash as the last parameter.
361
+ def extract_options!(array)
362
+ array.last.is_a?(Hash) ? array.pop : {}
363
+ end
364
+
365
+ # Handle the :if option for validations
366
+ def validation_if_proc(o, i)
367
+ case i
368
+ when Symbol then o.send(i)
369
+ when Proc then o.instance_eval(&i)
370
+ when nil then true
371
+ else raise(::Sequel::Error, "invalid value for :if validation option")
372
+ end
373
+ end
374
+ end
375
+
376
+ module InstanceMethods
377
+ # Validates the object.
378
+ def validate
379
+ model.validate(self)
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,150 @@
1
+ module Sequel
2
+ module Plugins
3
+ module ValidationHelpers
4
+ # ValidationHelpers contains instance method equivalents for most of the previous
5
+ # default validations. The names and APIs have changed, though.
6
+ #
7
+ # The validates_unique validation has a unique API, but the other validations have
8
+ # the API explained here:
9
+ #
10
+ # Arguments:
11
+ # * atts - Single attribute symbol or an array of attribute symbols specifying the
12
+ # attribute(s) to validate.
13
+ # Options:
14
+ # * :allow_blank - Whether to skip the validation if the value is blank. You should
15
+ # make sure all objects respond to blank if you use this option, which you can do by
16
+ # requiring 'sequel/extensions/blank'
17
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
18
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
19
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
20
+ # doesn't specify it, so the database will use the table's default value. This is different
21
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
22
+ # If your database table has a non NULL default, this may be a good option to use. You
23
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
24
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
25
+ # database's default.
26
+ # * :allow_nil - Whether to skip the validation if the value is nil.
27
+ # * :message - The message to use
28
+ module InstanceMethods
29
+ # Check that the attribute values are the given exact length.
30
+ def validates_exact_length(exact, atts, opts={})
31
+ validatable_attributes(atts, opts){|a,v,m| (m || "is not #{exact} characters") unless v && v.length == exact}
32
+ end
33
+
34
+ # Check the string representation of the attribute value(s) against the regular expression with.
35
+ def validates_format(with, atts, opts={})
36
+ validatable_attributes(atts, opts){|a,v,m| (m || 'is invalid') unless v.to_s =~ with}
37
+ end
38
+
39
+ # Check attribute value(s) is included in the given set.
40
+ def validates_includes(set, atts, opts={})
41
+ validatable_attributes(atts, opts){|a,v,m| (m || "is not in range or set: #{set.inspect}") unless set.include?(v)}
42
+ end
43
+
44
+ # Check attribute value(s) string representation is a valid integer.
45
+ def validates_integer(atts, opts={})
46
+ validatable_attributes(atts, opts) do |a,v,m|
47
+ begin
48
+ Kernel.Integer(v.to_s)
49
+ nil
50
+ rescue
51
+ m || 'is not a number'
52
+ end
53
+ end
54
+ end
55
+
56
+ # Check that the attribute values length is in the specified range.
57
+ def validates_length_range(range, atts, opts={})
58
+ validatable_attributes(atts, opts){|a,v,m| (m || "is outside the allowed range") unless v && range.include?(v.length)}
59
+ end
60
+
61
+ # Check that the attribute values are not longer than the given max length.
62
+ def validates_max_length(max, atts, opts={})
63
+ validatable_attributes(atts, opts){|a,v,m| (m || "is longer than #{max} characters") unless v && v.length <= max}
64
+ end
65
+
66
+ # Check that the attribute values are not shorter than the given min length.
67
+ def validates_min_length(min, atts, opts={})
68
+ validatable_attributes(atts, opts){|a,v,m| (m || "is shorter than #{min} characters") unless v && v.length >= min}
69
+ end
70
+
71
+ # Check that the attribute value(s) is not a string. This is generally useful
72
+ # in conjunction with raise_on_typecast_failure = false, where you are
73
+ # passing in string values for non-string attributes (such as numbers and dates).
74
+ # If typecasting fails (invalid number or date), the value of the attribute will
75
+ # be a string in an invalid format, and if typecasting succeeds, the value will
76
+ # not be a string.
77
+ def validates_not_string(atts, opts={})
78
+ validatable_attributes(atts, opts) do |a,v,m|
79
+ next unless v.is_a?(String)
80
+ next m if m
81
+ (sch = db_schema[a] and typ = sch[:type]) ? "is not a valid #{typ}" : "is a string"
82
+ end
83
+ end
84
+
85
+ # Check attribute value(s) string representation is a valid float.
86
+ def validates_numeric(atts, opts={})
87
+ validatable_attributes(atts, opts) do |a,v,m|
88
+ begin
89
+ Kernel.Float(v.to_s)
90
+ nil
91
+ rescue
92
+ m || 'is not a number'
93
+ end
94
+ end
95
+ end
96
+
97
+ # Check attribute value(s) is not considered blank by the database, but allow false values.
98
+ def validates_presence(atts, opts={})
99
+ validatable_attributes(atts, opts){|a,v,m| (m || "is not present") if model.db.send(:blank_object?, v) && v != false}
100
+ end
101
+
102
+ # Checks that there are no duplicate values in the database for the given
103
+ # attributes. Pass an array of fields instead of multiple
104
+ # fields to specify that the combination of fields must be unique,
105
+ # instead of that each field should have a unique value.
106
+ #
107
+ # This means that the code:
108
+ # validates_unique([:column1, :column2])
109
+ # validates the grouping of column1 and column2 while
110
+ # validates_unique(:column1, :column2)
111
+ # validates them separately.
112
+ #
113
+ # You should also add a unique index in the
114
+ # database, as this suffers from a fairly obvious race condition.
115
+ #
116
+ # This validation does not respect the :allow_* options that the other validations accept,
117
+ # since it can deals with multiple attributes at once.
118
+ #
119
+ # Possible Options:
120
+ # * :message - The message to use (default: 'is already taken')
121
+ def validates_unique(*atts)
122
+ message = (atts.pop[:message] if atts.last.is_a?(Hash)) || 'is already taken'
123
+ atts.each do |a|
124
+ ds = model.filter(Array(a).map{|x| [x, send(x)]})
125
+ errors.add(a, message) unless (new? ? ds : ds.exclude(pk_hash)).count == 0
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ # Skip validating any attribute that matches one of the allow_* options.
132
+ # Otherwise, yield the attribute, value, and passed option :message to
133
+ # the block. If the block returns anything except nil or false, add it as
134
+ # an error message for that attributes.
135
+ def validatable_attributes(atts, opts)
136
+ am, an, ab, m = opts.values_at(:allow_missing, :allow_nil, :allow_blank, :message)
137
+ Array(atts).each do |a|
138
+ next if am && !values.has_key?(a)
139
+ v = send(a)
140
+ next if an && v.nil?
141
+ next if ab && v.respond_to?(:blank?) && v.blank?
142
+ if message = yield(a, v, m)
143
+ errors.add(a, message)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end