hashie 1.2.0 → 2.0.0

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