samlown-couchrest_extended_document 1.0.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.
- data/LICENSE +176 -0
- data/README.md +46 -0
- data/Rakefile +67 -0
- data/THANKS.md +19 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +130 -0
- data/lib/couchrest/casted_array.rb +25 -0
- data/lib/couchrest/casted_model.rb +59 -0
- data/lib/couchrest/extended_document.rb +318 -0
- data/lib/couchrest/mixins.rb +12 -0
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/callbacks.rb +532 -0
- data/lib/couchrest/mixins/class_proxy.rb +120 -0
- data/lib/couchrest/mixins/collection.rb +260 -0
- data/lib/couchrest/mixins/design_doc.rb +121 -0
- data/lib/couchrest/mixins/document_queries.rb +80 -0
- data/lib/couchrest/mixins/extended_attachments.rb +70 -0
- data/lib/couchrest/mixins/properties.rb +154 -0
- data/lib/couchrest/mixins/validation.rb +245 -0
- data/lib/couchrest/mixins/views.rb +148 -0
- data/lib/couchrest/monkeypatches.rb +5 -0
- data/lib/couchrest/property.rb +50 -0
- data/lib/couchrest/support/couchrest.rb +56 -0
- data/lib/couchrest/support/rails.rb +42 -0
- data/lib/couchrest/typecast.rb +175 -0
- data/lib/couchrest/validation/auto_validate.rb +156 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +125 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
- data/lib/couchrest/validation/validators/format_validator.rb +122 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +139 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
- data/spec/couchrest/attribute_protection_spec.rb +150 -0
- data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
- data/spec/couchrest/casted_model_spec.rb +406 -0
- data/spec/couchrest/extended_doc_attachment_spec.rb +135 -0
- data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
- data/spec/couchrest/extended_doc_spec.rb +824 -0
- data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
- data/spec/couchrest/extended_doc_view_spec.rb +473 -0
- data/spec/couchrest/property_spec.rb +628 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +22 -0
- data/spec/fixtures/more/cat.rb +22 -0
- data/spec/fixtures/more/course.rb +22 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +6 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/more/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +49 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +181 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module DocumentQueries
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Load all documents that have the "couchrest-type" field equal to the
|
12
|
+
# name of the current class. Take the standard set of
|
13
|
+
# CouchRest::Database#view options.
|
14
|
+
def all(opts = {}, &block)
|
15
|
+
view(:all, opts, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the number of documents that have the "couchrest-type" field
|
19
|
+
# equal to the name of the current class. Takes the standard set of
|
20
|
+
# CouchRest::Database#view options
|
21
|
+
def count(opts = {}, &block)
|
22
|
+
all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load the first document that have the "couchrest-type" field equal to
|
26
|
+
# the name of the current class.
|
27
|
+
#
|
28
|
+
# ==== Returns
|
29
|
+
# Object:: The first object instance available
|
30
|
+
# or
|
31
|
+
# Nil:: if no instances available
|
32
|
+
#
|
33
|
+
# ==== Parameters
|
34
|
+
# opts<Hash>::
|
35
|
+
# View options, see <tt>CouchRest::Database#view</tt> options for more info.
|
36
|
+
def first(opts = {})
|
37
|
+
first_instance = self.all(opts.merge!(:limit => 1))
|
38
|
+
first_instance.empty? ? nil : first_instance.first
|
39
|
+
end
|
40
|
+
|
41
|
+
# Load a document from the database by id
|
42
|
+
# No exceptions will be raised if the document isn't found
|
43
|
+
#
|
44
|
+
# ==== Returns
|
45
|
+
# Object:: if the document was found
|
46
|
+
# or
|
47
|
+
# Nil::
|
48
|
+
#
|
49
|
+
# === Parameters
|
50
|
+
# id<String, Integer>:: Document ID
|
51
|
+
# db<Database>:: optional option to pass a custom database to use
|
52
|
+
def get(id, db = database)
|
53
|
+
begin
|
54
|
+
get!(id, db)
|
55
|
+
rescue
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Load a document from the database by id
|
61
|
+
# An exception will be raised if the document isn't found
|
62
|
+
#
|
63
|
+
# ==== Returns
|
64
|
+
# Object:: if the document was found
|
65
|
+
# or
|
66
|
+
# Exception
|
67
|
+
#
|
68
|
+
# === Parameters
|
69
|
+
# id<String, Integer>:: Document ID
|
70
|
+
# db<Database>:: optional option to pass a custom database to use
|
71
|
+
def get!(id, db = database)
|
72
|
+
doc = db.get id
|
73
|
+
create_from_database(doc)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module ExtendedAttachments
|
4
|
+
|
5
|
+
# creates a file attachment to the current doc
|
6
|
+
def create_attachment(args={})
|
7
|
+
raise ArgumentError unless args[:file] && args[:name]
|
8
|
+
return if has_attachment?(args[:name])
|
9
|
+
self['_attachments'] ||= {}
|
10
|
+
set_attachment_attr(args)
|
11
|
+
rescue ArgumentError => e
|
12
|
+
raise ArgumentError, 'You must specify :file and :name'
|
13
|
+
end
|
14
|
+
|
15
|
+
# reads the data from an attachment
|
16
|
+
def read_attachment(attachment_name)
|
17
|
+
database.fetch_attachment(self, attachment_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# modifies a file attachment on the current doc
|
21
|
+
def update_attachment(args={})
|
22
|
+
raise ArgumentError unless args[:file] && args[:name]
|
23
|
+
return unless has_attachment?(args[:name])
|
24
|
+
delete_attachment(args[:name])
|
25
|
+
set_attachment_attr(args)
|
26
|
+
rescue ArgumentError => e
|
27
|
+
raise ArgumentError, 'You must specify :file and :name'
|
28
|
+
end
|
29
|
+
|
30
|
+
# deletes a file attachment from the current doc
|
31
|
+
def delete_attachment(attachment_name)
|
32
|
+
return unless self['_attachments']
|
33
|
+
self['_attachments'].delete attachment_name
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns true if attachment_name exists
|
37
|
+
def has_attachment?(attachment_name)
|
38
|
+
!!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns URL to fetch the attachment from
|
42
|
+
def attachment_url(attachment_name)
|
43
|
+
return unless has_attachment?(attachment_name)
|
44
|
+
"#{database.root}/#{self.id}/#{attachment_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns URI to fetch the attachment from
|
48
|
+
def attachment_uri(attachment_name)
|
49
|
+
return unless has_attachment?(attachment_name)
|
50
|
+
"#{database.uri}/#{self.id}/#{attachment_name}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def get_mime_type(file)
|
56
|
+
::MIME::Types.type_for(file.path).empty? ?
|
57
|
+
'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_attachment_attr(args)
|
61
|
+
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
|
62
|
+
self['_attachments'][args[:name]] = {
|
63
|
+
'content_type' => content_type,
|
64
|
+
'data' => args[:file].read
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
end # module ExtendedAttachments
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'time'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'property')
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'casted_array')
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'typecast')
|
5
|
+
|
6
|
+
module CouchRest
|
7
|
+
module Mixins
|
8
|
+
module Properties
|
9
|
+
|
10
|
+
class IncludeError < StandardError; end
|
11
|
+
|
12
|
+
include ::CouchRest::More::Typecast
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
16
|
+
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
17
|
+
self.properties ||= []
|
18
|
+
EOS
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
raise CouchRest::Mixins::Properties::IncludeError, "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (base.new.respond_to?(:[]) && base.new.respond_to?(:[]=))
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply_defaults
|
24
|
+
return if self.respond_to?(:new?) && (new? == false)
|
25
|
+
return unless self.class.respond_to?(:properties)
|
26
|
+
return if self.class.properties.empty?
|
27
|
+
# TODO: cache the default object
|
28
|
+
self.class.properties.each do |property|
|
29
|
+
key = property.name.to_s
|
30
|
+
# let's make sure we have a default
|
31
|
+
unless property.default.nil?
|
32
|
+
if property.default.class == Proc
|
33
|
+
self[key] = property.default.call
|
34
|
+
else
|
35
|
+
self[key] = Marshal.load(Marshal.dump(property.default))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def cast_keys
|
42
|
+
return unless self.class.properties
|
43
|
+
self.class.properties.each do |property|
|
44
|
+
cast_property(property)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def cast_property(property, assigned=false)
|
49
|
+
return unless property.casted
|
50
|
+
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
51
|
+
# Don't cast the property unless it has a value
|
52
|
+
return unless self[key]
|
53
|
+
if property.type.is_a?(Array)
|
54
|
+
klass = property.type[0]
|
55
|
+
self[key] = [self[key]] unless self[key].is_a?(Array)
|
56
|
+
arr = self[key].collect do |value|
|
57
|
+
value = typecast_value(value, klass, property.init_method)
|
58
|
+
associate_casted_to_parent(value, assigned)
|
59
|
+
value
|
60
|
+
end
|
61
|
+
# allow casted_by calls to be passed up chain by wrapping in CastedArray
|
62
|
+
self[key] = klass != String ? ::CouchRest::CastedArray.new(arr) : arr
|
63
|
+
self[key].casted_by = self if self[key].respond_to?(:casted_by)
|
64
|
+
else
|
65
|
+
self[key] = typecast_value(self[key], property.type, property.init_method)
|
66
|
+
associate_casted_to_parent(self[key], assigned)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def associate_casted_to_parent(casted, assigned)
|
71
|
+
casted.casted_by = self if casted.respond_to?(:casted_by)
|
72
|
+
casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
|
73
|
+
end
|
74
|
+
|
75
|
+
def cast_property_by_name(property_name)
|
76
|
+
return unless self.class.properties
|
77
|
+
property = self.class.properties.detect{|property| property.name == property_name}
|
78
|
+
return unless property
|
79
|
+
cast_property(property, true)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
module ClassMethods
|
84
|
+
|
85
|
+
def property(name, options={})
|
86
|
+
existing_property = self.properties.find{|p| p.name == name.to_s}
|
87
|
+
if existing_property.nil? || (existing_property.default != options[:default])
|
88
|
+
define_property(name, options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
# This is not a thread safe operation, if you have to set new properties at runtime
|
95
|
+
# make sure to use a mutex.
|
96
|
+
def define_property(name, options={})
|
97
|
+
# check if this property is going to casted
|
98
|
+
options[:casted] = !!(options[:cast_as] || options[:type])
|
99
|
+
property = CouchRest::Property.new(name, (options.delete(:cast_as) || options.delete(:type)), options)
|
100
|
+
create_property_getter(property)
|
101
|
+
create_property_setter(property) unless property.read_only == true
|
102
|
+
properties << property
|
103
|
+
end
|
104
|
+
|
105
|
+
# defines the getter for the property (and optional aliases)
|
106
|
+
def create_property_getter(property)
|
107
|
+
# meth = property.name
|
108
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
109
|
+
def #{property.name}
|
110
|
+
self['#{property.name}']
|
111
|
+
end
|
112
|
+
EOS
|
113
|
+
|
114
|
+
if property.type == 'boolean'
|
115
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
116
|
+
def #{property.name}?
|
117
|
+
if self['#{property.name}'].nil? || self['#{property.name}'] == false || self['#{property.name}'].to_s.downcase == 'false'
|
118
|
+
false
|
119
|
+
else
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
EOS
|
124
|
+
end
|
125
|
+
|
126
|
+
if property.alias
|
127
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
128
|
+
alias #{property.alias.to_sym} #{property.name.to_sym}
|
129
|
+
EOS
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# defines the setter for the property (and optional aliases)
|
134
|
+
def create_property_setter(property)
|
135
|
+
property_name = property.name
|
136
|
+
class_eval <<-EOS
|
137
|
+
def #{property_name}=(value)
|
138
|
+
self['#{property_name}'] = value
|
139
|
+
cast_property_by_name('#{property_name}')
|
140
|
+
end
|
141
|
+
EOS
|
142
|
+
|
143
|
+
if property.alias
|
144
|
+
class_eval <<-EOS
|
145
|
+
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
146
|
+
EOS
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end # module ClassMethods
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
# Extracted from dm-validations 0.9.10
|
2
|
+
#
|
3
|
+
# Copyright (c) 2007 Guy van den Berg
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
class Object
|
25
|
+
def validatable?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'pathname'
|
31
|
+
|
32
|
+
dir = File.join(Pathname(__FILE__).dirname.expand_path, '..', 'validation')
|
33
|
+
|
34
|
+
require File.join(dir, 'validation_errors')
|
35
|
+
require File.join(dir, 'contextual_validators')
|
36
|
+
require File.join(dir, 'auto_validate')
|
37
|
+
|
38
|
+
require File.join(dir, 'validators', 'generic_validator')
|
39
|
+
require File.join(dir, 'validators', 'required_field_validator')
|
40
|
+
require File.join(dir, 'validators', 'absent_field_validator')
|
41
|
+
require File.join(dir, 'validators', 'format_validator')
|
42
|
+
require File.join(dir, 'validators', 'length_validator')
|
43
|
+
require File.join(dir, 'validators', 'numeric_validator')
|
44
|
+
require File.join(dir, 'validators', 'method_validator')
|
45
|
+
require File.join(dir, 'validators', 'confirmation_validator')
|
46
|
+
|
47
|
+
module CouchRest
|
48
|
+
module Validation
|
49
|
+
|
50
|
+
def self.included(base)
|
51
|
+
base.extlib_inheritable_accessor(:auto_validation)
|
52
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
53
|
+
# Callbacks
|
54
|
+
define_callbacks :validate
|
55
|
+
|
56
|
+
# Turn off auto validation by default
|
57
|
+
self.auto_validation ||= false
|
58
|
+
|
59
|
+
# Force the auto validation for the class properties
|
60
|
+
# This feature is still not fully ported over,
|
61
|
+
# test are lacking, so please use with caution
|
62
|
+
def self.auto_validate!
|
63
|
+
self.auto_validation = true
|
64
|
+
end
|
65
|
+
|
66
|
+
# share the validations with subclasses
|
67
|
+
def self.inherited(subklass)
|
68
|
+
self.validators.contexts.each do |k, v|
|
69
|
+
subklass.validators.contexts[k] = v.dup
|
70
|
+
end
|
71
|
+
super
|
72
|
+
end
|
73
|
+
EOS
|
74
|
+
|
75
|
+
base.extend(ClassMethods)
|
76
|
+
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
77
|
+
define_callbacks :validate
|
78
|
+
if method_defined?(:_run_save_callbacks)
|
79
|
+
set_callback :save, :before, :check_validations
|
80
|
+
end
|
81
|
+
EOS
|
82
|
+
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
83
|
+
def self.define_property(name, options={})
|
84
|
+
super
|
85
|
+
auto_generate_validations(properties.last) if properties && properties.size > 0
|
86
|
+
autovalidation_check = true
|
87
|
+
end
|
88
|
+
RUBY_EVAL
|
89
|
+
end
|
90
|
+
|
91
|
+
# Ensures the object is valid for the context provided, and otherwise
|
92
|
+
# throws :halt and returns false.
|
93
|
+
#
|
94
|
+
def check_validations(context = :default)
|
95
|
+
throw(:halt, false) unless context.nil? || valid?(context)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Return the ValidationErrors
|
99
|
+
#
|
100
|
+
def errors
|
101
|
+
@errors ||= ValidationErrors.new
|
102
|
+
end
|
103
|
+
|
104
|
+
# Mark this resource as validatable. When we validate associations of a
|
105
|
+
# resource we can check if they respond to validatable? before trying to
|
106
|
+
# recursivly validate them
|
107
|
+
#
|
108
|
+
def validatable?
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Alias for valid?(:default)
|
113
|
+
#
|
114
|
+
def valid_for_default?
|
115
|
+
valid?(:default)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Check if a resource is valid in a given context
|
119
|
+
#
|
120
|
+
def valid?(context = :default)
|
121
|
+
recursive_valid?(self, context, true)
|
122
|
+
end
|
123
|
+
|
124
|
+
# checking on casted objects
|
125
|
+
def validate_casted_arrays
|
126
|
+
result = true
|
127
|
+
array_casted_properties = self.class.properties.select { |property| property.casted && property.type.instance_of?(Array) }
|
128
|
+
array_casted_properties.each do |property|
|
129
|
+
casted_values = self.send(property.name)
|
130
|
+
next unless casted_values.is_a?(Array) && casted_values.first.respond_to?(:valid?)
|
131
|
+
casted_values.each do |value|
|
132
|
+
result = (result && value.valid?) if value.respond_to?(:valid?)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
# Do recursive validity checking
|
139
|
+
#
|
140
|
+
def recursive_valid?(target, context, state)
|
141
|
+
valid = state
|
142
|
+
target.each do |key, prop|
|
143
|
+
if prop.is_a?(Array)
|
144
|
+
prop.each do |item|
|
145
|
+
if item.validatable?
|
146
|
+
valid = recursive_valid?(item, context, valid) && valid
|
147
|
+
end
|
148
|
+
end
|
149
|
+
elsif prop.validatable?
|
150
|
+
valid = recursive_valid?(prop, context, valid) && valid
|
151
|
+
end
|
152
|
+
end
|
153
|
+
target._run_validate_callbacks do
|
154
|
+
target.class.validators.execute(context, target) && valid
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def validation_property_value(name)
|
160
|
+
self.respond_to?(name, true) ? self.send(name) : nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get the corresponding Object property, if it exists.
|
164
|
+
def validation_property(field_name)
|
165
|
+
properties.find{|p| p.name == field_name}
|
166
|
+
end
|
167
|
+
|
168
|
+
module ClassMethods
|
169
|
+
include CouchRest::Validation::ValidatesPresent
|
170
|
+
include CouchRest::Validation::ValidatesAbsent
|
171
|
+
include CouchRest::Validation::ValidatesIsConfirmed
|
172
|
+
# include CouchRest::Validation::ValidatesIsPrimitive
|
173
|
+
# include CouchRest::Validation::ValidatesIsAccepted
|
174
|
+
include CouchRest::Validation::ValidatesFormat
|
175
|
+
include CouchRest::Validation::ValidatesLength
|
176
|
+
# include CouchRest::Validation::ValidatesWithin
|
177
|
+
include CouchRest::Validation::ValidatesIsNumber
|
178
|
+
include CouchRest::Validation::ValidatesWithMethod
|
179
|
+
# include CouchRest::Validation::ValidatesWithBlock
|
180
|
+
# include CouchRest::Validation::ValidatesIsUnique
|
181
|
+
include CouchRest::Validation::AutoValidate
|
182
|
+
|
183
|
+
# Return the set of contextual validators or create a new one
|
184
|
+
#
|
185
|
+
def validators
|
186
|
+
@validations ||= ContextualValidators.new
|
187
|
+
end
|
188
|
+
|
189
|
+
# Clean up the argument list and return a opts hash, including the
|
190
|
+
# merging of any default opts. Set the context to default if none is
|
191
|
+
# provided. Also allow :context to be aliased to :on, :when & group
|
192
|
+
#
|
193
|
+
def opts_from_validator_args(args, defaults = nil)
|
194
|
+
opts = args.last.kind_of?(Hash) ? args.pop : {}
|
195
|
+
context = :default
|
196
|
+
context = opts[:context] if opts.has_key?(:context)
|
197
|
+
context = opts.delete(:on) if opts.has_key?(:on)
|
198
|
+
context = opts.delete(:when) if opts.has_key?(:when)
|
199
|
+
context = opts.delete(:group) if opts.has_key?(:group)
|
200
|
+
opts[:context] = context
|
201
|
+
opts.merge!(defaults) unless defaults.nil?
|
202
|
+
opts
|
203
|
+
end
|
204
|
+
|
205
|
+
# Given a new context create an instance method of
|
206
|
+
# valid_for_<context>? which simply calls valid?(context)
|
207
|
+
# if it does not already exist
|
208
|
+
#
|
209
|
+
def create_context_instance_methods(context)
|
210
|
+
name = "valid_for_#{context.to_s}?" # valid_for_signup?
|
211
|
+
if !self.instance_methods.include?(name)
|
212
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
213
|
+
def #{name} # def valid_for_signup?
|
214
|
+
valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
|
215
|
+
end # end
|
216
|
+
EOS
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Create a new validator of the given klazz and push it onto the
|
221
|
+
# requested context for each of the attributes in the fields list
|
222
|
+
#
|
223
|
+
def add_validator_to_context(opts, fields, klazz)
|
224
|
+
fields.each do |field|
|
225
|
+
validator = klazz.new(field.to_sym, opts)
|
226
|
+
if opts[:context].is_a?(Symbol)
|
227
|
+
unless validators.context(opts[:context]).include?(validator)
|
228
|
+
validators.context(opts[:context]) << validator
|
229
|
+
create_context_instance_methods(opts[:context])
|
230
|
+
end
|
231
|
+
elsif opts[:context].is_a?(Array)
|
232
|
+
opts[:context].each do |c|
|
233
|
+
unless validators.context(c).include?(validator)
|
234
|
+
validators.context(c) << validator
|
235
|
+
create_context_instance_methods(c)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
end # module ClassMethods
|
243
|
+
end # module Validation
|
244
|
+
|
245
|
+
end # module CouchRest
|