symbolized 0.0.0 → 0.0.1

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