Skip to content

Recursively converting child hash attributes to OpenStruct. #5

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

abhionlyone
Copy link

This PR can enable something like this:

person = OpenStruct.new
person.name = "John Smith"
person.age  = 70
person.more_info = {interests: ['singing', 'dancing'], tech_skills: ['Ruby', 'C++']}

puts person.more_info.interests
puts person.more_info.tech_skills

@abhionlyone
Copy link
Author

Hi @hsbt , Did you get a chance to review this PR? What do you think about the changes?

Regards,
Abhilash

@esparta
Copy link

esparta commented Oct 15, 2018

No maintainer or someone to have decision, but before going forward I'd suggest two things:

  • A set of benchmarking on this. How this will impact the current code.
  • Tests

Additionally, as being a new feature this should be proposed via https://bugs.ruby-lang.org ticket, will be discussed there (if not rejected before).

@marcandre
Copy link
Member

It's fine to propose a feature/change without having tests, etc., as a way to first check if the feature is acceptable or not.

In this case I believe it isn't, for compatibility reasons. OpenStruct doesn't respond to all the methods of Hash, so if someone is already storing a hash in an attribute of an OpenStruct, she is expecting that attribute to be a Hash.

In the example above, person.more_info.transform_values{ ... } would no longer work.

@abhionlyone
Copy link
Author

@marcandre Thanks for the response!! I completely agree with your feedback. But there are a lot of people out there expecting OpenStruct to recursively convert a child hash to another OpenStruct object. So, why not provide them with an option to recursively convert child hash objects to OpenStruct object? Maybe allowing users to pass additional options to constructor would help?

Like this:

def initialize(hash=nil, options={})
  ...
end

@marcandre
Copy link
Member

Adding a recursive: true or similar option to the constructor seems to me like a much better idea 😄

The official forum to get a new feature accepted is https://bugs.ruby-lang.org/ . Best thing at this point would be to make the feature request there to see if there are any objections.

If you'd like to work on the implementation, you can amend this PR too. You'll need tests, also I'd use respond_to? :to_hash as a check.

@abhionlyone
Copy link
Author

@marcandre Thanks! Like you suggested I will create a feature request on https://bugs.ruby-lang.org/ 😃

lib/ostruct.rb Outdated
if hash
hash.each_pair do |k, v|
k = k.to_sym
@table[k] = v
@table[k] = (recursive && v.is_a?(Hash)) ? OpenStruct.new(v, recursive: true) : v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recursive is not defined as a keyword argument.

lib/ostruct.rb Outdated
if hash
hash.each_pair do |k, v|
k = k.to_sym
@table[k] = v
@table[k] = (recursive && v.is_a?(Hash)) ? OpenStruct.new(v,true) : v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I pointed out, best use respond_to?(:to_hash) instead of is_a?(Hash)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry missed that. Updated the code!

@imnithin
Copy link

+1 for the PR

lib/ostruct.rb Outdated
@@ -88,12 +87,13 @@ class OpenStruct
#
# data # => #<OpenStruct country="Australia", capital="Canberra">
#
def initialize(hash=nil)
def initialize(hash=nil, is_recursive=false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be more readable to use a keyword argument I'd say

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated! Thanks :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use keyword argument the below test will fail:

def test_accessor_defines_method
    os = OpenStruct.new(foo: 42)
    assert os.respond_to? :foo
    assert_equal([], os.singleton_methods)
    assert_equal(42, os.foo)
    assert_equal([:foo, :foo=], os.singleton_methods.sort)
end

But for better readability I changed the constructor to this:

def initialize(hash=nil, options={recursive: false})
    @table = {}
    @recursive = options.fetch(:recursive, false)
    if hash
      hash.each_pair do |k, v|
        k = k.to_sym
        @table[k] = (@recursive && v.respond_to?(:to_hash)) ? OpenStruct.new(v,true) : v
      end
    end
end

@abhionlyone abhionlyone force-pushed the master branch 3 times, most recently from bd2c661 to 696ba34 Compare October 16, 2018 08:33
@abhionlyone
Copy link
Author

Just FYI, A feature request has been created here https://bugs.ruby-lang.org/issues/15225

lib/ostruct.rb Outdated
if hash
hash.each_pair do |k, v|
k = k.to_sym
@table[k] = v
@table[k] = (@recursive && v.respond_to?(:to_hash)) ? OpenStruct.new(v,true) : v

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenStruct.new(v, options) or OpenStruct.new(v, recursive: true)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should be OpenStruct.new(v, options). Will update the PR in a bit.

lib/ostruct.rb Outdated
@@ -202,7 +202,7 @@ def method_missing(mid, *args) # :nodoc:
if len != 1
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
end
modifiable?[new_ostruct_member!(mname)] = args[0]
modifiable?[new_ostruct_member!(mname)] = (@recursive && args[0].respond_to?(:to_hash)) ? OpenStruct.new(args[0],true) : args[0]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here
OpenStruct.new(v, recursive: true)

@n-rodriguez
Copy link

Hi there! Any news?

@marcandre
Copy link
Member

I updated https://bugs.ruby-lang.org/issues/15225

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants