Skip to content

Commit 19c75b5

Browse files
Lee Richmondrichmolj
authored andcommitted
Ensure multiple extra_attrs without blocks work correctly
Two pieces of context needed: First, there's a bunch of logic around serializer attributes: subclassing resources, explicit serializers, defaults, overrides, etc. And all of this gets dicey considering it all wraps and hacks an external library, `jsonapi-serializable`. So we're pretty dumb about saying "any time we're doing something that can affect serialization, re-configure everything". Second, we recently accommodated the scenario where a parent resource class guards an attribute, and a subclass removes that guard. The logic added says "if this was previously guarded, remove the guard - as we're adding the property again, the guard will get added again if it needs to be". These two scenarios conflicted. If you had two `extra_attribute` calls, the second would cause the first to be re-applied as part of the "reconfigure everything" approach. And when re-applied, we remove the prior guard...expecting it to be added again as part of the idempotent nature of the call. This worked as expected if you passed a block, because you'd end up back [here](https://github.com/graphiti-api/graphiti/blob/master/lib/graphiti/util/serializer_attributes.rb#L20). But if you didn't pass a block, you'd end up [here](https://github.com/graphiti-api/graphiti/blob/master/lib/graphiti/util/serializer_attributes.rb#L24), with the logic being "the only thing we need to do here is add the guard back. This was dumb! If you're going to do the "reconfigure everything" approach, you need to ensure you're following the same codepath - not this special codepath that was added. This had the adverse effect that no guard was re-added (for extra attributes, the conditional [is added in a different way](https://github.com/graphiti-api/graphiti/blob/master/lib/graphiti/extensions/extra_attribute.rb#L43) than a normal guard). So the fix is to ensure we always follow the same codepath, which will now correctly re-apply the guard. And added tests for the scenario where multiple `extra_attributes` are added with and without blocks. Finally, I'm not so sure about the wisdom of "reconfigure everything". This was added 3 years ago as part of the major refactor that replaced `jsonapi_suite` and became the `graphiti` we know and love today. It's possible that the scenarios we were worried about are no longer relevant, and this is more of a hindrance than a help. For now, simplest fix.
1 parent 4ba0f36 commit 19c75b5

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

lib/graphiti/util/serializer_attributes.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ def apply
1616

1717
if @name == :id
1818
@serializer.id(&proc)
19-
elsif @attr[:proc]
19+
elsif @attr[:proc] ||
20+
!previously_applied? ||
21+
previously_applied_via_resource?
2022
@serializer.send(_method, @name, serializer_options, &proc)
21-
elsif @serializer.attribute_blocks[@name].nil?
22-
@serializer.send(_method, @name, serializer_options, &proc)
23-
elsif @serializer.send(applied_method).include?(@name)
24-
@serializer.field_condition_blocks[@name] = guard if guard?
25-
else
23+
else # Previously applied via explicit serializer, so wrap it
2624
inner = @serializer.attribute_blocks.delete(@name)
2725
wrapped = wrap_proc(inner)
2826
@serializer.send(_method, @name, serializer_options, &wrapped)
@@ -34,6 +32,14 @@ def apply
3432

3533
private
3634

35+
def previously_applied?
36+
@serializer.attribute_blocks[@name].present?
37+
end
38+
39+
def previously_applied_via_resource?
40+
@serializer.send(applied_method).include?(@name)
41+
end
42+
3743
def previously_guarded?
3844
@serializer.field_condition_blocks[@name]
3945
end

lib/graphiti/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Graphiti
2-
VERSION = "1.2.37"
2+
VERSION = "1.2.38"
33
end

spec/extra_fields_spec.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,64 @@ def attributes
2929
.to match_array(%w[first_name last_name age stack_ranking])
3030
end
3131

32+
context "when multiple extra attributes" do
33+
context "added with blocks" do
34+
before do
35+
resource.extra_attribute :a, :string do
36+
"a"
37+
end
38+
resource.extra_attribute :b, :string do
39+
"b"
40+
end
41+
end
42+
43+
context "when not requested" do
44+
it "does not render them" do
45+
render
46+
expect(attributes.keys).to match_array(%w[first_name last_name age])
47+
end
48+
end
49+
50+
context "when one is requested" do
51+
before do
52+
params[:extra_fields] = {employees: "b"}
53+
end
54+
55+
it "is rendered" do
56+
render
57+
expect(attributes.keys).to match_array(%w[first_name last_name age b])
58+
end
59+
end
60+
end
61+
62+
context "added without blocks" do
63+
before do
64+
allow_any_instance_of(PORO::Employee).to receive(:a) { "a" }
65+
allow_any_instance_of(PORO::Employee).to receive(:b) { "b" }
66+
resource.extra_attribute :a, :string
67+
resource.extra_attribute :b, :string
68+
end
69+
70+
context "when not requested" do
71+
it "does not render them" do
72+
render
73+
expect(attributes.keys).to match_array(%w[first_name last_name age])
74+
end
75+
end
76+
77+
context "when one is requested" do
78+
before do
79+
params[:extra_fields] = {employees: "b"}
80+
end
81+
82+
it "is rendered" do
83+
render
84+
expect(attributes.keys).to match_array(%w[first_name last_name age b])
85+
end
86+
end
87+
end
88+
end
89+
3290
context "when altering scope based on extra attrs" do
3391
context "when the extra attr exists" do
3492
before do

spec/serialization_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@ def admin?(object, attribute)
871871

872872
context "without a guard" do
873873
before do
874+
allow_any_instance_of(PORO::Employee).to receive(:foo) { "bar" }
874875
resource.attribute :foo, :string
875876
end
876877

@@ -895,6 +896,7 @@ def admin?(object, attribute)
895896

896897
context "with a guard" do
897898
before do
899+
allow_any_instance_of(PORO::Employee).to receive(:foo) { "bar" }
898900
resource.class_eval do
899901
attribute :foo, :string, readable: :overriden?
900902

0 commit comments

Comments
 (0)