yssk22-couch_resource 0.1.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.
@@ -0,0 +1,520 @@
1
+ #
2
+ # This file is based on active_record/validations.rb (ActiveRecord 2.1.0)
3
+ # and customize some sentences to work as CouchResource validations.
4
+ #
5
+ require File.join(File.dirname(__FILE__), "struct")
6
+ require File.join(File.dirname(__FILE__), "error")
7
+
8
+ module CouchResource
9
+
10
+ # Raised by save! and create! when the record is invalid.
11
+ # Use the record method to retrieve the record which did not validate.
12
+ class RecordInvalid < CouchResourceError
13
+ attr_reader :record
14
+ def initialize(record)
15
+ @record = record
16
+ super("Validation failed: #{@record.errors.full_messages.join(", ")}")
17
+ end
18
+ end
19
+
20
+ # CouchResource::Errors is the completely same as ActiveRecord::Errors
21
+ class Errors
22
+ include Enumerable
23
+
24
+ def initialize(base) # :nodoc:
25
+ @base, @errors = base, {}
26
+ end
27
+
28
+ @@default_error_messages = {
29
+ :inclusion => "is not included in the list",
30
+ :exclusion => "is reserved",
31
+ :invalid => "is invalid",
32
+ :confirmation => "doesn't match confirmation",
33
+ :accepted => "must be accepted",
34
+ :empty => "can't be empty",
35
+ :blank => "can't be blank",
36
+ :too_long => "is too long (maximum is %d characters)",
37
+ :too_short => "is too short (minimum is %d characters)",
38
+ :wrong_length => "is the wrong length (should be %d characters)",
39
+ :taken => "has already been taken",
40
+ :not_a_number => "is not a number",
41
+ :greater_than => "must be greater than %d",
42
+ :greater_than_or_equal_to => "must be greater than or equal to %d",
43
+ :equal_to => "must be equal to %d",
44
+ :less_than => "must be less than %d",
45
+ :less_than_or_equal_to => "must be less than or equal to %d",
46
+ :odd => "must be odd",
47
+ :even => "must be even",
48
+ :children => "is not valid" # append for object or array
49
+ }
50
+
51
+ cattr_accessor :default_error_messages
52
+
53
+ def add_to_base(msg)
54
+ add(:base, msg)
55
+ end
56
+
57
+ def add(attribute, msg = @@default_error_messages[:invalid])
58
+ @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
59
+ @errors[attribute.to_s] << msg
60
+ end
61
+
62
+ def add_on_empty(attributes, msg = @@default_error_messages[:empty])
63
+ for attr in [attributes].flatten
64
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
65
+ is_empty = value.respond_to?("empty?") ? value.empty? : false
66
+ add(attr, msg) unless !value.nil? && !is_empty
67
+ end
68
+ end
69
+
70
+ def add_on_blank(attributes, msg = @@default_error_messages[:blank])
71
+ for attr in [attributes].flatten
72
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
73
+ add(attr, msg) if value.blank?
74
+ end
75
+ end
76
+
77
+ def invalid?(attribute)
78
+ !@errors[attribute.to_s].nil?
79
+ end
80
+
81
+ def on(attribute)
82
+ errors = @errors[attribute.to_s]
83
+ return nil if errors.nil?
84
+ errors.size == 1 ? errors.first : errors
85
+ end
86
+
87
+ alias :[] :on
88
+
89
+ def on_base
90
+ on(:base)
91
+ end
92
+
93
+ def each
94
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
95
+ end
96
+
97
+ def each_full
98
+ full_messages.each { |msg| yield msg }
99
+ end
100
+
101
+ def full_messages
102
+ full_messages = []
103
+
104
+ @errors.each_key do |attr|
105
+ @errors[attr].each do |msg|
106
+ next if msg.nil?
107
+ if attr == "base"
108
+ full_messages << msg
109
+ else
110
+ full_messages << @base.class.human_attribute_name(attr) + " " + msg
111
+ end
112
+ end
113
+ end
114
+ full_messages
115
+ end
116
+
117
+ def empty?
118
+ @errors.empty?
119
+ end
120
+
121
+ def clear
122
+ @errors = {}
123
+ end
124
+
125
+ def size
126
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
127
+ end
128
+
129
+ alias_method :count, :size
130
+ alias_method :length, :size
131
+
132
+ def to_xml(options={})
133
+ options[:root] ||= "errors"
134
+ options[:indent] ||= 2
135
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
136
+
137
+ options[:builder].instruct! unless options.delete(:skip_instruct)
138
+ options[:builder].errors do |e|
139
+
140
+ full_messages.each { |msg| e.error(msg) }
141
+ end
142
+ end
143
+
144
+ def to_json
145
+ self.to_hash.to_json
146
+ end
147
+
148
+ def to_hash
149
+ array = []
150
+ @errors.each_key do |attr|
151
+ @errors[attr].each do |msg|
152
+ next if msg.nil?
153
+ if attr == "base"
154
+ array << { :message => msg }
155
+ else
156
+ array << {
157
+ :message => @base.class.human_attribute_name(attr) + " " + msg,
158
+ :attr => attr
159
+ }
160
+ end
161
+ end
162
+ end
163
+ { :errors => array }
164
+ end
165
+ end
166
+
167
+ module Validations
168
+ VALIDATIONS = %w( validate validate_on_create validate_on_update )
169
+
170
+ def self.included(base)
171
+ base.send(:extend, ClassMethods)
172
+ base.send(:include, InstanceMethods)
173
+ base.send(:include, ActiveSupport::Callbacks)
174
+ base.define_callbacks *VALIDATIONS
175
+ end
176
+
177
+ module ClassMethods
178
+ DEFAULT_VALIDATION_OPTIONS = {
179
+ :on => :save,
180
+ :allow_nil => false,
181
+ :allow_blank => false,
182
+ :message => nil
183
+ }.freeze
184
+
185
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
186
+ ALL_NUMERICALITY_CHECKS = {
187
+ :greater_than => '>', :greater_than_or_equal_to => '>=',
188
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
189
+ :odd => 'odd?', :even => 'even?' }.freeze
190
+
191
+ # This method is the same as ActiveRecord::Validations.validates_each(*attr)
192
+ #
193
+ # class Person
194
+ # string :first_name, :validates => {
195
+ # [:each,{
196
+ # :proc => Proc.new do |record, attr, value|
197
+ # record.errors.add attr, "starts with z." if value[0] == ?z
198
+ # end
199
+ # }]
200
+ # end
201
+ #
202
+ # or
203
+ #
204
+ # class Person
205
+ # string :first_name
206
+ # validates_each :first_name do |record, attr, value|
207
+ # record.errors.add attr, "starts with z." if value[0] == ?z
208
+ # end
209
+ # end
210
+ def validates_each(*attrs)
211
+ options = attrs.extract_options!.symbolize_keys
212
+ attrs = attrs.flatten
213
+
214
+ send(validation_method(options[:on] || :save), options) do |record|
215
+ attrs.each do |attr|
216
+ value = record.get_attribute(attr)
217
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
218
+ yield record, attr, value
219
+ end
220
+ end
221
+ end
222
+
223
+ # This method is the same as ActiveRecord::Validations.vaildates_confirmation_of(*attr_names)
224
+ #
225
+ # class Person
226
+ # string :password, :validates => [:confirmation_of]
227
+ # end
228
+ #
229
+ # or
230
+ #
231
+ # class Person
232
+ # string :password
233
+ # validates_confirmation_of :password
234
+ # end
235
+ def validates_confirmation_of(*attr_names)
236
+ configuration = {
237
+ :message => CouchResource::Errors.default_error_messages[:confirmation],
238
+ :on => :save
239
+ }
240
+ configuration.update(attr_names.extract_options!)
241
+
242
+ attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
243
+
244
+ validates_each(attr_names, configuration) do |record, attr_name, value|
245
+ unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
246
+ record.errors.add(attr_name, configuration[:message])
247
+ end
248
+ end
249
+ end
250
+
251
+ # This method is the same as ActiveRecord::Validations.vaildates_confirmation_of(*attr_names)
252
+ # This method is not implemented because the validator works for only virtual attributes.
253
+ def validates_acceptance_of
254
+ raise "Not Implemented"
255
+ end
256
+
257
+ # This method is the same as ActiveRecord::Validations.vaildates_presense_of(*attr_names)
258
+ #
259
+ # class Person
260
+ # string :first_name, :validates => [:presense_of]
261
+ # end
262
+ #
263
+ # or
264
+ #
265
+ # class Person
266
+ # string :first_name
267
+ # validates_presense_of :first_name
268
+ # end
269
+ #
270
+ def validates_presense_of(*attr_names)
271
+ configuration = { :message => CouchResource::Errors.default_error_messages[:blank], :on => :save }
272
+ configuration.update(attr_names.extract_options!)
273
+
274
+ # can't use validates_each here, because it cannot cope with nonexistent attributes,
275
+ # while errors.add_on_empty can
276
+ send(validation_method(configuration[:on]), configuration) do |record|
277
+ record.errors.add_on_blank(attr_names, configuration[:message])
278
+ end
279
+ end
280
+
281
+
282
+ # This method is the same as ActiveRecord::Validations.vaildates_length_of(*attrs)
283
+ # class Person
284
+ # string :first_name, :validates => [
285
+ # [:lenfth_of, {:minimum => 1, :maximum => 16}]
286
+ # ]
287
+ # end
288
+ #
289
+ # or
290
+ #
291
+ # class Person
292
+ # string :first_name
293
+ # validates_presense_of :first_name, :minumum => 1, :maximum => 16
294
+ # end
295
+ def validates_length_of(*attrs)
296
+ # Merge given options with defaults.
297
+ options = {
298
+ :too_long => CouchResource::Errors.default_error_messages[:too_long],
299
+ :too_short => CouchResource::Errors.default_error_messages[:too_short],
300
+ :wrong_length => CouchResource::Errors.default_error_messages[:wrong_length]
301
+ }.merge(DEFAULT_VALIDATION_OPTIONS)
302
+ options.update(attrs.extract_options!.symbolize_keys)
303
+
304
+ # Ensure that one and only one range option is specified.
305
+ range_options = ALL_RANGE_OPTIONS & options.keys
306
+ case range_options.size
307
+ when 0
308
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
309
+ when 1
310
+ # Valid number of options; do nothing.
311
+ else
312
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
313
+ end
314
+
315
+ # Get range option and value.
316
+ option = range_options.first
317
+ option_value = options[range_options.first]
318
+
319
+ case option
320
+ when :within, :in
321
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
322
+
323
+ too_short = options[:too_short] % option_value.begin
324
+ too_long = options[:too_long] % option_value.end
325
+
326
+ validates_each(attrs, options) do |record, attr, value|
327
+ value = value.split(//) if value.kind_of?(String)
328
+ if value.nil? or value.size < option_value.begin
329
+ record.errors.add(attr, too_short)
330
+ elsif value.size > option_value.end
331
+ record.errors.add(attr, too_long)
332
+ end
333
+ end
334
+ when :is, :minimum, :maximum
335
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
336
+
337
+ # Declare different validations per option.
338
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
339
+ message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
340
+
341
+ message = (options[:message] || options[message_options[option]]) % option_value
342
+
343
+ validates_each(attrs, options) do |record, attr, value|
344
+ value = value.split(//) if value.kind_of?(String)
345
+ record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
346
+ end
347
+ end
348
+ end
349
+
350
+ alias_method :validates_size_of, :validates_length_of
351
+
352
+ # This method is the same as ActiveRecord::Validations.vaildates_uniqueness_of(*attr_names)
353
+ # This method is not implemented because you should define a new design document for validation of uniqueness to validate.
354
+ def validates_uniqueness_of
355
+ raise "Not Implemented"
356
+ end
357
+
358
+ # This method is the same as ActiveRecord::Validations.vaildates_format_of(*attr_names)
359
+ def validates_format_of(*attr_names)
360
+ configuration = { :message => CouchResource::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
361
+ configuration.update(attr_names.extract_options!)
362
+
363
+ raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
364
+
365
+ validates_each(attr_names, configuration) do |record, attr_name, value|
366
+ record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
367
+ end
368
+ end
369
+
370
+ # This method is the same as ActiveRecord::Validations.vaildates_inclusion_of(*attr_names)
371
+ def validates_inclusion_of(*attr_names)
372
+ configuration = { :message => CouchResource::Errors.default_error_messages[:inclusion], :on => :save }
373
+ configuration.update(attr_names.extract_options!)
374
+
375
+ enum = configuration[:in] || configuration[:within]
376
+
377
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
378
+
379
+ validates_each(attr_names, configuration) do |record, attr_name, value|
380
+ record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
381
+ end
382
+ end
383
+
384
+ # This method is the same as ActiveRecord::Validations.vaildates_exclusion_of(*attr_names)
385
+ def validates_exclusion_of(*attr_names)
386
+ configuration = { :message => CouchResource::Errors.default_error_messages[:exclusion], :on => :save }
387
+ configuration.update(attr_names.extract_options!)
388
+
389
+ enum = configuration[:in] || configuration[:within]
390
+
391
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
392
+
393
+ validates_each(attr_names, configuration) do |record, attr_name, value|
394
+ record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
395
+ end
396
+ end
397
+
398
+ # This method is the same as ActiveRecord::Validations.vaildates_numericality_of(*attr_names)
399
+ def validates_numericality_of(*attr_names)
400
+ configuration = { :on => :save, :only_integer => false, :allow_nil => false }
401
+ configuration.update(attr_names.extract_options!)
402
+
403
+
404
+ numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
405
+
406
+ (numericality_options - [ :odd, :even ]).each do |option|
407
+ raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric)
408
+ end
409
+
410
+ validates_each(attr_names,configuration) do |record, attr_name, value|
411
+ raw_value = record.send("#{attr_name}_before_type_cast") || value
412
+
413
+ next if configuration[:allow_nil] and raw_value.nil?
414
+
415
+ if configuration[:only_integer]
416
+ unless raw_value.to_s =~ /\A[+-]?\d+\Z/
417
+ record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
418
+ next
419
+ end
420
+ raw_value = raw_value.to_i
421
+ else
422
+ begin
423
+ raw_value = Kernel.Float(raw_value.to_s)
424
+ rescue ArgumentError, TypeError
425
+ record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
426
+ next
427
+ end
428
+ end
429
+
430
+ numericality_options.each do |option|
431
+ case option
432
+ when :odd, :even
433
+ record.errors.add(attr_name, configuration[:message] || CouchResource::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
434
+ else
435
+ message = configuration[:message] || CouchResource::Errors.default_error_messages[option]
436
+ message = message % configuration[option] if configuration[option]
437
+ record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
438
+ end
439
+ end
440
+ end
441
+ end
442
+
443
+ # This method is original for CouchResource validation to validate child object in this class.
444
+ # class Person
445
+ # object :children, :is_a => Children, :validates => [
446
+ # [:children_of, {:on => :create, :allow_nil => true }]
447
+ # ]
448
+ # end
449
+ def validates_children_of(*attr_names)
450
+ configuration = { :message => CouchResource::Errors.default_error_messages[:children], :on => :save }
451
+ configuration.update(attr_names.extract_options!)
452
+ validates_each(attr_names, configuration) do |record, attr_name, value|
453
+ if value.respond_to?(:valid?)
454
+ record.errors.add(attr_name, configuration[:message] % value) unless value.valid?
455
+ end
456
+ end
457
+ end
458
+
459
+ private
460
+ def validation_method(on)
461
+ case on
462
+ when :save then :validate
463
+ when :create then :validate_on_create
464
+ when :update then :validate_on_update
465
+ end
466
+ end
467
+ end
468
+
469
+ module InstanceMethods
470
+ def errors
471
+ @errors ||= Errors.new(self)
472
+ end
473
+
474
+ def valid?
475
+ errors.clear
476
+
477
+ run_callbacks(:validate)
478
+ validate
479
+ # validate the object only which has #new? method.
480
+ if respond_to?(:new?)
481
+ if new?
482
+ run_callbacks(:validate_on_create)
483
+ validate_on_create
484
+ else
485
+ run_callbacks(:validate_on_update)
486
+ validate_on_update
487
+ end
488
+ end
489
+
490
+ errors.empty?
491
+ end
492
+
493
+ def save_with_validation(perform_validation=true)
494
+ if perform_validation && valid? || !perform_validation
495
+ save_without_validation
496
+ else
497
+ false
498
+ end
499
+ end
500
+
501
+ def save_with_validation!
502
+ if valid?
503
+ save_without_validation!
504
+ else
505
+ raise RecordInvalid.new(self)
506
+ end
507
+ end
508
+
509
+ protected
510
+ def validate
511
+ end
512
+
513
+ def validate_on_create
514
+ end
515
+
516
+ def validate_on_update
517
+ end
518
+ end
519
+ end
520
+ end