From: "ybiquitous (Masafumi Koba) via ruby-core" Date: 2025-06-02T10:17:51+00:00 Subject: [ruby-core:122378] [Ruby Feature#21387] Proposal to add Data#[] Issue #21387 has been updated by ybiquitous (Masafumi Koba). @zverok @eregon Thanks for the feedback. Reconsidering my motivation, I may have expected `Data` to be something like an *immutable* `Struct` (or even an *immutable* `Hash`). I'm still attracted to this idea of an immutable `Hash`-like data structure, but I must agree with the possibility of interface pollution that `Data` would suffer like `OpenStruct`, as you worry. As long as there is no fascinating use case, we should probably stop enhancing the `Data` interface. Also, I can work around the issue by extending `Data` only in my app, not monkey patching, like this: ```ruby module DynamicallyAccessibleData def [](name) unless members.include?(name.to_sym) raise NameError, "no member '#{name}' in data" end public_send(name) end end Person = Data.define(:name, :age).include(DynamicallyAccessibleData) person = Person.new(name: "Charlie", age: 35) person[:name] #=> "Charlie" person[:age] #=> 35 person[:foo] #=> NameError: no member 'foo' in data ``` I'll leave the ticket open for a while since anyone else might be interested, but if no one reacts, I'll close it. Otherwise, please close it if you find it clear that nobody does. Thanks. ---------------------------------------- Feature #21387: Proposal to add Data#[] https://bugs.ruby-lang.org/issues/21387#change-113524 * 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/