dm-validations 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +72 -0
- data/Rakefile +74 -0
- data/TODO +16 -0
- data/lib/dm-validations.rb +208 -0
- data/lib/dm-validations/absent_field_validator.rb +60 -0
- data/lib/dm-validations/acceptance_validator.rb +76 -0
- data/lib/dm-validations/auto_validate.rb +98 -0
- data/lib/dm-validations/confirmation_validator.rb +76 -0
- data/lib/dm-validations/contextual_validators.rb +56 -0
- data/lib/dm-validations/custom_validator.rb +72 -0
- data/lib/dm-validations/format_validator.rb +94 -0
- data/lib/dm-validations/formats/email.rb +40 -0
- data/lib/dm-validations/generic_validator.rb +92 -0
- data/lib/dm-validations/length_validator.rb +113 -0
- data/lib/dm-validations/method_validator.rb +58 -0
- data/lib/dm-validations/numeric_validator.rb +66 -0
- data/lib/dm-validations/primitive_validator.rb +60 -0
- data/lib/dm-validations/required_field_validator.rb +88 -0
- data/lib/dm-validations/support/object.rb +5 -0
- data/lib/dm-validations/uniqueness_validator.rb +61 -0
- data/lib/dm-validations/validation_errors.rb +63 -0
- data/lib/dm-validations/within_validator.rb +41 -0
- data/spec/integration/absent_field_validator_spec.rb +34 -0
- data/spec/integration/acceptance_validator_spec.rb +87 -0
- data/spec/integration/auto_validate_spec.rb +262 -0
- data/spec/integration/confirmation_validator_spec.rb +66 -0
- data/spec/integration/contextual_validators_spec.rb +28 -0
- data/spec/integration/custom_validator_spec.rb +9 -0
- data/spec/integration/format_validator_spec.rb +118 -0
- data/spec/integration/generic_validator_spec.rb +9 -0
- data/spec/integration/length_validator_spec.rb +113 -0
- data/spec/integration/method_validator_spec.rb +31 -0
- data/spec/integration/numeric_validator_spec.rb +192 -0
- data/spec/integration/primitive_validator_spec.rb +25 -0
- data/spec/integration/required_field_validator_spec.rb +93 -0
- data/spec/integration/uniqueness_validator_spec.rb +81 -0
- data/spec/integration/validation_errors_spec.rb +18 -0
- data/spec/integration/validation_spec.rb +339 -0
- data/spec/integration/within_validator_spec.rb +35 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +26 -0
- metadata +104 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Guy van den Berg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
This is a DataMapper plugin that provides validations for DataMapper model
|
2
|
+
classes.
|
3
|
+
|
4
|
+
== Setup
|
5
|
+
DataMapper validation capabilities are automatically available when you
|
6
|
+
'require dm-validations' into your application.
|
7
|
+
|
8
|
+
More specifically, DataMapper::Validate is automatically included into
|
9
|
+
DataMapper::Resource when you require dm-validations.
|
10
|
+
|
11
|
+
== Specifying Model Validations
|
12
|
+
There are two primary ways to implement validations for your models:
|
13
|
+
|
14
|
+
1) Placing validation methods with properties as params in your class
|
15
|
+
definitions like:
|
16
|
+
- validates_length :name
|
17
|
+
- validates_length [:name, :description]
|
18
|
+
|
19
|
+
2) Using auto-validations, please see DataMapper::Validate::AutoValidate
|
20
|
+
|
21
|
+
An example class with validations declared:
|
22
|
+
|
23
|
+
require 'dm-validations'
|
24
|
+
|
25
|
+
class Account
|
26
|
+
include DataMapper::Resource
|
27
|
+
|
28
|
+
property :name, String
|
29
|
+
validates_length :name
|
30
|
+
end
|
31
|
+
|
32
|
+
See all of the DataMapper::Validate module's XYZValidator(s) to learn about the
|
33
|
+
complete collections of validators available to you.
|
34
|
+
|
35
|
+
== Validating
|
36
|
+
DataMapper validations, when included, alter the default save/create/update
|
37
|
+
process for a model. Unless you specify a context the resource must be
|
38
|
+
valid in the :default context before saving.
|
39
|
+
|
40
|
+
You may manually validate a resource using the valid? method, which will
|
41
|
+
return true if the resource is valid, and false if it is invalid.
|
42
|
+
|
43
|
+
In addition to the valid? method, there is also an all_valid? method that
|
44
|
+
recursively walks both the current object and its associated objects and returns
|
45
|
+
its true/false result for the entire walk.
|
46
|
+
|
47
|
+
== Working with Validation Errors
|
48
|
+
If your validators find errors in your model, they will populate the
|
49
|
+
DataMapper::Validate::ValidationErrors object that is available through each of
|
50
|
+
your models via calls to your model's errors method.
|
51
|
+
|
52
|
+
For example:
|
53
|
+
|
54
|
+
my_account = Account.new(:name => "Jose")
|
55
|
+
if my_account.save
|
56
|
+
# my_account is valid and has been saved
|
57
|
+
else
|
58
|
+
my_account.errors.each do |e|
|
59
|
+
puts e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
See DataMapper::Validate::ValidationErrors for all you can do with your model's
|
64
|
+
errors method.
|
65
|
+
|
66
|
+
== Contextual Validations
|
67
|
+
|
68
|
+
DataMapper Validations also provide a means of grouping your validations into
|
69
|
+
contexts. This enables you to run different sets of validations under ...
|
70
|
+
different contexts.
|
71
|
+
|
72
|
+
TO BE ADDED... For now, see
|
data/Rakefile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
require 'pathname'
|
8
|
+
|
9
|
+
CLEAN.include '{log,pkg}/'
|
10
|
+
|
11
|
+
desc "Generate Documentation"
|
12
|
+
rd = Rake::RDocTask.new do |rdoc|
|
13
|
+
rdoc.rdoc_dir = 'doc'
|
14
|
+
rdoc.title = "DataMapper Validations"
|
15
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
16
|
+
rdoc.rdoc_files.include(FileList[ 'lib/**/*.rb', 'README', 'LICENSE'])
|
17
|
+
end
|
18
|
+
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
s.name = 'dm-validations'
|
21
|
+
s.version = '0.9.2'
|
22
|
+
s.platform = Gem::Platform::RUBY
|
23
|
+
s.has_rdoc = true
|
24
|
+
s.extra_rdoc_files = %w[ README LICENSE TODO ]
|
25
|
+
s.summary = 'DataMapper plugin for performing validations on data models'
|
26
|
+
s.description = s.summary
|
27
|
+
s.author = 'Guy van den Berg'
|
28
|
+
s.email = 'vandenberg.guy@gmail.com'
|
29
|
+
s.homepage = 'http://github.com/sam/dm-more/tree/master/dm-validations'
|
30
|
+
s.require_path = 'lib'
|
31
|
+
s.files = FileList[ '{lib,spec}/**/*.rb', 'spec/spec.opts', 'Rakefile', *s.extra_rdoc_files ]
|
32
|
+
s.add_dependency('dm-core', "=#{s.version}")
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => [ :spec ]
|
36
|
+
|
37
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
38
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
39
|
+
|
40
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
41
|
+
pkg.gem_spec = spec
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Install #{spec.name} #{spec.version} (default ruby)"
|
45
|
+
task :install => [ :package ] do
|
46
|
+
sh "#{SUDO} gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Uninstall #{spec.name} #{spec.version} (default ruby)"
|
50
|
+
task :uninstall => [ :clobber ] do
|
51
|
+
sh "#{SUDO} gem uninstall #{spec.name} -v#{spec.version} -I -x", :verbose => false
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace :jruby do
|
55
|
+
desc "Install #{spec.name} #{spec.version} with JRuby"
|
56
|
+
task :install => [ :package ] do
|
57
|
+
sh %{#{SUDO} jruby -S gem install --local pkg/#{spec.name}-#{spec.version} --no-update-sources}, :verbose => false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'Run specifications'
|
62
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
63
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
64
|
+
t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
65
|
+
|
66
|
+
begin
|
67
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
68
|
+
t.rcov_opts << '--exclude' << 'spec'
|
69
|
+
t.rcov_opts << '--text-summary'
|
70
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
71
|
+
rescue Exception
|
72
|
+
# rcov not installed
|
73
|
+
end
|
74
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Short TODO list
|
2
|
+
===============
|
3
|
+
|
4
|
+
* Add logic_validator -- validates(“You need more money!”) { |instance| instance.money < 1_000_000 }
|
5
|
+
* Allow for the translation of strings in messages
|
6
|
+
* Add the canned RFC2822 email format to format_validator
|
7
|
+
* Add a validates_acceptance_of validator
|
8
|
+
* Add a validates_each validator
|
9
|
+
* Add a validates_true validator
|
10
|
+
|
11
|
+
* Validators do not accept associations except validates_uniqueness_of - fix this
|
12
|
+
* Add auto validation on cardinality of associations
|
13
|
+
|
14
|
+
---
|
15
|
+
TODO tickets may also be found in the DataMapper Issue Tracker:
|
16
|
+
http://wm.lighthouseapp.com/projects/4819-datamapper/overview
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
gem 'dm-core', '=0.9.2'
|
5
|
+
require 'dm-core'
|
6
|
+
|
7
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'dm-validations'
|
8
|
+
|
9
|
+
require dir / 'validation_errors'
|
10
|
+
require dir / 'contextual_validators'
|
11
|
+
require dir / 'auto_validate'
|
12
|
+
|
13
|
+
require dir / 'generic_validator'
|
14
|
+
require dir / 'required_field_validator'
|
15
|
+
require dir / 'primitive_validator'
|
16
|
+
require dir / 'absent_field_validator'
|
17
|
+
require dir / 'confirmation_validator'
|
18
|
+
require dir / 'format_validator'
|
19
|
+
require dir / 'length_validator'
|
20
|
+
require dir / 'within_validator'
|
21
|
+
require dir / 'numeric_validator'
|
22
|
+
require dir / 'method_validator'
|
23
|
+
require dir / 'uniqueness_validator'
|
24
|
+
require dir / 'acceptance_validator'
|
25
|
+
require dir / 'custom_validator'
|
26
|
+
|
27
|
+
require dir / 'support' / 'object'
|
28
|
+
|
29
|
+
module DataMapper
|
30
|
+
module Validate
|
31
|
+
|
32
|
+
def self.included(model)
|
33
|
+
if model.method_defined?(:save) && !model.method_defined?(:save!)
|
34
|
+
model.send(:alias_method, :save!, :save)
|
35
|
+
model.send(:alias_method, :save, :save_with_validations)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validate the resource before saving. Use #save! to save
|
40
|
+
# the record without validations.
|
41
|
+
#
|
42
|
+
def save_with_validations(context = :default)
|
43
|
+
return false unless valid?(context)
|
44
|
+
save!
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the ValidationErrors
|
48
|
+
#
|
49
|
+
def errors
|
50
|
+
@errors ||= ValidationErrors.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Mark this resource as validatable. When we validate associations of a
|
54
|
+
# resource we can check if they respond to validatable? before trying to
|
55
|
+
# recursivly validate them
|
56
|
+
#
|
57
|
+
def validatable?()
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
# Alias for valid?(:default)
|
62
|
+
#
|
63
|
+
def valid_for_default?
|
64
|
+
valid?(:default)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if a resource is valid in a given context
|
68
|
+
#
|
69
|
+
def valid?(context = :default)
|
70
|
+
self.class.validators.execute(context,self)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Begin a recursive walk of the model checking validity
|
74
|
+
#
|
75
|
+
def all_valid?(context = :default)
|
76
|
+
recursive_valid?(self,context,true)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Do recursive validity checking
|
80
|
+
#
|
81
|
+
def recursive_valid?(target, context, state)
|
82
|
+
valid = state
|
83
|
+
target.instance_variables.each do |ivar|
|
84
|
+
ivar_value = target.instance_variable_get(ivar)
|
85
|
+
if ivar_value.validatable?
|
86
|
+
valid = valid && recursive_valid?(ivar_value,context,valid)
|
87
|
+
elsif ivar_value.respond_to?(:each)
|
88
|
+
ivar_value.each do |item|
|
89
|
+
if item.validatable?
|
90
|
+
valid = valid && recursive_valid?(item,context,valid)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
return valid && target.valid?
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def validation_property_value(name)
|
100
|
+
return self.instance_variable_get("@#{name}") if self.instance_variables.include?(name)
|
101
|
+
return self.send(name) if self.respond_to?(name)
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the corresponding Resource property, if it exists.
|
106
|
+
#
|
107
|
+
# Note: DataMapper validations can be used on non-DataMapper resources.
|
108
|
+
# In such cases, the return value will be nil.
|
109
|
+
def validation_property(field_name)
|
110
|
+
if DataMapper::Resource > self.class
|
111
|
+
self.class.properties(self.repository.name)[field_name]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def validation_association_keys(name)
|
116
|
+
if self.class.relationships.has_key?(name)
|
117
|
+
result = []
|
118
|
+
relation = self.class.relationships[name]
|
119
|
+
relation.child_key.each do |key|
|
120
|
+
result << key.name
|
121
|
+
end
|
122
|
+
return result
|
123
|
+
end
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
module ClassMethods
|
128
|
+
include DataMapper::Validate::ValidatesPresent
|
129
|
+
include DataMapper::Validate::ValidatesAbsent
|
130
|
+
include DataMapper::Validate::ValidatesIsConfirmed
|
131
|
+
include DataMapper::Validate::ValidatesIsPrimitive
|
132
|
+
include DataMapper::Validate::ValidatesIsAccepted
|
133
|
+
include DataMapper::Validate::ValidatesFormat
|
134
|
+
include DataMapper::Validate::ValidatesLength
|
135
|
+
include DataMapper::Validate::ValidatesWithin
|
136
|
+
include DataMapper::Validate::ValidatesIsNumber
|
137
|
+
include DataMapper::Validate::ValidatesWithMethod
|
138
|
+
include DataMapper::Validate::ValidatesIsUnique
|
139
|
+
include DataMapper::Validate::AutoValidate
|
140
|
+
|
141
|
+
# Return the set of contextual validators or create a new one
|
142
|
+
#
|
143
|
+
def validators
|
144
|
+
@validations ||= ContextualValidators.new
|
145
|
+
end
|
146
|
+
|
147
|
+
# Clean up the argument list and return a opts hash, including the
|
148
|
+
# merging of any default opts. Set the context to default if none is
|
149
|
+
# provided. Also allow :context to be aliased to :on, :when & group
|
150
|
+
#
|
151
|
+
def opts_from_validator_args(args, defaults = nil)
|
152
|
+
opts = args.last.kind_of?(Hash) ? args.pop : {}
|
153
|
+
context = :default
|
154
|
+
context = opts[:context] if opts.has_key?(:context)
|
155
|
+
context = opts.delete(:on) if opts.has_key?(:on)
|
156
|
+
context = opts.delete(:when) if opts.has_key?(:when)
|
157
|
+
context = opts.delete(:group) if opts.has_key?(:group)
|
158
|
+
opts[:context] = context
|
159
|
+
opts.mergs!(defaults) unless defaults.nil?
|
160
|
+
opts
|
161
|
+
end
|
162
|
+
|
163
|
+
# Given a new context create an instance method of
|
164
|
+
# valid_for_<context>? which simply calls valid?(context)
|
165
|
+
# if it does not already exist
|
166
|
+
#
|
167
|
+
def create_context_instance_methods(context)
|
168
|
+
name = "valid_for_#{context.to_s}?"
|
169
|
+
if !self.instance_methods.include?(name)
|
170
|
+
class_eval <<-EOS
|
171
|
+
def #{name}
|
172
|
+
valid?('#{context.to_s}'.to_sym)
|
173
|
+
end
|
174
|
+
EOS
|
175
|
+
end
|
176
|
+
|
177
|
+
all = "all_valid_for_#{context.to_s}?"
|
178
|
+
if !self.instance_methods.include?(all)
|
179
|
+
class_eval <<-EOS
|
180
|
+
def #{all}
|
181
|
+
all_valid?('#{context.to_s}'.to_sym)
|
182
|
+
end
|
183
|
+
EOS
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Create a new validator of the given klazz and push it onto the
|
188
|
+
# requested context for each of the attributes in the fields list
|
189
|
+
#
|
190
|
+
def add_validator_to_context(opts, fields, klazz)
|
191
|
+
fields.each do |field|
|
192
|
+
if opts[:context].is_a?(Symbol)
|
193
|
+
validators.context(opts[:context]) << klazz.new(field, opts)
|
194
|
+
create_context_instance_methods(opts[:context])
|
195
|
+
elsif opts[:context].is_a?(Array)
|
196
|
+
opts[:context].each do |c|
|
197
|
+
validators.context(c) << klazz.new(field, opts)
|
198
|
+
create_context_instance_methods(c)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end # module ClassMethods
|
204
|
+
end # module Validate
|
205
|
+
|
206
|
+
Resource.append_inclusions Validate
|
207
|
+
Model.append_extensions Validate::ClassMethods
|
208
|
+
end # module DataMapper
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Validate
|
3
|
+
|
4
|
+
##
|
5
|
+
#
|
6
|
+
# @author Guy van den Berg
|
7
|
+
# @since 0.9
|
8
|
+
class AbsentFieldValidator < GenericValidator
|
9
|
+
|
10
|
+
def initialize(field_name, options={})
|
11
|
+
super
|
12
|
+
@field_name, @options = field_name, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(target)
|
16
|
+
field_value = target.send(field_name).blank?
|
17
|
+
return true if field_value
|
18
|
+
|
19
|
+
error_message = @options[:message] || "%s must be absent".t(Extlib::Inflection.humanize(@field_name))
|
20
|
+
add_error(target, error_message , @field_name)
|
21
|
+
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
end # class AbsentFieldValidator
|
25
|
+
|
26
|
+
module ValidatesAbsent
|
27
|
+
|
28
|
+
##
|
29
|
+
# Validates that the specified attribute is "blank" via the attribute's
|
30
|
+
# #blank? method.
|
31
|
+
#
|
32
|
+
# @note
|
33
|
+
# dm-core's support lib adds the #blank? method to many classes,
|
34
|
+
# @see lib/dm-core/support/blank.rb (dm-core) for more information.
|
35
|
+
#
|
36
|
+
# @example [Usage]
|
37
|
+
# require 'dm-validations'
|
38
|
+
#
|
39
|
+
# class Page
|
40
|
+
# include DataMapper::Resource
|
41
|
+
#
|
42
|
+
# property :unwanted_attribute, String
|
43
|
+
# property :another_unwanted, String
|
44
|
+
# property :yet_again, String
|
45
|
+
#
|
46
|
+
# validates_absent :unwanted_attribute
|
47
|
+
# validates_absent :another_unwanted, :yet_again
|
48
|
+
#
|
49
|
+
# # a call to valid? will return false unless
|
50
|
+
# # all three attributes are blank
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
def validates_absent(*fields)
|
54
|
+
opts = opts_from_validator_args(fields)
|
55
|
+
add_validator_to_context(opts, fields, DataMapper::Validate::AbsentFieldValidator)
|
56
|
+
end
|
57
|
+
|
58
|
+
end # module ValidatesAbsent
|
59
|
+
end # module Validate
|
60
|
+
end # module DataMapper
|