modelish 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.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.rvmrc +3 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +95 -0
- data/Rakefile +17 -0
- data/lib/modelish.rb +8 -0
- data/lib/modelish/base.rb +44 -0
- data/lib/modelish/property_types.rb +133 -0
- data/lib/modelish/validations.rb +208 -0
- data/lib/modelish/version.rb +3 -0
- data/modelish.gemspec +30 -0
- data/spec/modelish/base_spec.rb +285 -0
- data/spec/modelish/property_types_spec.rb +128 -0
- data/spec/modelish/validations_spec.rb +695 -0
- data/spec/modelish_spec.rb +7 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/property_examples.rb +38 -0
- data/spec/support/typed_property_examples.rb +90 -0
- data/spec/support/validation_examples.rb +70 -0
- metadata +161 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by Maeve Revels
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# modelish #
|
2
|
+
|
3
|
+
When a real modeling framework is too heavy, sometimes you want something just
|
4
|
+
a little modelish.
|
5
|
+
|
6
|
+
If a Hash or OpenStruct almost suits your needs, but you need bits of
|
7
|
+
model-like behavior such as simple validation and typed values, modelish can
|
8
|
+
help.
|
9
|
+
|
10
|
+
If you need persistence or anything but the most basic functionality, modelish
|
11
|
+
will frustrate you to no end.
|
12
|
+
|
13
|
+
## Installation ##
|
14
|
+
|
15
|
+
For the especially foolhardy, you can:
|
16
|
+
|
17
|
+
1. Add this to your Gemfile:
|
18
|
+
|
19
|
+
gem 'modelish', :git => 'git://github.com/maeve/modelish.git'
|
20
|
+
|
21
|
+
2. Execute `bundle install`
|
22
|
+
|
23
|
+
## Basics ##
|
24
|
+
|
25
|
+
The modelish syntax is very similar to some of the classes provided by
|
26
|
+
[hashie]. In fact, the initial implementation simply extended
|
27
|
+
[Hashie::Trash][trash] to add property types:
|
28
|
+
|
29
|
+
require 'modelish'
|
30
|
+
|
31
|
+
class Foo < Modelish::Base
|
32
|
+
property :my_date, :type => Date
|
33
|
+
property :my_float, :type => Float, :default => 0.0
|
34
|
+
property :my_funky_id, :type => Integer, :from => 'MyFUNKYId'
|
35
|
+
property :my_simple_property
|
36
|
+
end
|
37
|
+
|
38
|
+
You can then set properties as string value and have them automatically
|
39
|
+
converted to the appropriate type, while respecting default values and
|
40
|
+
key-mappings:
|
41
|
+
|
42
|
+
f = Foo.new('MyFUNKYId' => '42',
|
43
|
+
:my_date => '2011-03-10',
|
44
|
+
:my_simple_property => 'bar')
|
45
|
+
|
46
|
+
f.my_date
|
47
|
+
=> #<Date: 2011-03-10 (4911261/2,0,2299161)>
|
48
|
+
f.my_float
|
49
|
+
=> 0.0
|
50
|
+
f.my_funky_id
|
51
|
+
=> 42
|
52
|
+
f.my_simple_property
|
53
|
+
=> "bar"
|
54
|
+
|
55
|
+
modelish also supports defining simple property validations:
|
56
|
+
|
57
|
+
class Bar < Modelish::Base
|
58
|
+
property :important_field, :required => true
|
59
|
+
property :state, :max_length => 2
|
60
|
+
property :my_int, :type => Integer, :validate_type => true
|
61
|
+
property :another_field, :validator => lambda { |val| "val must respond to []" unless val.respond_to?(:[]) }
|
62
|
+
end
|
63
|
+
|
64
|
+
Validations can be run using methods that return an error map (keyed on property name), raise errors, or return a boolean value to indicate validation outcome.
|
65
|
+
|
66
|
+
valid_bar = Bar.new(:important_field => 'some value',
|
67
|
+
:state => 'OR',
|
68
|
+
:my_int => 42,
|
69
|
+
:another_field => Hash.new)
|
70
|
+
valid_bar.valid?
|
71
|
+
=> true
|
72
|
+
|
73
|
+
valid_bar.validate
|
74
|
+
=> {}
|
75
|
+
|
76
|
+
valid_bar.validate!
|
77
|
+
=> nil
|
78
|
+
|
79
|
+
|
80
|
+
invalid_bar = Bar.new(:state => 'a value that is too long',
|
81
|
+
:my_int => 'this is not an integer',
|
82
|
+
:another_field => Object.new)
|
83
|
+
invalid_bar.valid?
|
84
|
+
=> false
|
85
|
+
|
86
|
+
invalid_bar.validate
|
87
|
+
=> {:important_field=>[#<ArgumentError: important_field must not be nil or blank>], :my_int=>[#<ArgumentError: my_int must be of type Integer, but got "this is not an integer">], :another_field=>[#<ArgumentError: val must respond to []>], :state=>[#<ArgumentError: state must be less than 2 characters>]}
|
88
|
+
|
89
|
+
invalid_bar.validate!
|
90
|
+
ArgumentError: important_field must not be nil or blank
|
91
|
+
from /Users/maeverevels/projects/modelish/lib/modelish/validations.rb:31:in `validate!'
|
92
|
+
...
|
93
|
+
|
94
|
+
[hashie]: https://github.com/intridea/hashie
|
95
|
+
[trash]: http://rdoc.info/github/intridea/hashie/master/Hashie/Trash
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
namespace :doc do
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new do |task|
|
12
|
+
task.files = ['README.md', 'lib/**/*.rb']
|
13
|
+
task.options = [
|
14
|
+
'--markup', 'markdown',
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
data/lib/modelish.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'property_types'
|
3
|
+
require 'validations'
|
4
|
+
|
5
|
+
module Modelish
|
6
|
+
class Base < Hashie::Trash
|
7
|
+
include PropertyTypes
|
8
|
+
include Validations
|
9
|
+
|
10
|
+
# Creates a new attribute.
|
11
|
+
#
|
12
|
+
# @param [Symbol] name the name of the property
|
13
|
+
# @param [Hash] options configuration for the property
|
14
|
+
# @option opts [Object] :default the default value for this property
|
15
|
+
# when the value has not been explicitly
|
16
|
+
# set (defaults to nil)
|
17
|
+
# @option opts [#to_s] :from the original key name for this attribute
|
18
|
+
# (created as write-only)
|
19
|
+
# @option opts [Class,Proc] :type the type of the property value. For
|
20
|
+
# a list of accepted types, see
|
21
|
+
# {Modelish::PropertyTypes}
|
22
|
+
# @options opts [true,false] :required enables validation for the property
|
23
|
+
# value's presence; nil or blank values
|
24
|
+
# will cause validation methods to fail
|
25
|
+
# @options opts [Integer] :max_length the maximum allowable length for a valid
|
26
|
+
# property value
|
27
|
+
# @options opts [true,false] :validate_type enables validation for the property value's
|
28
|
+
# type based on the :type option
|
29
|
+
# @options opts [Proc] :validator A block that accepts a value and validates it;
|
30
|
+
# should return nil if validation passes, or an error
|
31
|
+
# message or error object if validation fails.
|
32
|
+
# See {Modelish::Validations}
|
33
|
+
def self.property(name, options={})
|
34
|
+
super
|
35
|
+
|
36
|
+
add_property_type(name, options[:type]) if options[:type]
|
37
|
+
|
38
|
+
add_validator(name) { |val| validate_required(name => val).first } if options[:required]
|
39
|
+
add_validator(name) { |val| validate_length(name, val, options[:max_length]) } if options[:max_length]
|
40
|
+
add_validator(name, &options[:validator]) if options[:validator]
|
41
|
+
add_validator(name) { |val| validate_type(name, val, options[:type]) } if options[:validate_type]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Modelish
|
4
|
+
# Mixes in behavior for automatically converting property types.
|
5
|
+
module PropertyTypes
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Adds a typed property to the model.
|
12
|
+
# This dynamically generates accessor/mutator methods that perform
|
13
|
+
# the appropriate type conversions on the property's value.
|
14
|
+
#
|
15
|
+
# Generated methods:
|
16
|
+
# * +<property_name>=(new_value)+ -- sets the property value.
|
17
|
+
# * +<property_name>+ -- returns the property value, converted to the configured
|
18
|
+
# type. If the value cannot be converted, no error will be
|
19
|
+
# raised, and the raw unconverted value will be returned.
|
20
|
+
# * +<property_name>!+ -- returns the property value, converted to the configured
|
21
|
+
# type. If the value cannot be converted, a TypeError will
|
22
|
+
# be raised.
|
23
|
+
# * +raw_<property_name> -- the original property value, without any type conversions.
|
24
|
+
#
|
25
|
+
# @param [Symbol] property_name the name of the property.
|
26
|
+
# @param [Class, Proc] property_type the type of the property's value.
|
27
|
+
# Valid types include:
|
28
|
+
# * +Integer+
|
29
|
+
# * +Float+
|
30
|
+
# * +Array+
|
31
|
+
# * +Date+ -- converts using Date.parse on value.to_s
|
32
|
+
# * +Symbol+ -- also converts from camel case to snake case
|
33
|
+
# * +String+
|
34
|
+
# * any arbitrary +Class+ -- will attempt conversion by passing the raw
|
35
|
+
# value into the class's initializer
|
36
|
+
# * an instance of +Proc+ -- will convert the value by executing the proc,
|
37
|
+
# passing in the raw value as an argument
|
38
|
+
def add_property_type(property_name, property_type=String)
|
39
|
+
accessor = property_name.to_sym
|
40
|
+
property_types[accessor] = property_type
|
41
|
+
|
42
|
+
raw_accessor = define_raw_accessor(accessor)
|
43
|
+
bang_accessor = define_bang_accessor(accessor)
|
44
|
+
|
45
|
+
typed_accessor = "_typed_#{accessor}".to_sym
|
46
|
+
typed_mutator = "#{typed_accessor}=".to_sym
|
47
|
+
to_safe = "_to_safe_#{accessor}".to_sym
|
48
|
+
|
49
|
+
class_eval do
|
50
|
+
attr_accessor typed_accessor
|
51
|
+
private typed_accessor, typed_mutator
|
52
|
+
|
53
|
+
define_method(to_safe) do
|
54
|
+
self.send(bang_accessor) rescue self.send(raw_accessor)
|
55
|
+
end
|
56
|
+
private to_safe
|
57
|
+
|
58
|
+
define_method(accessor) do
|
59
|
+
val = self.send(typed_accessor)
|
60
|
+
|
61
|
+
unless val || self.send(raw_accessor).nil?
|
62
|
+
val = self.send(to_safe)
|
63
|
+
self.send(typed_mutator, val)
|
64
|
+
end
|
65
|
+
|
66
|
+
val
|
67
|
+
end
|
68
|
+
|
69
|
+
define_method("#{accessor}=") do |val|
|
70
|
+
self.send("#{raw_accessor}=", val)
|
71
|
+
self.send(typed_mutator, self.send(to_safe))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def property_types
|
77
|
+
@property_types ||= {}
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def define_raw_accessor(name)
|
82
|
+
accessor = name.to_sym
|
83
|
+
raw_accessor = "raw_#{name}".to_sym
|
84
|
+
|
85
|
+
mutator = "#{name}=".to_sym
|
86
|
+
raw_mutator = "raw_#{name}=".to_sym
|
87
|
+
|
88
|
+
class_eval do
|
89
|
+
if method_defined?(accessor) && method_defined?(mutator)
|
90
|
+
alias_method(raw_accessor, accessor)
|
91
|
+
alias_method(raw_mutator, mutator)
|
92
|
+
else
|
93
|
+
attr_accessor raw_accessor
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
raw_accessor
|
98
|
+
end
|
99
|
+
|
100
|
+
def define_bang_accessor(property_name)
|
101
|
+
bang_accessor = "#{property_name}!".to_sym
|
102
|
+
converter = value_converter(property_types[property_name.to_sym])
|
103
|
+
|
104
|
+
class_eval do
|
105
|
+
define_method(bang_accessor) do
|
106
|
+
value = self.send("raw_#{property_name}")
|
107
|
+
(converter && value) ? converter.call(value) : value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
bang_accessor
|
112
|
+
end
|
113
|
+
|
114
|
+
def value_converter(property_type)
|
115
|
+
if property_type == Date
|
116
|
+
lambda { |val| Date.parse(val.to_s) }
|
117
|
+
elsif property_type == Symbol
|
118
|
+
lambda { |val| val.to_s.strip.gsub(/([A-Z]+)([A-Z][a-z])/, '\\1_\\2').
|
119
|
+
gsub(/([a-z\d])([A-Z])/, '\\1_\\2').
|
120
|
+
gsub(/\s+|-/,'_').downcase.to_sym }
|
121
|
+
elsif property_type == String
|
122
|
+
lambda { |val| val.to_s.strip }
|
123
|
+
elsif Kernel.respond_to?(property_type.to_s)
|
124
|
+
lambda { |val| Kernel.send(property_type.to_s, val) }
|
125
|
+
elsif property_type.respond_to?(:call)
|
126
|
+
property_type
|
127
|
+
else
|
128
|
+
lambda { |val| property_type.new(val) }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Modelish
|
4
|
+
module Validations
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Validates all properties based on configured validators.
|
10
|
+
#
|
11
|
+
# @return [Hash<Symbol,Array>] map of errors where key is the property name
|
12
|
+
# and value is the list of errors
|
13
|
+
# @see ClassMethods#add_validator
|
14
|
+
def validate
|
15
|
+
errors = {}
|
16
|
+
|
17
|
+
call_validators do |name,message|
|
18
|
+
errors[name] ||= []
|
19
|
+
errors[name] << to_error(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
errors
|
23
|
+
end
|
24
|
+
|
25
|
+
# Validates all properties based on configured validators.
|
26
|
+
#
|
27
|
+
# @raise ArgumentError when any property fails validation
|
28
|
+
def validate!
|
29
|
+
call_validators do |name,message|
|
30
|
+
error = to_error(message)
|
31
|
+
raise error if error
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid?
|
37
|
+
validate.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def call_validators(&error_handler)
|
42
|
+
self.class.validators.each_pair do |k,v|
|
43
|
+
v.each do |prop_validator|
|
44
|
+
if msg = prop_validator.call(self.send(k))
|
45
|
+
error_handler.call(k, msg)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_error(msg)
|
52
|
+
msg.is_a?(StandardError) ? msg : ArgumentError.new(msg.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
# Sets up a block containing validation logic for a given property.
|
57
|
+
# Each property may have 0 or more validators.
|
58
|
+
#
|
59
|
+
# @param [String,Symbol] property_name the name of the property to validate
|
60
|
+
# @param [#call] validator the block containing the validation logic; must return
|
61
|
+
# either an error object or a string containing the error
|
62
|
+
# message if validation fails.
|
63
|
+
#
|
64
|
+
# @example adding a validator that only allows non-nil values
|
65
|
+
# class MyModel
|
66
|
+
# include Modelish::Validations
|
67
|
+
# attr_accessor :foo
|
68
|
+
# add_validator(:foo) { |val| val.nil? ? 'foo must not be nil': nil }
|
69
|
+
# end
|
70
|
+
def add_validator(property_name, &validator)
|
71
|
+
validators[property_name.to_sym] ||= []
|
72
|
+
validators[property_name.to_sym] << validator
|
73
|
+
|
74
|
+
class_eval do
|
75
|
+
attr_accessor property_name.to_sym unless method_defined?(property_name.to_sym)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# A map of registered validator blocks, keyed on property_name.
|
80
|
+
def validators
|
81
|
+
@validators ||= {}
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validates the required values, returning a list of errors when validation
|
85
|
+
# fails.
|
86
|
+
#
|
87
|
+
# @param [Hash] args the map of name => value pairs to validate
|
88
|
+
# @return [Array<ArgumentError>] a list of ArgumentErrors for validation failures.
|
89
|
+
def validate_required(args)
|
90
|
+
errors = []
|
91
|
+
args.each do |name, value|
|
92
|
+
errors << ArgumentError.new("#{name} must not be nil or blank") if value.nil? || value.to_s.strip.empty?
|
93
|
+
end
|
94
|
+
errors
|
95
|
+
end
|
96
|
+
|
97
|
+
# Validates the required values, raising an error when validation fails.
|
98
|
+
#
|
99
|
+
# @param [Hash] args the map of name => value pairs to validate
|
100
|
+
# @raise [ArgumentError] when any value in args hash is nil or empty. The name
|
101
|
+
# key will be used to construct an informative error message.
|
102
|
+
def validate_required!(args)
|
103
|
+
errors = validate_required(args)
|
104
|
+
raise errors.first unless errors.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Validates the required values, returning a boolean indicating validation success.
|
108
|
+
#
|
109
|
+
# @param [Hash] args the map of name => value pairs to validate
|
110
|
+
# @return [true,false] true when validation passes; false when validation fails
|
111
|
+
def validate_required?(args)
|
112
|
+
validate_required(args).empty?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Validates the length of a value, raising an error to indicate validation failure.
|
116
|
+
#
|
117
|
+
# @param (see .validate_length)
|
118
|
+
# @raise [ArgumentError] when the value is longer than max_length
|
119
|
+
def validate_length!(name, value, max_length)
|
120
|
+
error = validate_length(name, value, max_length)
|
121
|
+
raise error if error
|
122
|
+
end
|
123
|
+
|
124
|
+
# Validates the length of a value, returning a boolean to indicate validation
|
125
|
+
# success.
|
126
|
+
#
|
127
|
+
# @param (see .validate_length)
|
128
|
+
# @return [true,false] true if value does not exceed max_length; false otherwise
|
129
|
+
def validate_length?(name, value, max_length)
|
130
|
+
validate_length(name, value, max_length).nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
# Validates the length of a value, returning an error when validation fails.
|
134
|
+
#
|
135
|
+
# @param [Symbol,String] name the name of the property/argument to be validated
|
136
|
+
# @param [#length] value the value to be validated
|
137
|
+
# @param [#to_i] max_length the maximum allowable length
|
138
|
+
# @raise [ArgumentError] when the value is longer than max_length
|
139
|
+
def validate_length(name, value, max_length)
|
140
|
+
if max_length.to_i > 0 && value.to_s.length > max_length.to_i
|
141
|
+
ArgumentError.new("#{name} must be less than #{max_length} characters")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Validates the type of the value, returning a boolean indicating validation
|
146
|
+
# outcome.
|
147
|
+
#
|
148
|
+
# @see #validate_type
|
149
|
+
# @param {see #validate_type}
|
150
|
+
# @return [true,false] true when the value is the correct type; false otherwise
|
151
|
+
def validate_type?(name, value, type)
|
152
|
+
validate_type(name, value, type).nil?
|
153
|
+
end
|
154
|
+
|
155
|
+
# Validates the type of the value, raising an error when the value is not
|
156
|
+
# of the correct type.
|
157
|
+
#
|
158
|
+
# @see #validate_type
|
159
|
+
# @param {see #validate_type}
|
160
|
+
# @raise [ArgumentError] when the value is not the correct type
|
161
|
+
def validate_type!(name, value, type)
|
162
|
+
error = validate_type(name, value, type)
|
163
|
+
raise error if error
|
164
|
+
end
|
165
|
+
|
166
|
+
# Validates the type of the value, returning an error when the value cannot
|
167
|
+
# be converted to the appropriate type.
|
168
|
+
#
|
169
|
+
# @param [Symbol,String] name the name of the property/argument to be validated
|
170
|
+
# @param [Object] value the value to be validated
|
171
|
+
# @param [Class,Proc] type the type of the class to be validated. Supported types include:
|
172
|
+
# * +Integer+
|
173
|
+
# * +Float+
|
174
|
+
# * +Date+
|
175
|
+
# * +Symbol+
|
176
|
+
# * any arbitrary +Class+ -- validates based on the results of is_a?
|
177
|
+
# @return [ArgumentError] when validation fails
|
178
|
+
def validate_type(name, value, type)
|
179
|
+
error = nil
|
180
|
+
|
181
|
+
begin
|
182
|
+
if value && type
|
183
|
+
# Can't use a case statement because of the way === is implemented on some classes
|
184
|
+
if type == Integer
|
185
|
+
is_valid = (value.is_a?(Integer) || value.to_s =~ /^\-?\d+$/)
|
186
|
+
elsif type == Float
|
187
|
+
is_valid = (value.is_a?(Float) || value.to_s =~ /^\-?\d+\.?\d*$/)
|
188
|
+
elsif type == Date
|
189
|
+
is_valid = (value.is_a?(Date) || Date.parse(value))
|
190
|
+
elsif type == Symbol
|
191
|
+
is_valid = value.respond_to?(:to_sym)
|
192
|
+
else
|
193
|
+
is_valid = value.is_a?(type)
|
194
|
+
end
|
195
|
+
|
196
|
+
unless is_valid
|
197
|
+
error = ArgumentError.new("#{name} must be of type #{type}, but got #{value.inspect}")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
rescue StandardError => e
|
201
|
+
error = ArgumentError.new("An error occurred validating #{name} with value #{value.inspect}: #{e.message}")
|
202
|
+
end
|
203
|
+
|
204
|
+
error
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|