lib/configurable/config_hash.rb in configurable-0.7.0 vs lib/configurable/config_hash.rb in configurable-1.0.0
- old
+ new
@@ -1,114 +1,76 @@
-require 'configurable/nest_config'
-
module Configurable
# ConfigHash acts like a hash that maps get and set operations as specified
- # in a Configurable's class configurations.
- #
- # class Sample
- # include Configurable
- # config :key
- # end
- #
- # sample = Sample.new
- # sample.config.class # => ConfigHash
- #
- # sample.key = 'value'
- # sample.config[:key] # => 'value'
- #
- # sample.config[:key] = 'another'
- # sample.key # => 'another'
- #
- # Non-configuration keys are sent to an underlying data store:
- #
- # sample.config[:not_delegated] = 'value'
- # sample.config[:not_delegated] # => 'value'
- #
- # sample.config.store # => {:not_delegated => 'value'}
- # sample.config.to_hash # => {:key => 'another', :not_delegated => 'value'}
- #
- # ==== IndifferentAccess
- #
- # A ConfigHash uses the receiver class configurations to determine when and
- # how to map get/set operations. In cases where multiple keys need to map
- # in the same way (for example when you want indifferent access for strings
- # and symbols), simply extend the class configurations so that the AGET ([])
- # method returns the correct Config in all cases.
- #
- # ==== Inconsistency
- #
- # ConfigHashes can fall into an inconsistent state if you manually add values
- # to store that would normally be mapped to the receiver. This is both easy
- # to avoid and easy to repair.
- #
- # To avoid inconsistency, don't manually add values to the store and set
- # import_store to true during initialization. To repair inconsistency,
- # import the current store to self.
- #
- # config_hash = Sample.new.config
- # config_hash[:key] = 'a'
- # config_hash.store[:key] = 'b'
- #
- # config_hash[:key] # => 'a'
- # config_hash.to_hash # => {:key => 'b'}
- # config_hash.inconsistent? # => true
- #
- # config_hash.import(config_hash.store)
- #
- # config_hash[:key] # => 'b'
- # config_hash.to_hash # => {:key => 'b'}
- # config_hash.inconsistent? # => false
- #
+ # in by a receiver's class configurations. Non-configuration keys are sent
+ # to an underlying data store.
class ConfigHash
-
+ # A frozen empty hash returned by configs for unbound config hashes.
+ EMPTY_HASH = {}.freeze
+
# The bound receiver
attr_reader :receiver
-
- # The underlying data store; setting values in store directly
- # can result in an inconsistent state. Use []= instead.
+
+ # The underlying data store; setting values in store directly can result
+ # in an inconsistent state. Use []= instead.
attr_reader :store
-
- # Initializes a new ConfigHash. Initialize normally imports values from
- # store to ensure it doesn't contain entries that could be stored on the
- # receiver.
- #
- # Setting import_store to false allows quick initialization but can result
- # in an inconsistent state.
- def initialize(receiver, store={}, import_store=true)
- @receiver = receiver
+
+ # Initializes a new ConfigHash.
+ def initialize(store={}, receiver=nil)
@store = store
-
- import(store) if import_store
+ @receiver = receiver
end
- # Returns receiver.class.configurations.
- def configs
- receiver.class.configurations
- end
-
- # Imports stored values that can be mapped to the receiver. The values
- # are removed from store in the process. Returns self.
+ # Binds the configs in store to the receiver by setting each on the
+ # receiver (via config.set). Bound configs are removed from store.
#
- # Primarily used to create a consistent state for self (see above).
- def import(store)
- configs = self.configs # cache as an optimization
- store.keys.each do |key|
- next unless config = configs[key]
- config.set(receiver, store.delete(key))
+ # Unbinds self from the current receiver, if needed.
+ def bind(receiver)
+ unbind if bound?
+
+ @receiver = receiver
+ configs.each_pair do |key, config|
+ value = store.has_key?(key) ? store.delete(key) : config.default
+ config.set(receiver, value)
end
self
end
- # Returns true if the store has entries that can be stored on the
- # receiver.
- def inconsistent?
- configs = self.configs # cache as an optimization
- store.keys.any? {|key| configs[key] }
+ # Unbinds the configs set on receiver by getting each value (via
+ # config.get) and setting the result into store. Does nothing if no
+ # receiver is set.
+ def unbind
+ if bound?
+ configs.each_pair do |key, config|
+ store[key] = config.get(receiver)
+ end
+ @receiver = nil
+ end
+ self
end
+ # Returns true if bound to a receiver.
+ def bound?
+ @receiver ? true : false
+ end
+
+ # Returns the class configs for the bound receiver, or an empty hash if
+ # unbound (specifically EMPTY_HASH).
+ def configs
+ # Caching here is not necessary or preferred as configurations are
+ # cached on the class (which allows late inclusion of configurable
+ # modules to work properly).
+ bound? ? receiver.class.configs : EMPTY_HASH
+ end
+
+ # Returns true if bound to a receiver and no configs values are set in
+ # store (ie all config values are stored on the receiver).
+ def consistent?
+ bound? && (store.keys & configs.keys).empty?
+ end
+
# Retrieves the value for the key, either from the receiver or the store.
def [](key)
if config = configs[key]
config.get(receiver)
else
@@ -127,30 +89,27 @@
# Returns the union of configs and store keys.
def keys
configs.keys | store.keys
end
-
+
# True if the key is a key in configs or store.
def has_key?(key)
- configs[key] != nil || store.has_key?(key)
+ configs.has_key?(key) || store.has_key?(key)
end
# Merges another with self.
def merge!(another)
- # cache configs and inline set as a significant optimization
configs = self.configs
- (configs.keys | another.keys).each do |key|
- next unless another.has_key?(key)
-
- value = another[key]
+ another.each_pair do |key, value|
if config = configs[key]
config.set(receiver, value)
else
store[key] = value
end
end
+ self
end
# Calls block once for each key-value pair stored in self.
def each_pair # :yields: key, value
configs.each_pair do |key, config|
@@ -164,33 +123,49 @@
# Equal if the to_hash values of self and another are equal.
def ==(another)
another.respond_to?(:to_hash) && to_hash == another.to_hash
end
-
+
# Returns self as a hash. Any ConfigHash values are recursively
# hashified, to account for nesting.
- def to_hash(scrub=false, &block)
+ def to_hash
hash = {}
each_pair do |key, value|
if value.kind_of?(ConfigHash)
- value = value.to_hash(scrub, &block)
+ value = value.to_hash
end
- if scrub
- config = configs[key]
- next if config && config.default == value
- end
-
- if block_given?
- yield(hash, key, value)
- else
- hash[key] = value
- end
+ hash[key] = value
end
hash
end
-
+
+ # Returns self exported as a hash of raw configs (ie name keys and uncast
+ # values).
+ def export
+ configs.export(to_hash)
+ end
+
+ # Imports a hash of raw configs (ie name keys and uncast values) and
+ # merges the result with self.
+ def import(another)
+ merge! configs.import(another)
+ end
+
+ def parse(argv=ARGV, options={}, &block)
+ parse!(argv.dup, options, &block)
+ end
+
+ def parse!(argv=ARGV, options={}, &block)
+ parser(options, &block).parse!(argv)
+ end
+
+ def parser(options={}, &block)
+ options = {:assign_defaults => false}.merge(options)
+ configs.to_parser(self, options, &block)
+ end
+
# Returns an inspection string.
def inspect
"#<#{self.class}:#{object_id} to_hash=#{to_hash.inspect}>"
end
end
\ No newline at end of file