Skip to content

Commit 15cb07e

Browse files
committed
Deprecate writing to Base#attributes
The Base class defines an `attr_accessor :attributes` that is marked with `# :nodoc:`. Technically, this means that any interaction with `Base#attributes` or `Base#attributes=` is not part of the public interface, and is free to be changed or removed without breaking the public API. However, given the project's age and the long-term period of time with minimal changes to the public interface, this PR proposes that there be a public deprecation of **writing** to the Hash instance returned by the `Base#attributes` method. The migration to `ActiveModel::Attributes` proposed in [#410][] will involve changes to the `Base#attributes` method (due to [ActiveModel::Attributes#attributes][]). Reading from the value returned by `ActiveModel::Attributes#attributes` will remain the same, but writing to that value will have no affect since the value is a Hash copy created by [ActiveModel::AttributeSet#to_hash][], and not the Hash instance used internally. Similarly, `ActiveModel::Attributes` does not expose a corresponding `#attributes=` method. By deprecating `#attributes=`, changes made that incorporate `ActiveModel::Attributes` will not need to add an otherwise unnecessary `#attributes=` implementation. Once deprecated and part of a release cycle, the `ActiveResource::AttributeSet` class can be removed, and the Active Model migration's backwards compatibility burden can be reduced. [#410]: #410 [ActiveModel::Attributes#attributes]: https://edgeapi.rubyonrails.org/classes/ActiveModel/Attributes.html#method-i-attributes [ActiveModel::AttributeSet#to_hash]: https://github.com/rails/rails/blob/v8.1.1/activemodel/lib/active_model/attribute_set.rb#L36-L39
1 parent e577c6d commit 15cb07e

File tree

4 files changed

+70
-4
lines changed

4 files changed

+70
-4
lines changed

lib/active_resource.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ module ActiveResource
3535

3636
URI_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
3737

38+
autoload :AttributeSet
3839
autoload :Base
3940
autoload :Callbacks
4041
autoload :Coder
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveResource
4+
class AttributeSet < DelegateClass(Hash) # :nodoc:
5+
MESSAGE = "Writing to the attributes hash is deprecated. Set attributes directly on the instance instead."
6+
7+
def []=(key, value)
8+
ActiveResource.deprecator.warn(MESSAGE)
9+
super
10+
end
11+
alias_method :store, :[]=
12+
13+
def update(*other_hashes)
14+
ActiveResource.deprecator.warn(MESSAGE)
15+
super
16+
end
17+
alias_method :merge!, :update
18+
19+
def is_a?(other)
20+
__getobj__.is_a?(other)
21+
end
22+
end
23+
end

lib/active_resource/base.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,9 +1276,17 @@ def split_options(options = {})
12761276
end
12771277
end
12781278

1279-
attr_accessor :attributes # :nodoc:
12801279
attr_accessor :prefix_options # :nodoc:
12811280

1281+
def attributes=(value) # :nodoc:
1282+
ActiveResource.deprecator.warn("#attributes= is deprecated. Call #load on the instance instead.")
1283+
@attributes = value
1284+
end
1285+
1286+
def attributes # :nodoc:
1287+
AttributeSet.new(@attributes)
1288+
end
1289+
12821290
# If no schema has been defined for the class (see
12831291
# <tt>ActiveResource::schema=</tt>), the default automatic schema is
12841292
# generated from the current instance's attributes
@@ -1385,7 +1393,7 @@ def id
13851393

13861394
# Sets the <tt>\id</tt> attribute of the resource.
13871395
def id=(id)
1388-
attributes[self.class.primary_key] = id
1396+
@attributes[self.class.primary_key] = id
13891397
end
13901398

13911399
# Test for equality. Resource are equal if and only if +other+ is the same object or
@@ -1439,7 +1447,7 @@ def hash
14391447
# next_invoice.customer # => That Company
14401448
def dup
14411449
self.class.new.tap do |resource|
1442-
resource.attributes = @attributes
1450+
resource.send :instance_variable_set, "@attributes", @attributes
14431451
resource.prefix_options = @prefix_options
14441452
end
14451453
end
@@ -1842,7 +1850,7 @@ def method_missing(method_symbol, *arguments) # :nodoc:
18421850
if method_name =~ /(=|\?)$/
18431851
case $1
18441852
when "="
1845-
attributes[$`] = arguments.first
1853+
@attributes[$`] = arguments.first
18461854
when "?"
18471855
attributes[$`]
18481856
end

test/cases/base_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,4 +1743,38 @@ def test_paths_without_format
17431743
ensure
17441744
ActiveResource::Base.include_format_in_path = true
17451745
end
1746+
1747+
def test_deprecate_attributes_write
1748+
person = Person.find(1)
1749+
1750+
assert_deprecated("#attributes= is deprecated. Call #load on the instance instead.", ActiveResource.deprecator) do
1751+
person.attributes = { "name" => "changed" }
1752+
end
1753+
1754+
assert_equal "changed", person.name
1755+
end
1756+
1757+
def test_deprecate_attributes_store
1758+
[ :[]=, :store ].each do |store|
1759+
person = Person.find(1)
1760+
1761+
assert_deprecated("Writing to the attributes hash is deprecated. Set attributes directly on the instance instead.", ActiveResource.deprecator) do
1762+
person.attributes.send(store, "name", "changed")
1763+
end
1764+
1765+
assert_equal "changed", person.name
1766+
end
1767+
end
1768+
1769+
def test_deprecate_attributes_update
1770+
[ :update, :merge! ].each do |update|
1771+
person = Person.find(1)
1772+
1773+
assert_deprecated("Writing to the attributes hash is deprecated. Set attributes directly on the instance instead.", ActiveResource.deprecator) do
1774+
person.attributes.send(update, "name" => "changed")
1775+
end
1776+
1777+
assert_equal "changed", person.name
1778+
end
1779+
end
17461780
end

0 commit comments

Comments
 (0)