From a5b0c60714e1e8d8c182af830a26e1c7c884271d Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Tue, 30 May 2017 16:16:19 +0300 Subject: [PATCH] Implement mattr_acessor :default option --- activesupport/CHANGELOG.md | 9 ++++ .../core_ext/module/attribute_accessors.rb | 43 ++++++++----------- .../module/attribute_accessor_test.rb | 38 +++++++++++++++- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 4c3eded38a..c4c6e139ff 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Add default option to module and class attribute accessors. + + mattr_accessor :settings, default: {} + + Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`, + and `cattr_writer` as well. + + *Genadi Samokovarov* + * Add `Date#prev_occurring` and `Date#next_occurring` to return specified next/previous occurring day of week. *Shota Iguchi* diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 2c24081eb9..9244cfa157 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -38,13 +38,10 @@ class Module # # Person.new.hair_colors # => NoMethodError # - # - # Also, you can pass a block to set up the attribute with a default value. + # You can set a default value for the attribute. # # module HairColors - # mattr_reader :hair_colors do - # [:brown, :black, :blonde, :red] - # end + # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red] # end # # class Person @@ -52,8 +49,7 @@ class Module # end # # Person.new.hair_colors # => [:brown, :black, :blonde, :red] - def mattr_reader(*syms) - options = syms.extract_options! + def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@ -64,14 +60,16 @@ class Module end EOS - unless options[:instance_reader] == false || options[:instance_accessor] == false + if instance_reader && instance_accessor class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} end EOS end - class_variable_set("@@#{sym}", yield) if block_given? + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? end end alias :cattr_reader :mattr_reader @@ -107,12 +105,10 @@ class Module # # Person.new.hair_colors = [:blonde, :red] # => NoMethodError # - # Also, you can pass a block to set up the attribute with a default value. + # You can set a default value for the attribute. # # module HairColors - # mattr_writer :hair_colors do - # [:brown, :black, :blonde, :red] - # end + # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red] # end # # class Person @@ -120,8 +116,7 @@ class Module # end # # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] - def mattr_writer(*syms) - options = syms.extract_options! + def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@ -132,14 +127,16 @@ class Module end EOS - unless options[:instance_writer] == false || options[:instance_accessor] == false + if instance_writer && instance_accessor class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj end EOS end - send("#{sym}=", yield) if block_given? + + sym_default_value = (block_given? && default.nil?) ? yield : default + send("#{sym}=", sym_default_value) unless sym_default_value.nil? end end alias :cattr_writer :mattr_writer @@ -197,12 +194,10 @@ class Module # Person.new.hair_colors = [:brown] # => NoMethodError # Person.new.hair_colors # => NoMethodError # - # Also you can pass a block to set up the attribute with a default value. + # You can set a default value for the attribute. # # module HairColors - # mattr_accessor :hair_colors do - # [:brown, :black, :blonde, :red] - # end + # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red] # end # # class Person @@ -210,9 +205,9 @@ class Module # end # # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] - def mattr_accessor(*syms, &blk) - mattr_reader(*syms, &blk) - mattr_writer(*syms) + def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) + mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk) + mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default) end alias :cattr_accessor :mattr_accessor end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 464a000d59..9b185e9381 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -12,7 +12,14 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase cattr_accessor(:defa) { "default_accessor_value" } cattr_reader(:defr) { "default_reader_value" } cattr_writer(:defw) { "default_writer_value" } + cattr_accessor(:deff) { false } cattr_accessor(:quux) { :quux } + + cattr_accessor :def_accessor, default: "default_accessor_value" + cattr_reader :def_reader, default: "default_reader_value" + cattr_writer :def_writer, default: "default_writer_value" + cattr_accessor :def_false, default: false + cattr_accessor(:def_priority, default: false) { :no_priority } end @class = Class.new @class.instance_eval { include m } @@ -24,6 +31,21 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase assert_nil @object.foo end + def test_mattr_default_keyword_arguments + assert_equal "default_accessor_value", @module.def_accessor + assert_equal "default_reader_value", @module.def_reader + assert_equal "default_writer_value", @module.class_variable_get(:@@def_writer) + end + + def test_mattr_can_default_to_false + assert_equal false, @module.def_false + assert_equal false, @module.deff + end + + def test_mattr_default_priority + assert_equal false, @module.def_priority + end + def test_should_set_mattr_value @module.foo = :test assert_equal :test, @object.foo @@ -91,9 +113,23 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase assert_equal "default_writer_value", @module.class_variable_get("@@defw") end - def test_should_not_invoke_default_value_block_multiple_times + def test_method_invocation_should_not_invoke_the_default_block count = 0 + @module.cattr_accessor(:defcount) { count += 1 } + assert_equal 1, count + assert_no_difference "count" do + @module.defcount + end + end + + def test_declaring_multiple_attributes_at_once_invokes_the_block_multiple_times + count = 0 + + @module.cattr_accessor(:defn1, :defn2) { count += 1 } + + assert_equal 1, @module.defn1 + assert_equal 2, @module.defn2 end end