summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2015-06-15 17:24:41 -0700
committerJohn Keiser <john@johnkeiser.com>2015-06-18 15:26:09 -0700
commit20a953d0dbc16d6a0f095a9bf0096175c3727ed9 (patch)
treebdd2bf87c1a59912a409cf49cafb2a185b5068b5
parent36b2a267f14969c73a3d4d5410cacc461523c291 (diff)
downloadchef-20a953d0dbc16d6a0f095a9bf0096175c3727ed9.tar.gz
Add property computed: proc { ... } for non-sticky value
-rw-r--r--lib/chef/mixin/params_validate.rb39
-rw-r--r--spec/unit/property_spec.rb145
2 files changed, 170 insertions, 14 deletions
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 3edc761ffc..c5836122a2 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -97,6 +97,8 @@ class Chef
else
default = NOT_PASSED
end
+ computed = validation.delete(:computed)
+ computed ||= validation.delete('computed')
coerce = validation.delete(:coerce)
coerce ||= validation.delete('coerce')
name_property = validation.delete(:name_property)
@@ -131,8 +133,12 @@ class Chef
# Get the default value
else
_pv_required(opts, symbol, required, explicitly_allows_nil?(symbol, validation)) if required
- _pv_default(opts, symbol, default) unless default == NOT_PASSED
- _pv_name_property(opts, symbol, name_property)
+ if !opts.has_key?(symbol)
+ got_default = true
+ _pv_computed(opts, symbol, computed)
+ _pv_default(opts, symbol, default) unless default == NOT_PASSED
+ _pv_name_property(opts, symbol, name_property)
+ end
if opts.has_key?(symbol)
# Handle lazy defaults.
@@ -150,7 +156,7 @@ class Chef
validate(opts, { symbol => validation })
# Defaults are presently "stickily" set on the instance
- self.instance_variable_set(iv_symbol, opts[symbol])
+ self.instance_variable_set(iv_symbol, opts[symbol]) unless got_default && computed
end
end
@@ -321,6 +327,33 @@ class Chef
end
#
+ # A computed default value (non-sticky).
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, computed: proc { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, computed: proc { |r| r.x+2 }
+ # ```
+ #
+ def _pv_computed(opts, key, computed_value)
+ if !opts.has_key?(key) && computed_value
+ if computed_value.arity >= 1
+ opts[key] = computed_value.call(self)
+ else
+ opts[key] = instance_eval(&computed_value)
+ end
+ end
+ end
+
+ #
# List of regexes values that must match.
#
# Uses regex.match() to evaluate. At least one must match for the value to
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
index 8c01bf7b49..f048355269 100644
--- a/spec/unit/property_spec.rb
+++ b/spec/unit/property_spec.rb
@@ -281,9 +281,9 @@ describe "Chef::Resource.property" do
resource.x lazy { 10 }
expect(resource.property_is_set?(:x)).to be_truthy
end
- it "when x is retrieved, property_is_set?(:x) is false" do
+ it "when x is retrieved, property_is_set?(:x) is true" do
resource.x
- expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.property_is_set?(:x)).to be_truthy
end
end
@@ -367,8 +367,7 @@ describe "Chef::Resource.property" do
expect(resource.x.object_id).to eq(value.object_id)
end
it "Multiple instances of x receive the exact same value" do
- # TODO this isn't really great behavior, but it's noted here so we find out
- # if it changed.
+ # Bleagh. But it's what we do.
expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id)
end
end
@@ -377,11 +376,11 @@ describe "Chef::Resource.property" do
it "when x is not set, it returns {}" do
expect(resource.x).to eq({})
end
- # it "The value is different each time it is called" do
- # value = resource.x
- # expect(value).to eq({})
- # expect(resource.x.object_id).not_to eq(value.object_id)
- # end
+ it "The value is the same each time it is called" do
+ value = resource.x
+ expect(value).to eq({})
+ expect(resource.x.object_id).to eq(value.object_id)
+ end
it "Multiple instances of x receive different values" do
expect(resource.x.object_id).not_to eq(resource_class.new('blah2').x.object_id)
end
@@ -407,7 +406,7 @@ describe "Chef::Resource.property" do
it "x is run in the context of each instance it is run in" do
expect(resource.x).to eq "blah1"
expect(resource_class.new('another').x).to eq "another2"
- # expect(resource.x).to eq "blah3"
+ expect(resource.x).to eq "blah1"
end
end
@@ -418,7 +417,7 @@ describe "Chef::Resource.property" do
it "x is passed the value of each instance it is run in" do
expect(resource.x).to eq "classblah1"
expect(resource_class.new('another').x).to eq "classanother2"
- # expect(resource.x).to eq "classblah3"
+ expect(resource.x).to eq "classblah1"
end
end
end
@@ -535,6 +534,130 @@ describe "Chef::Resource.property" do
end
end
+ context "Chef::Resource::PropertyType#computed" do
+ context "hash computed default" do
+ with_property ':x, computed: proc { {} }' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ it "The value is different each time it is called" do
+ value = resource.x
+ expect(value).to eq({})
+ expect(resource.x.object_id).not_to eq(value.object_id)
+ end
+ it "Multiple instances of x receive different values" do
+ expect(resource.x.object_id).not_to eq(resource_class.new('blah2').x.object_id)
+ end
+ end
+ end
+
+ context "with a class with 'blah' as both class and instance methods" do
+ before do
+ resource_class.class_eval do
+ def self.blah
+ 'class'
+ end
+ def blah
+ "#{name}#{next_index}"
+ end
+ end
+ end
+
+ with_property ':x, computed: proc { blah }' do
+ it "x is run in context of the instance" do
+ expect(resource.x).to eq "blah1"
+ end
+ it "x is run in the context of each instance it is run in" do
+ expect(resource.x).to eq "blah1"
+ expect(resource_class.new('another').x).to eq "another2"
+ expect(resource.x).to eq "blah3"
+ end
+ end
+
+ with_property ':x, computed: proc { |x| "#{blah}#{x.blah}" }' do
+ it "x is run in context of the class (where it was defined) and passed the instance" do
+ expect(resource.x).to eq "classblah1"
+ end
+ it "x is passed the value of each instance it is run in" do
+ expect(resource.x).to eq "classblah1"
+ expect(resource_class.new('another').x).to eq "classanother2"
+ expect(resource.x).to eq "classblah3"
+ end
+ end
+ end
+
+ context "validation of computed defaults" do
+ with_property ":x, String, computed: proc { Namer.next_index }" do
+ it "when the resource is created, no error is raised" do
+ resource
+ end
+ it "when x is set, no error is raised" do
+ expect(resource.x 'hi').to eq 'hi'
+ expect(resource.x).to eq 'hi'
+ end
+ it "when x is retrieved, a validation error is raised" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ":x, computed: proc { Namer.next_index }, is: proc { |v| Namer.next_index; true }" do
+ it "validation is run each time" do
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq 3
+ expect(Namer.current_index).to eq 4
+ end
+ end
+ end
+
+ context "coercion of computed defaults" do
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do
+ it "coercion is run each time x is retrieved" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq '103'
+ expect(Namer.current_index).to eq 4
+ end
+ end
+
+ context "validation and coercion of computed defaults" do
+ with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do
+ it "when x is retrieved, it is coerced before validating and passes" do
+ expect(resource.x).to eq '101'
+ end
+ end
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do
+ it "when x is retrieved, it is coerced before validating and fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, computed: proc { 10 }' do
+ it "coercion and validation is only run the first time x is retrieved" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq '103'
+ expect(Namer.current_index).to eq 4
+ end
+ end
+ end
+ end
+ end
+
context "Chef::Resource#lazy" do
with_property ':x' do
it "setting x to a lazy value does not run it immediately" do