hashie-pre 2.0.0.beta

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.
@@ -0,0 +1,150 @@
1
+ require 'hashie/hash'
2
+ require 'set'
3
+
4
+ module Hashie
5
+ # A Dash is a 'defined' or 'discrete' Hash, that is, a Hash
6
+ # that has a set of defined keys that are accessible (with
7
+ # optional defaults) and only those keys may be set or read.
8
+ #
9
+ # Dashes are useful when you need to create a very simple
10
+ # lightweight data object that needs even fewer options and
11
+ # resources than something like a DataMapper resource.
12
+ #
13
+ # It is preferrable to a Struct because of the in-class
14
+ # API for defining properties as well as per-property defaults.
15
+ class Dash < Hashie::Hash
16
+ include Hashie::PrettyInspect
17
+ alias_method :to_s, :inspect
18
+
19
+ # Defines a property on the Dash. Options are
20
+ # as follows:
21
+ #
22
+ # * <tt>:default</tt> - Specify a default value for this property,
23
+ # to be returned before a value is set on the property in a new
24
+ # Dash.
25
+ #
26
+ # * <tt>:required</tt> - Specify the value as required for this
27
+ # property, to raise an error if a value is unset in a new or
28
+ # existing Dash.
29
+ #
30
+ def self.property(property_name, options = {})
31
+ property_name = property_name.to_sym
32
+
33
+ self.properties << property_name
34
+
35
+ if options.has_key?(:default)
36
+ self.defaults[property_name] = options[:default]
37
+ elsif self.defaults.has_key?(property_name)
38
+ self.defaults.delete property_name
39
+ end
40
+
41
+ unless instance_methods.map { |m| m.to_s }.include?("#{property_name}=")
42
+ class_eval <<-ACCESSORS
43
+ def #{property_name}(&block)
44
+ self.[](#{property_name.to_s.inspect}, &block)
45
+ end
46
+
47
+ def #{property_name}=(value)
48
+ self.[]=(#{property_name.to_s.inspect}, value)
49
+ end
50
+ ACCESSORS
51
+ end
52
+
53
+ if defined? @subclasses
54
+ @subclasses.each { |klass| klass.property(property_name, options) }
55
+ end
56
+ required_properties << property_name if options.delete(:required)
57
+ end
58
+
59
+ class << self
60
+ attr_reader :properties, :defaults
61
+ attr_reader :required_properties
62
+ end
63
+ instance_variable_set('@properties', Set.new)
64
+ instance_variable_set('@defaults', {})
65
+ instance_variable_set('@required_properties', Set.new)
66
+
67
+ def self.inherited(klass)
68
+ super
69
+ (@subclasses ||= Set.new) << klass
70
+ klass.instance_variable_set('@properties', self.properties.dup)
71
+ klass.instance_variable_set('@defaults', self.defaults.dup)
72
+ klass.instance_variable_set('@required_properties', self.required_properties.dup)
73
+ end
74
+
75
+ # Check to see if the specified property has already been
76
+ # defined.
77
+ def self.property?(name)
78
+ properties.include? name.to_sym
79
+ end
80
+
81
+ # Check to see if the specified property is
82
+ # required.
83
+ def self.required?(name)
84
+ required_properties.include? name.to_sym
85
+ end
86
+
87
+ # You may initialize a Dash with an attributes hash
88
+ # just like you would many other kinds of data objects.
89
+ def initialize(attributes = {}, &block)
90
+ super(&block)
91
+
92
+ self.class.defaults.each_pair do |prop, value|
93
+ self[prop] = value
94
+ end
95
+
96
+ attributes.each_pair do |att, value|
97
+ self[att] = value
98
+ end if attributes
99
+ assert_required_properties_set!
100
+ end
101
+
102
+ alias_method :_regular_reader, :[]
103
+ alias_method :_regular_writer, :[]=
104
+ private :_regular_reader, :_regular_writer
105
+
106
+ # Retrieve a value from the Dash (will return the
107
+ # property's default value if it hasn't been set).
108
+ def [](property)
109
+ assert_property_exists! property
110
+ value = super(property.to_s)
111
+ yield value if block_given?
112
+ value
113
+ end
114
+
115
+ # Set a value on the Dash in a Hash-like way. Only works
116
+ # on pre-existing properties.
117
+ def []=(property, value)
118
+ assert_property_required! property, value
119
+ assert_property_exists! property
120
+ super(property.to_s, value)
121
+ end
122
+
123
+ private
124
+
125
+ def assert_property_exists!(property)
126
+ unless self.class.property?(property)
127
+ raise NoMethodError, "The property '#{property}' is not defined for this Dash."
128
+ end
129
+ end
130
+
131
+ def assert_required_properties_set!
132
+ self.class.required_properties.each do |required_property|
133
+ assert_property_set!(required_property)
134
+ end
135
+ end
136
+
137
+ def assert_property_set!(property)
138
+ if send(property).nil?
139
+ raise ArgumentError, "The property '#{property}' is required for this Dash."
140
+ end
141
+ end
142
+
143
+ def assert_property_required!(property, value)
144
+ if self.class.required?(property) && value.nil?
145
+ raise ArgumentError, "The property '#{property}' is required for this Dash."
146
+ end
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,101 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Coercion
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, InstanceMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+ def []=(key, value)
11
+ into = self.class.key_coercion(key) || self.class.value_coercion(value)
12
+
13
+ if value && into
14
+ if into.respond_to?(:coerce)
15
+ value = into.coerce(value)
16
+ else
17
+ value = into.new(value)
18
+ end
19
+ end
20
+
21
+ super(key, value)
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ # Set up a coercion rule such that any time the specified
27
+ # key is set it will be coerced into the specified class.
28
+ # Coercion will occur by first attempting to call Class.coerce
29
+ # and then by calling Class.new with the value as an argument
30
+ # in either case.
31
+ #
32
+ # @param [Object] key the key you would like to be coerced.
33
+ # @param [Class] into the class into which you want the key coerced.
34
+ #
35
+ # @example Coerce a "user" subhash into a User object
36
+ # class Tweet < Hash
37
+ # include Hashie::Extensions::Coercion
38
+ # coerce_key :user, User
39
+ # end
40
+ def coerce_key(key, into)
41
+ (@key_coercions ||= {})[key] = into
42
+ end
43
+
44
+ # Returns a hash of any existing key coercions.
45
+ def key_coercions
46
+ @key_coercions || {}
47
+ end
48
+
49
+ # Returns the specific key coercion for the specified key,
50
+ # if one exists.
51
+ def key_coercion(key)
52
+ key_coercions[key]
53
+ end
54
+
55
+ # Set up a coercion rule such that any time a value of the
56
+ # specified type is set it will be coerced into the specified
57
+ # class.
58
+ #
59
+ # @param [Class] from the type you would like coerced.
60
+ # @param [Class] into the class into which you would like the value coerced.
61
+ # @option options [Boolean] :strict (true) whether use exact source class only or include ancestors
62
+ #
63
+ # @example Coerce all hashes into this special type of hash
64
+ # class SpecialHash < Hash
65
+ # include Hashie::Extensions::Coercion
66
+ # coerce_value Hash, SpecialHash
67
+ #
68
+ # def initialize(hash = {})
69
+ # super
70
+ # hash.each_pair do |k,v|
71
+ # self[k] = v
72
+ # end
73
+ # end
74
+ # end
75
+ def coerce_value(from, into, options = {})
76
+ options = {:strict => true}.merge(options)
77
+
78
+ if options[:strict]
79
+ (@strict_value_coercions ||= {})[from] = into
80
+ else
81
+ while from.superclass && from.superclass != Object
82
+ (@lenient_value_coercions ||= {})[from] = into
83
+ from = from.superclass
84
+ end
85
+ end
86
+ end
87
+
88
+ # Return all value coercions that have the :strict rule as true.
89
+ def strict_value_coercions; @strict_value_coercions || {} end
90
+ # Return all value coercions that have the :strict rule as false.
91
+ def lenient_value_coercions; @value_coercions || {} end
92
+
93
+ # Fetch the value coercion, if any, for the specified object.
94
+ def value_coercion(value)
95
+ from = value.class
96
+ strict_value_coercions[from] || lenient_value_coercions[from]
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,7 @@
1
+ module Hashie
2
+ module Extensions
3
+ module DeepMerge
4
+ # TODO: Implement deep merging.
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,110 @@
1
+ module Hashie
2
+ module Extensions
3
+ # IndifferentAccess gives you the ability to not care
4
+ # whether your hash has string or symbol keys. Made famous
5
+ # in Rails for accessing query and POST parameters, this
6
+ # is a handy tool for making sure your hash has maximum
7
+ # utility.
8
+ #
9
+ # One unique feature of this mixin is that it will recursively
10
+ # inject itself into sub-hash instances without modifying
11
+ # the actual class of the sub-hash.
12
+ #
13
+ # @example
14
+ # class MyHash < Hash
15
+ # include Hashie::Extensions::MergeInitializer
16
+ # include Hashie::Extensions::IndifferentAccess
17
+ # end
18
+ #
19
+ # h = MyHash.new(:foo => 'bar', 'baz' => 'blip')
20
+ # h['foo'] # => 'bar'
21
+ # h[:foo] # => 'bar'
22
+ # h[:baz] # => 'blip'
23
+ # h['baz'] # => 'blip'
24
+ #
25
+ module IndifferentAccess
26
+ def self.included(base)
27
+ base.class_eval do
28
+ alias_method :regular_writer, :[]=
29
+ alias_method :[]=, :indifferent_writer
30
+ %w(default update fetch delete key? values_at).each do |m|
31
+ alias_method "regular_#{m}", m
32
+ alias_method m, "indifferent_#{m}"
33
+ end
34
+ end
35
+ end
36
+
37
+ # This will inject indifferent access into an instance of
38
+ # a hash without modifying the actual class. This is what
39
+ # allows IndifferentAccess to spread to sub-hashes.
40
+ def self.inject!(hash)
41
+ (class << hash; self; end).send :include, Hashie::Extensions::IndifferentAccess
42
+ hash.convert!
43
+ end
44
+
45
+ # Injects indifferent access into a duplicate of the hash
46
+ # provided. See #inject!
47
+ def self.inject(hash)
48
+ inject!(hash.dup)
49
+ end
50
+
51
+ def convert_key(key)
52
+ key.to_s
53
+ end
54
+
55
+ # Iterates through the keys and values, reconverting them to
56
+ # their proper indifferent state. Used when IndifferentAccess
57
+ # is injecting itself into member hashes.
58
+ def convert!
59
+ keys.each do |k|
60
+ regular_writer convert_key(k), convert_value(self.regular_delete(k))
61
+ end
62
+ self
63
+ end
64
+
65
+ def convert_value(value)
66
+ if hash_lacking_indifference?(value)
67
+ Hashie::Extensions::IndifferentAccess.inject(value.dup)
68
+ elsif value.is_a?(::Array)
69
+ value.dup.replace(value.map { |e| convert_value(e) })
70
+ else
71
+ value
72
+ end
73
+ end
74
+
75
+ def indifferent_default(key = nil)
76
+ return self[convert_key(key)] if key?(key)
77
+ regular_default(key)
78
+ end
79
+
80
+ def indifferent_update(other_hash)
81
+ return regular_update(other_hash) if hash_with_indifference?(other_hash)
82
+ other_hash.each_pair do |k,v|
83
+ self[k] = v
84
+ end
85
+ end
86
+
87
+ def indifferent_writer(key, value); regular_writer convert_key(key), convert_value(value) end
88
+ def indifferent_fetch(key, *args); regular_fetch convert_key(key), *args end
89
+ def indifferent_delete(key); regular_delete convert_key(key) end
90
+ def indifferent_key?(key); regular_key? convert_key(key) end
91
+ def indifferent_values_at(*indices); indices.map{|i| self[i] } end
92
+
93
+ def indifferent_access?; true end
94
+
95
+ protected
96
+
97
+ def hash_lacking_indifference?(other)
98
+ other.is_a?(::Hash) &&
99
+ !(other.respond_to?(:indifferent_access?) &&
100
+ other.indifferent_access?)
101
+ end
102
+
103
+ def hash_with_indifference?(other)
104
+ other.is_a?(::Hash) &&
105
+ other.respond_to?(:indifferent_access?) &&
106
+ other.indifferent_access?
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,52 @@
1
+ module Hashie
2
+ module Extensions
3
+ module StringifyKeys
4
+ # Convert all keys in the hash to strings.
5
+ #
6
+ # @example
7
+ # test = {:abc => 'def'}
8
+ # test.stringify_keys!
9
+ # test # => {'abc' => 'def'}
10
+ def stringify_keys!
11
+ keys.each do |k|
12
+ self[k.to_s] = self.delete(k)
13
+ end
14
+ self
15
+ end
16
+
17
+ # Return a new hash with all keys converted
18
+ # to strings.
19
+ def stringify_keys
20
+ dup.stringify_keys!
21
+ end
22
+ end
23
+
24
+ module SymbolizeKeys
25
+ # Convert all keys in the hash to strings.
26
+ #
27
+ # @example
28
+ # test = {'abc' => 'def'}
29
+ # test.symbolize_keys!
30
+ # test # => {:abc => 'def'}
31
+ def symbolize_keys!
32
+ keys.each do |k|
33
+ self[k.to_sym] = self.delete(k)
34
+ end
35
+ self
36
+ end
37
+
38
+ # Return a new hash with all keys converted
39
+ # to symbols.
40
+ def symbolize_keys
41
+ dup.symbolize_keys!
42
+ end
43
+ end
44
+
45
+ module KeyConversion
46
+ def self.included(base)
47
+ base.send :include, SymbolizeKeys
48
+ base.send :include, StringifyKeys
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ module Hashie
2
+ module Extensions
3
+ # The MergeInitializer is a super-simple mixin that allows
4
+ # you to initialize a subclass of Hash with another Hash
5
+ # to give you faster startup time for Hash subclasses. Note
6
+ # that you can still provide a default value as a second
7
+ # argument to the initializer.
8
+ #
9
+ # @example
10
+ # class MyHash < Hash
11
+ # include Hashie::Extensions::MergeInitializer
12
+ # end
13
+ #
14
+ # h = MyHash.new(:abc => 'def')
15
+ # h[:abc] # => 'def'
16
+ #
17
+ module MergeInitializer
18
+ def initialize(hash = {}, default = nil, &block)
19
+ default ? super(default) : super(&block)
20
+ update(hash)
21
+ end
22
+ end
23
+ end
24
+ end