hashie-model 1.0.0.alpha

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,124 @@
1
+ module Hashie
2
+ module Extensions
3
+ # MethodReader allows you to access keys of the hash
4
+ # via method calls. This gives you an OStruct like way
5
+ # to access your hash's keys. It will recognize keys
6
+ # either as strings or symbols.
7
+ #
8
+ # Note that while nil keys will be returned as nil,
9
+ # undefined keys will raise NoMethodErrors. Also note that
10
+ # #respond_to? has been patched to appropriately recognize
11
+ # key methods.
12
+ #
13
+ # @example
14
+ # class User < Hash
15
+ # include Hashie::Extensions::MethodReader
16
+ # end
17
+ #
18
+ # user = User.new
19
+ # user['first_name'] = 'Michael'
20
+ # user.first_name # => 'Michael'
21
+ #
22
+ # user[:last_name] = 'Bleigh'
23
+ # user.last_name # => 'Bleigh'
24
+ #
25
+ # user[:birthday] = nil
26
+ # user.birthday # => nil
27
+ #
28
+ # user.not_declared # => NoMethodError
29
+ module MethodReader
30
+ def respond_to?(name)
31
+ return true if key?(name.to_s) || key?(name.to_sym)
32
+ super
33
+ end
34
+
35
+ def method_missing(name, *args)
36
+ return self[name.to_s] if key?(name.to_s)
37
+ return self[name.to_sym] if key?(name.to_sym)
38
+ super
39
+ end
40
+ end
41
+
42
+ # MethodWriter gives you #key_name= shortcuts for
43
+ # writing to your hash. Keys are written as strings,
44
+ # override #convert_key if you would like to have symbols
45
+ # or something else.
46
+ #
47
+ # Note that MethodWriter also overrides #respond_to such
48
+ # that any #method_name= will respond appropriately as true.
49
+ #
50
+ # @example
51
+ # class MyHash < Hash
52
+ # include Hashie::Extensions::MethodWriter
53
+ # end
54
+ #
55
+ # h = MyHash.new
56
+ # h.awesome = 'sauce'
57
+ # h['awesome'] # => 'sauce'
58
+ #
59
+ module MethodWriter
60
+ def respond_to?(name)
61
+ return true if name.to_s =~ /=$/
62
+ super
63
+ end
64
+
65
+ def method_missing(name, *args)
66
+ if args.size == 1 && name.to_s =~ /(.*)=$/
67
+ return self[convert_key($1)] = args.first
68
+ end
69
+
70
+ super
71
+ end
72
+
73
+ def convert_key(key)
74
+ key.to_s
75
+ end
76
+ end
77
+
78
+ # MethodQuery gives you the ability to check for the truthiness
79
+ # of a key via method calls. Note that it will return false if
80
+ # the key is set to a non-truthful value, not if the key isn't
81
+ # set at all. Use #key? for checking if a key has been set.
82
+ #
83
+ # MethodQuery will check against both string and symbol names
84
+ # of the method for existing keys. It also patches #respond_to
85
+ # to appropriately detect the query methods.
86
+ #
87
+ # @example
88
+ # class MyHash < Hash
89
+ # include Hashie::Extensions::MethodQuery
90
+ # end
91
+ #
92
+ # h = MyHash.new
93
+ # h['abc'] = 123
94
+ # h.abc? # => true
95
+ # h['def'] = nil
96
+ # h.def? # => false
97
+ # h.hji? # => NoMethodError
98
+ module MethodQuery
99
+ def respond_to?(name)
100
+ return true if name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym))
101
+ super
102
+ end
103
+
104
+ def method_missing(name, *args)
105
+ if args.empty? && name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym))
106
+ return self[$1] || self[$1.to_sym]
107
+ end
108
+
109
+ super
110
+ end
111
+ end
112
+
113
+ # A macro module that will automatically include MethodReader,
114
+ # MethodWriter, and MethodQuery, giving you the ability to read,
115
+ # write, and query keys in a hash using method call shortcuts.
116
+ module MethodAccess
117
+ def self.included(base)
118
+ [MethodReader, MethodWriter, MethodQuery].each do |mod|
119
+ base.send :include, mod
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,47 @@
1
+ module Hashie
2
+ module Extensions
3
+ # The Structure extension provides facilities for declaring
4
+ # properties that a Hash can have. This provides for the
5
+ # creation of structures that still behave like hashes but
6
+ # do not allow setting non-allowed keys.
7
+ #
8
+ # @example
9
+ # class RestrictedHash < Hash
10
+ # include Hashie::Extensions::MergeInitializer
11
+ # include Hashie::Extensions::Structure
12
+ #
13
+ # key :first
14
+ # key :second, :default => 'foo'
15
+ # end
16
+ #
17
+ # h = RestrictedHash.new(:first => 1)
18
+ # h[:first] # => 1
19
+ # h[:second] # => 'foo'
20
+ # h[:third] # => ArgumentError
21
+ #
22
+ module Structure
23
+ def self.included(base)
24
+ base.extend ClassMethods
25
+ base.class_eval do
26
+ @permitted_keys = superclass.permitted_keys if superclass.respond_to?(:permitted_keys)
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ def key(key, options = {})
32
+ (@permitted_keys ||= []) << key
33
+
34
+ if options[:default]
35
+ (@default_values ||= {})[key] = options.delete(:default)
36
+ end
37
+
38
+ permitted_keys
39
+ end
40
+
41
+ def permitted_keys
42
+ @permitted_keys
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ require 'hashie/hash_extensions'
2
+
3
+ module Hashie
4
+ # A Hashie Hash is simply a Hash that has convenience
5
+ # functions baked in such as stringify_keys that may
6
+ # not be available in all libraries.
7
+ class Hash < Hash
8
+ include Hashie::HashExtensions
9
+
10
+ # Converts a mash back to a hash (with stringified keys)
11
+ def to_hash
12
+ out = {}
13
+ keys.each do |k|
14
+ out[k] = Hashie::Hash === self[k] ? self[k].to_hash : self[k]
15
+ end
16
+ out
17
+ end
18
+
19
+ # The C geneartor for the json gem doesn't like mashies
20
+ def to_json(*args)
21
+ to_hash.to_json(*args)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ module Hashie
2
+ module HashExtensions
3
+ def self.included(base)
4
+ # Don't tread on existing extensions of Hash by
5
+ # adding methods that are likely to exist.
6
+ %w(stringify_keys stringify_keys!).each do |hashie_method|
7
+ base.send :alias_method, hashie_method, "hashie_#{hashie_method}" unless base.instance_methods.include?(hashie_method)
8
+ end
9
+ end
10
+
11
+ # Destructively convert all of the keys of a Hash
12
+ # to their string representations.
13
+ def hashie_stringify_keys!
14
+ self.keys.each do |k|
15
+ unless String === k
16
+ self[k.to_s] = self.delete(k)
17
+ end
18
+ end
19
+ self
20
+ end
21
+
22
+ # Convert all of the keys of a Hash
23
+ # to their string representations.
24
+ def hashie_stringify_keys
25
+ self.dup.stringify_keys!
26
+ end
27
+
28
+ # Convert this hash into a Mash
29
+ def to_mash
30
+ ::Hashie::Mash.new(self)
31
+ end
32
+ end
33
+
34
+ module PrettyInspect
35
+ def self.included(base)
36
+ base.send :alias_method, :hash_inspect, :inspect
37
+ base.send :alias_method, :inspect, :hashie_inspect
38
+ end
39
+
40
+ def hashie_inspect
41
+ ret = "#<#{self.class.to_s}"
42
+ stringify_keys.keys.sort.each do |key|
43
+ ret << " #{key}=#{self[key].inspect}"
44
+ end
45
+ ret << ">"
46
+ ret
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,191 @@
1
+ require 'hashie/hash'
2
+
3
+ module Hashie
4
+ # Mash allows you to create pseudo-objects that have method-like
5
+ # accessors for hash keys. This is useful for such implementations
6
+ # as an API-accessing library that wants to fake robust objects
7
+ # without the overhead of actually doing so. Think of it as OpenStruct
8
+ # with some additional goodies.
9
+ #
10
+ # A Mash will look at the methods you pass it and perform operations
11
+ # based on the following rules:
12
+ #
13
+ # * No punctuation: Returns the value of the hash for that key, or nil if none exists.
14
+ # * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
15
+ # * Existence (<tt>?</tt>): Returns true or false depending on whether that key has been set.
16
+ # * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes.
17
+ #
18
+ # == Basic Example
19
+ #
20
+ # mash = Mash.new
21
+ # mash.name? # => false
22
+ # mash.name = "Bob"
23
+ # mash.name # => "Bob"
24
+ # mash.name? # => true
25
+ #
26
+ # == Hash Conversion Example
27
+ #
28
+ # hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
29
+ # mash = Mash.new(hash)
30
+ # mash.a.b # => 23
31
+ # mash.a.d.e # => "abc"
32
+ # mash.f.first.g # => 44
33
+ # mash.f.last # => 12
34
+ #
35
+ # == Bang Example
36
+ #
37
+ # mash = Mash.new
38
+ # mash.author # => nil
39
+ # mash.author! # => <Mash>
40
+ #
41
+ # mash = Mash.new
42
+ # mash.author!.name = "Michael Bleigh"
43
+ # mash.author # => <Mash name="Michael Bleigh">
44
+ #
45
+ class Mash < Hashie::Hash
46
+ include Hashie::PrettyInspect
47
+ alias_method :to_s, :inspect
48
+
49
+ # If you pass in an existing hash, it will
50
+ # convert it to a Mash including recursively
51
+ # descending into arrays and hashes, converting
52
+ # them as well.
53
+ def initialize(source_hash = nil, default = nil, &blk)
54
+ deep_update(source_hash) if source_hash
55
+ default ? super(default) : super(&blk)
56
+ end
57
+
58
+ class << self; alias [] new; end
59
+
60
+ def id #:nodoc:
61
+ key?("id") ? self["id"] : super
62
+ end
63
+
64
+ def type #:nodoc:
65
+ key?("type") ? self["type"] : super
66
+ end
67
+
68
+ alias_method :regular_reader, :[]
69
+ alias_method :regular_writer, :[]=
70
+
71
+ # Retrieves an attribute set in the Mash. Will convert
72
+ # any key passed in to a string before retrieving.
73
+ def [](key)
74
+ value = regular_reader(convert_key(key))
75
+ yield value if block_given?
76
+ value
77
+ end
78
+
79
+ # Sets an attribute in the Mash. Key will be converted to
80
+ # a string before it is set, and Hashes will be converted
81
+ # into Mashes for nesting purposes.
82
+ def []=(key,value) #:nodoc:
83
+ regular_writer(convert_key(key), convert_value(value))
84
+ end
85
+
86
+ # This is the bang method reader, it will return a new Mash
87
+ # if there isn't a value already assigned to the key requested.
88
+ def initializing_reader(key)
89
+ ck = convert_key(key)
90
+ regular_writer(ck, self.class.new) unless key?(ck)
91
+ regular_reader(ck)
92
+ end
93
+
94
+ def delete(key)
95
+ super(convert_key(key))
96
+ end
97
+
98
+ alias_method :regular_dup, :dup
99
+ # Duplicates the current mash as a new mash.
100
+ def dup
101
+ self.class.new(self, self.default)
102
+ end
103
+
104
+ def key?(key)
105
+ super(convert_key(key))
106
+ end
107
+ alias_method :has_key?, :key?
108
+ alias_method :include?, :key?
109
+ alias_method :member?, :key?
110
+
111
+ # Performs a deep_update on a duplicate of the
112
+ # current mash.
113
+ def deep_merge(other_hash)
114
+ dup.deep_update(other_hash)
115
+ end
116
+ alias_method :merge, :deep_merge
117
+
118
+ # Recursively merges this mash with the passed
119
+ # in hash, merging each hash in the hierarchy.
120
+ def deep_update(other_hash)
121
+ other_hash.each_pair do |k,v|
122
+ key = convert_key(k)
123
+ if regular_reader(key).is_a?(Mash) and v.is_a?(::Hash)
124
+ regular_reader(key).deep_update(v)
125
+ else
126
+ regular_writer(key, convert_value(v, true))
127
+ end
128
+ end
129
+ self
130
+ end
131
+ alias_method :deep_merge!, :deep_update
132
+ alias_method :update, :deep_update
133
+ alias_method :merge!, :update
134
+
135
+ # Performs a shallow_update on a duplicate of the current mash
136
+ def shallow_merge(other_hash)
137
+ dup.shallow_update(other_hash)
138
+ end
139
+
140
+ # Merges (non-recursively) the hash from the argument,
141
+ # changing the receiving hash
142
+ def shallow_update(other_hash)
143
+ other_hash.each_pair do |k,v|
144
+ regular_writer(convert_key(k), convert_value(v, true))
145
+ end
146
+ self
147
+ end
148
+
149
+ # Will return true if the Mash has had a key
150
+ # set in addition to normal respond_to? functionality.
151
+ def respond_to?(method_name, include_private=false)
152
+ return true if key?(method_name)
153
+ super
154
+ end
155
+
156
+ def method_missing(method_name, *args, &blk)
157
+ return self.[](method_name, &blk) if key?(method_name)
158
+ match = method_name.to_s.match(/(.*?)([?=!]?)$/)
159
+ case match[2]
160
+ when "="
161
+ self[match[1]] = args.first
162
+ when "?"
163
+ !!self[match[1]]
164
+ when "!"
165
+ initializing_reader(match[1])
166
+ else
167
+ default(method_name, *args, &blk)
168
+ end
169
+ end
170
+
171
+ protected
172
+
173
+ def convert_key(key) #:nodoc:
174
+ key.to_s
175
+ end
176
+
177
+ def convert_value(val, duping=false) #:nodoc:
178
+ case val
179
+ when self.class
180
+ val.dup
181
+ when ::Hash
182
+ val = val.dup if duping
183
+ self.class.new(val)
184
+ when Array
185
+ val.collect{ |e| convert_value(e) }
186
+ else
187
+ val
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,55 @@
1
+ require 'hashie/dash'
2
+
3
+ module Hashie
4
+ # A Trash is a 'translated' Dash where the keys can be remapped from a source
5
+ # hash.
6
+ #
7
+ # Trashes are useful when you need to read data from another application,
8
+ # such as a Java api, where the keys are named differently from how we would
9
+ # in Ruby.
10
+ class Trash < Hashie::Dash
11
+
12
+ # Defines a property on the Trash. Options are as follows:
13
+ #
14
+ # * <tt>:default</tt> - Specify a default value for this property, to be
15
+ # returned before a value is set on the property in a new Dash.
16
+ # * <tt>:from</tt> - Specify the original key name that will be write only.
17
+ def self.property(property_name, options = {})
18
+ super
19
+
20
+ if options[:from]
21
+ translations << options[:from].to_sym
22
+ class_eval <<-RUBY
23
+ def #{options[:from]}=(val)
24
+ self[:#{property_name}] = val
25
+ end
26
+ RUBY
27
+ end
28
+ end
29
+
30
+ # Set a value on the Dash in a Hash-like way. Only works
31
+ # on pre-existing properties.
32
+ def []=(property, value)
33
+ if self.class.translations.include? property.to_sym
34
+ send("#{property}=", value)
35
+ elsif property_exists? property
36
+ super
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def self.translations
43
+ @translations ||= []
44
+ end
45
+
46
+ # Raises an NoMethodError if the property doesn't exist
47
+ #
48
+ def property_exists?(property)
49
+ unless self.class.property?(property.to_sym)
50
+ raise NoMethodError, "The property '#{property}' is not defined for this Trash."
51
+ end
52
+ true
53
+ end
54
+ end
55
+ end