hashie 1.2.0 → 2.0.0

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.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ pkg
6
6
  *.gem
7
7
  .bundle
8
8
  .rvmrc
9
+ Gemfile.lock
@@ -0,0 +1 @@
1
+ -m markdown
@@ -0,0 +1,16 @@
1
+ # CHANGELOG
2
+
3
+ ## 2.0.0
4
+
5
+ * update gemspec with license info jordimassaguerpla #72
6
+ * fix readme typo jcamenisch #71
7
+ * initialize with merge coerces values mattfawcett #27
8
+ * Hashie::Extensions::Coercion coerce_keys takes arguments mattfawcett #28
9
+ * Trash removes translated values on initialization sleverbor #39
10
+ * Mash#fetch works with symbol or string keys arthwood #66
11
+ * Hashie::Hash inherits from ::Hash to avoid ambiguity meh, orend #49
12
+ * update respond_to? method signature to match ruby core definition dlupu #62
13
+ * DeepMerge extension nashby #41
14
+ * Dash defaults are dup'ed before assigned ohrite #63
15
+ * remove id, type, and object_id as special allowable keys jch #77
16
+ * merge and update accepts a block jch #78
@@ -0,0 +1,27 @@
1
+ ## Note on Patches/Pull Requests
2
+
3
+ Thanks for taking the time to contribute back! To make it easier for us to
4
+ review your changes, try to follow these guidelines:
5
+
6
+ * Keep changesets small and on topic. Itching to refactor or clean something
7
+ up? Do it in a separate branch.
8
+ * Stay consistent with existing code conventions.
9
+ * Break changes into smaller logical commits.
10
+
11
+ To propose a change:
12
+
13
+ * [Fork the project.](https://help.github.com/articles/fork-a-repo)
14
+ * Make your feature addition or bug fix.
15
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
16
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
17
+ * [Send me a pull request](https://help.github.com/articles/using-pull-requests). Bonus points for topic branches.
18
+ * [Check that your pull request passes the build](https://travis-ci.org/intridea/hashie/pull_requests).
19
+
20
+ ## Bug triage
21
+
22
+ Have a problem? File an [issue here](https://github.com/intridea/hashie/issues).
23
+
24
+ To make bug squashing easier, include the following in your issue:
25
+
26
+ * What version of hashie are you using?
27
+ * Is it still a problem in master?
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
1
  guard 'rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb})
3
- watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
3
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
4
4
  watch('spec/spec_helper.rb') { "spec" }
5
5
  end
@@ -0,0 +1,240 @@
1
+ **Note:** This documentation is for the unreleased version 2.0 of
2
+ Hashie. See the [1-1-stable branch](https://github.com/intridea/hashie/tree/1-1-stable) for documentation of the released version.
3
+
4
+ # Hashie [![Build Status](https://secure.travis-ci.org/intridea/hashie.png)](http://travis-ci.org/intridea/hashie) [![Dependency Status](https://gemnasium.com/intridea/hashie.png)](https://gemnasium.com/intridea/hashie)
5
+
6
+ Hashie is a growing collection of tools that extend Hashes and make
7
+ them more useful.
8
+
9
+ ## Installation
10
+
11
+ Hashie is available as a RubyGem:
12
+
13
+ gem install hashie
14
+
15
+ ## Hash Extensions
16
+
17
+ New to version 2.0 of Hashie, the library has been broken up into a
18
+ number of atomically includeable Hash extension modules as described
19
+ below. This provides maximum flexibility for users to mix and match
20
+ functionality while maintaining feature parity with earlier versions of
21
+ Hashie.
22
+
23
+ Any of the extensions listed below can be mixed into a class by
24
+ `include`-ing `Hashie::Extensions::ExtensionName`.
25
+
26
+ ### Coercion
27
+
28
+ Coercions allow you to set up "coercion rules" based either on the key
29
+ or the value type to massage data as it's being inserted into the Hash.
30
+ Key coercions might be used, for example, in lightweight data modeling
31
+ applications such as an API client:
32
+
33
+ class Tweet < Hash
34
+ include Hashie::Extensions::Coercion
35
+ coerce_key :user, User
36
+ end
37
+
38
+ user_hash = {:name => "Bob"}
39
+ Tweet.new(:user => user_hash)
40
+ # => automatically calls User.coerce(user_hash) or
41
+ # User.new(user_hash) if that isn't present.
42
+
43
+ Value coercions, on the other hand, will coerce values based on the type
44
+ of the value being inserted. This is useful if you are trying to build a
45
+ Hash-like class that is self-propagating.
46
+
47
+ class SpecialHash < Hash
48
+ include Hashie::Extensions::Coercion
49
+ coerce_value Hash, SpecialHash
50
+
51
+ def initialize(hash = {})
52
+ super
53
+ hash.each_pair do |k,v|
54
+ self[k] = v
55
+ end
56
+ end
57
+ end
58
+
59
+ ### KeyConversion
60
+
61
+ The KeyConversion extension gives you the convenience methods of
62
+ `symbolize_keys` and `stringify_keys` along with their bang
63
+ counterparts. You can also include just stringify or just symbolize with
64
+ `Hashie::Extensions::StringifyKeys` or `Hashie::Extensions::SymbolizeKeys`.
65
+
66
+ ### MergeInitializer
67
+
68
+ The MergeInitializer extension simply makes it possible to initialize a
69
+ Hash subclass with another Hash, giving you a quick short-hand.
70
+
71
+ ### MethodAccess
72
+
73
+ The MethodAccess extension allows you to quickly build method-based
74
+ reading, writing, and querying into your Hash descendant. It can also be
75
+ included as individual modules, i.e. `Hashie::Extensions::MethodReader`,
76
+ `Hashie::Extensions::MethodWriter` and `Hashie::Extensions::MethodQuery`
77
+
78
+ class MyHash < Hash
79
+ include Hashie::Extensions::MethodAccess
80
+ end
81
+
82
+ h = MyHash.new
83
+ h.abc = 'def'
84
+ h.abc # => 'def'
85
+ h.abc? # => true
86
+
87
+ ### IndifferentAccess
88
+
89
+ This extension can be mixed in to instantly give you indifferent access
90
+ to your Hash subclass. This works just like the params hash in Rails and
91
+ other frameworks where whether you provide symbols or strings to access
92
+ keys, you will get the same results.
93
+
94
+ A unique feature of Hashie's IndifferentAccess mixin is that it will
95
+ inject itself recursively into subhashes *without* reinitializing the
96
+ hash in question. This means you can safely merge together indifferent
97
+ and non-indifferent hashes arbitrarily deeply without worrying about
98
+ whether you'll be able to `hash[:other][:another]` properly.
99
+
100
+ ### DeepMerge
101
+
102
+ This extension allow you to easily include a recursive merging
103
+ system to any Hash descendant:
104
+
105
+ class MyHash < Hash
106
+ include Hashie::Extensions::DeepMerge
107
+ end
108
+
109
+ h1 = MyHash.new
110
+ h2 = MyHash.new
111
+
112
+ h1 = {:x => {:y => [4,5,6]}, :z => [7,8,9]}
113
+ h2 = {:x => {:y => [7,8,9]}, :z => "xyz"}
114
+
115
+ h1.deep_merge(h2) #=> { :x => {:y => [7, 8, 9]}, :z => "xyz" }
116
+ h2.deep_merge(h1) #=> { :x => {:y => [4, 5, 6]}, :z => [7, 8, 9] }
117
+
118
+ ## Mash
119
+
120
+ Mash is an extended Hash that gives simple pseudo-object functionality
121
+ that can be built from hashes and easily extended. It is designed to
122
+ be used in RESTful API libraries to provide easy object-like access
123
+ to JSON and XML parsed hashes.
124
+
125
+ ### Example:
126
+
127
+ mash = Hashie::Mash.new
128
+ mash.name? # => false
129
+ mash.name # => nil
130
+ mash.name = "My Mash"
131
+ mash.name # => "My Mash"
132
+ mash.name? # => true
133
+ mash.inspect # => <Hashie::Mash name="My Mash">
134
+
135
+ mash = Mash.new
136
+ # use bang methods for multi-level assignment
137
+ mash.author!.name = "Michael Bleigh"
138
+ mash.author # => <Hashie::Mash name="Michael Bleigh">
139
+
140
+ mash = Mash.new
141
+ # use under-bang methods for multi-level testing
142
+ mash.author_.name? # => false
143
+ mash.inspect # => <Hashie::Mash>
144
+
145
+ **Note:** The `?` method will return false if a key has been set
146
+ to false or nil. In order to check if a key has been set at all, use the
147
+ `mash.key?('some_key')` method instead.
148
+
149
+ ## Dash
150
+
151
+ Dash is an extended Hash that has a discrete set of defined properties
152
+ and only those properties may be set on the hash. Additionally, you
153
+ can set defaults for each property. You can also flag a property as
154
+ required. Required properties will raise an exception if unset.
155
+
156
+ ### Example:
157
+
158
+ class Person < Hashie::Dash
159
+ property :name, :required => true
160
+ property :email
161
+ property :occupation, :default => 'Rubyist'
162
+ end
163
+
164
+ p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
165
+
166
+ p = Person.new(:name => "Bob")
167
+ p.name # => 'Bob'
168
+ p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
169
+ p.email = 'abc@def.com'
170
+ p.occupation # => 'Rubyist'
171
+ p.email # => 'abc@def.com'
172
+ p[:awesome] # => NoMethodError
173
+ p[:occupation] # => 'Rubyist'
174
+
175
+ ## Trash
176
+
177
+ A Trash is a Dash that allows you to translate keys on initialization.
178
+ It is used like so:
179
+
180
+ class Person < Hashie::Trash
181
+ property :first_name, :from => :firstName
182
+ end
183
+
184
+ This will automatically translate the <tt>firstName</tt> key to <tt>first_name</tt>
185
+ when it is initialized using a hash such as through:
186
+
187
+ Person.new(:firstName => 'Bob')
188
+
189
+ Trash also supports translations using lambda, this could be useful when dealing with
190
+ external API's. You can use it in this way:
191
+
192
+ class Result < Hashie::Trash
193
+ property :id, :transform_with => lambda { |v| v.to_i }
194
+ property :created_at, :from => :creation_date, :with => lambda { |v| Time.parse(v) }
195
+ end
196
+
197
+ this will produce the following
198
+
199
+ result = Result.new(:id => '123', :creation_date => '2012-03-30 17:23:28')
200
+ result.id.class # => Fixnum
201
+ result.created_at.class # => Time
202
+
203
+ ## Clash
204
+
205
+ Clash is a Chainable Lazy Hash that allows you to easily construct
206
+ complex hashes using method notation chaining. This will allow you
207
+ to use a more action-oriented approach to building options hashes.
208
+
209
+ Essentially, a Clash is a generalized way to provide much of the same
210
+ kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes
211
+ provide.
212
+
213
+ ### Example
214
+
215
+ c = Hashie::Clash.new
216
+ c.where(:abc => 'def').order(:created_at)
217
+ c # => {:where => {:abc => 'def'}, :order => :created_at}
218
+
219
+ # You can also use bang notation to chain into sub-hashes,
220
+ # jumping back up the chain with _end!
221
+ c = Hashie::Clash.new
222
+ c.where!.abc('def').ghi(123)._end!.order(:created_at)
223
+ c # => {:where => {:abc => 'def', :ghi => 123}, :order => :created_at}
224
+
225
+ # Multiple hashes are merged automatically
226
+ c = Hashie::Clash.new
227
+ c.where(:abc => 'def').where(:hgi => 123)
228
+ c # => {:where => {:abc => 'def', :hgi => 123}}
229
+
230
+ ## Contributing
231
+
232
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
233
+
234
+ ## Authors
235
+
236
+ * Michael Bleigh
237
+
238
+ ## Copyright
239
+
240
+ Copyright (c) 2009-2011 Intridea, Inc. (http://intridea.com/). See LICENSE for details.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -1,8 +1,8 @@
1
1
  require File.expand_path('../lib/hashie/version', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |gem|
4
- gem.authors = ["Michael Bleigh"]
5
- gem.email = ["michael@intridea.com"]
4
+ gem.authors = ["Michael Bleigh", "Jerry Cheung"]
5
+ gem.email = ["michael@intridea.com", "jollyjerry@gmail.com"]
6
6
  gem.description = %q{Hashie is a small collection of tools that make hashes more powerful. Currently includes Mash (Mocking Hash) and Dash (Discrete Hash).}
7
7
  gem.summary = %q{Your friendly neighborhood hash toolkit.}
8
8
  gem.homepage = 'https://github.com/intridea/hashie'
@@ -13,9 +13,11 @@ Gem::Specification.new do |gem|
13
13
  gem.name = "hashie"
14
14
  gem.require_paths = ['lib']
15
15
  gem.version = Hashie::VERSION
16
+ gem.license = "MIT"
16
17
 
17
18
  gem.add_development_dependency 'rake', '~> 0.9.2'
18
19
  gem.add_development_dependency 'rspec', '~> 2.5'
19
20
  gem.add_development_dependency 'guard'
20
21
  gem.add_development_dependency 'guard-rspec'
22
+ gem.add_development_dependency 'growl'
21
23
  end
@@ -1,9 +1,23 @@
1
1
  module Hashie
2
+ autoload :Clash, 'hashie/clash'
3
+ autoload :Dash, 'hashie/dash'
4
+ autoload :Hash, 'hashie/hash'
2
5
  autoload :HashExtensions, 'hashie/hash_extensions'
3
- autoload :PrettyInspect, 'hashie/hash_extensions'
4
- autoload :Hash, 'hashie/hash'
5
- autoload :Trash, 'hashie/trash'
6
- autoload :Mash, 'hashie/mash'
7
- autoload :Dash, 'hashie/dash'
8
- autoload :Clash, 'hashie/clash'
6
+ autoload :Mash, 'hashie/mash'
7
+ autoload :PrettyInspect, 'hashie/hash_extensions'
8
+ autoload :Trash, 'hashie/trash'
9
+
10
+ module Extensions
11
+ autoload :Coercion, 'hashie/extensions/coercion'
12
+ autoload :DeepMerge, 'hashie/extensions/deep_merge'
13
+ autoload :KeyConversion, 'hashie/extensions/key_conversion'
14
+ autoload :IndifferentAccess, 'hashie/extensions/indifferent_access'
15
+ autoload :MergeInitializer, 'hashie/extensions/merge_initializer'
16
+ autoload :MethodAccess, 'hashie/extensions/method_access'
17
+ autoload :MethodQuery, 'hashie/extensions/method_access'
18
+ autoload :MethodReader, 'hashie/extensions/method_access'
19
+ autoload :MethodWriter, 'hashie/extensions/method_access'
20
+ autoload :StringifyKeys, 'hashie/extensions/key_conversion'
21
+ autoload :SymbolizeKeys, 'hashie/extensions/key_conversion'
22
+ end
9
23
  end
@@ -12,8 +12,8 @@ module Hashie
12
12
  #
13
13
  # It is preferrable to a Struct because of the in-class
14
14
  # API for defining properties as well as per-property defaults.
15
- class Dash < Hashie::Hash
16
- include Hashie::PrettyInspect
15
+ class Dash < Hash
16
+ include PrettyInspect
17
17
  alias_method :to_s, :inspect
18
18
 
19
19
  # Defines a property on the Dash. Options are
@@ -90,12 +90,14 @@ module Hashie
90
90
  super(&block)
91
91
 
92
92
  self.class.defaults.each_pair do |prop, value|
93
- self[prop] = value
93
+ self[prop] = begin
94
+ value.dup
95
+ rescue TypeError
96
+ value
97
+ end
94
98
  end
95
99
 
96
- attributes.each_pair do |att, value|
97
- self[att] = value
98
- end if attributes
100
+ initialize_attributes(attributes)
99
101
  assert_required_properties_set!
100
102
  end
101
103
 
@@ -108,8 +110,13 @@ module Hashie
108
110
  def [](property)
109
111
  assert_property_exists! property
110
112
  value = super(property.to_s)
111
- yield value if block_given?
112
- value
113
+ # If the value is a lambda, proc, or whatever answers to call, eval the thing!
114
+ if value.is_a? Proc
115
+ self[property] = value.call # Set the result of the call as a value
116
+ else
117
+ yield value if block_given?
118
+ value
119
+ end
113
120
  end
114
121
 
115
122
  # Set a value on the Dash in a Hash-like way. Only works
@@ -122,6 +129,12 @@ module Hashie
122
129
 
123
130
  private
124
131
 
132
+ def initialize_attributes(attributes)
133
+ attributes.each_pair do |att, value|
134
+ self[att] = value
135
+ end if attributes
136
+ end
137
+
125
138
  def assert_property_exists!(property)
126
139
  unless self.class.property?(property)
127
140
  raise NoMethodError, "The property '#{property}' is not defined for this Dash."
@@ -0,0 +1,105 @@
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 or array of keys you would like to be coerced.
33
+ # @param [Class] into the class into which you want the key(s) 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(*attrs)
41
+ @key_coercions ||= {}
42
+ into = attrs.pop
43
+ attrs.each { |key| @key_coercions[key] = into }
44
+ end
45
+
46
+ alias :coerce_keys :coerce_key
47
+
48
+ # Returns a hash of any existing key coercions.
49
+ def key_coercions
50
+ @key_coercions || {}
51
+ end
52
+
53
+ # Returns the specific key coercion for the specified key,
54
+ # if one exists.
55
+ def key_coercion(key)
56
+ key_coercions[key]
57
+ end
58
+
59
+ # Set up a coercion rule such that any time a value of the
60
+ # specified type is set it will be coerced into the specified
61
+ # class.
62
+ #
63
+ # @param [Class] from the type you would like coerced.
64
+ # @param [Class] into the class into which you would like the value coerced.
65
+ # @option options [Boolean] :strict (true) whether use exact source class only or include ancestors
66
+ #
67
+ # @example Coerce all hashes into this special type of hash
68
+ # class SpecialHash < Hash
69
+ # include Hashie::Extensions::Coercion
70
+ # coerce_value Hash, SpecialHash
71
+ #
72
+ # def initialize(hash = {})
73
+ # super
74
+ # hash.each_pair do |k,v|
75
+ # self[k] = v
76
+ # end
77
+ # end
78
+ # end
79
+ def coerce_value(from, into, options = {})
80
+ options = {:strict => true}.merge(options)
81
+
82
+ if options[:strict]
83
+ (@strict_value_coercions ||= {})[from] = into
84
+ else
85
+ while from.superclass && from.superclass != Object
86
+ (@lenient_value_coercions ||= {})[from] = into
87
+ from = from.superclass
88
+ end
89
+ end
90
+ end
91
+
92
+ # Return all value coercions that have the :strict rule as true.
93
+ def strict_value_coercions; @strict_value_coercions || {} end
94
+ # Return all value coercions that have the :strict rule as false.
95
+ def lenient_value_coercions; @value_coercions || {} end
96
+
97
+ # Fetch the value coercion, if any, for the specified object.
98
+ def value_coercion(value)
99
+ from = value.class
100
+ strict_value_coercions[from] || lenient_value_coercions[from]
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end