From a3ed8ecbc20ed0a40df933e0c36afb6be5ed219b Mon Sep 17 00:00:00 2001 From: Lazy <2818242447@qq.com> Date: Tue, 9 Jun 2026 23:07:36 +0800 Subject: [PATCH] Support block defaults for module attributes --- demo/core/module/cattr.md | 17 +++++ demo/core/module/mattr.md | 49 ++++++++++++++ lib/core/facets/module/mattr.rb | 71 +++++++++++--------- test/core/module/test_mattr.rb | 113 ++++++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+), 30 deletions(-) create mode 100644 demo/core/module/mattr.md create mode 100644 test/core/module/test_mattr.rb diff --git a/demo/core/module/cattr.md b/demo/core/module/cattr.md index 7aa615b9c..a2b6bad8e 100644 --- a/demo/core/module/cattr.md +++ b/demo/core/module/cattr.md @@ -41,3 +41,20 @@ t.c = 50 t.c.assert == 50 +## Block defaults + + c = Class.new do + cattr_accessor(:setting) { :default } + end + + c.setting.assert == :default + c.new.setting.assert == :default + +Existing values are left in place. + + c = Class.new do + class_variable_set(:@@setting, :existing) + cattr_accessor(:setting) { :default } + end + + c.setting.assert == :existing diff --git a/demo/core/module/mattr.md b/demo/core/module/mattr.md new file mode 100644 index 000000000..4bf0bef32 --- /dev/null +++ b/demo/core/module/mattr.md @@ -0,0 +1,49 @@ +## Module#mattr + + require 'facets/module/mattr' + + c = Class.new do + mattr :setting do + :default + end + end + + c.setting.assert == :default + c.new.setting.assert == :default + +## Module#mattr_reader + + c = Class.new do + mattr_reader(:setting) { :default } + end + + c.setting.assert == :default + c.new.setting.assert == :default + +## Module#mattr_writer + + c = Class.new do + mattr_writer(:setting) { :default } + end + + c.class_variable_get(:@@setting).assert == :default + c.setting = :changed + c.class_variable_get(:@@setting).assert == :changed + +## Module#mattr_accessor + + c = Class.new do + mattr_accessor(:setting) { :default } + end + + c.setting.assert == :default + c.new.setting.assert == :default + +Existing values are left in place. + + c = Class.new do + class_variable_set(:@@setting, :existing) + mattr_accessor(:setting) { :default } + end + + c.setting.assert == :existing diff --git a/lib/core/facets/module/mattr.rb b/lib/core/facets/module/mattr.rb index c2f021780..d989f40e6 100644 --- a/lib/core/facets/module/mattr.rb +++ b/lib/core/facets/module/mattr.rb @@ -19,13 +19,19 @@ class Module # @uncommon # require 'facets/module/cattr' # - def cattr(*syms) + def cattr(*syms, &block) writers, readers = syms.flatten.partition{ |a| a.to_s =~ /=$/ } writers = writers.map{ |e| e.to_s.chomp('=').to_sym } ##readers.concat( writers ) # writers also get readers - cattr_reader(*readers) - cattr_writer(*writers) + cattr_reader(*readers, &block) + + if block + cattr_writer(*(writers - readers), &block) + cattr_writer(*(writers & readers)) + else + cattr_writer(*writers) + end return readers + writers end @@ -49,13 +55,13 @@ def cattr(*syms) # @uncommon # require 'facets/module/cattr' # - def cattr_reader(*syms) + def cattr_reader(*syms, &block) syms.flatten.each do |sym| - module_eval(<<-EOS, __FILE__, __LINE__) - unless defined? @@#{sym} - @@#{sym} = nil - end + unless class_variable_defined?("@@#{sym}") + class_variable_set("@@#{sym}", block ? block.call : nil) + end + module_eval(<<-EOS, __FILE__, __LINE__) def self.#{sym} @@#{sym} end @@ -91,13 +97,13 @@ def #{sym} # @uncommon # require 'facets/module/cattr' # - def cattr_writer(*syms) + def cattr_writer(*syms, &block) syms.flatten.each do |sym| - module_eval(<<-EOS, __FILE__, __LINE__) - unless defined? @@#{sym} - @@#{sym} = nil - end + unless class_variable_defined?("@@#{sym}") + class_variable_set("@@#{sym}", block ? block.call : nil) + end + module_eval(<<-EOS, __FILE__, __LINE__) def self.#{sym}=(obj) @@#{sym} = obj end @@ -130,8 +136,8 @@ def #{sym}=(obj) # @uncommon # require 'facets/module/cattr' # - def cattr_accessor(*syms) - cattr_reader(*syms) + cattr_writer(*syms) + def cattr_accessor(*syms, &block) + cattr_reader(*syms, &block) + cattr_writer(*syms) end # Creates a class-variable attribute that can @@ -159,13 +165,18 @@ def cattr_accessor(*syms) # @uncommon # require 'facets/module/mattr' # - def mattr(*syms) + def mattr(*syms, &block) writers, readers = syms.flatten.partition{ |a| a.to_s =~ /=$/ } writers = writers.collect{ |e| e.to_s.chomp('=').to_sym } ##readers.concat( writers ) # writers also get readers - mattr_writer( *writers ) - mattr_reader( *readers ) + if block + mattr_writer( *(writers - readers), &block ) + mattr_writer( *(writers & readers) ) + else + mattr_writer( *writers ) + end + mattr_reader( *readers, &block ) return readers + writers end @@ -189,13 +200,13 @@ def mattr(*syms) # @uncommon # require 'facets/module/mattr' # - def mattr_reader( *syms ) + def mattr_reader( *syms, &block ) syms.flatten.each do |sym| - module_eval(<<-EOS, __FILE__, __LINE__) - unless defined? @@#{sym} - @@#{sym} = nil - end + unless class_variable_defined?("@@#{sym}") + class_variable_set("@@#{sym}", block ? block.call : nil) + end + module_eval(<<-EOS, __FILE__, __LINE__) def self.#{sym} @@#{sym} end @@ -232,13 +243,13 @@ def #{sym} # @uncommon # require 'facets/module/mattr' # - def mattr_writer(*syms) + def mattr_writer(*syms, &block) syms.flatten.each do |sym| - module_eval(<<-EOS, __FILE__, __LINE__) - unless defined? @@#{sym} - @@#{sym} = nil - end + unless class_variable_defined?("@@#{sym}") + class_variable_set("@@#{sym}", block ? block.call : nil) + end + module_eval(<<-EOS, __FILE__, __LINE__) def self.#{sym}=(obj) @@#{sym} = obj end @@ -272,8 +283,8 @@ def #{sym}=(obj) # @uncommon # require 'facets/module/mattr' # - def mattr_accessor(*syms) - mattr_reader(*syms) + mattr_writer(*syms) + def mattr_accessor(*syms, &block) + mattr_reader(*syms, &block) + mattr_writer(*syms) end end diff --git a/test/core/module/test_mattr.rb b/test/core/module/test_mattr.rb new file mode 100644 index 000000000..55370199f --- /dev/null +++ b/test/core/module/test_mattr.rb @@ -0,0 +1,113 @@ +covers 'facets/module/mattr' + +test_case Module do + + method :cattr_reader do + + test "uses a block default" do + c = Class.new do + cattr_reader(:setting) { :default } + end + + c.setting.assert == :default + c.new.setting.assert == :default + end + + test "does not replace an existing value" do + c = Class.new do + class_variable_set(:@@existing_cattr_reader_setting, :existing) + cattr_reader(:existing_cattr_reader_setting) { :default } + end + + c.existing_cattr_reader_setting.assert == :existing + end + + end + + method :cattr_writer do + + test "uses a block default for writer-only attributes" do + c = Class.new do + cattr_writer(:setting) { :default } + end + + c.class_variable_get(:@@setting).assert == :default + c.setting = :changed + c.class_variable_get(:@@setting).assert == :changed + end + + end + + method :cattr_accessor do + + test "uses a block default once" do + calls = 0 + c = Class.new do + cattr_accessor(:cattr_accessor_setting) do + calls += 1 + :default + end + end + + c.cattr_accessor_setting.assert == :default + c.new.cattr_accessor_setting.assert == :default + calls.assert == 1 + end + + end + + method :mattr_reader do + + test "uses a block default" do + c = Class.new do + mattr_reader(:setting) { :default } + end + + c.setting.assert == :default + c.new.setting.assert == :default + end + + test "does not replace an existing value" do + c = Class.new do + class_variable_set(:@@existing_mattr_reader_setting, :existing) + mattr_reader(:existing_mattr_reader_setting) { :default } + end + + c.existing_mattr_reader_setting.assert == :existing + end + + end + + method :mattr_writer do + + test "uses a block default for writer-only attributes" do + c = Class.new do + mattr_writer(:setting) { :default } + end + + c.class_variable_get(:@@setting).assert == :default + c.setting = :changed + c.class_variable_get(:@@setting).assert == :changed + end + + end + + method :mattr_accessor do + + test "uses a block default once" do + calls = 0 + c = Class.new do + mattr_accessor(:mattr_accessor_setting) do + calls += 1 + :default + end + end + + c.mattr_accessor_setting.assert == :default + c.new.mattr_accessor_setting.assert == :default + calls.assert == 1 + end + + end + +end