Feature #1400

Please add a method to enumerate fields in OpenStruct

Added by Tomas Pospisek about 6 years ago. Updated over 2 years ago.

Status:Closed
Priority:Normal
Assignee:Marc-Andre Lafortune

Description

=begin
There are two ways to find out what fields an OpenStruct instance has. One is through inspect,
however that returns a String that needs to be parsed.

The second is by white box engineering, looking at OpenStructs source code and seeing that in
fact, it has a hash and getting the keys of that hash...

The second way is faster, more robust, but will break once OpenStruct will be re-engineered...

So I suggest to add an explicit method to return a list of fields in an OpenStruct instance:

--- ostruct.rb.old 2009-04-23 15:26:45.000000000 +0200
+++ ostruct.rb 2009-04-23 15:32:41.000000000 +0200
@@ -110,6 +110,15 @@
@table.delete name.to_sym
end

  • #
  • # Returns an Array containing the fields of an OpenStruct instance
  • #
  • # p record.fields # -> [:age, :pension, :name]
  • #
  • def fields
  • @table.keys
  • end
    +
    InspectKey = :inspect_key # :nodoc:

    #
    =end


Related issues

Related to Ruby trunk - Feature #6056: Enhancements to OpenStruct Closed 02/21/2012
Duplicated by Ruby trunk - Feature #5008: Equal rights for Hash (like Array, String, Integer, Float) Rejected 07/10/2011

Associated revisions

Revision 35342
Added by Marc-Andre Lafortune about 3 years ago

  • lib/ostruct.rb: Add OpenStruct#to_h [Feature #6276] [ref #1400] [rubyspec:9e0250b2fc6f]

Revision 35342
Added by Marc-Andre Lafortune about 3 years ago

  • lib/ostruct.rb: Add OpenStruct#to_h [Feature #6276] [ref #1400] [rubyspec:9e0250b2fc6f]

Revision 37372
Added by Marc-Andre Lafortune over 2 years ago

  • lib/ostruct.rb (each_pair): Add #each_pair [#1400]

Revision 37372
Added by Marc-Andre Lafortune over 2 years ago

  • lib/ostruct.rb (each_pair): Add #each_pair [#1400]

History

#1 Updated by Tomas Pospisek about 6 years ago

=begin
A usage example and context for the feature request here: http://www.sourcepole.ch/2009/4/23/what-fields-does-this-openstruct-instance-have
=end

#2 Updated by Marc-Andre Lafortune almost 6 years ago

  • Category set to lib

=begin
I believe this could be useful.

#members might be a better name, because it is similar in role to Struct.members

The problem is that any new method is a potential compatibility break. #members ?
=end

#3 Updated by Dan Rathbun over 5 years ago

=begin
This can be done right now. In several ways.
# obj is an class OpenStruct with some number of members.
ary = obj.methods(false).sort -> Array of attr getters and setters
keys=[]
ary.each {|i| keys.push(i) unless i.include?('=') }
# keys is now an Array of fieldnames

Also using existing method .marshal_dump which is really an attribute getter for @table (having a name different than the attribute.)
Just noticed .table as a attribute getter alias of .marshal_dump, BUT .table is set protected, while .marshal_dump is not?
Why is THAT?

# obj is an class OpenStruct with some number of members.
obj.marshal_dump.keys -> Array of keys
obj.marshal_dump.values -> Array of values
obj.marshal_dump.each {|k,v| ... }

The danger in accessing the Hash directly is someone's going to modify it without removing the attr getter and setter methods defined in the class instance. Which by the way is a bug in the current OpenStruct. It's delete_field method does just that, leaving the accessor methods without a key/value pair in @table.

There is also an opposite bug. IF an attempt is made, to create a field who's name is already used as a method (without the '='); new_ostruct_member does NOT create the accessor methods, BUT does NOT return a indicator of success, so method_missing just goes ahead and adds the field to @table.
Ex:
obj.inspect='Sherlock Holmes'
The unless block (line 72) in new_ostruct_member needs an else clause thats raises a NameError Exception "#{name} is already in use as a method of #{self}."

Additionally, .marshal_load is flawed. It wipes out @table instead of appending; and puts everything in @table before checking it. The arg x should be typechecked as an OpenStruct or Hash; it should be put first in a temp reference. Looks like another argument may be needed for overwrite/ignore of matching keys.
=end

#4 Updated by Kazuhiro NISHIYAMA over 5 years ago

  • Target version set to 2.0.0
  • Category set to lib

=begin

=end

#5 Updated by Yui NARUSE about 4 years ago

  • Status changed from Open to Assigned
  • Assignee set to Yukihiro Matsumoto

#6 Updated by Yusuke Endoh over 3 years ago

  • Assignee changed from Yukihiro Matsumoto to Marc-Andre Lafortune

Hello,

There is no maintainer for ostruct.
Marc-Andre, are you willing to be a maintainer and to commit
your patch? I give +1 for members.

I consider Dan's comment is not objection. What Tomas wants is
a way to get field names without depending on the openstruct
internal. But the ways that Dan proposed are deeply depending
on it. For other topics, please register bug ticket for each.

Yusuke Endoh mame@tsg.ne.jp

#7 Updated by Peter Vandenabeele over 3 years ago

On Mon, Feb 13, 2012 at 4:08 PM, Yusuke Endoh mame@tsg.ne.jp wrote:

Issue #1400 has been updated by Yusuke Endoh.

Assignee changed from Yukihiro Matsumoto to Marc-Andre Lafortune

Hello,

There is no maintainer for ostruct.
Marc-Andre, are you willing to be a maintainer and to commit
your patch? I give +1 for members.

I consider Dan's comment is not objection. What Tomas wants is
a way to get field names without depending on the openstruct
internal. But the ways that Dan proposed are deeply depending
on it. For other topics, please register bug ticket for each.

I use OpenStruct#marshal_dump for this purpose,
which is a public method and returns a clean hash.

$ irb
1.9.3p0 :001 > require 'ostruct'
=> false
1.9.3p0 :002 > car = OpenStruct.new
=> #
1.9.3p0 :003 > car.wheels = 4
=> 4
1.9.3p0 :004 > car.seats = 5
=> 5
1.9.3p0 :005 > car.marshal_dump
=> {:wheels=>4, :seats=>5}

Sincerely,

Peter

--
*** Available for a new project ***

Peter Vandenabeele
http://twitter.com/peter_v
http://rails.vandenabeele.com
http://coderwall.com/peter_v

#8 Updated by Yusuke Endoh over 3 years ago

Hello,

2012/2/14 Peter Vandenabeele peter@vandenabeele.com:

I use OpenStruct#marshal_dump for this purpose,
which is a public method and returns a clean hash.

Cleary, marshal_dump is only intended for Marshal.
I don't know what version policy is applied to marshal format
of a standard library, but I do NOT recommend using marshal_dump
for the purpose. I bet it is not guaranteed.

--
Yusuke Endoh mame@tsg.ne.jp

#9 Updated by Peter Vandenabeele over 3 years ago

On Mon, Feb 13, 2012 at 4:37 PM, Yusuke Endoh mame@tsg.ne.jp wrote:

Hello,

2012/2/14 Peter Vandenabeele peter@vandenabeele.com:

I use OpenStruct#marshal_dump for this purpose,
which is a public method and returns a clean hash.

Cleary, marshal_dump is only intended for Marshal.
I don't know what version policy is applied to marshal format
of a standard library, but I do NOT recommend using marshal_dump
for the purpose. I bet it is not guaranteed.

Many thanks for the clarification.

Peter

#10 Updated by Marc-Andre Lafortune over 3 years ago

Hi,

Yusuke Endoh wrote:

There is no maintainer for ostruct.
Marc-Andre, are you willing to be a maintainer and to commit
your patch? I give +1 for members.

With pleasure. I'll commit it tomorrow.

I consider Dan's comment is not objection. What Tomas wants is
a way to get field names without depending on the openstruct
internal. But the ways that Dan proposed are deeply depending
on it.

Agreed.

Marc-André

#11 Updated by Marc-Andre Lafortune over 3 years ago

After reviewing the library, I'm thinking it could be more useful to implement instead each_pair that would yield keys with the corresponding value (or return an enumerator if no block given).

*) It matches the equivalent Struct#each_pair
*) It's more powerful
*) It's highly unlikely to generate a conflict

Thoughts?

#12 Updated by Peter Vandenabeele over 3 years ago

On Wed, Feb 15, 2012 at 4:56 AM, Marc-Andre Lafortune <
ruby-core@marc-andre.ca> wrote:

Issue #1400 has been updated by Marc-Andre Lafortune.

After reviewing the library, I'm thinking it could be more useful to
implement instead each_pair that would yield keys with the corresponding
value (or return an enumerator if no block given).

*) It matches the equivalent Struct#each_pair
*) It's more powerful
*) It's highly unlikely to generate a conflict

Thoughts?

Seems good.

Is there a specified order for the enumeration? (I presume not, but
curious).

Peter

#13 Updated by Thomas Sawyer over 3 years ago

If #each_pair, why not #each? I realize it's an exception to the fields that can be used, but since a few of those are inevitable no matter what, it seems like an acceptable one.

#14 Updated by Thomas Sawyer over 3 years ago

It was also recommended to me to suggest #to_h here.

def to_h
  @table.dup
end

It would be a much easier way of working with the underlying table, such as getting field names.

openstruct.to_h.keys

And of course much more.

#15 Updated by Marc-Andre Lafortune over 3 years ago

Hi,

Peter Vandenabeele wrote:

Is there a specified order for the enumeration? (I presume not, but
curious).

As the implementation uses a hash, it would be the same order, i.e. order in which they were set.

Thomas Sawyer wrote:

If #each_pair, why not #each?

The nice thing about each_pair is that is common to both Hash and Struct and would have the same meaning for OpenStruct. Struct#each yields only the values, so that might cause some confusion?

It was also recommended to me to suggest #to_h here.

I think it would be nice to have this, and not only in OpenStruct. Matz seems positive about it too .

Here's what I have so far:
https://github.com/marcandre/ruby/compare/ostruct

#16 Updated by Koichi Sasada over 2 years ago

ping. status?

#17 Updated by Marc-Andre Lafortune over 2 years ago

  • Status changed from Assigned to Closed

This issue was solved with changeset r37372.
Tomas, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • lib/ostruct.rb (each_pair): Add #each_pair [#1400]

Also available in: Atom PDF