ward 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/.document +5 -0
- data/.gitignore +28 -0
- data/LICENSE +19 -0
- data/README.markdown +99 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/features/acceptance_matcher.feature +78 -0
- data/features/attribute_keyword.feature +13 -0
- data/features/close_to_matcher.feature +130 -0
- data/features/context_arguments.feature +47 -0
- data/features/equal_to_matcher.feature +25 -0
- data/features/error_messages.feature +69 -0
- data/features/external_validation.feature +15 -0
- data/features/has_matcher.feature +72 -0
- data/features/has_matcher_initialized_with_expectation.feature +94 -0
- data/features/has_matcher_relativities.feature +171 -0
- data/features/include_matcher.feature +28 -0
- data/features/is_keyword.feature +42 -0
- data/features/is_not_keyword.feature +62 -0
- data/features/match_matcher.feature +49 -0
- data/features/multiple_validators.feature +29 -0
- data/features/nil_matcher.feature +25 -0
- data/features/predicate_matcher.feature +23 -0
- data/features/present_matcher.feature +59 -0
- data/features/satisfy_matcher.feature +80 -0
- data/features/scenario_validation.feature +81 -0
- data/features/step_definitions/external_validation_steps.rb +69 -0
- data/features/step_definitions/generic_validation_steps.rb +33 -0
- data/features/step_definitions/object_definition_steps.rb +43 -0
- data/features/support/env.rb +12 -0
- data/features/support/object_builder.rb +33 -0
- data/features/support/struct.rb +38 -0
- data/lang/en.yml +56 -0
- data/lib/ward.rb +26 -0
- data/lib/ward/context.rb +70 -0
- data/lib/ward/context_chain.rb +87 -0
- data/lib/ward/dsl.rb +7 -0
- data/lib/ward/dsl/validation_block.rb +73 -0
- data/lib/ward/dsl/validation_builder.rb +190 -0
- data/lib/ward/errors.rb +213 -0
- data/lib/ward/matchers.rb +97 -0
- data/lib/ward/matchers/acceptance.rb +43 -0
- data/lib/ward/matchers/close_to.rb +60 -0
- data/lib/ward/matchers/equal_to.rb +33 -0
- data/lib/ward/matchers/has.rb +283 -0
- data/lib/ward/matchers/include.rb +54 -0
- data/lib/ward/matchers/match.rb +29 -0
- data/lib/ward/matchers/matcher.rb +68 -0
- data/lib/ward/matchers/nil.rb +30 -0
- data/lib/ward/matchers/predicate.rb +31 -0
- data/lib/ward/matchers/present.rb +56 -0
- data/lib/ward/matchers/satisfy.rb +65 -0
- data/lib/ward/spec.rb +17 -0
- data/lib/ward/spec/matcher_matcher.rb +114 -0
- data/lib/ward/support.rb +7 -0
- data/lib/ward/support/basic_object.rb +55 -0
- data/lib/ward/support/result.rb +49 -0
- data/lib/ward/validator.rb +147 -0
- data/lib/ward/validator_set.rb +115 -0
- data/lib/ward/version.rb +3 -0
- data/spec/lib/has_matcher_relativity_examples.rb +15 -0
- data/spec/lib/have_public_method_defined.rb +22 -0
- data/spec/rcov.opts +8 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/ward/context_chain_spec.rb +178 -0
- data/spec/ward/context_spec.rb +57 -0
- data/spec/ward/dsl/validation_block_spec.rb +27 -0
- data/spec/ward/dsl/validation_builder_spec.rb +212 -0
- data/spec/ward/errors_spec.rb +149 -0
- data/spec/ward/matchers/acceptance_spec.rb +16 -0
- data/spec/ward/matchers/close_to_spec.rb +57 -0
- data/spec/ward/matchers/equal_to_spec.rb +16 -0
- data/spec/ward/matchers/has_spec.rb +175 -0
- data/spec/ward/matchers/include_spec.rb +41 -0
- data/spec/ward/matchers/match_spec.rb +21 -0
- data/spec/ward/matchers/matcher_spec.rb +54 -0
- data/spec/ward/matchers/nil_spec.rb +16 -0
- data/spec/ward/matchers/predicate_spec.rb +19 -0
- data/spec/ward/matchers/present_spec.rb +16 -0
- data/spec/ward/matchers/satisfy_spec.rb +68 -0
- data/spec/ward/matchers_spec.rb +51 -0
- data/spec/ward/spec/have_public_method_defined_spec.rb +31 -0
- data/spec/ward/spec/matcher_matcher_spec.rb +217 -0
- data/spec/ward/validator_set_spec.rb +178 -0
- data/spec/ward/validator_spec.rb +264 -0
- data/tasks/features.rake +15 -0
- data/tasks/rcov.rake +24 -0
- data/tasks/spec.rake +18 -0
- data/tasks/yard.rake +9 -0
- data/ward.gemspec +176 -0
- metadata +239 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# The steps in this file are used to test objects
|
3
|
+
# which don't include the Validation module.
|
4
|
+
#
|
5
|
+
|
6
|
+
def validator_set
|
7
|
+
if @validator_set.nil?
|
8
|
+
if @validator_set_definition.nil?
|
9
|
+
raise 'No validator set defined'
|
10
|
+
else
|
11
|
+
@validator_set = Ward::ValidatorSet.build do |object|
|
12
|
+
eval(Array(@validator_set_definition).join("\n"))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@validator_set
|
18
|
+
end
|
19
|
+
|
20
|
+
Transform %r{^'(\w+)' scenario$} do |scenario|
|
21
|
+
scenario.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
Given %r{(?:using )?a validation set like} do |definition|
|
25
|
+
@validator_set_definition = definition
|
26
|
+
end
|
27
|
+
|
28
|
+
Then %r{^the validation set should pass$} do
|
29
|
+
validator_set.valid?(defined_object).should be_true
|
30
|
+
validator_set.validate(defined_object).should be_pass
|
31
|
+
end
|
32
|
+
|
33
|
+
Then %r{^the validation set should fail$} do
|
34
|
+
validator_set.valid?(defined_object).should be_false
|
35
|
+
validator_set.validate(defined_object).should be_fail
|
36
|
+
end
|
37
|
+
|
38
|
+
Then %r{^the validation set should pass when using the ('\w+' scenario)$} do |scenario|
|
39
|
+
validator_set.valid?(defined_object, scenario).should be_true
|
40
|
+
validator_set.validate(defined_object, scenario).should be_pass
|
41
|
+
end
|
42
|
+
|
43
|
+
Then %r{^the validation set should fail when using the ('\w+' scenario)$} do |scenario|
|
44
|
+
validator_set.valid?(defined_object, scenario).should be_false
|
45
|
+
validator_set.validate(defined_object, scenario).should be_fail
|
46
|
+
end
|
47
|
+
|
48
|
+
Then %r{^there should be no validation errors$} do
|
49
|
+
validator_set.validate(defined_object).errors.should be_empty
|
50
|
+
end
|
51
|
+
|
52
|
+
Then %r{^the error on '([^']+)' should be '([^']+)'$} do |attribute, msg|
|
53
|
+
result = validator_set.validate(defined_object)
|
54
|
+
|
55
|
+
if msg[0].chr == '/' and msg[-1].chr == '/'
|
56
|
+
# Regexp.
|
57
|
+
result.errors.on(attribute.to_sym).length.should == 1
|
58
|
+
result.errors.on(attribute.to_sym).first.should =~ eval(msg)
|
59
|
+
else
|
60
|
+
# Exact string match.
|
61
|
+
result.errors.on(attribute.to_sym).should == [msg]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Then %r{^there should be (\S+) validation errors? on '([^']+)'$} do |number, attribute|
|
66
|
+
result = validator_set.validate(defined_object)
|
67
|
+
number = 0 if number == 'no'
|
68
|
+
(result.errors.on(attribute.to_sym) || []).size.should == number.to_i
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Generic Validators =========================================================
|
3
|
+
#
|
4
|
+
# These steps allow us to write less specific scenarios which simply state
|
5
|
+
# that an attribute should or should not be valid, without having to worry
|
6
|
+
# what a valid or invalid value actually is.
|
7
|
+
#
|
8
|
+
# They are used in conjunction with the "the instance '<attribute>' attribute
|
9
|
+
# is (in)?valid" steps.
|
10
|
+
#
|
11
|
+
# We accomplish this by testing that an attribute is equal to the string
|
12
|
+
# "valid" -- any other value causes a failure.
|
13
|
+
#
|
14
|
+
|
15
|
+
When %r{^validating the ('\w+' attribute)$} do |attribute|
|
16
|
+
@validator_set_definition ||= []
|
17
|
+
@validator_set_definition << "object.#{attribute}.is.equal_to('valid')"
|
18
|
+
end
|
19
|
+
|
20
|
+
When %r{^validating the ('\w+' attribute) in the ('\w+' scenario)$} do |attribute, scenario|
|
21
|
+
@validator_set_definition ||= []
|
22
|
+
@validator_set_definition <<
|
23
|
+
"object.#{attribute}.is.equal_to('valid').scenario(:#{scenario})"
|
24
|
+
end
|
25
|
+
|
26
|
+
Given %r{^the instance ('\w+' attribute) is valid$} do |attribute|
|
27
|
+
When %{the instance '#{attribute}' attribute is '"valid"'}
|
28
|
+
end
|
29
|
+
|
30
|
+
Given %r{^the instance ('\w+' attribute) is invalid$} do |attribute|
|
31
|
+
When %{the instance '#{attribute}' attribute is '"invalid"'}
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# The steps in this file are used to build classes and
|
3
|
+
# objects which are later validated.
|
4
|
+
#
|
5
|
+
|
6
|
+
def object_builder
|
7
|
+
# Returns the ObjectBuilder instance for the current feature.
|
8
|
+
@object_builder ||= Ward::Spec::ObjectBuilder.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def defined_object
|
12
|
+
# Create an oject with the named attributes and values.
|
13
|
+
object_builder.to_instance
|
14
|
+
end
|
15
|
+
|
16
|
+
Transform %r{^'(\w+[!\?]?)' attribute$} do |attribute|
|
17
|
+
attribute.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
Given %r{^a class with an? ('\w+[!\?]?' attribute)$} do |attribute|
|
21
|
+
Given "the class also has a '#{attribute}' attribute"
|
22
|
+
end
|
23
|
+
|
24
|
+
Given %r{^the class also has an? ('\w+[!\?]?' attribute)$} do |attribute|
|
25
|
+
object_builder.attributes << attribute
|
26
|
+
end
|
27
|
+
|
28
|
+
Given %r{^the instance ('\w+[!\?]?' attribute) is '(.*)'$} do |attribute, value|
|
29
|
+
unless object_builder.attributes.include?(attribute)
|
30
|
+
raise "The #{attribute.inspect} attribute was not defined"
|
31
|
+
end
|
32
|
+
|
33
|
+
value = "''" if value =~ /^\s*$/ # Empty string.
|
34
|
+
|
35
|
+
# Attempt to evaluate the value. If the evaluation fails we assume that it
|
36
|
+
# is a string which should be used literally.
|
37
|
+
object_builder.values[attribute] =
|
38
|
+
begin eval(value) ; rescue NameError ; value ; end
|
39
|
+
end
|
40
|
+
|
41
|
+
Given %r{^the class has behaviour like$} do |behaviour|
|
42
|
+
object_builder.behaviours << behaviour
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '/../../lib'))
|
2
|
+
|
3
|
+
require 'time' # Used in the CloseTo matcher features.
|
4
|
+
|
5
|
+
require 'spec'
|
6
|
+
require 'spec/expectations'
|
7
|
+
|
8
|
+
require 'ward'
|
9
|
+
require 'ward/spec'
|
10
|
+
|
11
|
+
require File.join(File.dirname(__FILE__), '/object_builder')
|
12
|
+
require File.join(File.dirname(__FILE__), '/struct')
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ward
|
2
|
+
module Spec
|
3
|
+
# An object which allows new attributes and behaviour to be easily
|
4
|
+
# declared, and for validations to be added one at a time.
|
5
|
+
class ObjectBuilder
|
6
|
+
|
7
|
+
attr_reader :attributes, :values, :behaviours
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@attributes, @values, @behaviours = [], {}, []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create an oject with the named attributes and values.
|
14
|
+
#
|
15
|
+
# @return [Ward::Spec::Struct]
|
16
|
+
#
|
17
|
+
def to_instance
|
18
|
+
@attributes = [:__placeholder__] if @attributes.empty?
|
19
|
+
|
20
|
+
instance = Ward::Spec::Struct.new(*@attributes).new(
|
21
|
+
*@attributes.map { |attribute| @values[attribute] })
|
22
|
+
|
23
|
+
unless @behaviours.empty?
|
24
|
+
metaclass = (class << instance; self; end)
|
25
|
+
@behaviours.each { |behaviour| metaclass.class_eval(behaviour) }
|
26
|
+
end
|
27
|
+
|
28
|
+
instance
|
29
|
+
end
|
30
|
+
|
31
|
+
end # ObjectBuilder
|
32
|
+
end # Spec
|
33
|
+
end # Ward
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Ward
|
2
|
+
module Spec
|
3
|
+
# Used in the object definition steps; provides a version of Struct which
|
4
|
+
# does not respond to length or size, and allows the use of predicate and
|
5
|
+
# bang methods.
|
6
|
+
class Struct < ::Struct
|
7
|
+
|
8
|
+
undef_method :length
|
9
|
+
undef_method :size
|
10
|
+
|
11
|
+
def self.new(*orig_attributes)
|
12
|
+
attributes, aliases = [], {}
|
13
|
+
|
14
|
+
orig_attributes.each do |attribute|
|
15
|
+
case attribute.to_s
|
16
|
+
when /^(.+)!$/
|
17
|
+
attributes << "#{$1}_bang".to_sym
|
18
|
+
aliases["#{$1}_bang"] = attribute
|
19
|
+
when /^(.+)\?$/
|
20
|
+
attributes << "#{$1}_predicate".to_sym
|
21
|
+
aliases["#{$1}_predicate"] = attribute
|
22
|
+
else
|
23
|
+
attributes << attribute
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
struct_class = super(*attributes)
|
28
|
+
|
29
|
+
struct_class.class_eval(aliases.map do |plain, pretty|
|
30
|
+
"alias_method(:#{pretty}, :#{plain}) ; private(:#{plain})"
|
31
|
+
end.join("\n"))
|
32
|
+
|
33
|
+
struct_class
|
34
|
+
end
|
35
|
+
|
36
|
+
end # Struct
|
37
|
+
end # Spec
|
38
|
+
end # Ward
|
data/lang/en.yml
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
generic:
|
4
|
+
inclusive_conjunction: 'and'
|
5
|
+
exclusive_conjunction: 'or'
|
6
|
+
list_seperator: ','
|
7
|
+
|
8
|
+
acceptance:
|
9
|
+
positive: '%{context} should be accepted'
|
10
|
+
negative: '%{context} should not be accepted'
|
11
|
+
|
12
|
+
close_to:
|
13
|
+
positive: '%{context} should be within %{delta} of %{expected}'
|
14
|
+
negative: '%{context} should not be within %{delta} of %{expected}'
|
15
|
+
|
16
|
+
equal_to:
|
17
|
+
positive: '%{context} should be %{expected}'
|
18
|
+
negative: '%{context} should not be %{expected}'
|
19
|
+
|
20
|
+
exclude:
|
21
|
+
positive: '%{context} should not be one of %{expected}'
|
22
|
+
negative: '%{context} should be one of %{expected}'
|
23
|
+
|
24
|
+
has:
|
25
|
+
eql:
|
26
|
+
positive: '%{context} should have %{expected} %{collection}'
|
27
|
+
negative: '%{context} should not have %{expected} %{collection}'
|
28
|
+
lte:
|
29
|
+
positive: '%{context} should have at most %{expected} %{collection}'
|
30
|
+
negative: '%{context} should not have at most %{expected} %{collection}'
|
31
|
+
gte:
|
32
|
+
positive: '%{context} should have at least %{expected} %{collection}'
|
33
|
+
negative: '%{context} should not have at least %{expected} %{collection}'
|
34
|
+
between:
|
35
|
+
positive: '%{context} should have between %{lower} and %{upper} %{collection}'
|
36
|
+
negative: '%{context} should not have between %{lower} and %{upper} %{collection}'
|
37
|
+
|
38
|
+
include:
|
39
|
+
positive: '%{context} should be %{expected}'
|
40
|
+
negative: '%{context} should not be %{expected}'
|
41
|
+
|
42
|
+
match:
|
43
|
+
positive: '%{context} format is invalid'
|
44
|
+
negative: '%{context} format is invalid'
|
45
|
+
|
46
|
+
nil:
|
47
|
+
positive: '%{context} should be nil'
|
48
|
+
negative: '%{context} should not be nil'
|
49
|
+
|
50
|
+
present:
|
51
|
+
positive: '%{context} should be present'
|
52
|
+
negative: '%{context} should not be present'
|
53
|
+
|
54
|
+
satisfy:
|
55
|
+
positive: '%{context} is invalid'
|
56
|
+
negative: '%{context} is invalid'
|
data/lib/ward.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# Add Ruby 1.9-style string interpolation.
|
4
|
+
require 'active_support/core_ext/string/interpolation'
|
5
|
+
|
6
|
+
# Load the ActiveSupport inflector without the String extensions methods.
|
7
|
+
require 'active_support/inflector/inflections'
|
8
|
+
require 'active_support/inflector/transliterate'
|
9
|
+
require 'active_support/inflector/methods'
|
10
|
+
require 'active_support/inflections'
|
11
|
+
|
12
|
+
# On with the library...
|
13
|
+
require 'ward/support'
|
14
|
+
require 'ward/context'
|
15
|
+
require 'ward/context_chain'
|
16
|
+
require 'ward/dsl'
|
17
|
+
require 'ward/errors'
|
18
|
+
require 'ward/matchers'
|
19
|
+
require 'ward/validator'
|
20
|
+
require 'ward/validator_set'
|
21
|
+
require 'ward/version'
|
22
|
+
|
23
|
+
module Ward
|
24
|
+
# Raise when a validator couldn't be built as something was missing.
|
25
|
+
class IncompleteValidator < StandardError; end
|
26
|
+
end
|
data/lib/ward/context.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Ward
|
2
|
+
# A class which represents "somewhere" from which a value can be retrieved
|
3
|
+
# for validation.
|
4
|
+
#
|
5
|
+
# A context initialized with a +:length+ attribute assumes that the value
|
6
|
+
# for validation can be retrieved by calling +length+ on the target object.
|
7
|
+
#
|
8
|
+
class Context
|
9
|
+
|
10
|
+
# Returns the name of the attribute to be validated.
|
11
|
+
#
|
12
|
+
# @return [Symbol]
|
13
|
+
#
|
14
|
+
attr_reader :attribute
|
15
|
+
|
16
|
+
# Returns the 'natural name' of the attribute.
|
17
|
+
#
|
18
|
+
# This name is used when generating error messages, since you probably
|
19
|
+
# don't want you end users to be presented with (occasionally) obscure
|
20
|
+
# attribute names.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# :name # => Name
|
24
|
+
# :a_field # => A field
|
25
|
+
# :post_id # => Post
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
attr_reader :natural_name
|
30
|
+
|
31
|
+
# Creates a new validator instance.
|
32
|
+
#
|
33
|
+
# @param [#to_sym] attribute
|
34
|
+
# The name of the attribute to be validated.
|
35
|
+
# @param [*] *context_args
|
36
|
+
# Arguments to be used when calling the context.
|
37
|
+
# @param [Block] context_block
|
38
|
+
# A block to be used when calling the context.
|
39
|
+
#
|
40
|
+
def initialize(attribute, *context_args, &context_block)
|
41
|
+
@attribute = attribute.to_sym
|
42
|
+
@context_args, @context_block = context_args, context_block
|
43
|
+
|
44
|
+
@natural_name =
|
45
|
+
ActiveSupport::Inflector.humanize(@attribute.to_s).downcase
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the value of the context for the given +target+ object.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
#
|
52
|
+
# Context.new(:length).value('abc')
|
53
|
+
# # => 3
|
54
|
+
#
|
55
|
+
# Context.new(:length_as_string) do |target|
|
56
|
+
# target.length.to_s
|
57
|
+
# end.value('abc')
|
58
|
+
# # => '3'
|
59
|
+
#
|
60
|
+
# @param [Object] target
|
61
|
+
# The object from which the value is to be retrieved.
|
62
|
+
#
|
63
|
+
# @return [Object]
|
64
|
+
#
|
65
|
+
def value(target)
|
66
|
+
target.__send__(@attribute, *@context_args, &@context_block)
|
67
|
+
end
|
68
|
+
|
69
|
+
end # Context
|
70
|
+
end # Ward
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Ward
|
2
|
+
# ContextChain combines one or more {Context} instances in order to be able
|
3
|
+
# to retrieve values from composed objects.
|
4
|
+
#
|
5
|
+
# For example, if the chain contains two contexts, the first with a +length+
|
6
|
+
# attribute, and the second with a +to_s+ attribute, the chain would resolve
|
7
|
+
# to calling +target.length.to_s+ in order to retrieve a value for
|
8
|
+
# validation.
|
9
|
+
#
|
10
|
+
class ContextChain
|
11
|
+
|
12
|
+
# Creates a new ContextChain instance.
|
13
|
+
#
|
14
|
+
def initialize
|
15
|
+
@contexts = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the name of the attribute to be validated.
|
19
|
+
#
|
20
|
+
# Returns the attribute for the first context. If the chain is empty,
|
21
|
+
# :base is always returned.
|
22
|
+
#
|
23
|
+
# @return [Symbol]
|
24
|
+
#
|
25
|
+
# @see Context#attribute
|
26
|
+
#
|
27
|
+
def attribute
|
28
|
+
@contexts.empty? ? :base : @contexts.first.attribute
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the 'natural name' of the contained contexts.
|
32
|
+
#
|
33
|
+
# @return [String]
|
34
|
+
#
|
35
|
+
# @see Context#natural_name
|
36
|
+
#
|
37
|
+
def natural_name
|
38
|
+
@contexts.map { |context| context.natural_name }.join(' ')
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the value of the chain for the given +target+ object.
|
42
|
+
#
|
43
|
+
# @param [Object] target
|
44
|
+
# The object from which the value is to be retrieved.
|
45
|
+
#
|
46
|
+
# @return [Object]
|
47
|
+
#
|
48
|
+
def value(target)
|
49
|
+
if @contexts.size > 1
|
50
|
+
resolved = @contexts[0..-2].inject(target) do |intermediate, context|
|
51
|
+
context.value(intermediate) unless intermediate.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
raise ArgumentError,
|
55
|
+
"Couldn't retrieve a value for #{natural_name.downcase}; " \
|
56
|
+
"something along the way evaluated to nil" if resolved.nil?
|
57
|
+
|
58
|
+
@contexts.last.value(resolved)
|
59
|
+
elsif @contexts.size == 1
|
60
|
+
@contexts.first.value(target)
|
61
|
+
else
|
62
|
+
target
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the contexts contained in the chain as an Array.
|
67
|
+
#
|
68
|
+
# @return [Array<Ward::Context>]
|
69
|
+
# An array containing the contexts.
|
70
|
+
#
|
71
|
+
def to_a
|
72
|
+
@contexts.dup
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds a new context to the end of the chain.
|
76
|
+
#
|
77
|
+
# @param [Ward::Context] context
|
78
|
+
# The context to be added to the chain.
|
79
|
+
#
|
80
|
+
def push(context)
|
81
|
+
@contexts << context
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :<<, :push
|
85
|
+
|
86
|
+
end # ContextChain
|
87
|
+
end # Ward
|