Last active
May 29, 2026 04:45
-
-
Save ybiquitous/5d823fa52dfaa9a458293ba271ccbc71 to your computer and use it in GitHub Desktop.
Proposal: Add Module#class_reader to Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Module | |
| def class_reader(name, value) | |
| define_singleton_method(name) { value } | |
| end | |
| end | |
| # === Test === | |
| if ARGV.delete('--test') | |
| require 'minitest/autorun' | |
| class ClassReaderTest < Minitest::Test | |
| class Foo | |
| class_reader :default_list, ['foo'].map(&:to_sym).freeze | |
| end | |
| class Bar < Foo; end | |
| # Workaround by class ivar | |
| class Foo | |
| @default_list_workaround = ['foo'].map(&:to_sym).freeze | |
| def self.default_list_workaround = @default_list_workaround | |
| end | |
| # Just using `define_singleton_method` does not cache a passed value | |
| class Foo | |
| define_singleton_method(:default_list_no_cache) { ['foo'].map(&:to_sym).freeze } | |
| end | |
| def test_class_reader | |
| assert_equal [:foo], Foo.default_list | |
| assert_same Foo.default_list, Foo.default_list | |
| end | |
| def test_subclass | |
| assert_equal [:foo], Bar.default_list | |
| assert_same Bar.default_list, Foo.default_list | |
| end | |
| def test_workaround_by_class_ivar | |
| assert_equal [:foo], Foo.default_list_workaround | |
| assert_same Foo.default_list_workaround, Foo.default_list_workaround | |
| end | |
| def test_define_singleton_method_not_cache | |
| assert_equal [:foo], Foo.default_list_no_cache | |
| refute_same Foo.default_list_no_cache, Foo.default_list_no_cache | |
| end | |
| end | |
| end |
Author
Author
Prior art: Active Support's Class#class_attribute.
However, I don't want mutation, so I'm excluding ideas, such as class_writer and class_accessor.
Author
Another prior art: Active Support's Module#mattr_reader.
This one is closer to what I want, but the definition is a bit longer due to the more options, and the name (mattr_reader) doesn't seem intuitive.
class Foo
mattr_reader :default_list, default: [:foo].freeze, instance_reader: false
end
Author
Alternative names to class_reader:
singleton_readerconst_readerstatic_readerclass_attr_readerclass_constant_methoddefine_class_constantclass_valueclass_constantcattr_reader/mattr_reader(from Active Support)- etc.
Author
What is the difference between class_reader and define_singleton_method?
class_reader :default_list, [:foo].freeze[:foo].freeze is captured in the closure using the value second argument, so it's evaluated once.
define_singleton_method(:default_list) { [:foo].freeze }[:foo].freeze in the block is repeatedly evaluated once the default_list method is called.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
class_readercan resolve the problems: