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 aSymbolorString, e.g.,data[:id] == data["id"]. -
Data#[]returns a value associated with the givenname, e.g.,data[:id] == data.id. -
Data#[]raisesNameErrorif the givennameis 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_his provided, it seems reasonable that we could access aDatamember's value through[]. - From the similarity between
StructandData, some people might expectHash-like accessing via[]. - Unless
[]is provided, we have to callpublic_sendor convert to a hash viato_hwhen 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.