Skip to content

Commit 091e04a

Browse files
authored
Merge pull request rails#46681 from Shopify/object-with
Implement `Object#with`
2 parents 09f5d81 + 1884323 commit 091e04a

File tree

4 files changed

+141
-0
lines changed

4 files changed

+141
-0
lines changed

activesupport/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
* Add `Object#with` to set and restore public attributes around a block
2+
3+
```ruby
4+
client.timeout # => 5
5+
client.with(timeout: 1) do
6+
client.timeout # => 1
7+
end
8+
client.timeout # => 5
9+
```
10+
11+
*Jean Boussier*
12+
113
* Remove deprecated support to generate incorrect RFC 4122 UUIDs when providing a namespace ID that is not one of the
214
constants defined on `Digest::UUID`.
315

activesupport/lib/active_support/core_ext/object.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
require "active_support/core_ext/object/json"
1414
require "active_support/core_ext/object/to_param"
1515
require "active_support/core_ext/object/to_query"
16+
require "active_support/core_ext/object/with"
1617
require "active_support/core_ext/object/with_options"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
class Object
4+
# Sets and restore the provided attributes around a block.
5+
#
6+
# client.timeout # => 5
7+
# client.with(timeout: 1) do
8+
# client.timeout # => 1
9+
# end
10+
# client.timeout # => 5
11+
#
12+
# This method is a shorthand for the common begin/ensure pattern:
13+
#
14+
# old_value = object.attribute
15+
# begin
16+
# object.attribute = new_value
17+
# # do things
18+
# ensure
19+
# object.attribute = old_value
20+
# end
21+
#
22+
# It can be used on any object as long as both the reader and writer methods
23+
# are public.
24+
def with(**attributes)
25+
old_values = {}
26+
begin
27+
attributes.each do |key, value|
28+
old_values[key] = public_send(key)
29+
public_send("#{key}=", value)
30+
end
31+
yield
32+
ensure
33+
old_values.each do |key, old_value|
34+
public_send("#{key}=", old_value)
35+
end
36+
end
37+
end
38+
end
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../../abstract_unit"
4+
require "active_support/core_ext/object/with"
5+
6+
class BlankTest < ActiveSupport::TestCase
7+
class Record
8+
def initialize
9+
@public_attr = :public
10+
@another_public_attr = :another_public
11+
@mixed_attr = :mixed
12+
@protected_attr = :protected
13+
@private_attr = :private
14+
end
15+
16+
attr_accessor :public_attr, :another_public_attr
17+
18+
attr_reader :mixed_attr
19+
20+
protected
21+
attr_accessor :protected_attr
22+
23+
private
24+
attr_accessor :private_attr
25+
attr_writer :mixed_attr
26+
end
27+
28+
setup do
29+
@object = Record.new
30+
end
31+
32+
test "sets and restore attributes around a block" do
33+
assert_equal :public, @object.public_attr
34+
assert_equal :another_public, @object.another_public_attr
35+
36+
@object.with(public_attr: :changed, another_public_attr: :changed_too) do
37+
assert_equal :changed, @object.public_attr
38+
assert_equal :changed_too, @object.another_public_attr
39+
end
40+
41+
assert_equal :public, @object.public_attr
42+
assert_equal :another_public, @object.another_public_attr
43+
end
44+
45+
test "restore attribute if the block raised" do
46+
assert_equal :public, @object.public_attr
47+
assert_equal :another_public, @object.another_public_attr
48+
49+
assert_raise RuntimeError do
50+
@object.with(public_attr: :changed, another_public_attr: :changed_too) do
51+
assert_equal :changed, @object.public_attr
52+
assert_equal :changed_too, @object.another_public_attr
53+
raise "Oops"
54+
end
55+
end
56+
57+
assert_equal :public, @object.public_attr
58+
assert_equal :another_public, @object.another_public_attr
59+
end
60+
61+
test "restore attributes if one of the setter raised" do
62+
assert_equal :public, @object.public_attr
63+
assert_equal :mixed, @object.mixed_attr
64+
65+
assert_raise NoMethodError do
66+
@object.with(public_attr: :changed, mixed_attr: :changed_too) do
67+
assert false
68+
end
69+
end
70+
71+
assert_equal :public, @object.public_attr
72+
assert_equal :mixed, @object.mixed_attr
73+
end
74+
75+
test "only works with public attributes" do
76+
assert_raises NoMethodError do
77+
@object.with(private_attr: :changed) { }
78+
end
79+
80+
assert_raises NoMethodError do
81+
@object.with(protected_attr: :changed) { }
82+
end
83+
84+
assert_equal :mixed, @object.mixed_attr
85+
assert_raises NoMethodError do
86+
@object.with(mixed_attr: :changed) { }
87+
end
88+
assert_equal :mixed, @object.mixed_attr
89+
end
90+
end

0 commit comments

Comments
 (0)