Skip to content

Commit b897313

Browse files
committed
Base: implement read_attribute and write_attribute
Replace all access of `@attributes` and `attributes` key-value pairs with calls to `read_attribute` and `write_attribute`. The `read_attribute` and `write_attribute` implementations draw inspiration from [ActiveRecord::AttributeMethods::Read][] and [ActiveRecord::AttributeMethods::Write][], respectively. [rails/rails#53886][] proposes implementing each method at the Active Model layer. While that proposal is considered, this commit implements each method in terms of accessing the underlying `@attributes` hash instance. This change is also in support of a first-party integration with [ActiveModel::Attributes][] proposed in [#410][], and aims to be compatible with its attribute reading and writing interfaces. ```ruby person = Person.find(1) person.read_attribute("name") # => "Matz" person.name # => "Matz" person.write_attribute("name", "matz") person.name # => "matz" ``` [ActiveRecord::AttributeMethods::Read]: https://edgeapi.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Read.html#method-i-read_attribute [ActiveRecord::AttributeMethods::Write]: https://edgeapi.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Write.html#method-i-write_attribute [rails/rails#53886]: rails/rails#53886 [ActiveModel::Attributes]: https://edgeapi.rubyonrails.org/classes/ActiveModel/Attributes.html [#410]: #410
1 parent b7728b1 commit b897313

File tree

3 files changed

+118
-11
lines changed

3 files changed

+118
-11
lines changed

lib/active_resource/associations.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def defines_belongs_to_finder_method(reflection)
131131
if instance_variable_defined?(ivar_name)
132132
instance_variable_get(ivar_name)
133133
elsif attributes.include?(method_name)
134-
attributes[method_name]
134+
read_attribute(method_name)
135135
elsif association_id = send(reflection.foreign_key)
136136
instance_variable_set(ivar_name, reflection.klass.find(association_id))
137137
end
@@ -146,7 +146,7 @@ def defines_has_many_finder_method(reflection)
146146
if instance_variable_defined?(ivar_name)
147147
instance_variable_get(ivar_name)
148148
elsif attributes.include?(method_name)
149-
attributes[method_name]
149+
read_attribute(method_name)
150150
elsif !new_record?
151151
instance_variable_set(ivar_name, reflection.klass.find(:all, params: { "#{self.class.element_name}_id": self.id }))
152152
else
@@ -164,7 +164,7 @@ def defines_has_one_finder_method(reflection)
164164
if instance_variable_defined?(ivar_name)
165165
instance_variable_get(ivar_name)
166166
elsif attributes.include?(method_name)
167-
attributes[method_name]
167+
read_attribute(method_name)
168168
elsif reflection.klass.respond_to?(:singleton_name)
169169
instance_variable_set(ivar_name, reflection.klass.find(params: { "#{self.class.element_name}_id": self.id }))
170170
else

lib/active_resource/base.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,12 +1380,12 @@ def persisted?
13801380

13811381
# Gets the <tt>\id</tt> attribute of the resource.
13821382
def id
1383-
attributes[self.class.primary_key]
1383+
read_attribute(self.class.primary_key)
13841384
end
13851385

13861386
# Sets the <tt>\id</tt> attribute of the resource.
13871387
def id=(id)
1388-
attributes[self.class.primary_key] = id
1388+
write_attribute(self.class.primary_key, id)
13891389
end
13901390

13911391
# Test for equality. Resource are equal if and only if +other+ is the same object or
@@ -1596,7 +1596,7 @@ def load(attributes, remove_root = false, persisted = false)
15961596
attributes = Formats.remove_root(attributes) if remove_root
15971597

15981598
attributes.each do |key, value|
1599-
@attributes[key.to_s] =
1599+
write_attribute(key.to_s,
16001600
case value
16011601
when Array
16021602
resource = nil
@@ -1614,6 +1614,7 @@ def load(attributes, remove_root = false, persisted = false)
16141614
else
16151615
value.duplicable? ? value.dup : value
16161616
end
1617+
)
16171618
end
16181619
self
16191620
end
@@ -1693,13 +1694,29 @@ def to_xml(options = {})
16931694
end
16941695

16951696
def read_attribute_for_serialization(n)
1696-
if !attributes[n].nil?
1697-
attributes[n]
1697+
value = read_attribute(n)
1698+
1699+
if !value.nil?
1700+
value
16981701
elsif respond_to?(n)
16991702
send(n)
17001703
end
17011704
end
17021705

1706+
def read_attribute(attr_name)
1707+
name = attr_name.to_s
1708+
1709+
name = self.class.primary_key if name == "id" && self.class.primary_key
1710+
@attributes[name]
1711+
end
1712+
1713+
def write_attribute(attr_name, value)
1714+
name = attr_name.to_s
1715+
1716+
name = self.class.primary_key if name == "id" && self.class.primary_key
1717+
@attributes[name] = value
1718+
end
1719+
17031720
protected
17041721
def connection(refresh = false)
17051722
self.class.connection(refresh)
@@ -1842,12 +1859,12 @@ def method_missing(method_symbol, *arguments) # :nodoc:
18421859
if method_name =~ /(=|\?)$/
18431860
case $1
18441861
when "="
1845-
attributes[$`] = arguments.first
1862+
write_attribute($`, arguments.first)
18461863
when "?"
1847-
attributes[$`]
1864+
read_attribute($`)
18481865
end
18491866
else
1850-
return attributes[method_name] if attributes.include?(method_name)
1867+
return read_attribute(method_name) if attributes.include?(method_name)
18511868
# not set right now but we know about it
18521869
return nil if known_attributes.include?(method_name)
18531870
super
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 "abstract_unit"
4+
require "fixtures/person"
5+
6+
class AttributeMethodsTest < ActiveSupport::TestCase
7+
setup :setup_response
8+
9+
test "write_attribute string" do
10+
matz = Person.find(1)
11+
12+
assert_changes -> { matz.name }, to: "matz" do
13+
matz.write_attribute("name", "matz")
14+
end
15+
end
16+
17+
test "write_attribute symbol" do
18+
matz = Person.find(1)
19+
20+
assert_changes -> { matz.name }, to: "matz" do
21+
matz.write_attribute(:name, "matz")
22+
end
23+
end
24+
25+
test "write_attribute id" do
26+
matz = Person.find(1)
27+
28+
assert_changes -> { matz.id }, from: 1, to: "2" do
29+
matz.write_attribute(:id, "2")
30+
end
31+
end
32+
33+
test "write_attribute primary key" do
34+
previous_primary_key = Person.primary_key
35+
Person.primary_key = "pk"
36+
matz = Person.find(1)
37+
38+
assert_changes -> { matz.id }, from: 1, to: "2" do
39+
matz.write_attribute(:id, "2")
40+
end
41+
assert_changes -> { matz.id }, from: "2", to: 1 do
42+
matz.write_attribute("pk", 1)
43+
end
44+
assert_changes -> { matz.id }, from: 1, to: "2" do
45+
matz.id = "2"
46+
end
47+
ensure
48+
Person.primary_key = previous_primary_key
49+
end
50+
51+
test "write_attribute an unknown attribute" do
52+
person = Person.new
53+
54+
person.write_attribute("unknown", true)
55+
56+
assert_predicate person, :unknown
57+
end
58+
59+
test "read_attribute" do
60+
matz = Person.find(1)
61+
62+
assert_equal "Matz", matz.read_attribute("name")
63+
assert_equal "Matz", matz.read_attribute(:name)
64+
end
65+
66+
test "read_attribute id" do
67+
matz = Person.find(1)
68+
69+
assert_equal 1, matz.read_attribute("id")
70+
assert_equal 1, matz.read_attribute(:id)
71+
end
72+
73+
test "read_attribute primary key" do
74+
previous_primary_key = Person.primary_key
75+
Person.primary_key = "pk"
76+
matz = Person.find(1)
77+
78+
assert_equal 1, matz.id
79+
assert_equal 1, matz.read_attribute("pk")
80+
assert_equal 1, matz.read_attribute(:pk)
81+
ensure
82+
Person.primary_key = previous_primary_key
83+
end
84+
85+
test "read_attribute unknown attribute" do
86+
person = Person.new
87+
88+
person.read_attribute("unknown")
89+
end
90+
end

0 commit comments

Comments
 (0)