validate-rb 0.1.0.alpha.1 → 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/README.md +4 -4
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +6 -0
- data/lib/validate/version.rb +1 -1
- data/lib/validate-rb.rb +3 -0
- data/lib/validate.rb +1 -75
- data/validate-rb.gemspec +30 -0
- metadata +21 -122
- data/LICENSE +0 -21
- data/lib/validate/arguments.rb +0 -93
- data/lib/validate/assertions.rb +0 -27
- data/lib/validate/ast.rb +0 -381
- data/lib/validate/compare.rb +0 -34
- data/lib/validate/constraint.rb +0 -217
- data/lib/validate/constraints/validation_context.rb +0 -167
- data/lib/validate/constraints.rb +0 -337
- data/lib/validate/errors.rb +0 -40
- data/lib/validate/helpers.rb +0 -11
- data/lib/validate/scope.rb +0 -48
- data/lib/validate/validators/dsl.rb +0 -44
- data/lib/validate/validators.rb +0 -7
data/lib/validate/ast.rb
DELETED
@@ -1,381 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Validate
|
5
|
-
module AST
|
6
|
-
def self.build(*args, &block)
|
7
|
-
Generator.new
|
8
|
-
.generate(*args, &block)
|
9
|
-
.freeze
|
10
|
-
end
|
11
|
-
|
12
|
-
class DefinitionContext
|
13
|
-
module Builder
|
14
|
-
module_function
|
15
|
-
|
16
|
-
def all_constraints(*constraints)
|
17
|
-
Rules::Unanimous.new(constraints.map { |node| send(*node) })
|
18
|
-
end
|
19
|
-
|
20
|
-
def at_least_one_constraint(*constraints)
|
21
|
-
Rules::Affirmative.new(constraints.map { |node| send(*node) })
|
22
|
-
end
|
23
|
-
|
24
|
-
def no_constraints(*constraints)
|
25
|
-
Rules::Negative.new(constraints.map { |node| send(*node) })
|
26
|
-
end
|
27
|
-
|
28
|
-
def constraint(name, args, block, trace)
|
29
|
-
if defined?(Constraints) && Constraints.respond_to?(name)
|
30
|
-
begin
|
31
|
-
return Constraints.send(name, *(args.map { |node| send(*node) }), &block)
|
32
|
-
rescue => e
|
33
|
-
::Kernel.raise Error::ValidationRuleError, e.message, trace
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
Rules::Pending.new(name, args.map { |node| send(*node) }, block, trace)
|
38
|
-
end
|
39
|
-
|
40
|
-
def value(value)
|
41
|
-
value
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.create(*args, &block)
|
46
|
-
ast = AST.build(*args, &block)
|
47
|
-
context = new
|
48
|
-
ast.each { |node| context.add_constraint(Builder.send(*node)) }
|
49
|
-
context
|
50
|
-
end
|
51
|
-
|
52
|
-
def initialize
|
53
|
-
@constraints = {}
|
54
|
-
end
|
55
|
-
|
56
|
-
def add_constraint(constraint)
|
57
|
-
if @constraints.include?(constraint.name)
|
58
|
-
raise Error::ValidationRuleError,
|
59
|
-
"duplicate constraint #{constraint.name}"
|
60
|
-
end
|
61
|
-
|
62
|
-
@constraints[constraint.name] = constraint
|
63
|
-
self
|
64
|
-
end
|
65
|
-
|
66
|
-
def evaluate(ctx)
|
67
|
-
@constraints.each_value
|
68
|
-
.reject { |c| catch(:pending) { c.valid?(ctx.value, ctx) } }
|
69
|
-
.each { |c| ctx.add_violation(c) }
|
70
|
-
ctx
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
CORE_CONSTRAINTS = %i[
|
75
|
-
not_nil not_blank not_empty
|
76
|
-
is_a one_of validate
|
77
|
-
min max equal match
|
78
|
-
valid each_value unique
|
79
|
-
length
|
80
|
-
].freeze
|
81
|
-
|
82
|
-
class Generator < ::BasicObject
|
83
|
-
def initialize
|
84
|
-
@stack = []
|
85
|
-
end
|
86
|
-
|
87
|
-
def generate(*args, &block)
|
88
|
-
instance_exec(*args, &block)
|
89
|
-
|
90
|
-
if @stack.one? && @stack.first[0] == :all_constraints
|
91
|
-
return @stack.first[1..-1]
|
92
|
-
end
|
93
|
-
|
94
|
-
@stack
|
95
|
-
end
|
96
|
-
|
97
|
-
def &(other)
|
98
|
-
unless other == self
|
99
|
-
::Kernel.raise(
|
100
|
-
Error::ValidationRuleError,
|
101
|
-
'bad rule, only constraints and &, |, and ! operators allowed'
|
102
|
-
)
|
103
|
-
end
|
104
|
-
|
105
|
-
right = @stack.pop
|
106
|
-
left = @stack.pop
|
107
|
-
if right[0] == :all_constraints
|
108
|
-
right.insert(1, left)
|
109
|
-
@stack << right
|
110
|
-
else
|
111
|
-
@stack << [:all_constraints, left, right]
|
112
|
-
end
|
113
|
-
self
|
114
|
-
end
|
115
|
-
|
116
|
-
def |(other)
|
117
|
-
unless other == self
|
118
|
-
::Kernel.raise(
|
119
|
-
Error::ValidationRuleError,
|
120
|
-
'bad rule, only constraints and &, |, and ! operators allowed'
|
121
|
-
)
|
122
|
-
end
|
123
|
-
|
124
|
-
right = @stack.pop
|
125
|
-
left = @stack.pop
|
126
|
-
if right[0] == :at_least_one_constraint
|
127
|
-
right.insert(1, left)
|
128
|
-
@stack << right
|
129
|
-
else
|
130
|
-
@stack << [:at_least_one_constraint, left, right]
|
131
|
-
end
|
132
|
-
self
|
133
|
-
end
|
134
|
-
|
135
|
-
def !
|
136
|
-
prev = @stack.pop
|
137
|
-
if prev[0] == :no_constraints
|
138
|
-
@stack << prev[1]
|
139
|
-
elsif prev[0] == :all_constraints
|
140
|
-
prev[0] = :no_constraints
|
141
|
-
@stack << prev
|
142
|
-
else
|
143
|
-
@stack << [:no_constraints, prev]
|
144
|
-
end
|
145
|
-
self
|
146
|
-
end
|
147
|
-
|
148
|
-
private
|
149
|
-
|
150
|
-
def method_missing(method, *args, &block)
|
151
|
-
return super unless respond_to_missing?(method)
|
152
|
-
|
153
|
-
@stack << [
|
154
|
-
:constraint,
|
155
|
-
method,
|
156
|
-
args.map { |arg| [:value, arg] },
|
157
|
-
block,
|
158
|
-
::Kernel.caller
|
159
|
-
.reject { |line| line.include?(__FILE__) }
|
160
|
-
]
|
161
|
-
self
|
162
|
-
end
|
163
|
-
|
164
|
-
def respond_to_missing?(method, _ = false)
|
165
|
-
(defined?(Constraints) && Constraints.respond_to?(method)) || CORE_CONSTRAINTS.include?(method)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
module Combinator
|
170
|
-
extend Forwardable
|
171
|
-
def_delegators :@constraints, :[]
|
172
|
-
|
173
|
-
def respond_to_missing?(_, _ = false)
|
174
|
-
false
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
def constraint_message(index)
|
180
|
-
@constraints[index].message % Hash.new do |_, key|
|
181
|
-
if key.to_s.start_with?('constraint')
|
182
|
-
"%{#{key.to_s.gsub('constraint', "constraint[#{index}]")}}"
|
183
|
-
else
|
184
|
-
"%{#{key}}"
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
module Rules
|
191
|
-
class Pending < Constraint
|
192
|
-
include MonitorMixin
|
193
|
-
|
194
|
-
def initialize(name, args, block, caller)
|
195
|
-
@name = name
|
196
|
-
@args = args
|
197
|
-
@block = block
|
198
|
-
@caller = caller
|
199
|
-
@constraint = nil
|
200
|
-
|
201
|
-
extend SingleForwardable
|
202
|
-
mon_initialize
|
203
|
-
end
|
204
|
-
|
205
|
-
def name
|
206
|
-
load_constraint { return @name }.name
|
207
|
-
end
|
208
|
-
|
209
|
-
def valid?(value, ctx = Constraints::ValidationContext.none)
|
210
|
-
load_constraint { throw(:pending, true) }.valid?(value, ctx)
|
211
|
-
end
|
212
|
-
|
213
|
-
def to_s
|
214
|
-
load_constraint { return "[pending #{@name}]" }.to_s
|
215
|
-
end
|
216
|
-
|
217
|
-
def inspect
|
218
|
-
load_constraint { return "[pending #{@name}]" }.inspect
|
219
|
-
end
|
220
|
-
|
221
|
-
def ==(other)
|
222
|
-
load_constraint { return false } == other
|
223
|
-
end
|
224
|
-
|
225
|
-
def method_missing(method, *args)
|
226
|
-
load_constraint { return NameError }.__send__(method, *args)
|
227
|
-
end
|
228
|
-
|
229
|
-
def respond_to_missing?(method, pvt = false)
|
230
|
-
load_constraint { return false }.__send__(:respond_to_missing?, method, pvt)
|
231
|
-
end
|
232
|
-
|
233
|
-
private
|
234
|
-
|
235
|
-
def load_constraint
|
236
|
-
yield unless defined?(Constraints) && Constraints.respond_to?(@name)
|
237
|
-
|
238
|
-
synchronize do
|
239
|
-
return @constraint if @constraint
|
240
|
-
|
241
|
-
begin
|
242
|
-
@constraint = Constraints.send(@name, *@args, &@block)
|
243
|
-
rescue => e
|
244
|
-
::Kernel.raise Error::ValidationRuleError, e.message, @caller
|
245
|
-
end
|
246
|
-
|
247
|
-
def_delegators(:@constraint, :name, :valid?, :to_s,
|
248
|
-
:inspect, :==, :message)
|
249
|
-
|
250
|
-
@name = @args = @block = @caller = nil
|
251
|
-
@constraint
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
class Unanimous < Constraint
|
257
|
-
include Combinator
|
258
|
-
include Arguments
|
259
|
-
|
260
|
-
arg(:constraints) do
|
261
|
-
not_nil
|
262
|
-
length(min: 2)
|
263
|
-
each_value { is_a(Constraint) }
|
264
|
-
unique(:name)
|
265
|
-
end
|
266
|
-
def initialize(constraints)
|
267
|
-
@constraints = constraints.freeze
|
268
|
-
end
|
269
|
-
|
270
|
-
def valid?(value, _ = Constraints::ValidationContext.none)
|
271
|
-
ctx = Constraints::ValidationContext.root(value)
|
272
|
-
@constraints.all? do |c|
|
273
|
-
c.valid?(value, ctx) && !ctx.has_violations?
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def name
|
278
|
-
'both_' + @constraints.map(&:name).sort.join('_and_')
|
279
|
-
end
|
280
|
-
|
281
|
-
def inspect
|
282
|
-
return @constraints.first.inspect if @constraints.one?
|
283
|
-
|
284
|
-
"(#{@constraints.map(&:inspect).join(' & ')})"
|
285
|
-
end
|
286
|
-
|
287
|
-
def message
|
288
|
-
'both ' + @constraints
|
289
|
-
.size
|
290
|
-
.times
|
291
|
-
.map { |i| "[#{constraint_message(i)}]" }
|
292
|
-
.join(', and ')
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
class Affirmative < Constraint
|
297
|
-
include Combinator
|
298
|
-
include Arguments
|
299
|
-
|
300
|
-
arg(:constraints) do
|
301
|
-
not_nil
|
302
|
-
length(min: 2)
|
303
|
-
each_value { is_a(Constraint) }
|
304
|
-
unique(:name)
|
305
|
-
end
|
306
|
-
def initialize(constraints)
|
307
|
-
@constraints = constraints.freeze
|
308
|
-
end
|
309
|
-
|
310
|
-
def valid?(value, _ = Constraints::ValidationContext.none)
|
311
|
-
ctx = Constraints::ValidationContext.root(value)
|
312
|
-
@constraints.any? do |c|
|
313
|
-
ctx.clear_violations
|
314
|
-
c.valid?(value, ctx) && !ctx.has_violations?
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
def name
|
319
|
-
'either_' + @constraints.map(&:name).sort.join('_or_')
|
320
|
-
end
|
321
|
-
|
322
|
-
def inspect
|
323
|
-
return @constraints.first.inspect if @constraints.one?
|
324
|
-
|
325
|
-
"(#{@constraints.map(&:inspect).join(' | ')})"
|
326
|
-
end
|
327
|
-
|
328
|
-
def message
|
329
|
-
'either ' + @constraints
|
330
|
-
.size
|
331
|
-
.times
|
332
|
-
.map { |i| "[#{constraint_message(i)}]" }
|
333
|
-
.join(', or ')
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
class Negative < Constraint
|
338
|
-
include Combinator
|
339
|
-
include Arguments
|
340
|
-
|
341
|
-
arg(:constraints) do
|
342
|
-
not_nil
|
343
|
-
not_empty
|
344
|
-
each_value { is_a(Constraint) }
|
345
|
-
unique(:name)
|
346
|
-
end
|
347
|
-
def initialize(constraints)
|
348
|
-
@constraints = constraints.freeze
|
349
|
-
end
|
350
|
-
|
351
|
-
def valid?(value, _ = Constraints::ValidationContext.none)
|
352
|
-
ctx = Constraints::ValidationContext.root(value)
|
353
|
-
@constraints.none? do |c|
|
354
|
-
ctx.clear_violations
|
355
|
-
c.valid?(value, ctx) && !ctx.has_violations?
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
def name
|
360
|
-
'neither_' + @constraints.map(&:name).sort.join('_nor_')
|
361
|
-
end
|
362
|
-
|
363
|
-
def message
|
364
|
-
return "not [#{constraint_message(0)}]" if @constraints.one?
|
365
|
-
|
366
|
-
'neither ' + @constraints
|
367
|
-
.size
|
368
|
-
.times
|
369
|
-
.map { |i| "[#{constraint_message(i)}]" }
|
370
|
-
.join(', nor ')
|
371
|
-
end
|
372
|
-
|
373
|
-
def inspect
|
374
|
-
return "!#{@constraints.first.inspect}" if @constraints.one?
|
375
|
-
|
376
|
-
"!(#{@constraints.map(&:inspect).join(' & ')})"
|
377
|
-
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
end
|
381
|
-
end
|
data/lib/validate/compare.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Validate
|
4
|
-
module Compare
|
5
|
-
class ByAttributes
|
6
|
-
include Comparable
|
7
|
-
|
8
|
-
def initialize(attributes)
|
9
|
-
@attributes = attributes
|
10
|
-
end
|
11
|
-
|
12
|
-
def <=>(other)
|
13
|
-
@attributes.map { |attribute, value| value <=> other.send(attribute) }
|
14
|
-
.find { |result| !result.zero? } || 0
|
15
|
-
end
|
16
|
-
|
17
|
-
def method_missing(symbol, *args)
|
18
|
-
return super unless args.empty? && respond_to_missing?(symbol)
|
19
|
-
|
20
|
-
@attributes[symbol]
|
21
|
-
end
|
22
|
-
|
23
|
-
def respond_to_missing?(attribute, _ = false)
|
24
|
-
@attributes.include?(attribute)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
module_function
|
29
|
-
|
30
|
-
def attributes(**attributes)
|
31
|
-
ByAttributes.new(attributes)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/validate/constraint.rb
DELETED
@@ -1,217 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Validate
|
5
|
-
class Constraint
|
6
|
-
class Violation
|
7
|
-
attr_reader :value, :path, :constraint
|
8
|
-
|
9
|
-
def initialize(value, path, constraint)
|
10
|
-
@value = value
|
11
|
-
@path = path
|
12
|
-
@constraint = constraint
|
13
|
-
end
|
14
|
-
|
15
|
-
def message(template = @constraint.message)
|
16
|
-
(template % parameters).strip
|
17
|
-
end
|
18
|
-
|
19
|
-
alias to_s message
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def parameters
|
24
|
-
@parameters ||= Hash.new do |_, key|
|
25
|
-
String(instance_eval(key.to_s))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Option
|
31
|
-
attr_reader :name
|
32
|
-
|
33
|
-
def initialize(name, default:, assertion: nil, &assert_block)
|
34
|
-
@name = name
|
35
|
-
@default = default.is_a?(Proc) ? default : -> { default }
|
36
|
-
@assertion = assertion || assert_block && Assertions.create(&assert_block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def replace_default(default)
|
40
|
-
Option.new(@name, default: default, assertion: @assertion)
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_or_default(options)
|
44
|
-
value = options.delete(@name) { return @default&.call }
|
45
|
-
@assertion&.assert(value, message: "invalid option #{@name}")
|
46
|
-
value
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.inherited(child)
|
51
|
-
child.extend DSL
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.create_class(name, **defaults, &constraint_block)
|
55
|
-
Class.new(self) do
|
56
|
-
@supported_options = common_options.transform_values do |option|
|
57
|
-
defaults.include?(option.name) ? option.replace_default(defaults[option.name]) : option
|
58
|
-
end
|
59
|
-
include(@constraint_user_methods = Module.new)
|
60
|
-
@constraint_user_methods.define_method(:name) { name.to_s }
|
61
|
-
class_eval(&constraint_block)
|
62
|
-
if instance_variable_defined?(:@supported_options)
|
63
|
-
initialize { |**options| options }
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
module DSL
|
69
|
-
def constraint_name
|
70
|
-
@constraint_name ||= Assertions.create do
|
71
|
-
not_nil(message: 'constraint name must not be nil')
|
72
|
-
is_a(Symbol, message: 'constraint name must be a Symbol')
|
73
|
-
end
|
74
|
-
end
|
75
|
-
module_function :constraint_name
|
76
|
-
|
77
|
-
def common_options
|
78
|
-
@common_options ||= {
|
79
|
-
message: Option.new(:message, default: 'be %{constraint.name}') do
|
80
|
-
not_blank
|
81
|
-
is_a(String)
|
82
|
-
end
|
83
|
-
}.freeze
|
84
|
-
end
|
85
|
-
module_function :common_options
|
86
|
-
|
87
|
-
def option(
|
88
|
-
name,
|
89
|
-
default: lambda do
|
90
|
-
raise Error::KeyError,
|
91
|
-
"option #{name.inspect} is required for #{self.name}"
|
92
|
-
end,
|
93
|
-
&assert_block
|
94
|
-
)
|
95
|
-
constraint_name.assert(name)
|
96
|
-
if @supported_options.include?(name)
|
97
|
-
raise Error::ArgumentError, "duplicate option :#{name}"
|
98
|
-
end
|
99
|
-
|
100
|
-
@supported_options[name] = Option.new(
|
101
|
-
name,
|
102
|
-
default: default,
|
103
|
-
&assert_block
|
104
|
-
)
|
105
|
-
self
|
106
|
-
end
|
107
|
-
|
108
|
-
def initialize(&initialize_block)
|
109
|
-
supported_options = @supported_options
|
110
|
-
expects_kwargs = false
|
111
|
-
initialize_block.parameters.each do |(kind, name)|
|
112
|
-
if %i(keyreq key).include?(kind) && supported_options.include?(name)
|
113
|
-
raise Error::ArgumentError,
|
114
|
-
"key name #{name}: conflicts with an existing option"
|
115
|
-
end
|
116
|
-
expects_kwargs = true if kind == :keyrest
|
117
|
-
end
|
118
|
-
|
119
|
-
define_constraint_method(:initialize, initialize_block) do |*args, **kwargs, &block|
|
120
|
-
known_options, extra_kwargs =
|
121
|
-
kwargs.partition { |k, _| supported_options.include?(k) }
|
122
|
-
.map { |h| Hash[h] }
|
123
|
-
args << extra_kwargs if expects_kwargs || !extra_kwargs.empty?
|
124
|
-
|
125
|
-
merged_options = {}.merge!(super(*args, &block), known_options)
|
126
|
-
|
127
|
-
options = supported_options.each_with_object({}) do |(n, opt), opts|
|
128
|
-
opts[n] = opt.get_or_default(merged_options)
|
129
|
-
end
|
130
|
-
|
131
|
-
unless merged_options.empty?
|
132
|
-
raise Error::ArgumentError,
|
133
|
-
"undefined options #{merged_options.inspect}"
|
134
|
-
end
|
135
|
-
|
136
|
-
@options = options.freeze
|
137
|
-
end
|
138
|
-
remove_instance_variable(:@supported_options)
|
139
|
-
end
|
140
|
-
|
141
|
-
def evaluate(&validation_block)
|
142
|
-
define_constraint_method(:valid?, validation_block) do |*args|
|
143
|
-
catch(:result) do
|
144
|
-
super(*args[0...validation_block.arity])
|
145
|
-
:pass
|
146
|
-
end == :pass
|
147
|
-
end
|
148
|
-
self
|
149
|
-
end
|
150
|
-
|
151
|
-
def describe(&describe_block)
|
152
|
-
define_method(:to_s, &describe_block)
|
153
|
-
self
|
154
|
-
end
|
155
|
-
|
156
|
-
def key(&key_block)
|
157
|
-
define_method(:name, &key_block)
|
158
|
-
self
|
159
|
-
end
|
160
|
-
|
161
|
-
private
|
162
|
-
|
163
|
-
def define_constraint_method(name, body, &override)
|
164
|
-
@constraint_user_methods.__send__(:define_method, name, &body)
|
165
|
-
define_method(name, &override)
|
166
|
-
self
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
attr_reader :options
|
171
|
-
protected :options
|
172
|
-
|
173
|
-
def initialize(**options)
|
174
|
-
@options = options
|
175
|
-
end
|
176
|
-
|
177
|
-
def name
|
178
|
-
raise ::NotImplementedError
|
179
|
-
end
|
180
|
-
|
181
|
-
def valid?(value, ctx = Constraints::ValidationContext.none)
|
182
|
-
raise ::NotImplementedError
|
183
|
-
end
|
184
|
-
|
185
|
-
def to_s
|
186
|
-
name.to_s.gsub('_', ' ')
|
187
|
-
end
|
188
|
-
|
189
|
-
def inspect
|
190
|
-
"#<#{self.class.name} #{@options.map { |name, value| "#{name}: #{value.inspect}" }.join(', ')}>"
|
191
|
-
end
|
192
|
-
|
193
|
-
def ==(other)
|
194
|
-
other.is_a?(Constraint) && other.name == name && other.options == options
|
195
|
-
end
|
196
|
-
|
197
|
-
def respond_to_missing?(method, _ = false)
|
198
|
-
@options.include?(method)
|
199
|
-
end
|
200
|
-
|
201
|
-
def method_missing(method, *args)
|
202
|
-
return super unless args.empty? || respond_to_missing?(method)
|
203
|
-
|
204
|
-
@options[method]
|
205
|
-
end
|
206
|
-
|
207
|
-
private
|
208
|
-
|
209
|
-
def fail
|
210
|
-
throw(:result, :fail)
|
211
|
-
end
|
212
|
-
|
213
|
-
def pass
|
214
|
-
throw(:result, :pass)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|