Feature #21387
openProposal to add Data#[]
Description
Proposal¶
I propose to add a new instance method #[]
to the Data
class, similar to Struct#[]
.
If writing the method signature in RBS, it would be like this:
class Data
def []: (name: String | Symbol) -> untyped
end
Requirements:
-
Data#[]
accepts a member name as aSymbol
orString
, e.g.,data[:id] == data["id"]
. -
Data#[]
returns a value associated with the givenname
, e.g.,data[:id] == data.id
. -
Data#[]
raisesNameError
if the givenname
is not one of the members, e.g.,data[:invalid]
.
Note: Please assume that data = Data.define(:id).new(id: 100)
is given in the examples above.
Motivation¶
In Active Support Core Extensions of Rails, I found a use case that Data#[]
would be helpful with Enumerable#pluck
.
Please look at this example:
# data_test.rb
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "activesupport", "8.0.2"
end
require "active_support/core_ext/enumerable"
StructPerson = Struct.new(:name, :age)
DataPerson = Data.define(:name, :age)
struct_people = [
StructPerson.new("Bob", 25),
]
puts "Struct:"
puts "=> #{struct_people.pluck(:name)}"
data_people = [
DataPerson.new("Charlie", 35),
]
puts "Data:"
begin
puts "=> #{data_people.pluck(:name)}"
rescue => e
puts e.detailed_message(syntax_suggest: true)
end
Running this script outputs below:
$ ruby data_test.rb
Struct:
=> ["Bob"]
Data:
undefined method '[]' for an instance of DataPerson (NoMethodError)
map { |element| element[key] }
^^^^^
Note: This output resulted on ruby 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [arm64-darwin24]
.
The error reason is that the Enumerable#pluck
extension expects all the elements in the array to respond to []
.
See also the pluck
code here:
https://github.com/rails/rails/blob/v8.0.2/activesupport/lib/active_support/core_ext/enumerable.rb#L145-L152
If Data#[]
was implemented as follows,
# data_patch.rb
class Data
def [](name)
unless members.include?(name.to_sym)
raise NameError, "no member '#{name}' in data"
end
public_send(name)
end
end
The script is successful:
$ ruby -r ./data_patch.rb data_test.rb
Struct:
=> ["Bob"]
Data:
=> ["Charlie"]
Ref:
- https://guides.rubyonrails.org/v8.0.2/active_support_core_extensions.html#pluck
- https://api.rubyonrails.org/v8.0.2/classes/Enumerable.html#method-i-pluck
Although Enumerable#pluck
is just an example, I guess that there would be other cases where Data
objects should respond to []
.
Reasoning¶
From the long discussion in #16122 that introduced Data
, I understand that Data#[]
was rejected in #16122#note-28 because []
was like Enumerable
.
I also agree with rejecting Data#each
since Data
is not an Enumerable
, but []
seems acceptable to me.
Reasons:
-
[]
is sometimes used for non-container objects, such asActiveRecord::AttributeMethods#[]
. - Considering
Data#to_h
is provided, it seems reasonable that we could access aData
member's value through[]
. - From the similarity between
Struct
andData
, some people might expectHash
-like accessing via[]
. - Unless
[]
is provided, we have to callpublic_send
or convert to a hash viato_h
when we want to get member values with names. It'd be inefficient and easy to make mistakes.
Let me show an example:
Person = Data.define(:name, :age)
charlie = Person.new("Charlie", 35)
member_names_from_user_input = ["age", "hash"] # "hash" is a malicious input.
# Using #public_send
member_names_from_user_input.map { charlie.public_send(it) }
#=> [35, -1363726049241242821]
# Using #to_h and Hash#fetch with string keys
member_names_from_user_input.map { charlie.to_h.fetch(it) }
#=> key not found: "age" (KeyError)
# Using #to_h and Hash#fetch with symbol keys
member_names_from_user_input.map { charlie.to_h.fetch(it.to_sym) }
#=> key not found: :hash (KeyError)
# Using #[]
member_names_from_user_input.map { charlie[it] }
#=> no member 'hash' in data (NameError)
The last example is most elegant for the same goal. That's why I propose to add Data#[]
.
However, there might be downsides that I have overlooked, so I'd appreciate it if you could let me know.
Thanks.