cb_hashie 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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