diff options
author | danielsdeleo <dan@opscode.com> | 2012-10-09 08:21:25 -0700 |
---|---|---|
committer | Bryan McLellan <btm@opscode.com> | 2012-10-15 16:09:34 -0700 |
commit | 02963ffd7184e23e312c4e6df9f6b31d2f97ff54 (patch) | |
tree | 18081a5f81268678ffac0a5901deaa4d50b4941d /chef/lib/chef/node | |
parent | 8b7614c49425fce9bb7cf4907a0623138f924e63 (diff) | |
download | chef-02963ffd7184e23e312c4e6df9f6b31d2f97ff54.tar.gz |
[CHEF-1804] move immutable collections to their own file
Diffstat (limited to 'chef/lib/chef/node')
-rw-r--r-- | chef/lib/chef/node/attribute.rb | 379 | ||||
-rw-r--r-- | chef/lib/chef/node/immutable_collections.rb | 385 |
2 files changed, 386 insertions, 378 deletions
diff --git a/chef/lib/chef/node/attribute.rb b/chef/lib/chef/node/attribute.rb index 641692c28e..e12b1b7043 100644 --- a/chef/lib/chef/node/attribute.rb +++ b/chef/lib/chef/node/attribute.rb @@ -17,6 +17,7 @@ # limitations under the License. # +require 'chef/node/immutable_collections' require 'chef/mixin/deep_merge' require 'chef/log' @@ -31,384 +32,6 @@ class Chef class StaleAttributeRead < StandardError end - module Immutablize - def immutablize(root, value) - case value - when Hash - ImmutableMash.new(root, value) - when Array - ImmutableArray.new(root, value) - else - value - end - end - end - - # == ImmutableArray - # ImmutableArray is used to implement Array collections when reading node - # attributes. - # - # ImmutableArray acts like an ordinary Array, except: - # * Methods that mutate the array are overridden to raise an error, making - # the collection more or less immutable. - # * Since this class stores values computed from a parent - # Chef::Node::Attribute's values, it overrides all reader methods to - # detect staleness and raise an error if accessed when stale. - class ImmutableArray < Array - include Immutablize - - attr_reader :root - - alias :internal_push :<< - private :internal_push - - # A list of methods that mutate Array. Each of these is overridden to - # raise an error, making this instances of this class more or less - # immutable. - DISALLOWED_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 - ] - - # A list of methods that read values from the Array. Each of these is - # overridden to verify that the Chef::Node::Attribute object that this - # object belongs to has not been modified since the value was computed. - READER_METHODS = - [ - :&, - :*, - :+, - :-, - :[], - :all?, - :any?, - :assoc, - :at, - :chunk, - :collect, - :collect_concat, - :combination, - :compact, - :concat, - :count, - :cycle, - :detect, - :drop, - :drop_while, - :each, - :each_cons, - :each_entry, - :each_index, - :each_slice, - :each_with_index, - :each_with_object, - :empty?, - :entries, - :fetch, - :find, - :find_all, - :find_index, - :first, - :flat_map, - :flatten, - :grep, - :group_by, - :include?, - :index, - :inject, - :join, - :last, - :length, - :map, - :max, - :max_by, - :member?, - :min, - :min_by, - :minmax, - :minmax_by, - :none?, - :one?, - :pack, - :partition, - :permutation, - :product, - :rassoc, - :reduce, - :reject, - :repeated_combination, - :repeated_permutation, - :reverse, - :reverse_each, - :rindex, - :rotate, - :sample, - :select, - :shelljoin, - :shuffle, - :size, - :slice, - :slice_before, - :sort, - :sort_by, - :take, - :take_while, - :to_a, - :to_ary, - :to_set, - :transpose, - :uniq, - :values_at, - :zip, - :| - ] - - def initialize(root, array_data) - @root = root - @serial_number = root.serial_number - array_data.each do |value| - internal_push(immutablize(root, value)) - end - end - - # Redefine all of the methods that mutate a Hash to raise an error when called. - # This is the magic that makes this object "Immutable" - DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| - # Ruby 1.8 blocks can't have block arguments, so we must use string eval: - class_eval(<<-METHOD_DEFN) - def #{mutator_method_name}(*args, &block) - msg = "Node attributes are read-only when you do not specify which precedence level to set. " + - %Q(To set an attribute use code like `node.default["key"] = "value"') - raise ImmutableAttributeModification, msg - end - METHOD_DEFN - end - - READER_METHODS.each do |reader| - class_eval(<<-METHOD_DEFN) - def #{reader}(*args, &block) - if root.stale_subtree?(@serial_number) - raise StaleAttributeRead, "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`" - end - super - end - METHOD_DEFN - end - - def dup - Array.new(self) - end - end - - # == ImmutableMash - # ImmutableMash implements Hash/Dict behavior for reading values from node - # attributes. - # - # ImmutableMash acts like a Mash (Hash that is indifferent to String or - # Symbol keys), with some important exceptions: - # * Methods that mutate state are overridden to raise an error instead. - # * Methods that read from the collection are overriden so that they check - # if the Chef::Node::Attribute has been modified since an instance of - # this class was generated. An error is raised if the object detects that - # it is stale. - # * Values can be accessed in attr_reader-like fashion via method_missing. - class ImmutableMash < Mash - - include Immutablize - - attr_reader :root - - alias :internal_set :[]= - private :internal_set - - DISALLOWED_MUTATOR_METHODS = [ - :[]=, - :clear, - :collect!, - :default=, - :default_proc=, - :delete, - :delete_if, - :keep_if, - :map!, - :merge!, - :update, - :reject!, - :replace, - :select!, - :shift - ] - - READER_METHODS = [ - :[], - :all?, - :any?, - :assoc, - :chunk, - :collect, - :collect_concat, - :count, - :cycle, - :detect, - :drop, - :drop_while, - :each, - :each_cons, - :each_entry, - :each_key, - :each_pair, - :each_slice, - :each_value, - :each_with_index, - :each_with_object, - :empty?, - :entries, - :except, - :fetch, - :find, - :find_all, - :find_index, - :first, - :flat_map, - :flatten, - :grep, - :group_by, - :has_key?, - :has_value?, - :include?, - :index, - :inject, - :invert, - :key, - :key?, - :keys, - :length, - :map, - :max, - :max_by, - :member?, - :merge, - :min, - :min_by, - :minmax, - :minmax_by, - :none?, - :one?, - :partition, - :rassoc, - :reduce, - :reject, - :reverse_each, - :select, - :size, - :slice_before, - :sort, - :sort_by, - :store, - :symbolize_keys, - :take, - :take_while, - :to_a, - :to_hash, - :to_set, - :value?, - :values, - :values_at, - :zip - ] - - def initialize(root, mash_data) - @serial_number = root.serial_number - @root = root - mash_data.each do |key, value| - internal_set(key, immutablize(root, value)) - end - end - - alias :attribute? :has_key? - - # Redefine all of the methods that mutate a Hash to raise an error when called. - # This is the magic that makes this object "Immutable" - DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| - # Ruby 1.8 blocks can't have block arguments, so we must use string eval: - class_eval(<<-METHOD_DEFN) - def #{mutator_method_name}(*args, &block) - msg = "Node attributes are read-only when you do not specify which precedence level to set. " + - %Q(To set an attribute use code like `node.default["key"] = "value"') - raise ImmutableAttributeModification, msg - end - METHOD_DEFN - end - - READER_METHODS.each do |reader_method| - class_eval(<<-METHOD_DEFN) - def #{reader_method}(*args, &block) - if root.stale_subtree?(@serial_number) - raise StaleAttributeRead, "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`" - end - super - end - METHOD_DEFN - end - - def method_missing(symbol, *args) - if args.empty? - if key?(symbol) - self[symbol] - else - raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'" - end - # This will raise a ImmutableAttributeModification error: - 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'" - end - end - - # Mash uses #convert_value to mashify values on input. - # Since we're handling this ourselves, override it to be a no-op - def convert_value(value) - value - end - - # NOTE: #default and #default= are likely to be pretty confusing. For a - # regular ruby Hash, they control what value is returned for, e.g., - # hash[:no_such_key] #=> hash.default - # Of course, 'default' has a specific meaning in Chef-land - - def dup - Mash.new(self) - end - end class Node diff --git a/chef/lib/chef/node/immutable_collections.rb b/chef/lib/chef/node/immutable_collections.rb new file mode 100644 index 0000000000..80376ede82 --- /dev/null +++ b/chef/lib/chef/node/immutable_collections.rb @@ -0,0 +1,385 @@ + +class Chef + class Node + + module Immutablize + def immutablize(root, value) + case value + when Hash + ImmutableMash.new(root, value) + when Array + ImmutableArray.new(root, value) + else + value + end + end + end + + # == ImmutableArray + # ImmutableArray is used to implement Array collections when reading node + # attributes. + # + # ImmutableArray acts like an ordinary Array, except: + # * Methods that mutate the array are overridden to raise an error, making + # the collection more or less immutable. + # * Since this class stores values computed from a parent + # Chef::Node::Attribute's values, it overrides all reader methods to + # detect staleness and raise an error if accessed when stale. + class ImmutableArray < Array + include Immutablize + + attr_reader :root + + alias :internal_push :<< + private :internal_push + + # A list of methods that mutate Array. Each of these is overridden to + # raise an error, making this instances of this class more or less + # immutable. + DISALLOWED_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 + ] + + # A list of methods that read values from the Array. Each of these is + # overridden to verify that the Chef::Node::Attribute object that this + # object belongs to has not been modified since the value was computed. + READER_METHODS = + [ + :&, + :*, + :+, + :-, + :[], + :all?, + :any?, + :assoc, + :at, + :chunk, + :collect, + :collect_concat, + :combination, + :compact, + :concat, + :count, + :cycle, + :detect, + :drop, + :drop_while, + :each, + :each_cons, + :each_entry, + :each_index, + :each_slice, + :each_with_index, + :each_with_object, + :empty?, + :entries, + :fetch, + :find, + :find_all, + :find_index, + :first, + :flat_map, + :flatten, + :grep, + :group_by, + :include?, + :index, + :inject, + :join, + :last, + :length, + :map, + :max, + :max_by, + :member?, + :min, + :min_by, + :minmax, + :minmax_by, + :none?, + :one?, + :pack, + :partition, + :permutation, + :product, + :rassoc, + :reduce, + :reject, + :repeated_combination, + :repeated_permutation, + :reverse, + :reverse_each, + :rindex, + :rotate, + :sample, + :select, + :shelljoin, + :shuffle, + :size, + :slice, + :slice_before, + :sort, + :sort_by, + :take, + :take_while, + :to_a, + :to_ary, + :to_set, + :transpose, + :uniq, + :values_at, + :zip, + :| + ] + + def initialize(root, array_data) + @root = root + @serial_number = root.serial_number + array_data.each do |value| + internal_push(immutablize(root, value)) + end + end + + # Redefine all of the methods that mutate a Hash to raise an error when called. + # This is the magic that makes this object "Immutable" + DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| + # Ruby 1.8 blocks can't have block arguments, so we must use string eval: + class_eval(<<-METHOD_DEFN) + def #{mutator_method_name}(*args, &block) + msg = "Node attributes are read-only when you do not specify which precedence level to set. " + + %Q(To set an attribute use code like `node.default["key"] = "value"') + raise ImmutableAttributeModification, msg + end + METHOD_DEFN + end + + READER_METHODS.each do |reader| + class_eval(<<-METHOD_DEFN) + def #{reader}(*args, &block) + if root.stale_subtree?(@serial_number) + raise StaleAttributeRead, "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`" + end + super + end + METHOD_DEFN + end + + def dup + Array.new(self) + end + end + + # == ImmutableMash + # ImmutableMash implements Hash/Dict behavior for reading values from node + # attributes. + # + # ImmutableMash acts like a Mash (Hash that is indifferent to String or + # Symbol keys), with some important exceptions: + # * Methods that mutate state are overridden to raise an error instead. + # * Methods that read from the collection are overriden so that they check + # if the Chef::Node::Attribute has been modified since an instance of + # this class was generated. An error is raised if the object detects that + # it is stale. + # * Values can be accessed in attr_reader-like fashion via method_missing. + class ImmutableMash < Mash + + include Immutablize + + attr_reader :root + + alias :internal_set :[]= + private :internal_set + + DISALLOWED_MUTATOR_METHODS = [ + :[]=, + :clear, + :collect!, + :default=, + :default_proc=, + :delete, + :delete_if, + :keep_if, + :map!, + :merge!, + :update, + :reject!, + :replace, + :select!, + :shift + ] + + READER_METHODS = [ + :[], + :all?, + :any?, + :assoc, + :chunk, + :collect, + :collect_concat, + :count, + :cycle, + :detect, + :drop, + :drop_while, + :each, + :each_cons, + :each_entry, + :each_key, + :each_pair, + :each_slice, + :each_value, + :each_with_index, + :each_with_object, + :empty?, + :entries, + :except, + :fetch, + :find, + :find_all, + :find_index, + :first, + :flat_map, + :flatten, + :grep, + :group_by, + :has_key?, + :has_value?, + :include?, + :index, + :inject, + :invert, + :key, + :key?, + :keys, + :length, + :map, + :max, + :max_by, + :member?, + :merge, + :min, + :min_by, + :minmax, + :minmax_by, + :none?, + :one?, + :partition, + :rassoc, + :reduce, + :reject, + :reverse_each, + :select, + :size, + :slice_before, + :sort, + :sort_by, + :store, + :symbolize_keys, + :take, + :take_while, + :to_a, + :to_hash, + :to_set, + :value?, + :values, + :values_at, + :zip + ] + + def initialize(root, mash_data) + @serial_number = root.serial_number + @root = root + mash_data.each do |key, value| + internal_set(key, immutablize(root, value)) + end + end + + alias :attribute? :has_key? + + # Redefine all of the methods that mutate a Hash to raise an error when called. + # This is the magic that makes this object "Immutable" + DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| + # Ruby 1.8 blocks can't have block arguments, so we must use string eval: + class_eval(<<-METHOD_DEFN) + def #{mutator_method_name}(*args, &block) + msg = "Node attributes are read-only when you do not specify which precedence level to set. " + + %Q(To set an attribute use code like `node.default["key"] = "value"') + raise ImmutableAttributeModification, msg + end + METHOD_DEFN + end + + READER_METHODS.each do |reader_method| + class_eval(<<-METHOD_DEFN) + def #{reader_method}(*args, &block) + if root.stale_subtree?(@serial_number) + raise StaleAttributeRead, "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`" + end + super + end + METHOD_DEFN + end + + def method_missing(symbol, *args) + if args.empty? + if key?(symbol) + self[symbol] + else + raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'" + end + # This will raise a ImmutableAttributeModification error: + 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'" + end + end + + # Mash uses #convert_value to mashify values on input. + # Since we're handling this ourselves, override it to be a no-op + def convert_value(value) + value + end + + # NOTE: #default and #default= are likely to be pretty confusing. For a + # regular ruby Hash, they control what value is returned for, e.g., + # hash[:no_such_key] #=> hash.default + # Of course, 'default' has a specific meaning in Chef-land + + def dup + Mash.new(self) + end + end + + end +end |