New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integer key upsets Hashie::Mash with SymbolizeKeys extension #516
Comments
Yep, that's a bug. We'd love to accept a patch that makes it so the symbolize extension doesn't cause errors with other types of keys. Any interest in taking on that challenge? |
Just trying to repair this, made simple checks. But has been stuck in, don't know what to do there:
Diff
|
And found also another bug
Here ok.
=> {"publish"=>{"80"=>1080}} Integer key 80 was converted into String "80". |
Mashes are weird, to be honest. Much of the logic assumes that you're dealing with String/Symbol keys because otherwise you do not get the benefits of a Mash. This might be a bigger deal than it originally seemed. |
I'm interested in this. Curious about the desired behavior at the base mash level for non string/symbol keys? As @michaelherold says, the point of a mash is "pseudo-object functionality". Only strings and symbols can define a method call. In @deemytch's example, what would the expectation be given a hash with numeric string and Number keys? "servicelist"=>{"clerk"=>{"publish"=>{3000=>3000, "3000"=>5675}}}} So what is the goal of not throwing an error when mixing in the diff --git a/lib/hashie/extensions/symbolize_keys.rb b/lib/hashie/extensions/symbolize_keys.rb
index e4315bd..543e0f9 100644
--- a/lib/hashie/extensions/symbolize_keys.rb
+++ b/lib/hashie/extensions/symbolize_keys.rb
@@ -46,7 +46,7 @@ module Hashie
hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!)
hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
symbolize_keys_recursively!(hash[k])
- hash[k.to_sym] = hash.delete(k)
+ hash[convert_key(k)] = hash.delete(k)
end
hash
end
@@ -61,6 +61,12 @@ module Hashie
symbolize_keys!(new_hash)
end
end
+
+ private
+
+ def convert_key(key)
+ key.to_sym rescue key
+ end
end
diff --git a/lib/hashie/mash.rb b/lib/hashie/mash.rb
index 96b24c9..7894a2a 100644
--- a/lib/hashie/mash.rb
+++ b/lib/hashie/mash.rb
@@ -122,7 +122,7 @@ module Hashie
alias regular_writer []=
# Retrieves an attribute set in the Mash. Will convert
- # any key passed in to a string before retrieving.
+ # any Symbol key passed in to a string before retrieving.
def custom_reader(key)
default_proc.call(self, key) if default_proc && !key?(key)
value = regular_reader(convert_key(key))
@@ -130,14 +130,14 @@ module Hashie
value
end
- # Sets an attribute in the Mash. Key will be converted to
+ # Sets an attribute in the Mash. Symbol keys will be converted to
# a string before it is set, and Hashes will be converted
# into Mashes for nesting purposes.
def custom_writer(key, value, convert = true) #:nodoc:
- key_as_symbol = (key = convert_key(key)).to_sym
+ key_as_symbol = key.to_s.to_sym
log_built_in_message(key_as_symbol) if log_collision?(key_as_symbol)
- regular_writer(key, convert ? convert_value(value) : value)
+ regular_writer(convert_key(key), convert ? convert_value(value) : value)
end
alias [] custom_reader
@@ -371,7 +371,7 @@ module Hashie
end
def convert_key(key) #:nodoc:
- key.to_s
+ key.respond_to?(:to_sym) ? key.to_s : key
end and now your example "works" [1] pry(main)> yaml = {"app"=>{"id"=>"deployer", "progname"=>"deployer"}, "servicelist"=>{"clerk"=>{"publish"=>{3000=>3000, 3500=>5675}}}}
=> {"app"=>{"id"=>"deployer", "progname"=>"deployer"}, "servicelist"=>{"clerk"=>{"publish"=>{3000=>3000, 3500=>5675}}}}
[2] pry(main)> class SettingsHash < ::Hashie::Mash
[2] pry(main)* include Hashie::Extensions::Mash::SymbolizeKeys
[2] pry(main)* end
=> SettingsHash
[3] pry(main)> cfg = SettingsHash.new
=> {}
[4] pry(main)> cfg.merge! yaml
=> {:app=>{:id=>"deployer", :progname=>"deployer"}, :servicelist=>{:clerk=>{:publish=>{3000=>3000, 3500=>5675}}}} HOWEVER this is a breaking change now that mash keys are not blanket converted to strings. current behavior [16] pry(main)> hash = {animals: {"1": "cat", "1" => "dog", 1 => "also cat"}}
=> {:animals=>{:"1"=>"cat", "1"=>"dog", 1=>"also cat"}}
[17] pry(main)> mash = Hashie::Mash.new(hash)
=> {"animals"=>{"1"=>"also cat"}} new behavior [34] pry(main)> hash = {animals: {"1": "cat", "1" => "dog", 1 => "also cat"}}
=> {:animals=>{:"1"=>"cat", "1"=>"dog", 1=>"also cat"}}
[35] pry(main)> mash = Hashie::Mash.new(hash)
=> {"animals"=>{"1"=>"dog", 1=>"also cat"}} Since symbols and strings define the same method, indifferent access is implied, but complicating this scenario, Mashes also allow hashie/spec/hashie/mash_spec.rb Lines 980 to 986 in bfd3e9c
With the animals hash example above current behavior [18] pry(main)> mash.dig("animals", 1)
=> "also cat"
[19] pry(main)> mash.dig("animals", "1")
=> "also cat" new behavior? [36] pry(main)> mash.dig(:animals, 1)
=> "also cat"
[37] pry(main)> mash.dig(:animals, "1")
=> "dog" The same type of "fix" applied to the base Hashie symbolize keys mixin [1] pry(main)> MyHash = Class.new(Hash); MyHash.public_send(:include, Hashie::Extensions::SymbolizeKeys);
[2] pry(main)> hash = {"animals" => {"1": "cat", "1" => "dog", 1 => "also cat"}}
=> {"animals"=>{:"1"=>"cat", "1"=>"dog", 1=>"also cat"}}
[3] pry(main)> myhash = MyHash.new.merge(hash)
=> {"animals"=>{:"1"=>"cat", "1"=>"dog", 1=>"also cat"}}
[4] pry(main)> myhash.symbolize_keys
=> {:animals=>{:"1"=>"dog", 1=>"also cat"}} |
IMHO when I use numbers for keys, sure I don't hope to use it like that |
That doesn't really narrow the questions needing answers in order to patch this. It isn't possible to "fix" this and preserve the existing behavior of a
|
@deemytch given how [2] pry(main)> x = Hashie::Mash.new(ports: { 300 => "foo" })
=> {"ports"=>{"300"=>"foo"}}
[3] pry(main)> x.dig(:ports, 300)
=> "foo" |
Such use |
I agree with this angle. Try a PR? |
I don't really understand how this is an angle... my comments were trying to outline that making the required code changes to avoid throwing an error open some questions on expected behavior: the bottom line as I see it here is that in order to support including the
then this is impossible. If converting the strings is acceptable --then sure a PR is possible. This means that if you were relying on cfg.ports[300] in your code, where Further, accepting the conversion to strings as a means of "fixing" this by not throwing an error means that if your incoming |
Apologies, I'll clarify what I meant @carolineartz. First, it's an interesting problem and thank you for stepping up to solve it. I was agreeing with you on the issue and goals (we don't want an error) and I was agreeing on the solution (convert integers, and possibly other types, to strings then to symbols). I think it's the best we can do, and you're absolutely right about the side effects. I generally also value strong opinions from contributors on what we should be doing, cause what do I know? :) All I'm saying is "let's see a PR for that!" and "thank you". For the actual PR, my first question will be "which existing tests fail"? If the answer is none, then we're good to go with a paragraph in README and UPGRADING. If we are introducing regressions we should highlight those and @michaelherold can make a call on which way to go. Finally, if we made a mistake, it will be fun to write another chapter of the demonic possession of Hashie::Mash :) |
@dblock opinion-wise, I think the symbolization of keys should behave like the rails Here is a branch to the above effect showing the diffs. https://github.com/hashie/hashie/compare/master...carolineartz:fix-symbolize-keys?expand=1 These changes result in two expected spec failures.
hashie/spec/hashie/mash_spec.rb Line 896 in c066135
hashie/spec/hashie/mash_spec.rb Lines 902 to 904 in c066135
and
hashie/spec/hashie/mash_spec.rb Lines 980 to 986 in c066135
IMO, the first re: As for the second...I don't really understand why it was ever desired to have thoughts? |
Looks good to me:
I presume the first test leaves a numeric 4 as a key? That seems like a fix to a bug :) The second one is trying to make a We could introduce an invariable extension for non bi-directional values such as Integer, and make Mash treat everything invariably even if it can't go both ways, but we have to ask ourselves, what else could possibly go wrong? README changes and regressions in UPGRADING plus a major version bump will be important, please. |
@dblock on it. Will open a PR tonight or tomorrow. Thanks for the help and we'll move further discussion there. I'll link between this issue and the forthcoming PR. |
This was resolved in #521. |
I made a Hash like that:
Then wrote a class:
And that code was crashed with NoMethodError
The text was updated successfully, but these errors were encountered: