mongomatic 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -26,13 +26,27 @@ Mongomatic allows you to map your Ruby objects to Mongo documents. It is designe
26
26
  => BSON::ObjectID('4c32834f0218236321000001')
27
27
  u
28
28
  => #<User:0x00000100d0cbf8 @doc={"name"=>"Ben", "email"=>"me@somewhere.com", "_id"=>BSON::ObjectID('4c32834f0218236321000001')}, @removed=false, @validation_context=nil, @errors={}, @_initialized_validate_callbacks=true>
29
+ u["name"] = "Ben Myles"
30
+ => "Ben Myles"
31
+ u.update
32
+ => 142
29
33
 
30
- cursor = User.find({"name" => "Ben"})
31
- => #<Mongomatic::Cursor:0x00000100acd3d8 @obj_class=User, @mongo_cursor=DBResponse(flags=, cursor_id=, start=)>
34
+ cursor = User.find({"name" => "Ben Myles"})
35
+ => #<Mongomatic::Cursor:0x00000100cf5110 @obj_class=User, @mongo_cursor=DBResponse(flags=, cursor_id=, start=)> cursor.count
32
36
  cursor.count
33
37
  => 1
38
+ found = cursor.next
39
+ => #<User:0x00000100ccd408 @doc={"_id"=>BSON::ObjectID('4c32c3720218236526000001'), "name"=>"Ben Myles", "email"=>"me@somewhere.com"}, @removed=false>
40
+ found.remove
41
+ => 72
42
+ found
43
+ => #<User:0x00000101091f80 @doc={"_id"=>BSON::ObjectID('4c32c4480218236538000001'), "name"=>"Ben Myles", "email"=>"me@somewhere.com"}, @removed=true>
44
+ cursor = User.find({"name" => "Ben Myles"})
45
+ => #<Mongomatic::Cursor:0x00000100d9eb20 @obj_class=User, @mongo_cursor=DBResponse(flags=, cursor_id=, start=)>
46
+ cursor.count
47
+ => 0
34
48
  cursor.next
35
- => #<User:0x00000100a61e08 @doc={"_id"=>BSON::ObjectID('4c32834f0218236321000001'), "name"=>"Ben", "email"=>"me@somewhere.com"}, @removed=false>
49
+ => nil
36
50
 
37
51
  == Note on Patches/Pull Requests
38
52
 
@@ -1,7 +1,7 @@
1
1
  module Mongomatic
2
2
  class Base
3
- include ActiveModel::Validations
4
3
  include Mongomatic::Modifiers
4
+ include Validatable
5
5
 
6
6
  class << self
7
7
  def settings
@@ -108,10 +108,6 @@ module Mongomatic
108
108
 
109
109
  protected
110
110
 
111
- def read_attribute_for_validation(key)
112
- @doc[key.to_s]
113
- end
114
-
115
111
  def doc
116
112
  @doc
117
113
  end
data/lib/mongomatic.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  gem "bson", "= 1.0.3"
2
2
  gem "bson_ext", "= 1.0.1"
3
3
  gem "mongo", "= 1.0.3"
4
- gem "activemodel", ">= 3.0.0.beta4"
4
+ gem "activesupport", ">= 2.3.5"
5
5
 
6
6
  require "bson"
7
7
  require "mongo"
8
- require "active_model"
8
+
9
+ begin
10
+ require 'active_support/core_ext/object/blank' # newer versions of active_support (>= 3.0)
11
+ require 'active_support/core_ext/hash' # newer versions of active_support (>= 3.0)
12
+ rescue LoadError => e
13
+ require 'active_support/all' # support older versions of active_support (<= 2.3.5)
14
+ end
9
15
 
10
16
  module Mongomatic
11
17
  class << self
@@ -21,6 +27,8 @@ module Mongomatic
21
27
  end
22
28
  end
23
29
 
30
+ require "#{File.dirname(__FILE__)}/validatable"
31
+
24
32
  require "#{File.dirname(__FILE__)}/mongomatic/cursor"
25
33
  require "#{File.dirname(__FILE__)}/mongomatic/modifiers"
26
34
  require "#{File.dirname(__FILE__)}/mongomatic/base"
@@ -0,0 +1,15 @@
1
+ module Validatable
2
+ class ChildValidation #:nodoc:
3
+ attr_accessor :attribute, :map, :should_validate_proc
4
+
5
+ def initialize(attribute, map, should_validate_proc)
6
+ @attribute = attribute
7
+ @map = map
8
+ @should_validate_proc = should_validate_proc
9
+ end
10
+
11
+ def should_validate?(instance)
12
+ instance.instance_eval &should_validate_proc
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,106 @@
1
+ module Validatable
2
+ class Errors
3
+ extend Forwardable
4
+ include Enumerable
5
+
6
+ def_delegators :errors, :clear, :each, :each_pair, :empty?, :length, :size
7
+
8
+ # Returns true if the specified +attribute+ has errors associated with it.
9
+ #
10
+ # class Company < ActiveRecord::Base
11
+ # validates_presence_of :name, :address, :email
12
+ # validates_length_of :name, :in => 5..30
13
+ # end
14
+ #
15
+ # company = Company.create(:address => '123 First St.')
16
+ # company.errors.invalid?(:name) # => true
17
+ # company.errors.invalid?(:address) # => false
18
+ def invalid?(attribute)
19
+ !@errors[attribute.to_sym].nil?
20
+ end
21
+
22
+ # Adds an error to the base object instead of any particular attribute. This is used
23
+ # to report errors that don't tie to any specific attribute, but rather to the object
24
+ # as a whole. These error messages don't get prepended with any field name when iterating
25
+ # with +each_full+, so they should be complete sentences.
26
+ def add_to_base(msg)
27
+ add(:base, msg)
28
+ end
29
+
30
+ # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
31
+ def on_base
32
+ on(:base)
33
+ end
34
+
35
+ # call-seq: on(attribute)
36
+ #
37
+ # * Returns nil, if no errors are associated with the specified +attribute+.
38
+ # * Returns the error message, if one error is associated with the specified +attribute+.
39
+ # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
40
+ def on(attribute)
41
+ return nil if errors[attribute.to_sym].nil?
42
+ errors[attribute.to_sym].size == 1 ? errors[attribute.to_sym].first : errors[attribute.to_sym]
43
+ end
44
+
45
+ # Rails 3 API for errors, always return array.
46
+ def [](attribute)
47
+ errors[attribute.to_sym] || []
48
+ end
49
+
50
+ def add(attribute, message) #:nodoc:
51
+ errors[attribute.to_sym] = [] if errors[attribute.to_sym].nil?
52
+ errors[attribute.to_sym] << message
53
+ end
54
+
55
+ def merge!(errors) #:nodoc:
56
+ errors.each_pair{|k, v| add(k,v)}
57
+ self
58
+ end
59
+
60
+ # call-seq: replace(attribute)
61
+ #
62
+ # * Replaces the errors value for the given +attribute+
63
+ def replace(attribute, value)
64
+ errors[attribute.to_sym] = value
65
+ end
66
+
67
+ # call-seq: raw(attribute)
68
+ #
69
+ # * Returns an array of error messages associated with the specified +attribute+.
70
+ def raw(attribute)
71
+ errors[attribute.to_sym]
72
+ end
73
+
74
+ def errors #:nodoc:
75
+ @errors ||= {}
76
+ end
77
+
78
+ def count #:nodoc:
79
+ errors.values.flatten.size
80
+ end
81
+
82
+ # call-seq: full_messages -> an_array_of_messages
83
+ #
84
+ # Returns an array containing the full list of error messages.
85
+ def full_messages
86
+ full_messages = []
87
+
88
+ errors.each_key do |attribute|
89
+ errors[attribute].each do |msg|
90
+ next if msg.nil?
91
+
92
+ if attribute.to_s == "base"
93
+ full_messages << msg
94
+ else
95
+ full_messages << humanize(attribute.to_s) + " " + msg
96
+ end
97
+ end
98
+ end
99
+ full_messages
100
+ end
101
+
102
+ def humanize(lower_case_and_underscored_word) #:nodoc:
103
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,9 @@
1
+ module Validatable
2
+ class IncludedValidation #:nodoc:
3
+ attr_accessor :attribute
4
+
5
+ def initialize(attribute)
6
+ @attribute = attribute
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,314 @@
1
+ module Validatable
2
+ module Macros
3
+ # call-seq: validates_each(*args)
4
+ #
5
+ # Validates that the logic evaluates to true
6
+ #
7
+ # class Address
8
+ # include Validatable
9
+ # validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") if ZipCodeService.allows(zip_code) }
10
+ # end
11
+ #
12
+ # The logic option is required.
13
+ #
14
+ # Configuration options:
15
+ #
16
+ # * after_validate - A block that executes following the run of a validation
17
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
18
+ # * if - A block that when executed must return true of the validation will not occur
19
+ # * level - The level at which the validation should occur
20
+ # * logic - A block that executes to perform the validation
21
+ # * message - The message to add to the errors collection when the validation fails
22
+ # * times - The number of times the validation applies
23
+ def validates_each(*args)
24
+ add_validations(args, ValidatesEach)
25
+ end
26
+
27
+ # call-seq: validates_format_of(*args)
28
+ #
29
+ # Validates whether the value of the specified attribute is of the
30
+ # correct form by matching it against the regular expression provided.
31
+ #
32
+ # class Person
33
+ # include Validatable
34
+ # validates_format_of :first_name, :with => /[ A-Za-z]/
35
+ # end
36
+ #
37
+ # A regular expression must be provided or else an exception will be raised.
38
+ #
39
+ # Configuration options:
40
+ #
41
+ # * after_validate - A block that executes following the run of a validation
42
+ # * message - The message to add to the errors collection when the validation fails
43
+ # * times - The number of times the validation applies
44
+ # * level - The level at which the validation should occur
45
+ # * if - A block that when executed must return true of the validation will not occur
46
+ # * with - The regular expression used to validate the format
47
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
48
+ def validates_format_of(*args)
49
+ add_validations(args, ValidatesFormatOf)
50
+ end
51
+
52
+ # call-seq: validates_length_of(*args)
53
+ #
54
+ # Validates that the specified attribute matches the length restrictions supplied.
55
+ #
56
+ # class Person
57
+ # include Validatable
58
+ # validates_length_of :first_name, :maximum=>30
59
+ # validates_length_of :last_name, :minimum=>30
60
+ # end
61
+ #
62
+ # Configuration options:
63
+ #
64
+ # * after_validate - A block that executes following the run of a validation
65
+ # * message - The message to add to the errors collection when the validation fails
66
+ # * times - The number of times the validation applies
67
+ # * level - The level at which the validation should occur
68
+ # * if - A block that when executed must return true of the validation will not occur
69
+ # * minimum - The minimum size of the attribute
70
+ # * maximum - The maximum size of the attribute
71
+ # * is - The size the attribute must be
72
+ # * within - A range that the size of the attribute must fall within
73
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
74
+ def validates_length_of(*args)
75
+ add_validations(args, ValidatesLengthOf)
76
+ end
77
+
78
+ # call-seq: validates_numericality_of(*args)
79
+ #
80
+ # Validates that the specified attribute is numeric.
81
+ #
82
+ # class Person
83
+ # include Validatable
84
+ # validates_numericality_of :age
85
+ # end
86
+ #
87
+ # Configuration options:
88
+ #
89
+ # * after_validate - A block that executes following the run of a validation
90
+ # * message - The message to add to the errors collection when the validation fails
91
+ # * times - The number of times the validation applies
92
+ # * level - The level at which the validation should occur
93
+ # * if - A block that when executed must return true of the validation will not occur
94
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
95
+ # * only_integer - Whether the attribute must be an integer (default is false)
96
+ def validates_numericality_of(*args)
97
+ add_validations(args, ValidatesNumericalityOf)
98
+ end
99
+
100
+ # call-seq: validates_acceptance_of(*args)
101
+ #
102
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
103
+ #
104
+ # class Person
105
+ # include Validatable
106
+ # validates_acceptance_of :terms_of_service
107
+ # validates_acceptance_of :eula, :message => "must be abided"
108
+ # end
109
+ #
110
+ # Configuration options:
111
+ #
112
+ # * after_validate - A block that executes following the run of a validation
113
+ # * message - The message to add to the errors collection when the validation fails
114
+ # * times - The number of times the validation applies
115
+ # * level - The level at which the validation should occur
116
+ # * if - A block that when executed must return true of the validation will not occur
117
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
118
+ def validates_acceptance_of(*args)
119
+ add_validations(args, ValidatesAcceptanceOf)
120
+ end
121
+
122
+ # call-seq: validates_confirmation_of(*args)
123
+ #
124
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
125
+ #
126
+ # Class:
127
+ # class PersonPresenter
128
+ # include Validatable
129
+ # validates_confirmation_of :user_name, :password
130
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
131
+ # end
132
+ #
133
+ # View:
134
+ # <%= password_field "person", "password" %>
135
+ # <%= password_field "person", "password_confirmation" %>
136
+ #
137
+ # Configuration options:
138
+ #
139
+ # * after_validate - A block that executes following the run of a validation
140
+ # * case_sensitive - Whether or not to apply case-sensitivity on the comparison. Defaults to true.
141
+ # * message - The message to add to the errors collection when the validation fails
142
+ # * times - The number of times the validation applies
143
+ # * level - The level at which the validation should occur
144
+ # * if - A block that when executed must return true of the validation will not occur
145
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
146
+ def validates_confirmation_of(*args)
147
+ add_validations(args, ValidatesConfirmationOf)
148
+ end
149
+
150
+ # call-seq: validates_presence_of(*args)
151
+ #
152
+ # Validates that the specified attributes are not nil or an empty string
153
+ #
154
+ # class Person
155
+ # include Validatable
156
+ # validates_presence_of :first_name
157
+ # end
158
+ #
159
+ # The first_name attribute must be in the object and it cannot be nil or empty.
160
+ #
161
+ # Configuration options:
162
+ #
163
+ # * after_validate - A block that executes following the run of a validation
164
+ # * message - The message to add to the errors collection when the validation fails
165
+ # * times - The number of times the validation applies
166
+ # * level - The level at which the validation should occur
167
+ # * if - A block that when executed must return true of the validation will not occur
168
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
169
+ def validates_presence_of(*args)
170
+ add_validations(args, ValidatesPresenceOf)
171
+ end
172
+
173
+ # call-seq: validates_true_for(*args)
174
+ #
175
+ # Validates that the logic evaluates to true
176
+ #
177
+ # class Person
178
+ # include Validatable
179
+ # validates_true_for :first_name, :logic => lambda { first_name == 'Jamie' }
180
+ # end
181
+ #
182
+ # The logic option is required.
183
+ #
184
+ # Configuration options:
185
+ #
186
+ # * after_validate - A block that executes following the run of a validation
187
+ # * message - The message to add to the errors collection when the validation fails
188
+ # * times - The number of times the validation applies
189
+ # * level - The level at which the validation should occur
190
+ # * if - A block that when executed must return true of the validation will not occur
191
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
192
+ # * logic - A block that executes to perform the validation
193
+ def validates_true_for(*args)
194
+ add_validations(args, ValidatesTrueFor)
195
+ end
196
+
197
+ def validates_exclusion_of(*args)
198
+ add_validations(args, ValidatesExclusionOf)
199
+ end
200
+
201
+ def validates_inclusion_of(*args)
202
+ add_validations(args, ValidatesInclusionOf)
203
+ end
204
+
205
+ # call-seq: validates_associated(*args)
206
+ #
207
+ # Checks the validity of an associated object or objects and adds a single
208
+ # error if validation fails.
209
+ #
210
+ # class Person
211
+ # include Validatable
212
+ # attr_accessor :addresses
213
+ # validates_associated :addresses
214
+ # end
215
+ #
216
+ # Configuration options:
217
+ #
218
+ # * after_validate - A block that executes following the run of a validation
219
+ # * message - The message to add to the errors collection when the validation fails
220
+ # * times - The number of times the validation applies
221
+ # * level - The level at which the validation should occur
222
+ # * if - A block that when executed must return true of the validation will not occur
223
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
224
+ def validates_associated(*args)
225
+ add_validations(args, ValidatesAssociated)
226
+ end
227
+
228
+ # call-seq: include_validations_from(attribute)
229
+ #
230
+ # Includes all the validations that are defined on the attribute.
231
+ # class Person
232
+ # include Validatable
233
+ # validates_presence_of :name
234
+ # end
235
+ #
236
+ # class PersonPresenter
237
+ # include Validatable
238
+ # include_validataions_from :person
239
+ # attr_accessor :person
240
+ # def name
241
+ # person.name
242
+ # end
243
+ #
244
+ # def initialize(person)
245
+ # @person = person
246
+ # end
247
+ # end
248
+ #
249
+ # presenter = PersonPresenter.new(Person.new)
250
+ # presenter.valid? #=> false
251
+ # presenter.errors.on(:name) #=> "can't be blank"
252
+ #
253
+ # The name attribute whose validations should be added.
254
+ def include_validations_from(attribute_to_validate, options = {})
255
+ validations_to_include << IncludedValidation.new(attribute_to_validate)
256
+ end
257
+
258
+ # call-seq: include_errors_from(attribute_to_validate, options = {})
259
+ #
260
+ # Validates the specified attributes.
261
+ # class Person
262
+ # include Validatable
263
+ # validates_presence_of :name
264
+ # attr_accessor :name
265
+ # end
266
+ #
267
+ # class PersonPresenter
268
+ # include Validatable
269
+ # include_errors_from :person, :map => { :name => :namen }, :if => lambda { not person.nil? }
270
+ # attr_accessor :person
271
+ #
272
+ # def initialize(person)
273
+ # @person = person
274
+ # end
275
+ # end
276
+ #
277
+ # presenter = PersonPresenter.new(Person.new)
278
+ # presenter.valid? #=> false
279
+ # presenter.errors.on(:namen) #=> "can't be blank"
280
+ #
281
+ # The person attribute will be validated.
282
+ # If person is invalid the errors will be added to the PersonPresenter errors collection.
283
+ #
284
+ # Configuration options:
285
+ #
286
+ # * map - A hash that maps attributes of the child to attributes of the parent.
287
+ # * if - A block that when executed must return true of the validation will not occur.
288
+ def include_errors_from(attribute_to_validate, options = {})
289
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
290
+ end
291
+
292
+ def include_validations_for(attribute_to_validate, options = {}) #:nodoc:
293
+ puts "include_validations_for is deprecated; use include_errors_from instead"
294
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
295
+ end
296
+
297
+ # call-seq: before_validation(&block)
298
+ #
299
+ # Is called before valid? or valid_for_*?
300
+ #
301
+ # class Person
302
+ # include Validatable
303
+ # before_validation do
304
+ # self.name = "default name"
305
+ # end
306
+ #
307
+ # attr_accessor :name
308
+ # end
309
+ #
310
+ def before_validation(&block)
311
+ before_validations << block
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,21 @@
1
+ class Object #:nodoc:
2
+ module InstanceExecHelper #:nodoc:
3
+ end
4
+ include InstanceExecHelper
5
+ def instance_eval_with_params(*args, &block)
6
+ begin
7
+ old_critical, Thread.critical = Thread.critical, true
8
+ n = 0
9
+ n += 1 while respond_to?(mname="__instance_exec#{n}")
10
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
11
+ ensure
12
+ Thread.critical = old_critical
13
+ end
14
+ begin
15
+ ret = send(mname, *args)
16
+ ensure
17
+ InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
18
+ end
19
+ ret
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Validatable
2
+ module Requireable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def requires(*args)
5
+ required_options.concat args
6
+ end
7
+
8
+ def required_options
9
+ @required_options ||= []
10
+ end
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.extend ClassMethods
15
+ end
16
+
17
+ def requires(options)
18
+ required_options = self.class.required_options.inject([]) do |errors, attribute|
19
+ errors << attribute.to_s unless options.has_key?(attribute)
20
+ errors
21
+ end
22
+ raise ArgumentError.new("#{self.class} requires options: #{required_options.join(', ')}") if required_options.any?
23
+ true
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Validatable
2
+ module Understandable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def understands(*args)
5
+ understandings.concat args
6
+ end
7
+
8
+ def understandings
9
+ @understandings ||= []
10
+ end
11
+
12
+ def all_understandings
13
+ return understandings + self.superclass.all_understandings if self.superclass.respond_to? :all_understandings
14
+ understandings
15
+ end
16
+ end
17
+
18
+ def self.included(klass)
19
+ klass.extend ClassMethods
20
+ end
21
+
22
+ def must_understand(hash)
23
+ invalid_options = hash.inject([]) do |errors, (key, value)|
24
+ errors << key.to_s unless self.class.all_understandings.include?(key)
25
+ errors
26
+ end
27
+ raise ArgumentError.new("invalid options: #{invalid_options.join(', ')}") if invalid_options.any?
28
+ true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ module Validatable
2
+ module ClassMethods #:nodoc:
3
+
4
+ def validate_children(instance, group)
5
+ self.children_to_validate.each do |child_validation|
6
+ next unless child_validation.should_validate?(instance)
7
+ child_or_children = instance.send child_validation.attribute
8
+ [child_or_children].flatten.each do |child|
9
+ if (child.respond_to?(:valid_for_group?))
10
+ child.valid_for_group?(group)
11
+ else
12
+ child.valid?
13
+ end
14
+ child.errors.each do |attribute, messages|
15
+ if messages.is_a?(String)
16
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, messages)
17
+ else
18
+ messages.each do |message|
19
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, message)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def all_before_validations
28
+ if self.superclass.respond_to? :all_before_validations
29
+ return before_validations + self.superclass.all_before_validations
30
+ end
31
+ before_validations
32
+ end
33
+
34
+ def before_validations
35
+ @before_validations ||= []
36
+ end
37
+
38
+ def all_validations
39
+ if self.respond_to?(:superclass) && self.superclass.respond_to?(:all_validations)
40
+ return validations + self.superclass.all_validations
41
+ end
42
+ validations
43
+ end
44
+
45
+ def validations
46
+ @validations ||= []
47
+ end
48
+
49
+ def add_error(instance, attribute, msg)
50
+ instance.errors.add(attribute, msg)
51
+ end
52
+
53
+ def validation_keys_include?(key)
54
+ validations.map { |validation| validation.key }.include?(key)
55
+ end
56
+
57
+ def validations_to_include
58
+ @validations_to_include ||= []
59
+ end
60
+
61
+ protected
62
+
63
+ def add_validations(args, klass)
64
+ options = args.last.is_a?(Hash) ? args.pop : {}
65
+ args.each do |attribute|
66
+ new_validation = klass.new self, attribute, options
67
+ self.validations << new_validation
68
+ self.create_valid_method_for_groups new_validation.groups
69
+ end
70
+ end
71
+
72
+ def create_valid_method_for_groups(groups)
73
+ groups.each do |group|
74
+ self.class_eval do
75
+ define_method "valid_for_#{group}?".to_sym do
76
+ valid_for_group?(group)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def children_to_validate
83
+ @children_to_validate ||= []
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,106 @@
1
+ module Validatable
2
+ def self.included(klass) #:nodoc:
3
+ klass.extend Validatable::ClassMethods
4
+ klass.extend Validatable::Macros
5
+ end
6
+
7
+ # call-seq: valid?
8
+ #
9
+ # Returns true if no errors were added otherwise false. Only executes validations that have no :groups option specified
10
+ def valid?
11
+ valid_for_group?(nil)
12
+ end
13
+
14
+ # call-seq: errors
15
+ #
16
+ # Returns the Errors object that holds all information about attribute error messages.
17
+ def errors
18
+ @errors ||= Validatable::Errors.new
19
+ end
20
+
21
+ def valid_for_group?(group) #:nodoc:
22
+ errors.clear
23
+ run_before_validations
24
+ self.class.validate_children(self, group)
25
+ self.validate_group(group)
26
+ errors.empty?
27
+ end
28
+
29
+ def times_validated(key) #:nodoc:
30
+ times_validated_hash[key] || 0
31
+ end
32
+
33
+ def increment_times_validated_for(validation) #:nodoc:
34
+ if validation.key != nil
35
+ if times_validated_hash[validation.key].nil?
36
+ times_validated_hash[validation.key] = 1
37
+ else
38
+ times_validated_hash[validation.key] += 1
39
+ end
40
+ end
41
+ end
42
+
43
+ # call-seq: validate_only(key)
44
+ #
45
+ # Only executes a specified validation. The argument should follow a pattern based on the key of the validation.
46
+ # Examples:
47
+ # * validates_presence_of :name can be run with obj.validate_only("presence_of/name")
48
+ # * validates_presence_of :birthday, :key => "a key" can be run with obj.validate_only("presence_of/a key")
49
+ def validate_only(key)
50
+ validation_name, attribute_name = key.split("/")
51
+ validation_name = validation_name.split("_").collect{|word| word.capitalize}.join
52
+ validation_key = "#{self.class.name}/Validatable::Validates#{validation_name}/#{attribute_name}"
53
+ validation = self.class.all_validations.find { |validation| validation.key == validation_key }
54
+ raise ArgumentError.new("validation with key #{validation_key} could not be found") if validation.nil?
55
+ errors.clear
56
+ run_validation(validation)
57
+ end
58
+
59
+ protected
60
+ def times_validated_hash #:nodoc:
61
+ @times_validated_hash ||= {}
62
+ end
63
+
64
+ def validate_group(group) #:nodoc:
65
+ validation_levels.each do |level|
66
+ validations_for_level_and_group(level, group).each do |validation|
67
+ run_validation(validation) if validation.should_validate?(self)
68
+ end
69
+ return unless self.errors.empty?
70
+ end
71
+ end
72
+
73
+ def run_validation(validation) #:nodoc:
74
+ validation_result = validation.valid?(self)
75
+ add_error(validation.attribute, validation.message(self)) unless validation_result
76
+ increment_times_validated_for(validation)
77
+ validation.run_after_validate(validation_result, self, validation.attribute)
78
+ end
79
+
80
+ def run_before_validations #:nodoc:
81
+ self.class.all_before_validations.each do |block|
82
+ instance_eval &block
83
+ end
84
+ end
85
+
86
+ def add_error(attribute, message) #:nodoc:
87
+ self.class.add_error(self, attribute, message)
88
+ end
89
+
90
+ def validations_for_level_and_group(level, group) #:nodoc:
91
+ validations_for_level = self.all_validations.select { |validation| validation.level == level }
92
+ return validations_for_level.select { |validation| validation.groups.empty? } if group.nil?
93
+ validations_for_level.select { |validation| validation.groups.include?(group) }
94
+ end
95
+
96
+ def all_validations #:nodoc:
97
+ res = self.class.validations_to_include.inject(self.class.all_validations) do |result, included_validation_class|
98
+ result += self.send(included_validation_class.attribute).all_validations
99
+ result
100
+ end
101
+ end
102
+
103
+ def validation_levels #:nodoc:
104
+ self.class.all_validations.inject([1]) { |result, validation| result << validation.level }.uniq.sort
105
+ end
106
+ end
@@ -0,0 +1,14 @@
1
+ module Validatable
2
+ class ValidatesAcceptanceOf < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ value = instance[self.attribute.to_s]
5
+ return true if allow_nil && value.nil?
6
+ return true if allow_blank && value.blank?
7
+ %w(1 true t).include?(value)
8
+ end
9
+
10
+ def message(instance)
11
+ super || "must be accepted"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Validatable
2
+ class ValidatesAssociated < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ Array(instance.send(attribute)).compact.map do |child|
5
+ child.valid?
6
+ end.all?
7
+ end
8
+
9
+ def message(instance)
10
+ super || "is invalid"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Validatable
2
+ class ValidatesConfirmationOf < ValidationBase #:nodoc:
3
+ option :case_sensitive
4
+ default :case_sensitive => true
5
+
6
+ def initialize(klass, attribute, options={})
7
+ klass.class_eval { attr_accessor "#{attribute}_confirmation" }
8
+ super
9
+ end
10
+
11
+ def valid?(instance)
12
+ confirmation_value = instance.send("#{self.attribute}_confirmation")
13
+ return true if allow_nil && confirmation_value.nil?
14
+ return true if allow_blank && confirmation_value.blank?
15
+ return instance[self.attribute.to_s] == instance.send("#{self.attribute}_confirmation".to_sym) if case_sensitive
16
+ instance[self.attribute.to_s].to_s.casecmp(instance.send("#{self.attribute}_confirmation".to_sym).to_s) == 0
17
+ end
18
+
19
+ def message(instance)
20
+ super || "doesn't match confirmation"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Validatable
2
+ class ValidatesEach < ValidationBase #:nodoc:
3
+ required_option :logic
4
+
5
+ def valid?(instance)
6
+ instance.instance_eval(&logic)
7
+ true # return true so no error is added. should look in the future at doing this different.
8
+ end
9
+
10
+ def message(instance)
11
+ super || "is invalid"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesExclusionOf < ValidationBase #:nodoc:
3
+ required_option :within
4
+
5
+ def valid?(instance)
6
+ value = instance.send(attribute)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ !within.include?(value)
11
+ end
12
+
13
+ def message(instance)
14
+ super || "is reserved"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Validatable
2
+ class ValidatesFormatOf < ValidationBase #:nodoc:
3
+ required_option :with
4
+
5
+ def valid?(instance)
6
+ value = instance[self.attribute.to_s]
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+ not (value.to_s =~ self.with).nil?
10
+ end
11
+
12
+ def message(instance)
13
+ super || "is invalid"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesInclusionOf < ValidationBase
3
+ required_option :within
4
+
5
+ def valid?(instance)
6
+ value = instance.send(attribute)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ within.include?(value)
11
+ end
12
+
13
+ def message(instance)
14
+ super || "is not in the list"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Validatable
2
+ class ValidatesLengthOf < ValidationBase #:nodoc:
3
+ option :minimum, :maximum, :is, :within
4
+
5
+ def message(instance)
6
+ super || "is invalid"
7
+ end
8
+
9
+ def valid?(instance)
10
+ valid = true
11
+ value = instance[self.attribute.to_s]
12
+
13
+ if value.nil?
14
+ return true if allow_nil
15
+ value = ''
16
+ end
17
+
18
+ if value.blank?
19
+ return true if allow_blank
20
+ value = ''
21
+ end
22
+
23
+ valid &&= value.length <= maximum unless maximum.nil?
24
+ valid &&= value.length >= minimum unless minimum.nil?
25
+ valid &&= value.length == is unless is.nil?
26
+ valid &&= within.include?(value.length) unless within.nil?
27
+ valid
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Validatable
2
+ class ValidatesNumericalityOf < ValidationBase #:nodoc:
3
+ option :only_integer
4
+
5
+ def valid?(instance)
6
+ value = value_for(instance)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ value = value.to_s
11
+ regex = self.only_integer ? /\A[+-]?\d+\Z/ : /^\d*\.{0,1}\d+$/
12
+ not (value =~ regex).nil?
13
+ end
14
+
15
+ def message(instance)
16
+ super || "must be a number"
17
+ end
18
+
19
+ private
20
+ def value_for(instance)
21
+ before_typecast_method = "#{self.attribute}_before_typecast"
22
+ value_method = instance.respond_to?(before_typecast_method.intern) ? before_typecast_method : self.attribute
23
+ instance.send(value_method)
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesPresenceOf < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ value = instance[self.attribute.to_s]
5
+ return true if allow_nil && value.nil?
6
+ return true if allow_blank && value.blank?
7
+
8
+ return false if instance[self.attribute.to_s].nil?
9
+ value.respond_to?(:strip) ? instance[self.attribute.to_s].strip.length != 0 : true
10
+ end
11
+
12
+ def message(instance)
13
+ super || "can't be empty"
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,13 @@
1
+ module Validatable
2
+ class ValidatesTrueFor < ValidationBase #:nodoc:
3
+ required_option :logic
4
+
5
+ def valid?(instance)
6
+ instance.instance_eval(&logic) == true
7
+ end
8
+
9
+ def message(instance)
10
+ super || "is invalid"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,91 @@
1
+ module Validatable
2
+ class ValidationBase #:nodoc:
3
+ def self.required_option(*args)
4
+ option(*args)
5
+ requires(*args)
6
+ end
7
+
8
+ def self.option(*args)
9
+ attr_accessor(*args)
10
+ understands(*args)
11
+ end
12
+
13
+ def self.default(hash)
14
+ defaults.merge! hash
15
+ end
16
+
17
+ def self.defaults
18
+ @defaults ||= {}
19
+ end
20
+
21
+ def self.all_defaults
22
+ return defaults.merge(self.superclass.all_defaults) if self.superclass.respond_to? :all_defaults
23
+ defaults
24
+ end
25
+
26
+ def self.after_validate(&block)
27
+ after_validations << block
28
+ end
29
+
30
+ def self.after_validations
31
+ @after_validations ||= []
32
+ end
33
+
34
+ def self.all_after_validations
35
+ return after_validations + self.superclass.all_after_validations if self.superclass.respond_to? :all_after_validations
36
+ after_validations
37
+ end
38
+
39
+ include Understandable
40
+ include Requireable
41
+
42
+ option :message, :if, :times, :level, :groups, :key, :after_validate, :allow_nil, :allow_blank
43
+ default :level => 1, :groups => []
44
+ attr_accessor :attribute
45
+
46
+ def initialize(klass, attribute, options={})
47
+ must_understand options
48
+ requires options
49
+ self.class.all_understandings.each do |understanding|
50
+ options[understanding] = self.class.all_defaults[understanding] unless options.has_key? understanding
51
+ self.instance_variable_set("@#{understanding}", options[understanding])
52
+ end
53
+ self.attribute = attribute
54
+ self.groups = [self.groups] unless self.groups.is_a?(Array)
55
+ self.key = "#{klass.name}/#{self.class.name}/#{self.key || self.attribute}"
56
+ raise_error_if_key_is_dup(klass)
57
+ end
58
+
59
+ def raise_error_if_key_is_dup(klass)
60
+ message = "key #{self.key} must be unique, provide the :key option to specify a unique key"
61
+ raise ArgumentError.new(message) if klass.validation_keys_include? self.key
62
+ end
63
+
64
+ def should_validate?(instance)
65
+ result = validate_this_time?(instance)
66
+ case self.if
67
+ when Proc
68
+ result &&= instance.instance_eval(&self.if)
69
+ when Symbol, String
70
+ result &&= instance.instance_eval(self.if.to_s)
71
+ end
72
+ result
73
+ end
74
+
75
+ def message(instance)
76
+ @message.respond_to?(:call) ? instance.instance_eval(&@message) : @message
77
+ end
78
+
79
+ def validate_this_time?(instance)
80
+ return true if @times.nil?
81
+ self.times > instance.times_validated(self.key)
82
+ end
83
+
84
+ def run_after_validate(result, instance, attribute)
85
+ self.class.all_after_validations.each do |block|
86
+ block.call result, instance, attribute
87
+ end
88
+ instance.instance_eval_with_params result, attribute, &self.after_validate unless self.after_validate.nil?
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,29 @@
1
+ # gem "activesupport", "2.3.5"
2
+
3
+ require "forwardable"
4
+
5
+ require "#{File.dirname(__FILE__)}/validatable/object_extension"
6
+ require "#{File.dirname(__FILE__)}/validatable/errors"
7
+ require "#{File.dirname(__FILE__)}/validatable/validatable_class_methods"
8
+ require "#{File.dirname(__FILE__)}/validatable/macros"
9
+ require "#{File.dirname(__FILE__)}/validatable/validatable_instance_methods"
10
+ require "#{File.dirname(__FILE__)}/validatable/included_validation"
11
+ require "#{File.dirname(__FILE__)}/validatable/child_validation"
12
+ require "#{File.dirname(__FILE__)}/validatable/understandable"
13
+ require "#{File.dirname(__FILE__)}/validatable/requireable"
14
+ require "#{File.dirname(__FILE__)}/validatable/validations/validation_base"
15
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_format_of"
16
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_presence_of"
17
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_acceptance_of"
18
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_confirmation_of"
19
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_length_of"
20
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_true_for"
21
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_numericality_of"
22
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_exclusion_of"
23
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_inclusion_of"
24
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_each"
25
+ require "#{File.dirname(__FILE__)}/validatable/validations/validates_associated"
26
+
27
+ module Validatable
28
+ Version = "1.8.4"
29
+ end
@@ -7,7 +7,7 @@ class TestMongomatic < Test::Unit::TestCase
7
7
  p = Person.new
8
8
 
9
9
  assert !p.valid?
10
- assert_equal({:name=>["can't be blank"]}, p.errors)
10
+ assert_equal(["Name can't be empty"], p.errors.full_messages)
11
11
 
12
12
  p["name"] = "Ben Myles"
13
13
  p["birth_year"] = 1984
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ben Myles
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-07-05 00:00:00 -07:00
17
+ date: 2010-07-07 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -78,7 +78,7 @@ dependencies:
78
78
  type: :runtime
79
79
  version_requirements: *id004
80
80
  - !ruby/object:Gem::Dependency
81
- name: activemodel
81
+ name: activesupport
82
82
  prerelease: false
83
83
  requirement: &id005 !ruby/object:Gem::Requirement
84
84
  none: false
@@ -86,11 +86,10 @@ dependencies:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  segments:
89
+ - 2
89
90
  - 3
90
- - 0
91
- - 0
92
- - beta4
93
- version: 3.0.0.beta4
91
+ - 5
92
+ version: 2.3.5
94
93
  type: :runtime
95
94
  version_requirements: *id005
96
95
  description: Mongomatic is a simple Ruby object mapper for Mongo
@@ -107,6 +106,28 @@ files:
107
106
  - lib/mongomatic/base.rb
108
107
  - lib/mongomatic/cursor.rb
109
108
  - lib/mongomatic/modifiers.rb
109
+ - lib/validatable.rb
110
+ - lib/validatable/child_validation.rb
111
+ - lib/validatable/errors.rb
112
+ - lib/validatable/included_validation.rb
113
+ - lib/validatable/macros.rb
114
+ - lib/validatable/object_extension.rb
115
+ - lib/validatable/requireable.rb
116
+ - lib/validatable/understandable.rb
117
+ - lib/validatable/validatable_class_methods.rb
118
+ - lib/validatable/validatable_instance_methods.rb
119
+ - lib/validatable/validations/validates_acceptance_of.rb
120
+ - lib/validatable/validations/validates_associated.rb
121
+ - lib/validatable/validations/validates_confirmation_of.rb
122
+ - lib/validatable/validations/validates_each.rb
123
+ - lib/validatable/validations/validates_exclusion_of.rb
124
+ - lib/validatable/validations/validates_format_of.rb
125
+ - lib/validatable/validations/validates_inclusion_of.rb
126
+ - lib/validatable/validations/validates_length_of.rb
127
+ - lib/validatable/validations/validates_numericality_of.rb
128
+ - lib/validatable/validations/validates_presence_of.rb
129
+ - lib/validatable/validations/validates_true_for.rb
130
+ - lib/validatable/validations/validation_base.rb
110
131
  - LICENSE
111
132
  - README.rdoc
112
133
  - test/helper.rb