aetherknight / recursive-open-struct

OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
Other
276 stars 54 forks source link

'test' key in source hash breaks RecursiveOpenStruct ros['test'] or ros[:test] #42

Closed SaltwaterC closed 8 years ago

SaltwaterC commented 8 years ago

Hi.

require 'recursive-open-struct'

foo = {
  test: :bar
}

ros = RecursiveOpenStruct.new foo

# p ros.test
p ros[:test]
p ros['test']

Crashes in flames with:

recursive_open_struct.rb:42:in `test': wrong number of arguments (0 for 2..3) (ArgumentError)
recursive_open_struct.rb:42:in `[]'

I've left ros.test method call in there as uncommenting that line auto-magically fixes the code and it outputs the expected:

:bar
:bar
:bar

This issue appeared with 1.0.0 while 0.6.5 works properly. I still don't know what's so special about "test" as key as this is the only way I could reproduce the bug.

Cheers!

aetherknight commented 8 years ago

Interesting bug! I have a root cause analysis. The cause is due to the different code paths of send vs public_send and the opportunistic defining of methods on an ROS object does to try and better mimic OpenStruct's behavior in Ruby 2.3.0+.

First:

  def [](name)
    send name
  end

This performs a #send(name) instead of #public_send(name). #send checks all public, protected, and private methods for an object. Before you call ros.test, There already is a private #test method on the object, pulled in by Kernel. When you call [], the send(:test) tries to call this test method, which has an arity of 2..3.

When you call ros.test, #test is not a public method on the object. This triggers #method_missing, which eventually declares a new public method called test on the object's eigenclass, masking Kernel#test. After this point, #[] calls this method, which behaves as desired.

I'll take a quick look into fixing this.

aetherknight commented 8 years ago

@SaltwaterC I just pushed out version 1.0.1 to rubygems. Let me know if you have any other problems.

SaltwaterC commented 8 years ago

I only hit the bug as I was looping over an array to supply the keys to read from a ROS which coincidentally has a "test" value. Normally I use the method call style (i.e the whole purpose for using ROS in the first place).

Thank you for the explanation. It would have taken me a lot of time to piece all this info as I'm relatively new at Ruby. This also explains the issues that I was having with methods that were magically appearing in OpenStruct/RecursiveOpenStruct objects after including modules which define methods. The explanation being that the inclusion of a module in the global scope attaches those module methods to every object unless they are declared in module_functions. Leaving this here for future reference.

Thanks for taking care of this. I'll let you know if 1.0.1 is breaking in unexpected ways.