Merge pull request #50677 from seanpdoyle/current-attributes-defaults

Add `default:` support for `ActiveSupport::CurrentAttributes.attribute`
This commit is contained in:
Rafael Mendonça França 2024-01-09 19:43:37 -05:00 committed by GitHub
commit 1db9705396
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 3 deletions

View File

@ -1,3 +1,13 @@
* Add `default:` support for `ActiveSupport::CurrentAttributes.attribute`
```ruby
class Current < ActiveSupport::CurrentAttributes
attribute :counter, default: 0
end
```
*Sean Doyle*
* Yield instance to `Object#with` block
```ruby

View File

@ -102,7 +102,14 @@ module ActiveSupport
end
# Declares one or more attributes that will be given both class and instance accessor methods.
def attribute(*names)
#
# ==== Options
#
# * <tt>:default</tt> - The default value for the attributes. If the value
# is a proc or lambda, it will be called whenever an instance is
# constructed. Otherwise, the value will be duplicated with +#dup+.
# Default values are re-assigned when the attributes are reset.
def attribute(*names, default: nil)
invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
if invalid_attribute_names.any?
raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
@ -126,6 +133,8 @@ module ActiveSupport
end
singleton_class.delegate(*names.flat_map { |name| [name, "#{name}="] }, to: :instance, as: self)
defaults.merge! names.index_with { default }
end
# Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
@ -177,10 +186,12 @@ module ActiveSupport
end
end
class_attribute :defaults, instance_writer: false, default: {}
attr_accessor :attributes
def initialize
@attributes = {}
@attributes = merge_defaults!({})
end
# Expose one or more attributes within a block. Old values are returned after the block concludes.
@ -200,8 +211,21 @@ module ActiveSupport
# Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
def reset
run_callbacks :reset do
self.attributes = {}
self.attributes = merge_defaults!({})
end
end
private
def merge_defaults!(attributes)
defaults.each_with_object(attributes) do |(name, default), values|
value =
case default
when Proc then default.call
else default.dup
end
values[name] = value
end
end
end
end

View File

@ -11,6 +11,8 @@ class CurrentAttributesTest < ActiveSupport::TestCase
Person = Struct.new(:id, :name, :time_zone)
class Current < ActiveSupport::CurrentAttributes
attribute :counter_integer, default: 0
attribute :counter_callable, default: -> { 0 }
attribute :world, :account, :person, :request
delegate :time_zone, to: :person
@ -86,6 +88,30 @@ class CurrentAttributesTest < ActiveSupport::TestCase
assert_equal "world/1", Current.world
end
test "read and write attribute with default value" do
assert_equal 0, Current.counter_integer
Current.counter_integer += 1
assert_equal 1, Current.counter_integer
Current.reset
assert_equal 0, Current.counter_integer
end
test "read attribute with default callable" do
assert_equal 0, Current.counter_callable
Current.counter_callable += 1
assert_equal 1, Current.counter_callable
Current.reset
assert_equal 0, Current.counter_callable
end
test "read overwritten attribute method" do
Current.request = "request/1"
assert_equal "request/1 something", Current.request