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.
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