summaryrefslogtreecommitdiff
path: root/lib/chef/node/attribute_collections.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/node/attribute_collections.rb')
-rw-r--r--lib/chef/node/attribute_collections.rb191
1 files changed, 191 insertions, 0 deletions
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
new file mode 100644
index 0000000000..521687e8df
--- /dev/null
+++ b/lib/chef/node/attribute_collections.rb
@@ -0,0 +1,191 @@
+#--
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Node
+
+ # == AttrArray
+ # AttrArray is identical to Array, except that it keeps a reference to the
+ # "root" (Chef::Node::Attribute) object, and will trigger a cache
+ # invalidation on that object when mutated.
+ class AttrArray < Array
+
+ MUTATOR_METHODS = [
+ :<<,
+ :[]=,
+ :clear,
+ :collect!,
+ :compact!,
+ :default=,
+ :default_proc=,
+ :delete,
+ :delete_at,
+ :delete_if,
+ :fill,
+ :flatten!,
+ :insert,
+ :keep_if,
+ :map!,
+ :merge!,
+ :pop,
+ :push,
+ :update,
+ :reject!,
+ :reverse!,
+ :replace,
+ :select!,
+ :shift,
+ :slice!,
+ :sort!,
+ :sort_by!,
+ :uniq!,
+ :unshift
+ ]
+
+ # For all of the methods that may mutate an Array, we override them to
+ # also invalidate the cached merged_attributes on the root
+ # Node::Attribute object.
+ MUTATOR_METHODS.each do |mutator|
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator}(*args, &block)
+ root.reset_cache
+ super
+ end
+ METHOD_DEFN
+ end
+
+ attr_reader :root
+
+ def initialize(root, data)
+ @root = root
+ super(data)
+ end
+
+ end
+
+ # == VividMash
+ # VividMash is identical to a Mash, with a few exceptions:
+ # * It has a reference to the root Chef::Node::Attribute to which it
+ # belongs, and will trigger cache invalidation on that object when
+ # mutated.
+ # * It auto-vivifies, that is a reference to a missing element will result
+ # in the creation of a new VividMash for that key. (This only works when
+ # using the element reference method, `[]` -- other methods, such as
+ # #fetch, work as normal).
+ # * It supports a set_unless flag (via the root Attribute object) which
+ # allows `||=` style behavior (`||=` does not work with
+ # auto-vivification). This is only implemented for #[]=; methods such as
+ # #store work as normal.
+ # * attr_accessor style element set and get are supported via method_missing
+ class VividMash < Mash
+ attr_reader :root
+
+ # Methods that mutate a VividMash. Each of them is overridden so that it
+ # also invalidates the cached merged_attributes on the root Attribute
+ # object.
+ MUTATOR_METHODS = [
+ :clear,
+ :delete,
+ :delete_if,
+ :keep_if,
+ :merge!,
+ :update,
+ :reject!,
+ :replace,
+ :select!,
+ :shift
+ ]
+
+ # For all of the mutating methods on Mash, override them so that they
+ # also invalidate the cached `merged_attributes` on the root Attribute
+ # object.
+ MUTATOR_METHODS.each do |mutator|
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator}(*args, &block)
+ root.reset_cache
+ super
+ end
+ METHOD_DEFN
+ end
+
+ def initialize(root, data={})
+ @root = root
+ super(data)
+ end
+
+ def [](key)
+ value = super
+ if !key?(key)
+ value = self.class.new(root)
+ self[key] = value
+ else
+ value
+ end
+ end
+
+ def []=(key, value)
+ if set_unless? && key?(key)
+ self[key]
+ else
+ root.reset_cache
+ super
+ end
+ end
+
+ alias :attribute? :has_key?
+
+ def method_missing(symbol, *args)
+ if args.empty?
+ self[symbol]
+ elsif symbol.to_s =~ /=$/
+ key_to_set = symbol.to_s[/^(.+)=$/, 1]
+ self[key_to_set] = (args.length == 1 ? args[0] : args)
+ else
+ raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'. To set an attribute, use `#{symbol}=value' instead."
+ end
+ end
+
+ def set_unless?
+ @root.set_unless?
+ end
+
+ def convert_key(key)
+ super
+ end
+
+ # Mash uses #convert_value to mashify values on input.
+ # We override it here to convert hash or array values to VividMash or
+ # AttrArray for consistency and to ensure that the added parts of the
+ # attribute tree will have the correct cache invalidation behavior.
+ def convert_value(value)
+ case value
+ when VividMash
+ value
+ when Hash
+ VividMash.new(root, value)
+ when Array
+ AttrArray.new(root, value)
+ else
+ value
+ end
+ end
+
+ end
+
+ end
+end