symbolized 0.0.0 → 0.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a3f2960d6622db153aa61342406a2bc60d86d073
4
- data.tar.gz: 168b0e7e2d7c85977c7b1df7eb6c7751b99b8fb0
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODFiMWZjNjFhZjdjMzk1MDYyMmMzNDIxMzI4YmE4MTE2YTY2OWQzMw==
5
+ data.tar.gz: !binary |-
6
+ NmUwZGYzMDk0ZGI3ZTJiMjcxZWY5MmQxNjFhZWY3ZDJhOTIzY2U1NA==
5
7
  SHA512:
6
- metadata.gz: 431a2428e5f47e2e897251f5121c4bcb791c36c1311867583b2ef9d171805906df8d7c7b8351e191ebcf50595327bbc3f7257a7c9df6657ad5def12b7c327f02
7
- data.tar.gz: ff265ce246d29bd4b16972ab48704b5d255fc9c48ad12a204cc30efaf0736a62915dc2adc5d285f086c8cece9896a08c1abe1662a96ce3f53392db9662298300
8
+ metadata.gz: !binary |-
9
+ YjJhYTMyZDZkM2I2OWQ3N2UzNzAxY2RjZmE1MThiM2I5MGJjM2QwNTdjZDAx
10
+ ODBmMzZhNWNiNGVlNTEwMDFiOTY1MDdiZmRhYjUyNDJjNTIwYzAzMjM4N2M4
11
+ M2M5ZWUwNWM5OTY0OGE5ZjFiYWU1ODRiZDBhYTMxMTQ3YmIwYWU=
12
+ data.tar.gz: !binary |-
13
+ MDYyOGQ1N2ExNjEyNjQxNmI1NzY4NDVmOGUyMTY3NTVmNTgyMDVjNThkM2Nm
14
+ YWMyMzhiNmU2ZTIxMjU1OGE0MDFkYjQ3NDAzMDRmM2NlMGNhMDg4NTU2NmVh
15
+ MjA5ZWFjOTJiODFlM2MzMDk4NjllOGJhOTNkZmQ2NTg0NDlkMTA=
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Tamer Shlash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,85 @@
1
+ # Symbolized
2
+
3
+ [![Build Status](https://travis-ci.org/TamerShlash/symbolized.svg?branch=master)](https://travis-ci.org/TamerShlash/symbolized)
4
+
5
+ Symbolized provides a Hash with indifferent access, but with keys stored internally as symbols.
6
+ This is particularly useful when you have a very big amount of hashes that share the same keys,
7
+ and it may become inefficient to keep all these identical keys as strings. An example of this
8
+ case is when you have data processing pipelines that process millions of hashes with the same
9
+ keys.
10
+
11
+ ## Installation
12
+
13
+ You can either install it manually:
14
+
15
+ % [sudo] gem install symbolized
16
+
17
+ Or include it in your Gemfile:
18
+
19
+ gem 'symbolized'
20
+
21
+ And then run `bundle install`.
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'symbolized'
27
+
28
+ # You can create a SymbolizedHash directly:
29
+
30
+ symbolized_hash = SymbolizedHash.new
31
+ symbolized_hash['a'] = 'b'
32
+ symbolized_hash['a'] #=> 'b'
33
+ symbolized_hash[:a] #=> 'b'
34
+ symbolized_hash.keys #=> [:a]
35
+
36
+ # Or initialize it with a normal hash:
37
+
38
+ symbolized_hash = SymbolizedHash.new({'a' => 'b'})
39
+ symbolized_hash['a'] #=> 'b'
40
+ symbolized_hash[:a] #=> 'b'
41
+ symbolized_hash.keys #=> [:a]
42
+
43
+ # Or use the Hash#to_symbolized_hash core extension:
44
+
45
+ h = { 'a' => 'b' }
46
+ h['a'] #=> 'b'
47
+ h[:a] #=> nil
48
+ h.keys #=> ['a']
49
+
50
+ symbolized_hash = h.to_symbolized_hash
51
+ symbolized_hash['a'] #=> 'b'
52
+ symbolized_hash[:a] #=> 'b'
53
+ symbolized_hash.keys #=> [:a]
54
+
55
+ ```
56
+
57
+ The gem provides almost the same methods and functionality provided by ActiveSupport's `HashWithIndifferentAccess`, while storing keys internally as Symbols.
58
+
59
+ ## `ActiveSupport` Compatibility
60
+
61
+ This gem is built with intent to be as much as possible compatible with ActiveSupport. You can include both `Symbolized` and `ActiveSupport`, and you are guaranteed to get ActiveSupport functionality and core extensions, and still have `Symbolized` core extension and class.
62
+
63
+ ## Testing
64
+
65
+ Checkout [travis.yml](.travis.yml) to see which Ruby versions the gem has been tested against. Alternatively, if you want to test it yourself, you can clone the repo, run `bundle install` and then run `rake test`.
66
+
67
+ ## Suggestions, Discussions and Issues
68
+
69
+ Please propose suggestions, open discussions, or report bugs and issues [here](https://github.com/TamerShlash/symbolized/issues).
70
+
71
+ ## Contributing
72
+
73
+ 1. Fork the [repo](https://github.com/TamerShlash/symbolized)
74
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
75
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
76
+ 4. Push to the branch (`git push origin my-new-feature`)
77
+ 5. Create a new Pull Request
78
+
79
+ ## Credits
80
+
81
+ The current code of this gem is heavily based on [ActiveSupport 4.2 HashWithIndifferentAccess](https://github.com/rails/rails/tree/4-2-stable/activesupport). Some parts are direct clones, other parts have been modified and/or refactored.
82
+
83
+ ## License
84
+
85
+ Copyright (c) 2015 [Tamer Shlash](https://github.com/TamerShlash) ([@TamerShlash](https://twitter.com/TamerShlash)). Released under the MIT [License](LICENSE).
@@ -0,0 +1,2 @@
1
+ require 'symbolized/symbolized_hash'
2
+ require 'symbolized/core_ext/hash/symbolized_hash'
@@ -0,0 +1,166 @@
1
+ class Hash
2
+ # Returns a new hash with all keys converted using the block operation.
3
+ #
4
+ # hash = { name: 'Rob', age: '28' }
5
+ #
6
+ # hash.transform_keys{ |key| key.to_s.upcase }
7
+ # # => {"NAME"=>"Rob", "AGE"=>"28"}
8
+ def transform_keys
9
+ return enum_for(:transform_keys) unless block_given?
10
+ result = self.class.new
11
+ each_key do |key|
12
+ result[yield(key)] = self[key]
13
+ end
14
+ result
15
+ end
16
+
17
+ # Destructively convert all keys using the block operations.
18
+ # Same as transform_keys but modifies +self+.
19
+ def transform_keys!
20
+ return enum_for(:transform_keys!) unless block_given?
21
+ keys.each do |key|
22
+ self[yield(key)] = delete(key)
23
+ end
24
+ self
25
+ end
26
+
27
+ # Returns a new hash with all keys converted to strings.
28
+ #
29
+ # hash = { name: 'Rob', age: '28' }
30
+ #
31
+ # hash.stringify_keys
32
+ # # => {"name"=>"Rob", "age"=>"28"}
33
+ def stringify_keys
34
+ transform_keys{ |key| key.to_s }
35
+ end
36
+
37
+ # Destructively convert all keys to strings. Same as
38
+ # +stringify_keys+, but modifies +self+.
39
+ def stringify_keys!
40
+ transform_keys!{ |key| key.to_s }
41
+ end
42
+
43
+ # Returns a new hash with all keys converted to symbols, as long as
44
+ # they respond to +to_sym+.
45
+ #
46
+ # hash = { 'name' => 'Rob', 'age' => '28' }
47
+ #
48
+ # hash.symbolize_keys
49
+ # # => {:name=>"Rob", :age=>"28"}
50
+ def symbolize_keys
51
+ transform_keys{ |key| key.to_sym rescue key }
52
+ end
53
+ alias_method :to_options, :symbolize_keys
54
+
55
+ # Destructively convert all keys to symbols, as long as they respond
56
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
57
+ def symbolize_keys!
58
+ transform_keys!{ |key| key.to_sym rescue key }
59
+ end
60
+ alias_method :to_options!, :symbolize_keys!
61
+
62
+ # Validate all keys in a hash match <tt>*valid_keys</tt>, raising
63
+ # ArgumentError on a mismatch.
64
+ #
65
+ # Note that keys are treated differently than HashWithIndifferentAccess,
66
+ # meaning that string and symbol keys will not match.
67
+ #
68
+ # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
69
+ # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
70
+ # { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
71
+ def assert_valid_keys(*valid_keys)
72
+ valid_keys.flatten!
73
+ each_key do |k|
74
+ unless valid_keys.include?(k)
75
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
76
+ end
77
+ end
78
+ end
79
+
80
+ # Returns a new hash with all keys converted by the block operation.
81
+ # This includes the keys from the root hash and from all
82
+ # nested hashes and arrays.
83
+ #
84
+ # hash = { person: { name: 'Rob', age: '28' } }
85
+ #
86
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
87
+ # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
88
+ def deep_transform_keys(&block)
89
+ _deep_transform_keys_in_object(self, &block)
90
+ end
91
+
92
+ # Destructively convert all keys by using the block operation.
93
+ # This includes the keys from the root hash and from all
94
+ # nested hashes and arrays.
95
+ def deep_transform_keys!(&block)
96
+ _deep_transform_keys_in_object!(self, &block)
97
+ end
98
+
99
+ # Returns a new hash with all keys converted to strings.
100
+ # This includes the keys from the root hash and from all
101
+ # nested hashes and arrays.
102
+ #
103
+ # hash = { person: { name: 'Rob', age: '28' } }
104
+ #
105
+ # hash.deep_stringify_keys
106
+ # # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
107
+ def deep_stringify_keys
108
+ deep_transform_keys{ |key| key.to_s }
109
+ end
110
+
111
+ # Destructively convert all keys to strings.
112
+ # This includes the keys from the root hash and from all
113
+ # nested hashes and arrays.
114
+ def deep_stringify_keys!
115
+ deep_transform_keys!{ |key| key.to_s }
116
+ end
117
+
118
+ # Returns a new hash with all keys converted to symbols, as long as
119
+ # they respond to +to_sym+. This includes the keys from the root hash
120
+ # and from all nested hashes and arrays.
121
+ #
122
+ # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
123
+ #
124
+ # hash.deep_symbolize_keys
125
+ # # => {:person=>{:name=>"Rob", :age=>"28"}}
126
+ def deep_symbolize_keys
127
+ deep_transform_keys{ |key| key.to_sym rescue key }
128
+ end
129
+
130
+ # Destructively convert all keys to symbols, as long as they respond
131
+ # to +to_sym+. This includes the keys from the root hash and from all
132
+ # nested hashes and arrays.
133
+ def deep_symbolize_keys!
134
+ deep_transform_keys!{ |key| key.to_sym rescue key }
135
+ end
136
+
137
+ private
138
+ # support methods for deep transforming nested hashes and arrays
139
+ def _deep_transform_keys_in_object(object, &block)
140
+ case object
141
+ when Hash
142
+ object.each_with_object({}) do |(key, value), result|
143
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
144
+ end
145
+ when Array
146
+ object.map {|e| _deep_transform_keys_in_object(e, &block) }
147
+ else
148
+ object
149
+ end
150
+ end
151
+
152
+ def _deep_transform_keys_in_object!(object, &block)
153
+ case object
154
+ when Hash
155
+ object.keys.each do |key|
156
+ value = object.delete(key)
157
+ object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
158
+ end
159
+ object
160
+ when Array
161
+ object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
162
+ else
163
+ object
164
+ end
165
+ end
166
+ end unless defined? ActiveSupport
@@ -0,0 +1,22 @@
1
+ class Hash
2
+ # Merges the caller into +other_hash+. For example,
3
+ #
4
+ # options = options.reverse_merge(size: 25, velocity: 10)
5
+ #
6
+ # is equivalent to
7
+ #
8
+ # options = { size: 25, velocity: 10 }.merge(options)
9
+ #
10
+ # This is particularly useful for initializing an options hash
11
+ # with default values.
12
+ def reverse_merge(other_hash)
13
+ other_hash.merge(self)
14
+ end
15
+
16
+ # Destructive +reverse_merge+.
17
+ def reverse_merge!(other_hash)
18
+ # right wins if there is no left
19
+ merge!( other_hash ){|key,left,right| left }
20
+ end
21
+ alias_method :reverse_update, :reverse_merge!
22
+ end unless defined? ActiveSupport
@@ -0,0 +1,21 @@
1
+ require 'symbolized'
2
+
3
+ class Hash
4
+ # Returns a <tt>Symbolized::SymbolizedHash</tt> out of its receiver:
5
+ #
6
+ # { 'a' => 1 }.to_symbolized_hash[:a] # => 1
7
+ def to_symbolized_hash
8
+ Symbolized::SymbolizedHash.new_from_hash_copying_default(self)
9
+ end
10
+
11
+ # Called when object is nested under an object that receives
12
+ # #to_symbolized_hash. This method will be called on the current object
13
+ # by the enclosing object and is aliased to #to_symbolized_hash by
14
+ # default. Subclasses of Hash may overwrite this method to return +self+ if
15
+ # converting to a <tt>Symbolized::SymbolizedHash</tt> would not be desirable.
16
+ #
17
+ # b = { 'b' => 1 }
18
+ # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
19
+ # # => { :b => 1 }
20
+ alias nested_under_symbolized_hash to_symbolized_hash
21
+ end
@@ -0,0 +1,48 @@
1
+ unless defined? ActiveSupport
2
+ require 'symbolized/core_ext/object/duplicable'
3
+
4
+ class Object
5
+ # Returns a deep copy of object if it's duplicable. If it's
6
+ # not duplicable, returns +self+.
7
+ #
8
+ # object = Object.new
9
+ # dup = object.deep_dup
10
+ # dup.instance_variable_set(:@a, 1)
11
+ #
12
+ # object.instance_variable_defined?(:@a) # => false
13
+ # dup.instance_variable_defined?(:@a) # => true
14
+ def deep_dup
15
+ duplicable? ? dup : self
16
+ end
17
+ end
18
+
19
+ class Array
20
+ # Returns a deep copy of array.
21
+ #
22
+ # array = [1, [2, 3]]
23
+ # dup = array.deep_dup
24
+ # dup[1][2] = 4
25
+ #
26
+ # array[1][2] # => nil
27
+ # dup[1][2] # => 4
28
+ def deep_dup
29
+ map { |it| it.deep_dup }
30
+ end
31
+ end
32
+
33
+ class Hash
34
+ # Returns a deep copy of hash.
35
+ #
36
+ # hash = { a: { b: 'b' } }
37
+ # dup = hash.deep_dup
38
+ # dup[:a][:c] = 'c'
39
+ #
40
+ # hash[:a][:c] # => nil
41
+ # dup[:a][:c] # => "c"
42
+ def deep_dup
43
+ each_with_object(dup) do |(key, value), hash|
44
+ hash[key.deep_dup] = value.deep_dup
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Most objects are cloneable, but not all. For example you can't dup +nil+:
3
+ #
4
+ # nil.dup # => TypeError: can't dup NilClass
5
+ #
6
+ # Classes may signal their instances are not duplicable removing +dup+/+clone+
7
+ # or raising exceptions from them. So, to dup an arbitrary object you normally
8
+ # use an optimistic approach and are ready to catch an exception, say:
9
+ #
10
+ # arbitrary_object.dup rescue object
11
+ #
12
+ # Rails dups objects in a few critical spots where they are not that arbitrary.
13
+ # That rescue is very expensive (like 40 times slower than a predicate), and it
14
+ # is often triggered.
15
+ #
16
+ # That's why we hardcode the following cases and check duplicable? instead of
17
+ # using that rescue idiom.
18
+ #++
19
+ unless defined? ActiveSupport
20
+ class Object
21
+ # Can you safely dup this object?
22
+ #
23
+ # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects;
24
+ # true otherwise.
25
+ def duplicable?
26
+ true
27
+ end
28
+ end
29
+
30
+ class NilClass
31
+ # +nil+ is not duplicable:
32
+ #
33
+ # nil.duplicable? # => false
34
+ # nil.dup # => TypeError: can't dup NilClass
35
+ def duplicable?
36
+ false
37
+ end
38
+ end
39
+
40
+ class FalseClass
41
+ # +false+ is not duplicable:
42
+ #
43
+ # false.duplicable? # => false
44
+ # false.dup # => TypeError: can't dup FalseClass
45
+ def duplicable?
46
+ false
47
+ end
48
+ end
49
+
50
+ class TrueClass
51
+ # +true+ is not duplicable:
52
+ #
53
+ # true.duplicable? # => false
54
+ # true.dup # => TypeError: can't dup TrueClass
55
+ def duplicable?
56
+ false
57
+ end
58
+ end
59
+
60
+ class Symbol
61
+ # Symbols are not duplicable:
62
+ #
63
+ # :my_symbol.duplicable? # => false
64
+ # :my_symbol.dup # => TypeError: can't dup Symbol
65
+ def duplicable?
66
+ false
67
+ end
68
+ end
69
+
70
+ class Numeric
71
+ # Numbers are not duplicable:
72
+ #
73
+ # 3.duplicable? # => false
74
+ # 3.dup # => TypeError: can't dup Fixnum
75
+ def duplicable?
76
+ false
77
+ end
78
+ end
79
+
80
+ require 'bigdecimal'
81
+ class BigDecimal
82
+ # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
83
+ # raises TypeError exception. Checking here on the runtime whether BigDecimal
84
+ # will allow dup or not.
85
+ begin
86
+ BigDecimal.new('4.56').dup
87
+
88
+ def duplicable?
89
+ true
90
+ end
91
+ rescue TypeError
92
+ # can't dup, so use superclass implementation
93
+ end
94
+ end
95
+
96
+ class Method
97
+ # Methods are not duplicable:
98
+ #
99
+ # method(:puts).duplicable? # => false
100
+ # method(:puts).dup # => TypeError: allocator undefined for Method
101
+ def duplicable?
102
+ false
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,291 @@
1
+ require 'symbolized/core_ext/hash/keys'
2
+ require 'symbolized/core_ext/hash/reverse_merge'
3
+
4
+ module Symbolized
5
+ # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
6
+ # to be the same.
7
+ #
8
+ # rgb = SymbolizedHash.new
9
+ #
10
+ # rgb[:black] = '#000000'
11
+ # rgb[:black] # => '#000000'
12
+ # rgb['black'] # => '#000000'
13
+ #
14
+ # rgb['white'] = '#FFFFFF'
15
+ # rgb[:white] # => '#FFFFFF'
16
+ # rgb['white'] # => '#FFFFFF'
17
+ #
18
+ # Internally strings are mapped to symbols when used as keys in the entire
19
+ # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
20
+ # mapping belongs to the public interface. For example, given:
21
+ #
22
+ # hash = SymbolizedHash.new('a' => 1)
23
+ #
24
+ # You are guaranteed that the key is returned as a symbol:
25
+ #
26
+ # hash.keys # => [:a]
27
+ #
28
+ # Technically other types of keys are accepted:
29
+ #
30
+ # hash = SymbolizedHash.new('a' => 1)
31
+ # hash[0] = 0
32
+ # hash # => { :a => 1, 0 => 0 }
33
+ #
34
+ # but this class is intended for use cases where strings or symbols are the
35
+ # expected keys and it is convenient to understand both as the same. For
36
+ # example, processing data throught a multi-step pipeline where steps can
37
+ # be written by other people.
38
+ #
39
+ # Note that core extensions define <tt>Hash#to_symbolized_hash</tt>:
40
+ #
41
+ # rgb = { black: '#000000', 'white' => '#FFFFFF' }.to_symbolized_hash
42
+ #
43
+ # which may be handy.
44
+ class SymbolizedHash < Hash
45
+ # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
46
+ # this class.
47
+ def extractable_options?
48
+ true
49
+ end
50
+
51
+ def symbolized
52
+ dup
53
+ end
54
+
55
+ def nested_under_symbolized_hash
56
+ self
57
+ end
58
+
59
+ def initialize(constructor = {})
60
+ if constructor.respond_to?(:to_hash)
61
+ super()
62
+ update(constructor)
63
+ else
64
+ super(constructor)
65
+ end
66
+ end
67
+
68
+ def default(key = nil)
69
+ if key.is_a?(String) && include?(key = key.to_sym)
70
+ self[key]
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ def self.new_from_hash_copying_default(hash)
77
+ hash = hash.to_hash
78
+ new(hash).tap do |new_hash|
79
+ new_hash.default = hash.default
80
+ new_hash.default_proc = hash.default_proc if hash.default_proc
81
+ end
82
+ end
83
+
84
+ def self.[](*args)
85
+ new.merge!(Hash[*args])
86
+ end
87
+
88
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
89
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
90
+
91
+ # Assigns a new value to the hash:
92
+ #
93
+ # hash = SymbolizedHash.new
94
+ # hash[:key] = 'value'
95
+ #
96
+ # This value can be later fetched using either +:key+ or +'key'+.
97
+ def []=(key, value)
98
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
99
+ end
100
+
101
+ alias_method :store, :[]=
102
+
103
+ # Updates the receiver in-place, merging in the hash passed as argument:
104
+ #
105
+ # hash_1 = SymbolizedHash.new
106
+ # hash_1['key'] = 'value'
107
+ #
108
+ # hash_2 = SymbolizedHash.new
109
+ # hash_2['key'] = 'New Value!'
110
+ #
111
+ # hash_1.update(hash_2) # => { :key => 'New Value!' }
112
+ #
113
+ # The argument can be either a <tt>SymbolizedHash</tt> or a regular +Hash+.
114
+ # In either case the merge respects the semantics of indifferent access.
115
+ #
116
+ # If the argument is a regular hash with keys +:key+ and +"key"+ only one
117
+ # of the values end up in the receiver, but which one is unspecified.
118
+ #
119
+ # When given a block, the value for duplicated keys will be determined
120
+ # by the result of invoking the block with the duplicated key, the value
121
+ # in the receiver, and the value in +other_hash+. The rules for duplicated
122
+ # keys follow the semantics of indifferent access:
123
+ #
124
+ # hash_1[:key] = 10
125
+ # hash_2['key'] = 12
126
+ # hash_1.update(hash_2) { |key, old, new| old + new } # => { :key => 22 }
127
+ def update(other_hash)
128
+ if other_hash.is_a? SymbolizedHash
129
+ super(other_hash)
130
+ else
131
+ other_hash.to_hash.each_pair do |key, value|
132
+ if block_given? && key?(key)
133
+ value = yield(convert_key(key), self[key], value)
134
+ end
135
+ regular_writer(convert_key(key), convert_value(value))
136
+ end
137
+ self
138
+ end
139
+ end
140
+
141
+ alias_method :merge!, :update
142
+
143
+ # Checks the hash for a key matching the argument passed in:
144
+ #
145
+ # hash = SymbolizedHash.new
146
+ # hash[:key] = 'value'
147
+ # hash.key?(:key) # => true
148
+ # hash.key?('key') # => true
149
+ def key?(key)
150
+ super(convert_key(key))
151
+ end
152
+
153
+ alias_method :include?, :key?
154
+ alias_method :has_key?, :key?
155
+ alias_method :member?, :key?
156
+
157
+ # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
158
+ # either a string or a symbol:
159
+ #
160
+ # counters = SymbolizedHash.new
161
+ # counters['foo'] = 1
162
+ #
163
+ # counters.fetch(:foo) # => 1
164
+ # counters.fetch(:bar, 0) # => 0
165
+ # counters.fetch(:bar) { |key| 0 } # => 0
166
+ # counters.fetch('zoo') # => KeyError: key not found: :zoo
167
+ def fetch(key, *extras)
168
+ super(convert_key(key), *extras)
169
+ end
170
+
171
+ # Returns an array of the values at the specified indices:
172
+ #
173
+ # hash = SymbolizedHash.new
174
+ # hash['a'] = 'x'
175
+ # hash['b'] = 'y'
176
+ # hash.values_at(:a, :b) # => ["x", "y"]
177
+ def values_at(*indices)
178
+ indices.collect { |key| self[convert_key(key)] }
179
+ end
180
+
181
+ # Returns a shallow copy of the hash.
182
+ #
183
+ # hash = SymbolizedHash.new({ a: { b: 'b' } })
184
+ # dup = hash.dup
185
+ # dup[:a][:c] = 'c'
186
+ #
187
+ # hash[:a][:c] # => nil
188
+ # dup[:a][:c] # => "c"
189
+ def dup
190
+ self.class.new(self).tap do |new_hash|
191
+ set_defaults(new_hash)
192
+ end
193
+ end
194
+
195
+ # This method has the same semantics of +update+, except it does not
196
+ # modify the receiver but rather returns a new symbolized hash with
197
+ # indifferent access with the result of the merge.
198
+ def merge(hash, &block)
199
+ self.dup.update(hash, &block)
200
+ end
201
+
202
+ # Like +merge+ but the other way around: Merges the receiver into the
203
+ # argument and returns a new hash with indifferent access as result:
204
+ #
205
+ # hash = SymbolizedHash.new
206
+ # hash[:a] = nil
207
+ # hash.reverse_merge('a' => 0, 'b' => 1) # => { :a => nil, :b => 1 }
208
+ def reverse_merge(other_hash)
209
+ super(self.class.new_from_hash_copying_default(other_hash))
210
+ end
211
+
212
+ # Same semantics as +reverse_merge+ but modifies the receiver in-place.
213
+ def reverse_merge!(other_hash)
214
+ replace(reverse_merge( other_hash ))
215
+ end
216
+
217
+ # Replaces the contents of this hash with other_hash.
218
+ #
219
+ # h = { "a" => 100, "b" => 200 }
220
+ # h.replace({ "c" => 300, "d" => 400 }) # => { :c => 300, :d => 400 }
221
+ def replace(other_hash)
222
+ super(self.class.new_from_hash_copying_default(other_hash))
223
+ end
224
+
225
+ # Removes the specified key from the hash.
226
+ def delete(key)
227
+ super(convert_key(key))
228
+ end
229
+
230
+ def symbolize_keys!; self end
231
+ def deep_symbolize_keys!; self end
232
+ def symbolize_keys; dup end
233
+ def deep_symbolize_keys; dup end
234
+ undef :stringify_keys! if method_defined? :stringify_keys!
235
+ undef :deep_stringify_keys! if method_defined? :deep_stringify_keys!
236
+ def stringify_keys; to_hash.stringify_keys! end
237
+ def deep_stringify_keys; to_hash.deep_stringify_keys! end
238
+ def to_options!; self end
239
+
240
+ def select(*args, &block)
241
+ dup.tap { |hash| hash.select!(*args, &block) }
242
+ end
243
+
244
+ def reject(*args, &block)
245
+ dup.tap { |hash| hash.reject!(*args, &block) }
246
+ end
247
+
248
+ # Convert to a regular hash with symbol keys.
249
+ def to_hash
250
+ _new_hash = Hash.new
251
+ set_defaults(_new_hash)
252
+
253
+ each do |key, value|
254
+ _new_hash[key] = convert_value(value, for: :to_hash)
255
+ end
256
+ _new_hash
257
+ end
258
+
259
+ protected
260
+ def convert_key(key)
261
+ key.kind_of?(String) ? key.to_sym : key
262
+ end
263
+
264
+ def convert_value(value, options = {})
265
+ if value.is_a? Hash
266
+ if options[:for] == :to_hash
267
+ value.to_hash
268
+ else
269
+ value.nested_under_symbolized_hash
270
+ end
271
+ elsif value.is_a?(Array)
272
+ unless options[:for] == :assignment
273
+ value = value.dup
274
+ end
275
+ value.map! { |e| convert_value(e, options) }
276
+ else
277
+ value
278
+ end
279
+ end
280
+
281
+ def set_defaults(target)
282
+ if default_proc
283
+ target.default_proc = default_proc.dup
284
+ else
285
+ target.default = default
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ SymbolizedHash = Symbolized::SymbolizedHash
@@ -0,0 +1,3 @@
1
+ module Symbolized
2
+ VERSION = '0.0.1'
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: symbolized
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tamer Shlash
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-17 00:00:00.000000000 Z
11
+ date: 2015-07-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Hash with indifferent access, with keys stored internally as symbols.
14
14
  email: mr.tamershlash@gmail.com
@@ -16,7 +16,16 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - LICENSE
20
+ - README.md
19
21
  - lib/symbolized.rb
22
+ - lib/symbolized/core_ext/hash/keys.rb
23
+ - lib/symbolized/core_ext/hash/reverse_merge.rb
24
+ - lib/symbolized/core_ext/hash/symbolized_hash.rb
25
+ - lib/symbolized/core_ext/object/deep_dup.rb
26
+ - lib/symbolized/core_ext/object/duplicable.rb
27
+ - lib/symbolized/symbolized_hash.rb
28
+ - lib/symbolized/version.rb
20
29
  homepage: https://github.com/TamerShlash/symbolized
21
30
  licenses:
22
31
  - MIT
@@ -27,17 +36,17 @@ require_paths:
27
36
  - lib
28
37
  required_ruby_version: !ruby/object:Gem::Requirement
29
38
  requirements:
30
- - - ">="
39
+ - - ! '>='
31
40
  - !ruby/object:Gem::Version
32
41
  version: '0'
33
42
  required_rubygems_version: !ruby/object:Gem::Requirement
34
43
  requirements:
35
- - - ">="
44
+ - - ! '>='
36
45
  - !ruby/object:Gem::Version
37
46
  version: '0'
38
47
  requirements: []
39
48
  rubyforge_project:
40
- rubygems_version: 2.4.7
49
+ rubygems_version: 2.2.2
41
50
  signing_key:
42
51
  specification_version: 4
43
52
  summary: Symbolized HashWithIndifferentAccess