Git Product home page Git Product logo

recursive-open-struct's People

Contributors

aetherknight avatar benlangfeld avatar byroot avatar cben avatar edwardbetts avatar ekohl avatar faloi avatar fervic avatar fledman avatar gogainda avatar ilyaumanets avatar infertux avatar jrafanie avatar mattheworiordan avatar mculp avatar offirmo avatar pedrosena avatar pravi avatar pyeremenko avatar richard-degenne avatar sebastianvommeer avatar thiagogsr avatar tomchapin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

recursive-open-struct's Issues

Is recursive-open-struct really necessary?

All respect where due, I used this gem for the last year (thank you), then worked out I really didn't need it in my circumstances.

We can get JSON (included in Ruby standard library http://www.ruby-doc.org/stdlib-2.1.5/libdoc/json/rdoc/index.html, so no additional dependencies) to do the heavy lifting for us and instruct it to coerce nested attributes into OpenStructs. It will also remain respectful of arrays.

require 'json'
require 'ostruct'

hash = {:a=>1,:b=>[{:c=>2,:d=>[{:e=>3,:f=>4},{:e=>5,:f=>6}]},{:c=>4,:d=>[{:e=>7,:f=>8},{:e=>9,:f=>10}]},{:c=>6,:d=>[{:e=>11,:f=>12},{:e=>13,:f=>14}]}]}

json = hash.to_json
# => "{\"a\":1,\"b\":[{\"c\":2,\"d\":[{\"e\":3,\"f\":4},{\"e\":5,\"f\":6}]},{\"c\":4,\"d\":[{\"e\":7,\"f\":8},{\"e\":9,\"f\":10}]},{\"c\":6,\"d\":[{\"e\":11,\"f\":12},{\"e\":13,\"f\":14}]}]}"

object = JSON.parse(json, object_class: OpenStruct)
# => #<OpenStruct a=1, b=[#<OpenStruct c=2, d=[#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]>, #<OpenStruct c=4, d=[#<OpenStruct e=7, f=8>, #<OpenStruct e=9, f=10>]>, #<OpenStruct c=6, d=[#<OpenStruct e=11, f=12>, #<OpenStruct e=13, f=14>]>]>

object.a
# => 1

object.b
# => [#<OpenStruct c=2, d=[#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]>, #<OpenStruct c=4, d=[#<OpenStruct e=7, f=8>, #<OpenStruct e=9, f=10>]>, #<OpenStruct c=6, d=[#<OpenStruct e=11, f=12>, #<OpenStruct e=13, f=14>]>]

object.b.class
# => Array

object.b.size
# => 3

object.b[0].class
# => OpenStruct

object.b[0].c
# => 2

object.b[0].d
# => [#<OpenStruct e=3, f=4>, #<OpenStruct e=5, f=6>]

object.b[0].d.class
# => Array

object.b[0].d.size
# => 2

object.b[0].d[1]
# => #<OpenStruct e=5, f=6>

object.b[0].d[1].f
# => 6

Is this doing everything that this gem does, or am I missing something?

When using recurse_over_arrays true, adding and populating hash in an array doesn't work in some cases

Even when defining the object with recurse_over_arrays set to true, certain ways of filling the data in an array don't work. (the nicer , more object oriented ones)

Option A - works
obj.spec = {}
obj.spec.ports = [{ 'port' => 3000,
'targetPort' => 'http-server',
'protocol' => 'TCP'
}]

Option B - doesn't work
obj.spec = {}
obj.spec.ports = []
obj.spec.ports << { 'port' => 3000,
'targetPort' => 'http-server',
'protocol' => 'TCP'
}

Option C - doesn't work
obj.spec = {}
obj.spec.ports = []
obj.spec.ports[0]= {}
obj.spec.ports[0].port = 3000

Doesn't work for a property `test`

I am using v1.0.2 of RecursiveOpenStruct and found a strange bug with it. This bug can be reproduced using Rails console as follows:

$ rails c
Running via Spring preloader in process 488
Loading development environment (Rails 5.0.2)

[1] pry(main)> str = RecursiveOpenStruct.new({ test: 123 })
=> #<RecursiveOpenStruct test=123>

[2] pry(main)> str.send('test')
ArgumentError: wrong number of arguments (given 0, expected 2..3)
from (pry):2:in `test'

[3] pry(main)> str.test
=> 123

[4] pry(main)> str.send('test')
=> 123

As you can see, the property test cannot be accessed via #send at first, but can be accessed once str.send is invoked. Any thoughts?

rspec fails on ruby-head

Currently most of the tests fail on ruby-head. I'm currently not sure why, but I noticed this as my own gem started to fail on ruby-head recently.

What I observed is that it seems the recursive aspect is no longer working. There is nothing in the ruby changelog that I can pinpoint to be causing this, but I may be overlooking something.

If time permits, I'll investigate further myself but I'm not sure if that is going to happen any time soon.

undefined method `map' for nil:NilClass

Not sure if this is a known issue or if a repro test case is needed?

```NoMethodError: undefined method map' for nil:NilClass /home/lilith/.rbenv/versions/2.7.4/lib/ruby/2.7.0/ostruct.rb:328:in inspect'
/home/lilith/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:129:in `method_missing'

RecursiveOpenStruct#freeze does not play nice with Ruby 3.1

Summary

Freezing an instance of RecursiveOpenStruct in Ruby 3.1 prevents all access to the object, even read access.

Reproducing the issue

o = RecursiveOpenStruct.new({ a: 42 })
o.freeze

puts o.a

Expected behavior

# Ruby 2.6
42

# Ruby 3.1
42

Actual behavior

# Ruby 2.6
42

# Ruby 3.1
.bundle/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:142:in `define_method': can't modify frozen object: #<RecursiveOpenStruct a=42> (FrozenError)
	from .bundle/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:142:in `block in new_ostruct_member'
	from .bundle/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:140:in `class_eval'
	from .bundle/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:140:in `new_ostruct_member'
	from .bundle/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:125:in `method_missing'
	from (irb):2:in `<main>'

Preliminary investigation

The issue seems to be caused by a change in behavior in the underlying OpenStruct class introduced somewhere between Ruby 2.7 and Ruby 3.0.

The gist of the problem is that, once an object is frozen, it becomes impossible to define new methods on it.

Ruby 2.7 and prior

Up unitl Ruby 2.7, OpenStruct used #method_missing to lazily define reader and writer methods on attributes.

OpenStruct#freeze has a custom implementation that then defines all methods (using OpenStruct#new_ostruct_member!) before freezing the object. That way, we make sure that the methods are defined and accessible, since creating new methods on a frozen object is forbidden.

Since RecursiveOpenStruct does not override #freeze, it conserves a similar behavior, and all is well.

Ruby 3.0 and later

Starting with Ruby 3.0, OpenStruct changed strategies when it comes to defining methods. All the methods are defined eagerly during initialization (see OpenStruct#initialize). That means that the custom implementation of #freeze is no longer required, since all methods already exist whenever the object gets frozen.

However, RecursiveOpenStruct overrides #initialize and does not call super. That, in turn, means that when an instance of RecursiveOpenStruct receives #freeze, the methods are never defined before freezing. And, when attempting to access an attribute, the attempt to lazily define the methods fails with FrozenError.

Fix proposal

I see two ways to circumvent the problem.

  1. Calling super during initialization, in order to benefit from the OpenStruct current initialization strategy.
  2. Override #freeze to perform the definition of all attributes before freezing the object.

Solution 1 sounds better to me, since it would make it so that future evolutions of the OpenStruct implementation would automatically get carried over to RecursiveOpenStruct. I will prepare a PR that goes that way.

In general, a good rule of thumb is to always call super when overriding a method, unless we absolutely want to get rid of the original implementation.

Do you follow SemVer? If yes, can you mention it in README/Changelog?

Most projects assume there is no SemVer when there is no explicit declaration. So kubeclient has ~> 1.0.4 as requirement for recursive-open-struct. This means I have to maintain a patch in debian to be able to install 1.1.0 version. If you state your semver compliance, I can ask kubeclient maintainer to relax the dependency to ~> 1.0.

Installed with unusual file permissions

Hi,

We had trouble loading the gem in a Rails console on a server and discovered it was due to the file having no group and other permissions set:

$:~/.rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/recursive-open-struct-0.4.3$ ls -l lib/
total 16
-rw-r--r--  1 andrew.france  COMPANY\Domain Users    32 Aug 20 12:15 recursive-open-struct.rb
-rw-------  1 andrew.france  COMPANY\Domain Users  2872 Aug 20 12:15 recursive_open_struct.rb

I don't think git stores file permissions and I can't see anything in the gemspec that would set them. Perhaps when the gem was generated?

An array breaks nesting

=== RecursiveOpenStruct is unable to handle array if it is at the top level of object: ===

RecursiveOpenStruct.new([])

{NoMethodError} undefined method 'each' for []:Array

RecursiveOpenStruct.new([{id: 1}])

{NoMethodError} undefined method 'each_pair' for [{:id => 1}]:Array

=== Hashes nested into arrays remain hashes: ===

RecursiveOpenStruct.new({data: [{id: 1}, {id: 2}]}).data[0].class

Hash

:recurse_over_arrays option does not help.

Can't have numbers as methods

I don't think this is a problem with the gem itself, but if a hash starts with a number you can't access the RecursiveOpenStruct's below it.
http://stackoverflow.com/questions/10542354/what-are-the-restrictions-for-method-names-in-ruby

It would be cool to have an option to put a string at the front (or something that allows access below). I just flipped it back to a hash and was fine.

#Broken:

{"0"=>
  {"id"=>"",
   "coverage_id"=>"555",
   "first_name"=>"Ryan ",
   "last_name"=>"Jones",
   "birth_date(2i)"=>"11",
   "birth_date(3i)"=>"10",
   "birth_date(1i)"=>"1940"}
  }
}

#Some type of fix?

{"a0"=>
  {"id"=>"",
   "coverage_id"=>"555",
   "first_name"=>"Ryan ",
   "last_name"=>"Jones",
   "birth_date(2i)"=>"11",
   "birth_date(3i)"=>"10",
   "birth_date(1i)"=>"1940"}
  }
}```

Hash#dig and Hash#fetch patches

Took me a bit of time to realize my code was failing because RecursiveOpenStruct#dig (inherited from Hash) returns a plain Hash.

struct = RecursiveOpenStruct.new(a: {b: 1})

struct.dig(:a, :b)
# => {:b => 1 }

struct.dig("a", "b")
# => nil

I am using the following patch:

class RecursiveOpenStruct
  def dig(*keys)
    keys.reduce(self) do |memo, key|
      val = memo[key]
      val.is_a?(Hash) ? self.class.new(val) : val
    end
  end
end

x = RecursiveOpenStruct.new a: {b: 2}

x.dig :a, :b
# => 2

x.dig "a", "b"
# => 2

What are your thoughts on adding this function here?

While I'm at it, I also added a RecursiveOpenStruct#fetch method, because I found myself using it in code before realizing it didn't exist:

class RecursiveOpenStruct
  def fetch(*args, &blk)
    to_h.fetch *args, &blk
  end
end

tests fail with ruby version 2.7 NoMethodError: undefined method `[]=' for nil:NilClass

Tests are failing with ruby 2.7

44) RecursiveOpenStruct recursive behavior recursing over arrays modifying an array and recursing over it after appending a hash to an array can have new values be set
      Failure/Error: modifiable[new_ostruct_member!($1.to_sym)] = args[0]
      
      NoMethodError:
        undefined method `[]=' for nil:NilClass
      # ./lib/recursive_open_struct.rb:82:in `method_missing'
      # ./spec/recursive_open_struct/recursion_spec.rb:272:in `block (6 levels) in <top (required)>'

Finished in 0.03504 seconds (files took 0.21339 seconds to load)
114 examples, 44 failures

Freeze is not working + add deep freeze / immutable

Freeze does not work:

> r = RecursiveOpenStruct.new( { hello: 'world', foo: [ {bar: :baz} ] }, recurse_over_arrays: true)
> r.freeze
> r.delete_field('foo')
> r
=> #<RecursiveOpenStruct hello="world">
> r.bar = 'baz'
> r
=> #<RecursiveOpenStruct hello="world", bar="baz">

Deep freeze would be nice:

> r = RecursiveOpenStruct.new( { hello: 'world', foo: [ {bar: :baz} ] }, recurse_over_arrays: true)
> r.deep_freeze
> r.hello.reverse!
FrozenError (can't modify frozen String)
> r.foo << { dog: :cat }
FrozenError (can't modify frozen Array)
> r.foo.first.bar = :closed
FrozenError (can't modify frozen RecursiveOpenStruct)

This could be done after initialization if immutable: true has been given:

> r = RecursiveOpenStruct.new( { hello: 'world', foo: [ {bar: :baz} ] }, recurse_over_arrays: true, immutable: true)
> r.hello = 'universe'
FrozenError (can't modify frozen RecursiveOpenStruct)
> r.foo << { dog: :cat }
FrozenError (can't modify frozen Array)
> r.foo.first.bar = :closed
FrozenError (can't modify frozen RecursiveOpenStruct)

Cannot change the properties of a nested RecursiveOpenStruct

I'm not sure if this is a bug or a misunderstanding of mine: I cannot change the properties of a nested RecursiveOpenStruct:

o = RecursiveOpenStruct.new a: 1, b: { c: 1 }
=> #<RecursiveOpenStruct a=1, b={:c=>1}>
irb(main):003:0> o.a
=> 1
irb(main):004:0> o.b.c
=> 1
irb(main):006:0> o.a = 2
=> 2
irb(main):005:0> o.b.c = 2
=> 2
irb(main):003:0> o.a
=> 2
irb(main):004:0> o.b.c
=> 1

Why is o.b.c still 1? Is this the expected behaviour?

recursive-open-struct (0.3.1)

on ruby 2.3.1 recurse_over_arrays doesnt work

hi,
recently i switched to newer ruby in one of my project and noticed recurse_over_arrays option doesnt work any longer. Looks like it still is just a hash.
Im not sure how it is on other ruby versions.

Thank you

2.3.1 :001 > require 'recursive-open-struct'
 => true 
2.3.1 :002 > ros = RecursiveOpenStruct.new({array: [{field: 'value'}]}, recurse_over_arrays: true)
 => #<RecursiveOpenStruct array=[{:field=>"value"}]> 
2.3.1 :003 > ros.array[0].field
NoMethodError: undefined method `field' for {:field=>"value"}:Hash
    from (irb):3
    from /Users/kuba/.rvm/rubies/ruby-2.3.1/bin/irb:11:in `<main>'

[help] Need help with this issue

Hello, thanks for this gem, it's great. I'm trying this code but for some reason it doesn't work:

require "yaml"
require "recursive-open-struct"

class Config < RecursiveOpenStruct
    def initialize(filename)
        @filename = filename
        super(YAML.load_file(@filename), {recurse_over_arrays: true, preserve_original_keys: true})
    end
end

config = Config.new("config.yml")
puts config
puts config.devices

The result is this:

$ ruby config.rb
#<Config devices={"lan"=>"eth0", "wlan"=>"wlan0", "wan"=>"wan0", "bridge"=>"br0"}, lan={"address"=>"192.168.1.1", "dhcp"=>{"enabled"=>true, "start_host"=>100, "hosts"=>50, "lease"=>5, "dns"=>["1.1.1.1", "8.8.8.8"]}, "ap"=>{"enabled"=>true, "ssid"=>"", "password"=>"", "channel"=>5, "hidden"=>false, "isolated"=>false}}, wan={"setup"=>"static", "static"=>{"address"=>"172.26.0.7", "netmask"=>"255.255.255.0", "gateway"=>"172.26.0.1"}, "pppoe"=>{"user"=>"", "password"=>""}}, router={"mode"=>"router", "hostname"=>"raspberrypi", "login"=>{"username"=>"", "password"=>""}, "ntp"=>{"enabled"=>true, "zone"=>""}, "dmz_host"=>3, "dns"=>["1.0.0.1", "8.8.4.4"]}, routes=[{"network"=>"10.0.0.0", "netmask"=>"255.255.255.0", "host"=>8}, {"network"=>"10.0.1.0", "netmask"=>"255.255.255.0", "host"=>9}], port_forwarding=[{"name"=>"web", "host"=>6, "proto"=>"tcp", "ports"=>"80,443"}, {"name"=>"ssh", "host"=>7, "proto"=>"tcp", "ports"=>"22"}]>
Traceback (most recent call last):
	5: from config.rb:12:in `<main>'
	4: from /var/lib/gems/2.5.0/gems/recursive-open-struct-1.1.1/lib/recursive_open_struct.rb:104:in `method_missing'
	3: from /var/lib/gems/2.5.0/gems/recursive-open-struct-1.1.1/lib/recursive_open_struct.rb:120:in `block (2 levels) in new_ostruct_member'
	2: from /var/lib/gems/2.5.0/gems/recursive-open-struct-1.1.1/lib/recursive_open_struct.rb:59:in `[]'
	1: from /var/lib/gems/2.5.0/gems/recursive-open-struct-1.1.1/lib/recursive_open_struct.rb:59:in `new'
config.rb:5:in `initialize': wrong number of arguments (given 2, expected 1) (ArgumentError)

If I use OpenStruct instead of RecursiveOpenStruct, the code works (but it's not recursive).

Apparently, the first "puts" works, but not the other one.
Am I missing something? It's been a while from my coder days.

Thanks

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

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!

Indifferent access broken in 0.6.3

Unfortunately, the fix for #28 and corresponding pull #30 are breaking the implicit indifferent access behaviour that OpenStruct has and worked - at least mostly - until version 0.6.2.

The following code:

require 'recursive-open-struct'
puts "RecursiveOpenStruct version: #{RecursiveOpenStruct::VERSION}"
ros = RecursiveOpenStruct.new
ros[:foo] = 1
ros['foo'] = 2
raise '!!! BAD !!!' unless ros.foo == 2

will fail on 0.6.3, but works on 0.6.2 and earlier.

is there support to create ROS in an object oriented way?

All the examples I saw are creating ROS by "feeding" it a hash (with nested hashes)
Does it support creating ROS by setting its attributes one by one, specifically with nested attributes too?

Example:
service1 = RecursiveOpenStruct.new
service1.id = 12345
service.name ="redis"
service1.resources.memory="xyz"
service1.resources.cpu ="abc"

the above example fails on the part where there's a nested level (resources.memory and resources.cpu) with "undefined method `memory=' for nil:NilClass (NoMethodError)"

NameError: uninitialized constant RecursiveOpenStruct in Rails 3.2.9?

Now I have a method here that uses RecursiveOpenStruct.

def self.find(id)
  RecursiveOpenStruct.new(self.load(id))
end

self.load(id) is verified and it returns correct hash.

When I use find method, it returns

NameError (uninitialized constant Page::RecursiveOpenStruct):
  app/models/page.rb:29:in `find'
  app/controllers/us_controller.rb:10:in `start'

And when I try to use it in rails console,

1.9.3p194 :001 > RecursiveOpenStruct
NameError: uninitialized constant RecursiveOpenStruct
  from (irb):1
  from /Users/jkim/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.9/lib/rails/commands/console.rb:47:in `start'
  from /Users/jkim/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.9/lib/rails/commands/console.rb:8:in `start'
  from /Users/jkim/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.9/lib/rails/commands.rb:41:in `<top (required)>'
  from script/rails:6:in `require'
  from script/rails:6:in `<main>'

My dev environment

  • Rails 3.2.9
  • ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin11.4.2]
  • Mac OS X 10.7.5

My gemfile includes
gem "recursive-open-struct", "~> 0.2.1"

gemfile.lock also includes
recursive-open-struct (0.2.1)

I did bundle install numerous times and it installed successfully

...
Using rails (3.2.9) 
Using recursive-open-struct (0.2.1) 
Using sass (3.2.3) 
Using sass-rails (3.2.5) 
Using uglifier (1.3.0) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

Keys defined as strings return nil values

In v0.6.2, if a hash where keys are set as strings (as opposed to symbols) is passed into recursive open struct, all values retrieved are returned as nil.

Here is my code:

require 'recursive-open-struct'

# String type
ros = RecursiveOpenStruct.new( { "foo" => { "bar" => "val" }, "another_key" => "another_value", "hello" => "world" } )
puts ros.inspect
puts "foo = #{ros.foo}"
puts "another_key = #{ros.another_key}"
puts "hello = #{ros.hello}"

# Symbol type
ros = RecursiveOpenStruct.new( { :foo => { :bar => "val" }, :another_key => "another_value", :hello => "world" } )
puts ros.inspect
puts "foo = #{ros.foo}"
puts "another_key = #{ros.another_key}"
puts "hello = #{ros.hello}"

In v0.6.2, this is what I get in console:

C:\testing_recursive_ostruct>bundle exec ruby testing_recursive_ostruct.rb
#<RecursiveOpenStruct foo={"bar"=>"val"}, another_key="another_value", hello="world">
foo =
another_key =
hello =
#<RecursiveOpenStruct foo={:bar=>"val"}, another_key="another_value", hello="world">
foo = #<RecursiveOpenStruct bar="val">
another_key = another_value
hello = world

In v0.6.1, this is what I get in console:

C:\testing_recursive_ostruct>bundle exec ruby testing_recursive_ostruct.rb
#<RecursiveOpenStruct foo={"bar"=>"val"}, another_key="another_value", hello="world">
foo = #<RecursiveOpenStruct bar="val">
another_key = another_value
hello = world
#<RecursiveOpenStruct foo={:bar=>"val"}, another_key="another_value", hello="world">
foo = #<RecursiveOpenStruct bar="val">
another_key = another_value
hello = world

Get Defined Properties/Keys

Hi,

I just came across your gem as a replacement for Hashie::Mash w/ StrictKeyAccess enabled. I've been having some odd issues that others have had as well.

ROS seems like almost a perfect fit, but I have no way to easily introspect the properties/keys of an object. When I have raise_on_missing: enabled, I really would like to be able check for the presence of a key without resorting to respond_to?.

Specifically, I'm using this with Faraday and am trying out my own middleware based on the Mashify middleware but renamed to Rosify obviously :) . Essentially, it converts any response body to a ROS object when possible.

To get the list of keys I've added a refinement to use when I need it.

  refine RecursiveOpenStruct do
    def _struct_keys
      @table.keys
    end
  end

Is there anything I'm missing or why I shouldn't do something like this?

Thanks!

New RubyGems release?

Can a new version be released that includes 0c16caa? At the moment, I can't use the version in RubyGems due to missing this fix.

Unknown key with a block returns nil

This can cause confusion.

  > RecursiveOpenStruct.new(hello: 'world').each { |k, v| puts k.upcase }
  => nil
  > RecursiveOpenStruct.new(hello: 'world').eahc_pair { |k, v| puts k.upcase }
  => nil
  > RecursiveOpenStruct.new(hello: 'world').each_pair { |k, v| puts k.upcase }
  HELLO
  => #<RecursiveOpenStruct hello="world">
  > RecursiveOpenStruct.new(hello: 'world').transform_keys(&:upcase)
  => nil

Unknown key with a block should probably raise NoMethodError.

RSpec failure when testing RecursiveOpenStruct.new(nil)

When testing RecursiveOpenStruct (ROS) using RSpec in a Rails project, my test against initializing ROS with nil is failing and reports a 'no method error for nil class' when sending a key to the ROS instance.

I am wrapping calls to RecursiveOpenStruct in a method that makes the call:
RecursiveOpenStruct.new(hash, recurse_over_arrays: true)

I tested this with 2 different versions of RecursiveOpenStruct and received 2 different outcomes:
first using the current 0.6.4 version gem build, I instantiated the RecursiveOpenStruct class passing nil and recurse_over_arrays: true.
RecursiveOpenStruct.new(nil, recurse_over_arrays: true)
This test fails

Using the older version 0.5.0 of the gem and the same initialization, I have no issues with the tests failing.

When I pry the session in the failing test, I see that RecursiveOpenStruct.new(nil, recurse_over_arrays: true) returns a RecursiveOpenStruct instance, however when I send a key to the ROS instance that does not exist, then the "NoMethodError: undefined method []' for nil:NilClass" appears.

Pry session debug output:
[1] pry(#RSpec::ExampleGroups::ResponseDeserializer)> described_class.recursive_open_struct(nil).client_id
NoMethodError: undefined method []' for nil:NilClass
from .../.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/ostruct.rb:185:in method_missing'

It appears that somewhere in the method_missing calls, the ROS object gets lost, perhaps because the instance does not correctly maintain the @table object?

Files and additional test output are below.

Test Error:
`From: ../spec/lib/serializers/response_deserializer_spec.rb @ line 79 :

74:   it "handles nil" do
77:     nil_ostruct = described_class.recursive_open_struct(nil)
78:     binding.pry
79:     expect(nil_ostruct.client_id).to be_nil
80:     # expect{nil_ostruct.client_id}.to_not raise_error

`

[1] pry(#RSpec::ExampleGroups::ResponseDeserializer)> nil_ostruct
=> #RecursiveOpenStruct:0x3fc71154d338

[2] pry(#RSpec::ExampleGroups::ResponseDeserializer)> nil_ostruct.client
NoMethodError: undefined method []' for nil:NilClass
from .../.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/ostruct.rb:185:in method_missing'

More console output, notice that it is not possible to assign a new key-value pair to the ROS instance that was initialized with nil.

[10] pry(main)> b=RecursiveOpenStruct.new(nil)
=> #RecursiveOpenStruct:0x3ff34cd4f2b8

[11] pry(main)> b.test
NoMethodError: undefined method []' for nil:NilClass
from .../.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/ostruct.rb:185:in method_missing'

[12] pry(main)> b.test=0
NoMethodError: undefined method has_key?' for nil:NilClass
from .../.rvm/gems/ruby-2.2.2/gems/recursive-open-struct-0.6.4/lib/recursive_open_struct.rb:93:in get_key_from_table'

[13] pry(main)> b
=> #RecursiveOpenStruct:0x3ff34cd4f2b8

[6] pry(main)> a=OpenStruct.new(nil)
=> #

[7] pry(main)> a.test
=> nil

[8] pry(main)> a.test = 0
=> 0

[9] pry(main)> a
=> #

`
class ResponseDeserializer
class << self

def recursive_open_struct(json)
  hash = json.kind_of?(String) ? from_json_string(json) : json
  hash.kind_of?(Array) ? recurse_array(hash) : recurse(hash)
end

private

def recurse_array(array)
  new_array = []
  array.each do |el|
    new_array << recurse(el)
  end
  new_array
end

def recurse(hash)
  RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
end

def from_json_string(json)
  JSON.parse(json) unless json.nil?
end

end
end
`

Just curious, but why is the implementation here so complicated?

I'm sure i'm missing something, but why can't you just use a simple recursive function to convert a nested hash to nested open structs?
e.g

def convert_to_object(arg)
  return arg unless arg.is_a?(Hash)

  OpenStruct.new.tap do |os|
    arg.each do |k, v|
      case v
      when Hash
        os[k] = convert_to_object(v)
      when Array
        os[k] = v.map { |h| convert_to_object(h) }
      else
        os[k] = v
      end
    end
  end
end

Use it like so:

obj = convert_to_object(a: 1, b: 2, c: { j: 1, k: { u: 3 } })
obj.a #=> 1
obj.c.k.u #=> 3

On ros.inspect I can't see my array if recurse_over_arrays: true

Hi,

I'm trying to upgrade ROS from 0.5 to 1.0 and I noticed a behavior that was working previously:

bar = OpenStruct.new({a: 'a'})
bar.foos = []
bar.foos << {b: 'b'}
bar.inspect
=> "#<OpenStruct a=\"a\", foos=[{:b=>\"b\"}]>"

bar = RecursiveOpenStruct.new({a: 'a'})
bar.foos = []
bar.foos << {b: 'b'}
bar.inspect
=> "#<RecursiveOpenStruct a=\"a\", foos=[{:b=>\"b\"}]>"

bar = RecursiveOpenStruct.new({a: 'a'}, recurse_over_arrays: true)
bar.foos = []
bar.foos << {b: 'b'}
bar.inspect
=> "#<RecursiveOpenStruct a=\"a\", foos=[]>"

Apparently when I set recurse_over_arrays: true ROS is no longer able to return me this information when I use inspect (same happens when I try to call render json: ros on Rails for instance), however the information is apparently there:

bar.foos
=> [#<RecursiveOpenStruct b="b">]

Thanks

Can't serialize ROS as YAML

Ruby 2.7.1
recursive-open-struct 1.1.3
YAML 3.1.0

Deserializing a ROS from YAML does not work. I think this is because YAML/Psych calls respond_to_missing? before the object is fully loaded, see similar issue in stripe-ruby gem stripe/stripe-ruby@8bb6acf

irb(main):001:0> require 'recursive-open-struct'
=> true
irb(main):002:0> require 'yaml'
=> true
irb(main):003:0> ros = RecursiveOpenStruct.new({x: 1})
irb(main):004:0> yaml = YAML.dump(ros)
irb(main):005:0> YAML.load(yaml)
Traceback (most recent call last):
       16: from (irb):5
       15: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych.rb:279:in `load'
       14: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/nodes/node.rb:50:in `to_ruby'
       13: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
       12: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
       11: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
       10: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:313:in `visit_Psych_Nodes_Document'
        9: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
        8: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
        7: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
        6: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:208:in `visit_Psych_Nodes_Mapping'
        5: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:394:in `revive'
        4: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:401:in `init_with'
        3: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:401:in `respond_to?'
        2: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:105:in `respond_to_missing?'
        1: from /Users/aram/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:182:in `_get_key_from_table_'
NoMethodError (undefined method `has_key?' for nil:NilClass)

nested RecursiveOpenStructs are not updated in @table

This is the same issue as #5, which wasn't quite fixed.

If I create a RecursiveOpenStruct then change the value in a leaf node somewhere, the @table hash does not get updated.

[1] acme »  reos = RecursiveOpenStruct.new(:one => { :two => { :three => :four } })
=> #<RecursiveOpenStruct one={:two=>{:three=>:four}}>
[2] acme »  reos.one.two.three = :five
=> :five
[3] acme »  reos.to_h
=> {
  :one => {
    :two => {
      :three => :five
    }
  }
}
[4] acme »  reos
=> #<RecursiveOpenStruct one={:two=>{:three=>:four}}>
[5] acme »  reos.instance_variable_get(:@table)
=> {
  :one => {
    :two => {
      :three => :four
    }
  }
}

Therefore, if you dup reos or call to_json or anything, without going through to_h directly, it does not carry forward the change. This is especially a problem if you have nested RecursiveOpenStructs:

[34] acme »  reos = RecursiveOpenStruct.new(:one => RecursiveOpenStruct.new(:two => { :three => '15' }))
=> #<RecursiveOpenStruct one=#<RecursiveOpenStruct two={:three=>"15"}>>
[36] acme »  reos.one.two.three = 15
=> 15
[37] acme »  reos
=> #<RecursiveOpenStruct one=#<RecursiveOpenStruct two={:three=>"15"}>>
[38] acme »  reos.to_h
=> {
           :one => #<RecursiveOpenStruct two={:three=>"15"}>
}

Notice the value is still the string '15' instead of the integer.

`recurse_over_arrays` doesn't work at 2+ levels deep

irb(main):015:0> h = { :a => { :b => [ { :c => :d }, { :c => :e } ] } }
=> {:a=>{:b=>[{:c=>:d}, {:c=>:e}]}}

irb(main):016:0> RecursiveOpenStruct.new(h, :recurse_over_arrays => true).a.b.first
=> {:c=>:d}

irb(main):017:0> RecursiveOpenStruct.new(h, :recurse_over_arrays => true).a.b.first.class
=> Hash

It sees the first hash and creates a RecursiveOpenStruct, therefore, it will not recurse over the array in the inner hash.

Unable to deserialize recursively

Hi there! I believe this could be similar to this open issue: #69 but I figure I'd make one for our use case.

It seems like we are unable to

irb(main):001:0> require 'recursive-open-struct'
=> true
irb(main):002:0> Marshal.load(Marshal.dump(RecursiveOpenStruct.new(red: [RecursiveOpenStruct.new]))).red
/Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:80:in `[]': undefined method `[]' for nil:NilClass (NoMethodError)

    elsif v.is_a?(Array) and @options[:recurse_over_arrays]
                                     ^^^^^^^^^^^^^^^^^^^^^^
	from /Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/recursive-open-struct-1.1.3/lib/recursive_open_struct.rb:142:in `block (2 levels) in new_ostruct_member'
	from (irb):2:in `<main>'
	from /Users/maple.ong/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.3/exe/irb:11:in `<top (required)>'
	from /Users/maple.ong/.rbenv/versions/3.1.2/bin/irb:25:in `load'
	from /Users/maple.ong/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'

We are trying to upgrade our application to Ruby 3 but we are relying on this behaviour to work. Since it is broken on the latest version we cannot upgrade.

Not working with Ruby 2.3.0

I'm getting the following when using Ruby 2.3.0:

2.3.0 :001 > ros = RecursiveOpenStruct.new( { fooa: { foob: 'fooc' } } )
 => #<RecursiveOpenStruct fooa={:foob=>"fooc"}> 
2.3.0 :002 > ros.fooa
 => {:foob=>"fooc"} 
2.3.0 :003 > ros.fooa.foob
NoMethodError: undefined method `foob' for {:foob=>"fooc"}:Hash
        from (irb):3
        from /Users/dglancy/.rvm/gems/ruby-2.3.0/gems/railties-4.2.5/lib/rails/commands/console.rb:110:in `start'
        from /Users/dglancy/.rvm/gems/ruby-2.3.0/gems/railties-4.2.5/lib/rails/commands/console.rb:9:in `start'
        from /Users/dglancy/.rvm/gems/ruby-2.3.0/gems/railties-4.2.5/lib/rails/commands/commands_tasks.rb:68:in `console'
        from /Users/dglancy/.rvm/gems/ruby-2.3.0/gems/railties-4.2.5/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
        from /Users/dglancy/.rvm/gems/ruby-2.3.0/gems/railties-4.2.5/lib/rails/commands.rb:17:in `<top (required)>'
        from bin/rails:8:in `require'
        from bin/rails:8:in `<main>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.