sequel 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -0,0 +1,501 @@
1
+ = Model Validations
2
+
3
+ This guide is based on http://guides.rubyonrails.org/activerecord_validations_callbacks.html
4
+
5
+ == Overview
6
+
7
+ This guide is designed to teach you how to use Sequel::Model's validation support. It attempts
8
+ to explain how Sequel's validation support works, what validations are
9
+ useful for, and how to use the +validation_helpers+ plugin to add specific
10
+ types of validations to your models.
11
+
12
+ == Why Validations?
13
+
14
+ Validations are primarily useful for associating error messages to display to the user
15
+ with specific attributes on the model. It is also possible to use them to enforce
16
+ data integrity for model instances, but that's not a recommended use unless
17
+ the only way to modify the database is through model instances, or you have
18
+ complex data integrity requirements that aren't possible to specify via
19
+ database-level constraints.
20
+
21
+ == Data Integrity
22
+
23
+ Data Integrity is best handled by the database itself. For example, if you have
24
+ a date column that should never contain a NULL value, the column should be
25
+ specified in the database as NOT NULL. If you have an integer column that should
26
+ only have values from 1 to 10, there should be a CHECK constraint that ensures
27
+ that the value of that column is between 1 and 10. And if you have a varchar
28
+ column where the length of the entries should be between 2 and 255, you should
29
+ be setting the size of the varchar column to 255, and using a CHECK constraint
30
+ to ensure that all values have at least two characters.
31
+
32
+ Unfortunately, sometimes there are situations where that is not possible. For
33
+ example, if you are using MySQL and don't have control over the database configuration,
34
+ it's possible that if you attempt to insert a string with 300 characters into a
35
+ varchar(255) field, that MySQL will just silently truncate it for you, instead of
36
+ raising an error. In that case, it may be necessary to use a model validation
37
+ to enforce the database integrity.
38
+
39
+ Also, in some cases you may have data integrity requirements that are difficult to
40
+ enforce via database constraints, especially if you are targetting multiple
41
+ database types.
42
+
43
+ Finally, validations are generally easier to write than database constraints,
44
+ so if data integrity isn't of great importance, using validations to provide minimal
45
+ data integrity is probably fine.
46
+
47
+ == Usage
48
+
49
+ Regardless of whether you are using validations for data integrity or just for
50
+ error messages, the usage is the same. Whenever you attempt to save a model
51
+ instance, before sending the INSERT or UPDATE query to the database,
52
+ <tt>Sequel::Model</tt> will attempt to validate the instance by calling
53
+ +validate+. If +validate+ does not add any errors to the object, the object is
54
+ considered valid, and <tt>valid?</tt> will return true. If +validate+ adds any errors
55
+ to the object, <tt>valid?</tt> will return false, and the save will either return raise
56
+ a <tt>Sequel::ValidationFailed</tt> exception (the default), or return nil (if +raise_on_save_failure+
57
+ is false).
58
+
59
+ By validating the object before sending the database query, Sequel attempts to
60
+ ensure that invalidate objects are not saved in the database. However, if you
61
+ are not enforcing the same validations in the database via constraints, it's
62
+ possible that invalid data can get added to the database via some other method.
63
+ This leads to odd cases such as retrieving a model object from the database,
64
+ not making any changes to it, attempting to save it, and having the save
65
+ raise an error.
66
+
67
+ == Skipping Validations
68
+
69
+ <tt>Sequel::Model</tt> uses the +save+ method to save model objects, and all
70
+ saving of model objects passes through the +save+ method. This means that all
71
+ saving of model objects goes through the validation process.
72
+
73
+ The only way to skip validations when saving a model object is to pass the
74
+ <tt>:validate => false</tt> option to +save+. If you use that option, +save+ will
75
+ not attempt to validate the object before saving it.
76
+
77
+ Note that it's always possible to update the instance's database row without using
78
+ +save+, by using a dataset to update it. Validations will only be run if you call
79
+ +save+ on the model object, or another model method that calls +save+. For example,
80
+ the +create+ class method instantiates a new instance of the model, and then calls
81
+ +save+, so it validates the object. However, the +insert+ class method is a dataset
82
+ method that just inserts the raw hash into the database, so it doesn't validate the
83
+ object.
84
+
85
+ == <tt>valid?</tt> and +validate+
86
+
87
+ <tt>Sequel::Model</tt> uses the <tt>valid?</tt> method to check whether or not a
88
+ model instance is valid. This method should not be overridden. Instead, the
89
+ +validate+ method should be overridden to add validations to the model:
90
+
91
+ class Album < Sequel::Model
92
+ def validate
93
+ super
94
+ errors.add(:name, 'cannot be empty') if !name || name.empty?
95
+ end
96
+ end
97
+
98
+ Album.new.valid? # false
99
+ Album.new(:name=>'').valid? # false
100
+ Album.new(:name=>'RF').valid? # true
101
+
102
+ If the <tt>valid?</tt> method returns false, you can call the +errors+ method to
103
+ get an instance of <tt>Sequel::Model::Errors</tt> describing the errors on the model:
104
+
105
+ a = Album.new
106
+ # => #<Album @values={}>
107
+ a.valid?
108
+ # => false
109
+ a.errors
110
+ # => {:name=>["cannot be empty"]}
111
+
112
+ You may notice that the +errors+ method appears to return a hash. That's because
113
+ <tt>Sequel::Model::Errors</tt> is a subclass of Hash.
114
+
115
+ Note that calling the +errors+ method before the <tt>valid?</tt> method will result
116
+ in an +errors+ being empty:
117
+
118
+ Album.new.errors
119
+ # => {}
120
+
121
+ So just remember that you shouldn't check +errors+ until after you call <tt>valid?</tt>.
122
+
123
+ <tt>Sequel::Model::Errors</tt> has some helper methods that make it easy to get an array of
124
+ all of the instance's errors, or for checking for errors on a specific attribute. These
125
+ will be covered later in this guide.
126
+
127
+ == +validation_helpers+
128
+
129
+ While <tt>Sequel::Model</tt> does provide a validations framework, it does not define
130
+ any built-in validation helper methods that you can call. However, Sequel ships with a
131
+ plugin call +validation_helpers+ that handles most basic validation needs. So instead of
132
+ specifying validations like this:
133
+
134
+ class Album < Sequel::Model
135
+ def validate
136
+ super
137
+ errors.add(:name, 'cannot be empty') if !name || name.empty?
138
+ errors.add(:name, 'is already taken') if name && new? && Album[:name=>name]
139
+ errors.add(:website, 'cannot be empty') if !website || website.empty?
140
+ errors.add(:website, 'is not a valid URL') unless website =~ /\Ahttps?:\/\//
141
+ end
142
+ end
143
+
144
+ You can call simple methods such as:
145
+
146
+ class Album < Sequel::Model
147
+ plugin :validation_helpers
148
+ def validate
149
+ super
150
+ validates_presence [:name, :website]
151
+ validates_unique :name
152
+ validates_format /\Ahttps?:\/\//, :website, :message=>'is not a valid URL'
153
+ end
154
+ end
155
+
156
+ Other than +validates_unique+, which has it's own API, the methods defined by
157
+ +validation_helpers+ have one of the following two APIs:
158
+
159
+ <tt>(atts, opts={})</tt>:: For methods such as +validates_presence+, which do not
160
+ take an additional argument.
161
+ <tt>(arg, atts, opts={})</tt>:: For methods such as +validates_format+, which take an
162
+ additional argument.
163
+
164
+ For both of these APIs, +atts+ is either a column symbol or array of column symbols,
165
+ and +opts+ is an optional options hash.
166
+
167
+ The following methods are provided by +validation_helpers+:
168
+
169
+ === +validates_presence+
170
+
171
+ This is probably the most commonly used helper method, which checks if the specified attributes are not blank. In general, if an object responds to <tt>blank?</tt>, it calls the method to determine if the object is blank. Otherwise, nil is considered blank, empty strings or strings that just contain whitespace are blank, and objects that respond to <tt>empty?</tt> and return a true valid are considered blank. All other objects are considered non-blank for the purposes of +validates_presence+. This means that +validates_presence+ is safe to use on boolean columns where you want to ensure that either true or false is used, but not NULL.
172
+
173
+ class Album < Sequel::Model
174
+ def validate
175
+ validates_presence [:name, :website, :debut_album]
176
+ end
177
+ end
178
+
179
+ === +validates_format+
180
+
181
+ +validates_format+ is used to ensure that the string value of the specified attributes matches the specified regular expression. It's useful for checking that fields such as email addresses, URLs, UPC codes, ISBN codes, and the like, are in a specific format. It can also be used to validate that only certain characters are used in the string.
182
+
183
+ class Album < Sequel::Model
184
+ def validate
185
+ validates_format /\A\d\d\d-\d-\d{7}-\d-\d\z/, :isbn
186
+ validates_format /\a[0-9a-zA-Z:' ]+\z/, :name
187
+ end
188
+ end
189
+
190
+ === +validates_exact_length+, +validates_min_length+, +validates_max_length+, +validates_length_range+
191
+
192
+ These methods all deal with ensuring that the length of the specified attribute matches the criteria specified by the first argument to the method. +validates_exact_length+ is for checking that the length of the attribute is equal to that value, +validates_min_length+ is for checking that the length of the attribute is greater than or equal to that value, +validates_max_length+ is for checking that the length of the attribute is less than or equal to that value, and +validates_length_range+ is for checking that the length of the attribute falls in the value, which should be a range or another object that responds to <tt>include?</tt>.
193
+
194
+
195
+ class Album < Sequel::Model
196
+ def validate
197
+ validates_exact_length 17, :isbn
198
+ validates_min_length 3, :name
199
+ validates_max_length 100, :name
200
+ validates_length_range 3..100, :name
201
+ end
202
+ end
203
+
204
+ === +validates_integer+, +validates_numeric+
205
+
206
+ These methods check that the specified attributes can be valid integers or valid floats. +validates_integer+ tests the attribute value using <tt>Kernel.Integer</tt> and +validates_numeric+ tests the attribute using <tt>Kernel.Float</tt>. If the Kernel methods raise an exception, the validation fails, otherwise it succeeds.
207
+
208
+ class Album < Sequel::Model
209
+ def validate
210
+ validates_integer :copies_sold
211
+ validates_numeric :replaygain
212
+ end
213
+ end
214
+
215
+ === +validates_includes+
216
+
217
+ +validates_includes+ checks that the specified attributes are included in the first argument to the method, which is usually an array, but can be any object that responds to <tt>include?</tt>.
218
+
219
+ class Album < Sequel::Model
220
+ def validate
221
+ validates_includes [1, 2, 3, 4, 5], :rating
222
+ end
223
+ end
224
+
225
+ === +validates_type+
226
+
227
+ +validates_type+ checks that the specified attributes are instances of the class specified in the first argument. The class can be specified as the class itself, or as a string or symbol with the class name:
228
+
229
+ class Album < Sequel::Model
230
+ def validate
231
+ validates_type String, [:name, :website]
232
+ validates_type :Artist, :artist
233
+ end
234
+ end
235
+
236
+ === +validates_not_string+
237
+
238
+ +validates_not_string+ is a special validation designed to be used with the <tt>raise_on_typecast_failure = false</tt> setting. By default, Sequel raises errors when typecasting fails, immediately when you try to set the value on the object:
239
+
240
+ album = Album.new
241
+ album.copies_sold = 'banana' # raises Sequel::InvalidValue
242
+
243
+ The <tt>raise_on_typecast_failure = false</tt> setting tells Sequel to attempt to typecast values, but to silently ignore any errors raised:
244
+
245
+ Album.raise_on_typecast_failure = false
246
+ album = Album.new
247
+ album.copies_sold = 'banana'
248
+ album.copies_sold # => 'banana'
249
+
250
+ +validates_not_string+ is designed to be used in web applications where all user input comes in as strings. When the <tt>raise_on_typecast_failure = false</tt> setting is used in such an application, you can call +validates_not_string+ with all non-string columns. If any of those columns has a string value, it's because the column was set and Sequel was not able to typecast it correctly, which means it probably isn't valid. For example, let's say that you want to check that a couple columns contain valid dates:
251
+
252
+ class Album < Sequel::Model
253
+ self.raise_on_typecast_failure = false
254
+ def validate
255
+ validates_not_string [:release_date, :record_date]
256
+ end
257
+ end
258
+
259
+ album = Album.new
260
+ album.release_date = 'banana'
261
+ album.release_date # => 'banana'
262
+ album.record_date = '2010-05-17'
263
+ album.record_date # => #<Date: 4910667/2,0,2299161>
264
+ album.valid? # => false
265
+ album.errors # => {:release_date=>["is not a valid date"]}
266
+
267
+ For web applications, you usually want the <tt>raise_on_typecast_failure = false</tt> setting, so that you can accept all of the input without raising an error, and then present the user with all error messages. Without the setting, if the user submits any invalid data, Sequel will immediately raise an error. +validates_not_string+ is helpful because it allows you to check for typecasting errors on non-string columns, and provides a good default error message stating that it the attribute is not of the expected type.
268
+
269
+ === +validates_unique+
270
+
271
+ +validates_unique+ has a similar but different API than the other +validation_helpers+ methods. It takes an arbitrary number of arguments, which should be column symbols or arrays of column symbols. If any argument is a symbol, Sequel sets up a unique validation for just that column. If any argument is an array of symbols, Sequel sets up a unique validation for the combination of the columns. This means that you get different behavior depending on whether you call the object with an array or with separate arguments. For example:
272
+
273
+ validates_unique(:name, :artist_id)
274
+
275
+ Will set up a 2 separate uniqueness validations. It will make it so that no two albums can have the same name, and that each artist can only be associated with one album. In general, that's probably not what you want. You probably want it so that two albums can have the same name, unless they are by the same artist. To do that, you need to use an array:
276
+
277
+ validates_unique([:name, :artist_id])
278
+
279
+ That sets up a single uniqueness validation for the combination of the fields.
280
+
281
+ You can mix and match the two approaches. For example, if all albums should have a unique UPC, and no artist can have duplicate album names:
282
+
283
+ validates_unique(:upc, [:name, :artist_id])
284
+
285
+ +validates_unique+ also accepts a block to scope the uniqueness constraint. For example, if you want to ensure that all active albums have a unique name, but inactive albums can duplicate the name:
286
+
287
+ validates_unique(:name){|ds| ds.filter(:active)}
288
+
289
+ If you provide a block, it is called with the dataset to use for the uniqueness check, which you can then filter to scope the uniqueness validation to a subset of the model's dataset.
290
+
291
+ Additionally, you can also include an optional options hash as the last argument. Unlike the other validations, the options hash for +validates_unique+ only checks for two options:
292
+
293
+ :message :: The message to use
294
+ :only_if_modified :: Only check the uniqueness if the object is new or one of the columns has been modified.
295
+
296
+ +validates_unique+ is the only method in +validation_helpers+ that checks with the database. Attempting to validate uniqueness outside of the database suffers from a race condition, so any time you want to add a uniqueness validation, you should make sure to add a uniqueness constraint or unique index on the underlying database table. See the the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html] for details on how to do that.
297
+
298
+ == +validation_helpers+ Options
299
+
300
+ All +validation_helpers+ methods except +validates_unique+ accept the following options:
301
+
302
+ === <tt>:message</tt>
303
+
304
+ The most commonly used option, used to override the default validation message. Can be either a string or proc. If a string, it is used directly. If a proc, the proc is called and should return a string. If the validation method takes a argument before the array of attributes, that argument is passed as an argument to the proc. The exception is the +validates_not_string+ method, which doesn�t take an argument, but passes the schema type symbol as the argument to the proc.
305
+
306
+ class Album < Sequel::Model
307
+ def validate
308
+ validates_presence :copies_sold, :message=>'was not given'
309
+ validates_min_length 3, :name, :message=>proc{|s| "should be more than #{s} characters"}
310
+ end
311
+ end
312
+
313
+ === <tt>:allow_nil</tt>
314
+
315
+ The <tt>:allow_nil</tt> option skips the validation if the attribute value is nil or if the attribute is not present. It's commonly used when you have a +validates_presence+ method already on the attribute, and don't want multiple validation errors for the same attribute:
316
+
317
+ class Album < Sequel::Model
318
+ def validate
319
+ validates_presence :copies_sold
320
+ validates_integer :copies_sold, :allow_nil=>true
321
+ end
322
+ end
323
+
324
+ Without the <tt>:allow_nil</tt> option to +validates_integer+, if the copies_sold attribute was nil, you would get two separate validation errors, instead of a single validation error.
325
+
326
+ === <tt>:allow_blank</tt>
327
+
328
+ The <tt>:allow_blank</tt> is similar to the <tt>:allow_nil</tt> option, but instead of just skipping the attribute for nil values, it skips the attribute for all blank values. For example, let's say that artists can have a website. If they have one, it should be formatted like a URL, but it can be nil or an empty string if they don't have one.
329
+
330
+ class Album < Sequel::Model
331
+ def validate
332
+ validates_format /\Ahttps?:\/\//, :website, :allow_blank=>true
333
+ end
334
+ end
335
+ a = Album.new
336
+ a.website = ''
337
+ a.valid? # true
338
+
339
+ === <tt>:allow_missing</tt>
340
+
341
+ The <tt>:allow_missing</tt> option is different from the <tt>:allow_nil</tt> option, in that instead of checking if the attribute value is nil, it checks if the attribute is present in the model instance's values hash. <tt>:allow_nil</tt> will skip the validation when the attribute is in the values hash and has a nil value and when the attribute is not in the values hash. <tt>:allow_missing</tt> will only skip the validation when the attribute is not in the values hash. If the attribute is in the values hash but has a nil value, <tt>:allow_missing</tt> will not skip it.
342
+
343
+ The purpose of this option is to work correctly with missing columns when inserting or updating records. Sequel only sends the attributes in the values hash when doing an insert or update. If the attribute is not present in the values hash, Sequel doesn�t specify it, so the database will use the table�s default value when inserting the record, or not modify the value when saving it. This is different from having an attribute in the values hash with a value of nil, which Sequel will send as NULL. If your database table has a non NULL default, this may be a good option to use. You don�t want to use allow_nil, because if the attribute is in values but has a value nil, Sequel will attempt to insert a NULL value into the database, instead of using the database�s default.
344
+
345
+ == Conditional Validation
346
+
347
+ Because Sequel uses the +validate+ instance method to handle validation, making validations conditional is easy as it works exactly the same as ruby's standard conditionals. For example, if you only want to validate an attribute when creating an object:
348
+
349
+ validates_presence :name if new?
350
+
351
+ If you only want to validate the attribute when updating an existing object:
352
+
353
+ validates_integer :copies_sold unless new?
354
+
355
+ Let's say you only to make a validation conditional on the status of the object:
356
+
357
+ validates_presence :name if status_id > 1
358
+ validates_integer :copies_sold if status_id > 3
359
+
360
+ You can use all the standard ruby conditional expressions, such as +case+:
361
+
362
+ case status_id
363
+ when 1
364
+ validates_presence :name
365
+ when 2
366
+ validates_presence [:name, :artist_id]
367
+ when 3
368
+ validates_presence [:name, :artist_id, :copies_sold]
369
+ end
370
+
371
+ You can make the input to some validations dependent on the values of another attribute:
372
+
373
+ validates_min_length(status_id > 2 ? 5 : 10, [:name])
374
+ validates_presence(status_id < 2 ? :name : [:name, :artist_id])
375
+
376
+ Basically, there's no special syntax you have to use for conditional validations. Just handle conditionals the way you would in other ruby code.
377
+
378
+ == Default Error Messages
379
+
380
+ These are the default error messages for all of the helper methods in +validation_helpers+:
381
+
382
+ :exact_length :: is not #{arg} characters
383
+ :format :: is invalid
384
+ :includes :: is not in range or set: #{arg.inspect}
385
+ :integer :: is not a number
386
+ :length_range :: is too short or too long
387
+ :max_length :: is longer than #{arg} characters
388
+ :min_length :: is shorter than #{arg} characters
389
+ :not_string :: is not a valid #{schema_type}
390
+ :numeric :: is not a number
391
+ :type :: is not a #{arg}
392
+ :presence :: is not present
393
+ :unique :: is already taken
394
+
395
+ == Modifying the Default Options
396
+
397
+ It's easy to modify the default options used by +validation_helpers+. All of the default options are stored in the <tt>Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS</tt> hash. So you just need to modify that hash to change the default options. One way to do that is to use <tt>merge!</tt> to update the hash:
398
+
399
+ Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(
400
+ :presence=>{:message=>'cannot be empty'},
401
+ :includes=>{:message=>'invalid option', :allow_nil=>true},
402
+ :max_length=>{:message=>lambda{|i| "cannot be more than #{i} characters"}, :allow_nil=>true},
403
+ :format=>{:message=>'contains invalid characters', :allow_nil=>true})
404
+
405
+ This updates the default messages that will be used for the presence, includes, max_length, and format validations, and sets the default value of the <tt>:allow_nil</tt> option to true for the includes, max_length, and format validations.
406
+
407
+ == Custom Validations
408
+
409
+ Just as the first validation example showed, you aren't limited to the validation methods defined by +validation_helpers+. Inside the +validate+ method, you can add your own validations by adding to the instance's errors using <tt>errors.add</tt> whenever an attribute is not valid:
410
+
411
+ class Album < Sequel::Model
412
+ def validate
413
+ super
414
+ errors.add(:release_date, 'cannot be before record date') if release_date < record_date
415
+ end
416
+ end
417
+
418
+ Just like conditional validations, with custom validations you are just using the standard ruby conditionals, and calling <tt>errors.add</tt> with the column symbol and the error message if you detect invalid data.
419
+
420
+ It's fairly easy to create your own custom validations that can be reused in all your models. For example, if there is a common need to validate that one column in the model comes before another column:
421
+
422
+ class Sequel::Model
423
+ def validates_after(col1, col2)
424
+ errors.add(col1, "cannot be before #{col2}") if send(col1) < send(col2)
425
+ end
426
+ end
427
+ class Album < Sequel::Model
428
+ def validate
429
+ super
430
+ validates_after(:release_date, :record_date)
431
+ end
432
+ end
433
+
434
+ == Setting Validations for All Models
435
+
436
+ Let's say you want to add some default validations that apply to all of your model classes. It's fairly easy to do by overriding the +validate+ method in <tt>Sequel::Model</tt>, adding some validations to it, and if you override +validate+ in your model classes, just make sure to call +super+.
437
+
438
+ class Sequel::Model
439
+ def self.string_columns
440
+ @string_columns ||= columns.reject{|c| db_schema[c][:type] != :string}
441
+ end
442
+
443
+ def validate
444
+ super
445
+ validates_format(/\A[^\x00-\x08\x0e-\x1f\x7f\x81\x8d\x8f\x90\x9d]*\z/,
446
+ model.string_columns,
447
+ :message=>"contains invalid characters")
448
+ end
449
+ end
450
+
451
+ This will make sure that all string columns in the model are validated to make sure they don't contain any invalid characters. Just remember that if you override the +validate+ method in your model classes, you need to call +super+:
452
+
453
+ class Album < Sequel::Model
454
+ def validate
455
+ super # Important!
456
+ validates_presence :name
457
+ end
458
+ end
459
+
460
+ If you forget to call +super+, the validations that you defined in <tt>Sequel::Model</tt> will not be enforced. It's a good idea to call super whenever you override one of <tt>Sequel::Model</tt>'s methods, unless you specifically do not want the default behavior.
461
+
462
+ == <tt>Sequel::Model::Errors</tt>
463
+
464
+ As mentioned earlier, <tt>Sequel::Model::Errors</tt> is a subclass of Hash with a few special methods, the most common of which are described here:
465
+
466
+ === +add+
467
+
468
+ +add+ is the method used to add error messages for a given column. It takes the column symbol as the first argument and the error message as the second argument:
469
+
470
+ errors.add(:name, 'is not valid')
471
+
472
+ === +on+
473
+
474
+ +on+ is a method usually used after validation has been completed, to determine if there were any errors on a given attribute. It takes the column value, and returns an array of error messages if there were any, or nil if not:
475
+
476
+ errors.on(:name)
477
+
478
+ If you want to make some validations dependent upon the results of other validations, you may want to use +on+ inside your validates method:
479
+
480
+ validates_integer(:release_date) if errors.on(:record_date)
481
+
482
+ Here, you don't care about validating the release date if there were validation errors for the record date.
483
+
484
+ === +full_messages+
485
+
486
+ +full_messages+ returns an area of error messages for the object. It's commonly called after validation to get a list of error messages to display to the user:
487
+
488
+ album.errors
489
+ # => {:name=>["cannot be empty"]}
490
+ album.errors.full_messages
491
+ # => ["name cannot be empty"]
492
+
493
+ === +count+
494
+
495
+ +count+ returns the total number of error messages in the errors.
496
+
497
+ album.errors.count # => 1
498
+
499
+ == +validation_class_methods+
500
+
501
+ Sequel actually ships with two validation plugins. The recommended one that this guide focused on is the +validation_helpers+ plugin. However, Sequel also ships with the +validation_class_methods+ plugin, which uses class methods instead of instance methods to define validations. It exists mostly for legacy compatibility, but it is still supported.