modelish 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|