CARVIEW |
Select Language
HTTP/2 200
x-amz-id-2: 0QJJXWphxSzj2TuPxmmQ5ovGhT+Hvqotp2BeAIDFIwyek6PfwFExXGsCj5SXjlEh5DaKvHEkK+7/c+0VHWpyR1w/4IZk7VfL9utg1+7zFEQ=
x-amz-request-id: 0874HM246BAAA1PP
last-modified: Sat, 31 May 2025 06:21:34 GMT
etag: "125cfe9267682e4378a7b2a87abd1cb3"
x-amz-server-side-encryption: AES256
server: AmazonS3
content-encoding: gzip
via: 1.1 varnish, 1.1 varnish
content-type: text/plain; charset=utf-8
accept-ranges: bytes
age: 0
date: Wed, 23 Jul 2025 21:16:46 GMT
x-served-by: cache-tyo11945-TYO, cache-bom-vanm7210040-BOM
x-cache: MISS, MISS
x-cache-hits: 0, 0
x-timer: S1753305406.349245,VS0,VE426
vary: Accept-Encoding
content-length: 2265
From: "ybiquitous (Masafumi Koba) via ruby-core"
Date: 2025-05-31T06:10:28+00:00
Subject: [ruby-core:122350] [Ruby Feature#21387] Proposal to add Data#[]
Issue #21387 has been reported by ybiquitous (Masafumi Koba).
----------------------------------------
Feature #21387: Proposal to add Data#[]
https://bugs.ruby-lang.org/issues/21387
* Author: ybiquitous (Masafumi Koba)
* Status: Open
----------------------------------------
## Proposal
I propose to add a new instance method `#[]` to the `Data` class, similar to [`Struct#[]`](https://docs.ruby-lang.org/en/3.4/Struct.html#method-i-5B-5D).
If writing the method signature in RBS, it would be like this:
```ruby
class Data
def []: (name: String | Symbol) -> untyped
end
```
Requirements:
- `Data#[]` accepts a member name as a `Symbol` or `String`, e.g., `data[:id] == data["id"]`.
- `Data#[]` returns a value associated with the given `name`, e.g., `data[:id] == data.id`.
- `Data#[]` raises `NameError` if the given `name` 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:
```ruby
# 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:
```shell
$ 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,
```ruby
# 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:
```shell
$ 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 as [`ActiveRecord::AttributeMethods#[]`](https://api.rubyonrails.org/v8.0.2/classes/ActiveRecord/AttributeMethods.html#method-i-5B-5D).
- Considering `Data#to_h` is provided, it seems reasonable that we could access a `Data` member's value through `[]`.
- From the similarity between `Struct` and `Data`, some people might expect `Hash`-like accessing via `[]`.
- Unless `[]` is provided, we have to call `public_send` or convert to a hash via `to_h` when we want to get member values with names. It'd be inefficient and easy to make mistakes.
Let me show an example:
```ruby
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.
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/