viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,405 @@
1
+ module Sequel
2
+ extension :blank
3
+
4
+ module Plugins
5
+ # Sequel's built-in validation_class_methods plugin adds backwards compatibility
6
+ # for the legacy class-level validation methods (e.g. validates_presence_of :column).
7
+ #
8
+ # It is recommended to use the validation_helpers plugin instead of this one,
9
+ # as it is less complex and more flexible.
10
+ module ValidationClassMethods
11
+ # Setup the validations hash for the given model.
12
+ def self.apply(model)
13
+ model.class_eval do
14
+ @validation_mutex = Mutex.new
15
+ @validations = {}
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ # A hash of associations for this model class. Keys are column symbols,
21
+ # values are arrays of validation procs.
22
+ attr_reader :validations
23
+
24
+ # The Generator class is used to generate validation definitions using
25
+ # the validates {} idiom.
26
+ class Generator
27
+ # Initializes a new generator.
28
+ def initialize(receiver ,&block)
29
+ @receiver = receiver
30
+ instance_eval(&block)
31
+ end
32
+
33
+ # Delegates method calls to the receiver by calling receiver.validates_xxx.
34
+ def method_missing(m, *args, &block)
35
+ @receiver.send(:"validates_#{m}", *args, &block)
36
+ end
37
+ end
38
+
39
+ # Returns true if validations are defined.
40
+ def has_validations?
41
+ !validations.empty?
42
+ end
43
+
44
+ # Setup the validations hash in the subclass
45
+ def inherited(subclass)
46
+ super
47
+ subclass.class_eval do
48
+ @validation_mutex = Mutex.new
49
+ @validations = {}
50
+ end
51
+ end
52
+
53
+ # Instructs the model to skip validations defined in superclasses
54
+ def skip_superclass_validations
55
+ @skip_superclass_validations = true
56
+ end
57
+
58
+ # Instructs the model to skip validations defined in superclasses
59
+ def skip_superclass_validations?
60
+ defined?(@skip_superclass_validations) && @skip_superclass_validations
61
+ end
62
+
63
+ # Defines validations by converting a longhand block into a series of
64
+ # shorthand definitions. For example:
65
+ #
66
+ # class MyClass < Sequel::Model
67
+ # validates do
68
+ # length_of :name, :minimum => 6
69
+ # length_of :password, :minimum => 8
70
+ # end
71
+ # end
72
+ #
73
+ # is equivalent to:
74
+ # class MyClass < Sequel::Model
75
+ # validates_length_of :name, :minimum => 6
76
+ # validates_length_of :password, :minimum => 8
77
+ # end
78
+ def validates(&block)
79
+ Generator.new(self, &block)
80
+ end
81
+
82
+ # Validates the given instance.
83
+ def validate(o)
84
+ superclass.validate(o) if superclass.respond_to?(:validate) && !skip_superclass_validations?
85
+ validations.each do |att, procs|
86
+ v = case att
87
+ when Array
88
+ att.collect{|a| o.send(a)}
89
+ else
90
+ o.send(att)
91
+ end
92
+ procs.each {|tag, p| p.call(o, att, v)}
93
+ end
94
+ end
95
+
96
+ # Validates acceptance of an attribute. Just checks that the value
97
+ # is equal to the :accept option. This method is unique in that
98
+ # :allow_nil is assumed to be true instead of false.
99
+ #
100
+ # Possible Options:
101
+ # * :accept - The value required for the object to be valid (default: '1')
102
+ # * :message - The message to use (default: 'is not accepted')
103
+ def validates_acceptance_of(*atts)
104
+ opts = {
105
+ :message => 'is not accepted',
106
+ :allow_nil => true,
107
+ :accept => '1',
108
+ :tag => :acceptance,
109
+ }.merge!(extract_options!(atts))
110
+ atts << opts
111
+ validates_each(*atts) do |o, a, v|
112
+ o.errors.add(a, opts[:message]) unless v == opts[:accept]
113
+ end
114
+ end
115
+
116
+ # Validates confirmation of an attribute. Checks that the object has
117
+ # a _confirmation value matching the current value. For example:
118
+ #
119
+ # validates_confirmation_of :blah
120
+ #
121
+ # Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
122
+ # or email addresses on web forms.
123
+ #
124
+ # Possible Options:
125
+ # * :message - The message to use (default: 'is not confirmed')
126
+ def validates_confirmation_of(*atts)
127
+ opts = {
128
+ :message => 'is not confirmed',
129
+ :tag => :confirmation,
130
+ }.merge!(extract_options!(atts))
131
+ atts << opts
132
+ validates_each(*atts) do |o, a, v|
133
+ o.errors.add(a, opts[:message]) unless v == o.send(:"#{a}_confirmation")
134
+ end
135
+ end
136
+
137
+ # Adds a validation for each of the given attributes using the supplied
138
+ # block. The block must accept three arguments: instance, attribute and
139
+ # value, e.g.:
140
+ #
141
+ # validates_each :name, :password do |object, attribute, value|
142
+ # object.errors.add(attribute, 'is not nice') unless value.nice?
143
+ # end
144
+ #
145
+ # Possible Options:
146
+ # * :allow_blank - Whether to skip the validation if the value is blank.
147
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
148
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
149
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
150
+ # doesn't specify it, so the database will use the table's default value. This is different
151
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
152
+ # If your database table has a non NULL default, this may be a good option to use. You
153
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
154
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
155
+ # database's default.
156
+ # * :allow_nil - Whether to skip the validation if the value is nil.
157
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
158
+ # skipping this validation if it returns nil or false.
159
+ # * :tag - The tag to use for this validation.
160
+ def validates_each(*atts, &block)
161
+ opts = extract_options!(atts)
162
+ blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
163
+ proc do |o,a,v|
164
+ next if i && !validation_if_proc(o, i)
165
+ next if an && Array(v).all?{|x| x.nil?}
166
+ next if ab && Array(v).all?{|x| x.blank?}
167
+ next if am && Array(a).all?{|x| !o.values.has_key?(x)}
168
+ block.call(o,a,v)
169
+ end
170
+ else
171
+ block
172
+ end
173
+ tag = opts[:tag]
174
+ atts.each do |a|
175
+ a_vals = @validation_mutex.synchronize{validations[a] ||= []}
176
+ if tag && (old = a_vals.find{|x| x[0] == tag})
177
+ old[1] = blk
178
+ else
179
+ a_vals << [tag, blk]
180
+ end
181
+ end
182
+ end
183
+
184
+ # Validates the format of an attribute, checking the string representation of the
185
+ # value against the regular expression provided by the :with option.
186
+ #
187
+ # Possible Options:
188
+ # * :message - The message to use (default: 'is invalid')
189
+ # * :with - The regular expression to validate the value with (required).
190
+ def validates_format_of(*atts)
191
+ opts = {
192
+ :message => 'is invalid',
193
+ :tag => :format,
194
+ }.merge!(extract_options!(atts))
195
+
196
+ unless opts[:with].is_a?(Regexp)
197
+ raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
198
+ end
199
+
200
+ atts << opts
201
+ validates_each(*atts) do |o, a, v|
202
+ o.errors.add(a, opts[:message]) unless v.to_s =~ opts[:with]
203
+ end
204
+ end
205
+
206
+ # Validates the length of an attribute.
207
+ #
208
+ # Possible Options:
209
+ # * :is - The exact size required for the value to be valid (no default)
210
+ # * :maximum - The maximum size allowed for the value (no default)
211
+ # * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
212
+ # options if present)
213
+ # * :minimum - The minimum size allowed for the value (no default)
214
+ # * :too_long - The message to use use if it the value is too long (default: 'is too long')
215
+ # * :too_short - The message to use use if it the value is too short (default: 'is too short')
216
+ # * :within - The array/range that must include the size of the value for it to be valid (no default)
217
+ # * :wrong_length - The message to use use if it the value is not valid (default: 'is the wrong length')
218
+ def validates_length_of(*atts)
219
+ opts = {
220
+ :too_long => 'is too long',
221
+ :too_short => 'is too short',
222
+ :wrong_length => 'is the wrong length'
223
+ }.merge!(extract_options!(atts))
224
+
225
+ opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
226
+ atts << opts
227
+ validates_each(*atts) do |o, a, v|
228
+ if m = opts[:maximum]
229
+ o.errors.add(a, opts[:message] || opts[:too_long]) unless v && v.size <= m
230
+ end
231
+ if m = opts[:minimum]
232
+ o.errors.add(a, opts[:message] || opts[:too_short]) unless v && v.size >= m
233
+ end
234
+ if i = opts[:is]
235
+ o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && v.size == i
236
+ end
237
+ if w = opts[:within]
238
+ o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
239
+ end
240
+ end
241
+ end
242
+
243
+ # Validates whether an attribute is not a string. This is generally useful
244
+ # in conjunction with raise_on_typecast_failure = false, where you are
245
+ # passing in string values for non-string attributes (such as numbers and dates).
246
+ # If typecasting fails (invalid number or date), the value of the attribute will
247
+ # be a string in an invalid format, and if typecasting succeeds, the value will
248
+ # not be a string.
249
+ #
250
+ # Possible Options:
251
+ # * :message - The message to use (default: 'is a string' or 'is not a valid (integer|datetime|etc.)' if the type is known)
252
+ def validates_not_string(*atts)
253
+ opts = {
254
+ :tag => :not_string,
255
+ }.merge!(extract_options!(atts))
256
+ atts << opts
257
+ validates_each(*atts) do |o, a, v|
258
+ if v.is_a?(String)
259
+ unless message = opts[:message]
260
+ message = if sch = o.db_schema[a] and typ = sch[:type]
261
+ "is not a valid #{typ}"
262
+ else
263
+ "is a string"
264
+ end
265
+ end
266
+ o.errors.add(a, message)
267
+ end
268
+ end
269
+ end
270
+
271
+ # Validates whether an attribute is a number.
272
+ #
273
+ # Possible Options:
274
+ # * :message - The message to use (default: 'is not a number')
275
+ # * :only_integer - Whether only integers are valid values (default: false)
276
+ def validates_numericality_of(*atts)
277
+ opts = {
278
+ :message => 'is not a number',
279
+ :tag => :numericality,
280
+ }.merge!(extract_options!(atts))
281
+ atts << opts
282
+ validates_each(*atts) do |o, a, v|
283
+ begin
284
+ if opts[:only_integer]
285
+ Kernel.Integer(v.to_s)
286
+ else
287
+ Kernel.Float(v.to_s)
288
+ end
289
+ rescue
290
+ o.errors.add(a, opts[:message])
291
+ end
292
+ end
293
+ end
294
+
295
+ # Validates the presence of an attribute. Requires the value not be blank,
296
+ # with false considered present instead of absent.
297
+ #
298
+ # Possible Options:
299
+ # * :message - The message to use (default: 'is not present')
300
+ def validates_presence_of(*atts)
301
+ opts = {
302
+ :message => 'is not present',
303
+ :tag => :presence,
304
+ }.merge!(extract_options!(atts))
305
+ atts << opts
306
+ validates_each(*atts) do |o, a, v|
307
+ o.errors.add(a, opts[:message]) if v.blank? && v != false
308
+ end
309
+ end
310
+
311
+ # Validates that an attribute is within a specified range or set of values.
312
+ #
313
+ # Possible Options:
314
+ # * :in - An array or range of values to check for validity (required)
315
+ # * :message - The message to use (default: 'is not in range or set: <specified range>')
316
+ def validates_inclusion_of(*atts)
317
+ opts = extract_options!(atts)
318
+ unless opts[:in] && opts[:in].respond_to?(:include?)
319
+ raise ArgumentError, "The :in parameter is required, and respond to include?"
320
+ end
321
+ opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
322
+ atts << opts
323
+ validates_each(*atts) do |o, a, v|
324
+ o.errors.add(a, opts[:message]) unless opts[:in].include?(v)
325
+ end
326
+ end
327
+
328
+ # Validates only if the fields in the model (specified by atts) are
329
+ # unique in the database. Pass an array of fields instead of multiple
330
+ # fields to specify that the combination of fields must be unique,
331
+ # instead of that each field should have a unique value.
332
+ #
333
+ # This means that the code:
334
+ # validates_uniqueness_of([:column1, :column2])
335
+ # validates the grouping of column1 and column2 while
336
+ # validates_uniqueness_of(:column1, :column2)
337
+ # validates them separately.
338
+ #
339
+ # You should also add a unique index in the
340
+ # database, as this suffers from a fairly obvious race condition.
341
+ #
342
+ # Possible Options:
343
+ # * :message - The message to use (default: 'is already taken')
344
+ def validates_uniqueness_of(*atts)
345
+ opts = {
346
+ :message => 'is already taken',
347
+ :tag => :uniqueness,
348
+ }.merge!(extract_options!(atts))
349
+
350
+ atts << opts
351
+ validates_each(*atts) do |o, a, v|
352
+ error_field = a
353
+ a = Array(a)
354
+ v = Array(v)
355
+ ds = o.class.filter(a.zip(v))
356
+ num_dups = ds.count
357
+ allow = if num_dups == 0
358
+ # No unique value in the database
359
+ true
360
+ elsif num_dups > 1
361
+ # Multiple "unique" values in the database!!
362
+ # Someone didn't add a unique index
363
+ false
364
+ elsif o.new?
365
+ # New record, but unique value already exists in the database
366
+ false
367
+ elsif ds.first === o
368
+ # Unique value exists in database, but for the same record, so the update won't cause a duplicate record
369
+ true
370
+ else
371
+ false
372
+ end
373
+ o.errors.add(error_field, opts[:message]) unless allow
374
+ end
375
+ end
376
+
377
+ private
378
+
379
+ # Removes and returns the last member of the array if it is a hash. Otherwise,
380
+ # an empty hash is returned This method is useful when writing methods that
381
+ # take an options hash as the last parameter.
382
+ def extract_options!(array)
383
+ array.last.is_a?(Hash) ? array.pop : {}
384
+ end
385
+
386
+ # Handle the :if option for validations
387
+ def validation_if_proc(o, i)
388
+ case i
389
+ when Symbol then o.send(i)
390
+ when Proc then o.instance_eval(&i)
391
+ when nil then true
392
+ else raise(::Sequel::Error, "invalid value for :if validation option")
393
+ end
394
+ end
395
+ end
396
+
397
+ module InstanceMethods
398
+ # Validates the object.
399
+ def validate
400
+ model.validate(self)
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,223 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The validation_helpers plugin contains instance method equivalents for most of the legacy
4
+ # class-level validations. The names and APIs are different, though. Example:
5
+ #
6
+ # class Album < Sequel::Model
7
+ # plugin :validation_helpers
8
+ # def validate
9
+ # validates_min_length 1, :num_tracks
10
+ # end
11
+ # end
12
+ #
13
+ # The validates_unique validation has a unique API, but the other validations have
14
+ # the API explained here:
15
+ #
16
+ # Arguments:
17
+ # * atts - Single attribute symbol or an array of attribute symbols specifying the
18
+ # attribute(s) to validate.
19
+ # Options:
20
+ # * :allow_blank - Whether to skip the validation if the value is blank. You should
21
+ # make sure all objects respond to blank if you use this option, which you can do by:
22
+ # Sequel.extension :blank
23
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
24
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
25
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
26
+ # doesn't specify it, so the database will use the table's default value. This is different
27
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
28
+ # If your database table has a non NULL default, this may be a good option to use. You
29
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
30
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
31
+ # database's default.
32
+ # * :allow_nil - Whether to skip the validation if the value is nil.
33
+ # * :message - The message to use. Can be a string which is used directly, or a
34
+ # proc which is called. If the validation method takes a argument before the array of attributes,
35
+ # that argument is passed as an argument to the proc. The exception is the
36
+ # validates_not_string method, which doesn't take an argument, but passes
37
+ # the schema type symbol as the argument to the proc.
38
+ #
39
+ # The default validation options for all models can be modified by
40
+ # changing the values of the Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS hash. You
41
+ # change change the default options on a per model basis
42
+ # by overriding a private instance method default_validation_helpers_options.
43
+ module ValidationHelpers
44
+ # Default validation options used by Sequel. Can be modified to change the error
45
+ # messages for all models (e.g. for internationalization), or to set certain
46
+ # default options for validations (e.g. :allow_nil=>true for all validates_format).
47
+ DEFAULT_OPTIONS = {
48
+ :exact_length=>{:message=>lambda{|exact| "is not #{exact} characters"}},
49
+ :format=>{:message=>lambda{|with| 'is invalid'}},
50
+ :includes=>{:message=>lambda{|set| "is not in range or set: #{set.inspect}"}},
51
+ :integer=>{:message=>lambda{"is not a number"}},
52
+ :length_range=>{:message=>lambda{|range| "is too short or too long"}},
53
+ :max_length=>{:message=>lambda{|max| "is longer than #{max} characters"}},
54
+ :min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}},
55
+ :not_string=>{:message=>lambda{|type| type ? "is not a valid #{type}" : "is a string"}},
56
+ :numeric=>{:message=>lambda{"is not a number"}},
57
+ :type=>{:message=>lambda{|klass| "is not a #{klass}"}},
58
+ :presence=>{:message=>lambda{"is not present"}},
59
+ :unique=>{:message=>lambda{'is already taken'}}
60
+ }
61
+
62
+ module InstanceMethods
63
+ # Check that the attribute values are the given exact length.
64
+ def validates_exact_length(exact, atts, opts={})
65
+ validatable_attributes_for_type(:exact_length, atts, opts){|a,v,m| validation_error_message(m, exact) unless v && v.length == exact}
66
+ end
67
+
68
+ # Check the string representation of the attribute value(s) against the regular expression with.
69
+ def validates_format(with, atts, opts={})
70
+ validatable_attributes_for_type(:format, atts, opts){|a,v,m| validation_error_message(m, with) unless v.to_s =~ with}
71
+ end
72
+
73
+ # Check attribute value(s) is included in the given set.
74
+ def validates_includes(set, atts, opts={})
75
+ validatable_attributes_for_type(:includes, atts, opts){|a,v,m| validation_error_message(m, set) unless set.include?(v)}
76
+ end
77
+
78
+ # Check attribute value(s) string representation is a valid integer.
79
+ def validates_integer(atts, opts={})
80
+ validatable_attributes_for_type(:integer, atts, opts) do |a,v,m|
81
+ begin
82
+ Kernel.Integer(v.to_s)
83
+ nil
84
+ rescue
85
+ validation_error_message(m)
86
+ end
87
+ end
88
+ end
89
+
90
+ # Check that the attribute values length is in the specified range.
91
+ def validates_length_range(range, atts, opts={})
92
+ validatable_attributes_for_type(:length_range, atts, opts){|a,v,m| validation_error_message(m, range) unless v && range.include?(v.length)}
93
+ end
94
+
95
+ # Check that the attribute values are not longer than the given max length.
96
+ def validates_max_length(max, atts, opts={})
97
+ validatable_attributes_for_type(:max_length, atts, opts){|a,v,m| validation_error_message(m, max) unless v && v.length <= max}
98
+ end
99
+
100
+ # Check that the attribute values are not shorter than the given min length.
101
+ def validates_min_length(min, atts, opts={})
102
+ validatable_attributes_for_type(:min_length, atts, opts){|a,v,m| validation_error_message(m, min) unless v && v.length >= min}
103
+ end
104
+
105
+ # Check that the attribute value(s) is not a string. This is generally useful
106
+ # in conjunction with raise_on_typecast_failure = false, where you are
107
+ # passing in string values for non-string attributes (such as numbers and dates).
108
+ # If typecasting fails (invalid number or date), the value of the attribute will
109
+ # be a string in an invalid format, and if typecasting succeeds, the value will
110
+ # not be a string.
111
+ def validates_not_string(atts, opts={})
112
+ validatable_attributes_for_type(:not_string, atts, opts){|a,v,m| validation_error_message(m, (db_schema[a]||{})[:type]) if v.is_a?(String)}
113
+ end
114
+
115
+ # Check attribute value(s) string representation is a valid float.
116
+ def validates_numeric(atts, opts={})
117
+ validatable_attributes_for_type(:numeric, atts, opts) do |a,v,m|
118
+ begin
119
+ Kernel.Float(v.to_s)
120
+ nil
121
+ rescue
122
+ validation_error_message(m)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Check if value is an instance of a class
128
+ def validates_type(klass, atts, opts={})
129
+ klass = klass.to_s.constantize if klass.is_a?(String) || klass.is_a?(Symbol)
130
+ validatable_attributes_for_type(:type, atts, opts){|a,v,m| validation_error_message(m, klass) if v && !v.is_a?(klass)}
131
+ end
132
+
133
+ # Check attribute value(s) is not considered blank by the database, but allow false values.
134
+ def validates_presence(atts, opts={})
135
+ validatable_attributes_for_type(:presence, atts, opts){|a,v,m| validation_error_message(m) if model.db.send(:blank_object?, v) && v != false}
136
+ end
137
+
138
+ # Checks that there are no duplicate values in the database for the given
139
+ # attributes. Pass an array of fields instead of multiple
140
+ # fields to specify that the combination of fields must be unique,
141
+ # instead of that each field should have a unique value.
142
+ #
143
+ # This means that the code:
144
+ # validates_unique([:column1, :column2])
145
+ # validates the grouping of column1 and column2 while
146
+ # validates_unique(:column1, :column2)
147
+ # validates them separately.
148
+ #
149
+ # You can pass a block, which is yielded the dataset in which the columns
150
+ # must be unique. So if you are doing a soft delete of records, in which
151
+ # the name must be unique, but only for active records:
152
+ #
153
+ # validates_unique(:name){|ds| ds.filter(:active)}
154
+ #
155
+ # You should also add a unique index in the
156
+ # database, as this suffers from a fairly obvious race condition.
157
+ #
158
+ # This validation does not respect the :allow_* options that the other validations accept,
159
+ # since it can deal with a grouping of multiple attributes.
160
+ #
161
+ # Possible Options:
162
+ # * :message - The message to use (default: 'is already taken')
163
+ # * :only_if_modified - Only check the uniqueness if the object is new or
164
+ # one of the columns has been modified.
165
+ def validates_unique(*atts)
166
+ opts = default_validation_helpers_options(:unique)
167
+ if atts.last.is_a?(Hash)
168
+ opts = opts.merge(atts.pop)
169
+ end
170
+ message = validation_error_message(opts[:message])
171
+ atts.each do |a|
172
+ arr = Array(a)
173
+ next if opts[:only_if_modified] && !new? && !arr.any?{|x| changed_columns.include?(x)}
174
+ ds = model.filter(arr.map{|x| [x, send(x)]})
175
+ ds = yield(ds) if block_given?
176
+ ds = ds.exclude(pk_hash) unless new?
177
+ errors.add(a, message) unless ds.count == 0
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ # The default options hash for the given type of validation. Can
184
+ # be overridden on a per-model basis for different per model defaults.
185
+ # The hash return must include a :message option that is either a
186
+ # proc or string.
187
+ def default_validation_helpers_options(type)
188
+ DEFAULT_OPTIONS[type]
189
+ end
190
+
191
+ # Skip validating any attribute that matches one of the allow_* options.
192
+ # Otherwise, yield the attribute, value, and passed option :message to
193
+ # the block. If the block returns anything except nil or false, add it as
194
+ # an error message for that attributes.
195
+ def validatable_attributes(atts, opts)
196
+ am, an, ab, m = opts.values_at(:allow_missing, :allow_nil, :allow_blank, :message)
197
+ Array(atts).each do |a|
198
+ next if am && !values.has_key?(a)
199
+ v = send(a)
200
+ next if an && v.nil?
201
+ next if ab && v.respond_to?(:blank?) && v.blank?
202
+ if message = yield(a, v, m)
203
+ errors.add(a, message)
204
+ end
205
+ end
206
+ end
207
+
208
+ # Merge the given options with the default options for the given type
209
+ # and call validatable_attributes with the merged options.
210
+ def validatable_attributes_for_type(type, atts, opts, &block)
211
+ validatable_attributes(atts, default_validation_helpers_options(type).merge(opts), &block)
212
+ end
213
+
214
+ # The validation error message to use, as a string. If message
215
+ # is a Proc, call it with the args. Otherwise, assume it is a string and
216
+ # return it.
217
+ def validation_error_message(message, *args)
218
+ message.is_a?(Proc) ? message.call(*args) : message
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end