aetherknight / recursive-open-struct Goto Github PK
View Code? Open in Web Editor NEWOpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
License: Other
OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs
License: Other
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?
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
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?
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.
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'
It is no longer supported.
Freezing an instance of RecursiveOpenStruct
in Ruby 3.1 prevents all access to the object, even read access.
o = RecursiveOpenStruct.new({ a: 42 })
o.freeze
puts o.a
# Ruby 2.6
42
# Ruby 3.1
42
# 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>'
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.
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.
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
.
I see two ways to circumvent the problem.
super
during initialization, in order to benefit from the OpenStruct
current initialization strategy.#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.
RecursiveOpenStruct.new object, recurse_over_arrays: true
This code resembles me Java. Let's change it to RecursiveOpenStruct.new object, arrays: true
. It's obvious that "arrays" stands for resursing over them.
It works to add methods to a regular Struct, and would be very useful for me if I could do it to a RecursiveOpenStruct too.
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.
Hi all, just creating this to let you know that RecursiveOpenStruct is being benchmarked here:
arturoherrero/ofstruct#28 (comment)
Thanks for the good work!
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?
=== 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.
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"}
}
}```
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 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 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)
Travis is failing running specs for Ruby 1.9 and I backtraced it until I found the following
https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/34620. Then looking into Ruby's repository I found the fix was only applied to for versions 2.0 and above: ruby/ruby@2d952c6
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)
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>'
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
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!
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.
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)"
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 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.
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
Although the project/gem is 'recursive-open-struct', it must be required as 'recursive_open_struct'. This is confusing, and someone should be able to load it either way.
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!
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.
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
.
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
`
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
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
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)
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.
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.
I originally thought RecursiveOpenStruck is lost after map() call. I had a mistake - I did object[:element]
instead of object.element
. However, shouldn't both calls just return RecursiveOpenStruck for the sake of consistency?
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.
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>'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.