data_bindings 0.0.1
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 +5 -0
- data/Gemfile +4 -0
- data/README.md +167 -0
- data/Rakefile +13 -0
- data/data_bindings.gemspec +35 -0
- data/lib/data_bindings/adapters/bson.rb +32 -0
- data/lib/data_bindings/adapters/json.rb +32 -0
- data/lib/data_bindings/adapters/native.rb +50 -0
- data/lib/data_bindings/adapters/params.rb +91 -0
- data/lib/data_bindings/adapters/ruby.rb +44 -0
- data/lib/data_bindings/adapters/tnetstring.rb +32 -0
- data/lib/data_bindings/adapters/xml.rb +74 -0
- data/lib/data_bindings/adapters/yaml.rb +26 -0
- data/lib/data_bindings/adapters.rb +12 -0
- data/lib/data_bindings/bound.rb +331 -0
- data/lib/data_bindings/converters.rb +83 -0
- data/lib/data_bindings/generator.rb +140 -0
- data/lib/data_bindings/unbound.rb +76 -0
- data/lib/data_bindings/util.rb +78 -0
- data/lib/data_bindings/version.rb +3 -0
- data/lib/data_bindings.rb +52 -0
- data/test/array_test.rb +55 -0
- data/test/bson_test.rb +23 -0
- data/test/converter_test.rb +36 -0
- data/test/data_bindings_test.rb +67 -0
- data/test/fixtures/1.json +1 -0
- data/test/json_test.rb +40 -0
- data/test/native_test.rb +64 -0
- data/test/params_test.rb +60 -0
- data/test/test_helper.rb +17 -0
- data/test/tnetstring_test.rb +23 -0
- data/test/validation_test.rb +217 -0
- data/test/xml_test.rb +20 -0
- data/test/yaml_test.rb +19 -0
- metadata +282 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module TNetstring
|
|
4
|
+
include Ruby
|
|
5
|
+
include DataBindings::GemRequirement
|
|
6
|
+
|
|
7
|
+
# Constructs a wrapped object from a Tnetstring
|
|
8
|
+
# @param [String] str The Tnetstring object
|
|
9
|
+
# @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
|
|
10
|
+
def from_tnetstring(str)
|
|
11
|
+
from_ruby(::TNetstring.parse(str)[0])
|
|
12
|
+
end
|
|
13
|
+
gentle_require_gem :from_tnetstring, 'tnetstring'
|
|
14
|
+
|
|
15
|
+
module Convert
|
|
16
|
+
include ConverterHelper
|
|
17
|
+
include DataBindings::GemRequirement
|
|
18
|
+
|
|
19
|
+
# Creates a String repsentation of a Ruby Hash or Array.
|
|
20
|
+
# @param [Generator] generator The generator that invokes this constructor
|
|
21
|
+
# @param [Symbol] name The name of the binding used on this object
|
|
22
|
+
# @param [Array, Hash] obj The object to be represented in JSON
|
|
23
|
+
# @return [String] The Tnetstring representation of this object
|
|
24
|
+
def force_convert_to_tnetstring
|
|
25
|
+
::TNetstring.dump(self.to_hash)
|
|
26
|
+
end
|
|
27
|
+
gentle_require_gem :force_convert_to_tnetstring, 'tnetstring'
|
|
28
|
+
standard_converter :convert_to_tnetstring
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
module XML
|
|
4
|
+
include Ruby
|
|
5
|
+
include DataBindings::GemRequirement
|
|
6
|
+
|
|
7
|
+
# Constructs a wrapped object from a JSON string
|
|
8
|
+
# @param [String] str The JSON object
|
|
9
|
+
# @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
|
|
10
|
+
def from_xml(str)
|
|
11
|
+
from_ruby(from_xml_obj(Nokogiri::XML(str)))
|
|
12
|
+
end
|
|
13
|
+
gentle_require_gem :from_xml, 'nokogiri'
|
|
14
|
+
|
|
15
|
+
def from_xml_obj(o)
|
|
16
|
+
case o.type
|
|
17
|
+
when Nokogiri::XML::Node::DOCUMENT_NODE
|
|
18
|
+
from_xml_obj(o.children[0])
|
|
19
|
+
when Nokogiri::XML::Node::TEXT_NODE
|
|
20
|
+
o.text
|
|
21
|
+
when Nokogiri::XML::Node::ELEMENT_NODE
|
|
22
|
+
if o.children.size == 1 and o.children[0].text?
|
|
23
|
+
from_xml_obj(o.children[0])
|
|
24
|
+
elsif o.children[0].name == '0'
|
|
25
|
+
o.children.map{ |c| from_xml_obj(c) }
|
|
26
|
+
else
|
|
27
|
+
Hash[o.children.map { |n| [n.name, from_xml_obj(n)] }]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
gentle_require_gem :from_xml_obj, 'nokogiri'
|
|
32
|
+
|
|
33
|
+
# Creates a String repsentation of a Ruby Hash or Array.
|
|
34
|
+
# @param [Generator] generator The generator that invokes this constructor
|
|
35
|
+
# @param [Symbol] name The name of the binding used on this object
|
|
36
|
+
# @param [Array, Hash] obj The object to be represented in JSON
|
|
37
|
+
# @return [String] The JSON representation of this object
|
|
38
|
+
|
|
39
|
+
module Convert
|
|
40
|
+
include DataBindings::GemRequirement
|
|
41
|
+
include ConverterHelper
|
|
42
|
+
|
|
43
|
+
def force_convert_to_xml
|
|
44
|
+
Convert.construct(@generator, @name, self, @binding_block)
|
|
45
|
+
end
|
|
46
|
+
gentle_require_gem :force_convert_to_xml, 'builder'
|
|
47
|
+
standard_converter :convert_to_xml
|
|
48
|
+
|
|
49
|
+
def self.construct(generator, name, obj, builder = nil)
|
|
50
|
+
root = builder.nil?
|
|
51
|
+
builder ||= Builder::XmlMarkup.new
|
|
52
|
+
builder.instruct!(:xml, :encoding => "UTF-8") if root
|
|
53
|
+
case obj
|
|
54
|
+
when Array
|
|
55
|
+
builder.__send__(name || "doc") do |b|
|
|
56
|
+
obj.each_with_index(o, i)
|
|
57
|
+
construct(generator, i.to_s, o, b)
|
|
58
|
+
end
|
|
59
|
+
when Hash
|
|
60
|
+
builder.__send__(name || "doc") do |b|
|
|
61
|
+
obj.each do |k, v|
|
|
62
|
+
construct(generator, k, v, b)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
builder.__send__(name, obj)
|
|
67
|
+
end
|
|
68
|
+
builder.target! if root
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module DataBindings
|
|
4
|
+
module Adapters
|
|
5
|
+
module YAML
|
|
6
|
+
include Ruby
|
|
7
|
+
|
|
8
|
+
def from_yaml(str)
|
|
9
|
+
from_ruby(::YAML::load(str))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def from_yaml_file(f)
|
|
13
|
+
from_ruby(::YAML::load_file(f))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Convert
|
|
17
|
+
include ConverterHelper
|
|
18
|
+
|
|
19
|
+
def force_convert_to_yaml
|
|
20
|
+
::YAML::dump(self.to_nonindifferent_hash)
|
|
21
|
+
end
|
|
22
|
+
standard_converter :convert_to_yaml
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Adapters
|
|
3
|
+
autoload :BSON, 'data_bindings/adapters/bson'
|
|
4
|
+
autoload :JSON, 'data_bindings/adapters/json'
|
|
5
|
+
autoload :Native, 'data_bindings/adapters/native'
|
|
6
|
+
autoload :Ruby, 'data_bindings/adapters/ruby'
|
|
7
|
+
autoload :YAML, 'data_bindings/adapters/yaml'
|
|
8
|
+
autoload :Params, 'data_bindings/adapters/params'
|
|
9
|
+
autoload :XML, 'data_bindings/adapters/xml'
|
|
10
|
+
autoload :TNetstring, 'data_bindings/adapters/tnetstring'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
module DataBindings
|
|
2
|
+
module Bound
|
|
3
|
+
ValidationError = Class.new(RuntimeError)
|
|
4
|
+
NoBindingName = Class.new(RuntimeError)
|
|
5
|
+
|
|
6
|
+
class Errors < DataBindings::IndifferentHash
|
|
7
|
+
attr_accessor :base
|
|
8
|
+
|
|
9
|
+
def join(st = nil)
|
|
10
|
+
(base ? [base].concat(values) : values).join(st)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def valid?
|
|
14
|
+
base.nil? && empty?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def clear
|
|
18
|
+
super
|
|
19
|
+
@base = nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#include DataBindings::WriterInterceptor
|
|
24
|
+
|
|
25
|
+
attr_reader :errors, :source, :name, :generator
|
|
26
|
+
|
|
27
|
+
def valid?
|
|
28
|
+
calculate_validness
|
|
29
|
+
errors.valid?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def calculate_validness
|
|
33
|
+
if @last_hash.nil? || @last_hash != hash
|
|
34
|
+
@from = self if @last_hash
|
|
35
|
+
errors.clear
|
|
36
|
+
validate
|
|
37
|
+
@last_hash = hash
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid!
|
|
42
|
+
valid? or raise FailedValidation.new("Object was invalid with the following errors: #{errors.join(", ")}", errors, source)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pre_convert
|
|
47
|
+
valid!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def cast_element(lookup_name, source, type, opts = nil, &blk)
|
|
51
|
+
name = get_parameter_name(lookup_name, source, opts)
|
|
52
|
+
el = name && source[name]
|
|
53
|
+
raise_on_error = opts && opts.key?(:raise_on_error) ? opts[:raise_on_error] : false
|
|
54
|
+
allow_nil = opts && opts.key?(:allow_nil) ? opts[:allow_nil] : false
|
|
55
|
+
el ||= opts[:default] if opts && opts.key?(:default)
|
|
56
|
+
|
|
57
|
+
if el.nil? && name.nil?
|
|
58
|
+
@errors[lookup_name] = generate_error("not found", raise_on_error)
|
|
59
|
+
nil
|
|
60
|
+
else
|
|
61
|
+
new_el = if type.nil?
|
|
62
|
+
# anything goes
|
|
63
|
+
case el
|
|
64
|
+
when Array, Hash
|
|
65
|
+
blk ? register_sub(name, @generator.from_ruby(el).bind(&blk), raise_on_error) : el
|
|
66
|
+
else
|
|
67
|
+
el
|
|
68
|
+
end
|
|
69
|
+
elsif type == String
|
|
70
|
+
@errors[lookup_name] = generate_error("was not a String", raise_on_error) unless el.is_a?(String)
|
|
71
|
+
el
|
|
72
|
+
elsif type == Integer
|
|
73
|
+
begin
|
|
74
|
+
Integer(el)
|
|
75
|
+
rescue ArgumentError, TypeError
|
|
76
|
+
@errors[lookup_name] = generate_error("was not an Integer", raise_on_error)
|
|
77
|
+
el
|
|
78
|
+
end
|
|
79
|
+
elsif type == Float
|
|
80
|
+
begin
|
|
81
|
+
Float(el)
|
|
82
|
+
rescue ArgumentError, TypeError
|
|
83
|
+
@errors[lookup_name] = generate_error("was not a Float", raise_on_error)
|
|
84
|
+
el
|
|
85
|
+
end
|
|
86
|
+
elsif Array === type
|
|
87
|
+
if el.is_a?(Array)
|
|
88
|
+
@errors[lookup_name] = generate_error("did not match the length", raise_on_error) if opts && opts[:length] && !(opts[:length] === el.size)
|
|
89
|
+
if type.first
|
|
90
|
+
register_sub(name, @generator.from_ruby(el).bind_array { all_elements type.first }, raise_on_error)
|
|
91
|
+
elsif blk
|
|
92
|
+
register_sub(name, @generator.from_ruby(el).bind_array { all_elements &blk }, raise_on_error)
|
|
93
|
+
else
|
|
94
|
+
el
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
@errors[lookup_name] = generate_error("was not an Array", raise_on_error)
|
|
98
|
+
el
|
|
99
|
+
end
|
|
100
|
+
elsif type == :boolean
|
|
101
|
+
if allow_nil
|
|
102
|
+
el.nil? ? nil : DataBindings.true_boolean?(el)
|
|
103
|
+
else
|
|
104
|
+
DataBindings.true_boolean?(el)
|
|
105
|
+
end
|
|
106
|
+
elsif Symbol === type
|
|
107
|
+
if el.is_a?(Hash)
|
|
108
|
+
register_sub(name, @generator.from_ruby(el).bind(type), raise_on_error)
|
|
109
|
+
else
|
|
110
|
+
@errors[lookup_name] = generate_error("was not a Hash", raise_on_error)
|
|
111
|
+
el
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
raise "Unknown type #{type.inspect}"
|
|
115
|
+
end
|
|
116
|
+
if inclusion = opts && opts[:in]
|
|
117
|
+
@errors[lookup_name] = generate_error("was not included in #{inclusion.inspect}", raise_on_error) unless inclusion.include?(new_el)
|
|
118
|
+
end
|
|
119
|
+
@errors[lookup_name] = generate_error("was nil", raise_on_error) if new_el.nil? && !allow_nil
|
|
120
|
+
new_el
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def dump_val(val)
|
|
127
|
+
if val.respond_to?(:to_hash)
|
|
128
|
+
val.to_hash
|
|
129
|
+
elsif val.respond_to?(:to_ary)
|
|
130
|
+
val.to_ary
|
|
131
|
+
else
|
|
132
|
+
val
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def convert_target
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def generate_error(str, raise_on_error)
|
|
141
|
+
raise_on_error ? raise(ValidationError, str) : str
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def register_sub(name, sub, raise_on_error)
|
|
145
|
+
unless sub.valid?
|
|
146
|
+
@errors[name] = generate_error(sub.errors.to_s, raise_on_error)
|
|
147
|
+
end
|
|
148
|
+
sub
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def validate
|
|
152
|
+
reset_validation_state
|
|
153
|
+
run_validation
|
|
154
|
+
enforce_strictness if @strict
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def init_bound(generator, source, name, opts, validator)
|
|
158
|
+
@errors, @generator, @source, @name, @opts, @validator = Errors.new, generator, source, name, opts, validator
|
|
159
|
+
@from = @source
|
|
160
|
+
@strict = opts && opts.key?(:strict) ? opts[:strict] : generator.strict?
|
|
161
|
+
reset_validation_state
|
|
162
|
+
valid?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class BoundObject < DataBindings::IndifferentHash
|
|
166
|
+
include Bound
|
|
167
|
+
|
|
168
|
+
def initialize(generator, array_expected, source, name, opts, &blk)
|
|
169
|
+
raise BindingMismatch if array_expected
|
|
170
|
+
init_bound(generator, source, name, opts, blk)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def to_hash
|
|
174
|
+
keys.inject(DataBindings::IndifferentHash.new) { |h, k|
|
|
175
|
+
val = self[k]
|
|
176
|
+
h[k] = dump_val(val)
|
|
177
|
+
h
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def to_nonindifferent_hash
|
|
182
|
+
keys.inject({}) { |h, k|
|
|
183
|
+
val = self[k]
|
|
184
|
+
h[k.to_s] = dump_val(val)
|
|
185
|
+
h
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def to_native
|
|
190
|
+
valid!
|
|
191
|
+
data = inject(IndifferentHash.new) { |h, (k, v)|
|
|
192
|
+
h[k] = v.respond_to?(:to_native) ? v.to_native : v
|
|
193
|
+
h
|
|
194
|
+
}
|
|
195
|
+
if constructor = generator.native_constructors[name]
|
|
196
|
+
o = constructor[data.to_hash]
|
|
197
|
+
else
|
|
198
|
+
OpenStruct.new(data)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def property(name, type = nil, opts = nil, &blk)
|
|
203
|
+
type, opts = nil, type if type.is_a?(Hash)
|
|
204
|
+
self[name] = cast_element(name, @from, type, opts, &blk)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def required(name, type = nil, opts = nil, &blk)
|
|
208
|
+
type, opts = nil, type if type.is_a?(Hash)
|
|
209
|
+
opts ||= {}
|
|
210
|
+
opts[:allow_nil] = false
|
|
211
|
+
property(name, type, opts, &blk)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def optional(name, type = nil, opts = nil, &blk)
|
|
215
|
+
type, opts = nil, type if type.is_a?(Hash)
|
|
216
|
+
opts ||= {}
|
|
217
|
+
opts[:allow_nil] = true
|
|
218
|
+
property(name, type, opts, &blk)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def all_properties(type = nil, opts = nil)
|
|
222
|
+
type, opts = nil, type if type.is_a?(Hash)
|
|
223
|
+
@from.keys.each do |key|
|
|
224
|
+
property key, type, opts
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def enforce_strictness
|
|
229
|
+
@errors.base = "hasn't been fully matched" unless size == @from.size
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def copy_source
|
|
233
|
+
replace @source
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
def get_parameter_name(name, source, opts)
|
|
239
|
+
name = if opts && opts[:alias]
|
|
240
|
+
aliases = Array(opts[:alias])
|
|
241
|
+
index = aliases.index {|k| source.key?(k) }
|
|
242
|
+
index ? aliases[index] : name
|
|
243
|
+
else
|
|
244
|
+
name
|
|
245
|
+
end
|
|
246
|
+
source.key?(name) ? name : nil
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def reset_validation_state
|
|
250
|
+
errors.clear
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def run_validation
|
|
254
|
+
instance_eval(&@validator)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
class BoundArray < Array
|
|
259
|
+
include Bound
|
|
260
|
+
|
|
261
|
+
def initialize(generator, array_expected, source, name, opts, &blk)
|
|
262
|
+
raise BindingMismatch unless array_expected
|
|
263
|
+
init_bound(generator, source, name, opts, blk)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def to_ary
|
|
267
|
+
self.inject([]) { |a, v|
|
|
268
|
+
a << dump_val(v)
|
|
269
|
+
}
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def to_native
|
|
273
|
+
valid!
|
|
274
|
+
inject([]) {|a, el| a << (el.respond_to?(:to_native) ? el.to_native : el); a}
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def elements(size = nil, type = nil, opts = nil, &blk)
|
|
278
|
+
if size.nil? || size.respond_to?(:to_int)
|
|
279
|
+
size ||= source.size
|
|
280
|
+
# consume all
|
|
281
|
+
(@pos...(@pos+size)).each do |i|
|
|
282
|
+
self[@pos] = cast_element(@pos, @from, type, opts, &blk)
|
|
283
|
+
@pos += 1
|
|
284
|
+
end
|
|
285
|
+
elsif size.respond_to?(:min) && size.respond_to?(:max)
|
|
286
|
+
original_pos = @pos
|
|
287
|
+
while (@pos - original_pos) <= size.max
|
|
288
|
+
begin
|
|
289
|
+
opts ||= {}
|
|
290
|
+
opts[:raise_on_error] = @pos >= size.min
|
|
291
|
+
self[@pos] = cast_element(@pos, @from, type, opts, &blk)
|
|
292
|
+
rescue ValidationError
|
|
293
|
+
break
|
|
294
|
+
end
|
|
295
|
+
@pos += 1
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
raise "Size isn't understood: #{size.inspect}"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def copy_source
|
|
303
|
+
replace @source
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def enforce_strictness
|
|
307
|
+
@errors.base = "hasn't been fully matched" unless @pos.succ == source.size
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def all_elements(type = nil, &blk)
|
|
311
|
+
elements(nil, type, &blk)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
def get_parameter_name(name, source, opts)
|
|
317
|
+
source.at(name) ? name : nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def reset_validation_state
|
|
321
|
+
@pos = 0
|
|
322
|
+
errors.clear
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def run_validation
|
|
326
|
+
errors.base = "didn't match legnth #{@opts[:length]}" if @opts && @opts[:length] && !(@opts[:length] === size)
|
|
327
|
+
instance_eval(&@validator)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|