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