Skip to content

Commit 648cb28

Browse files
Lee Richmondrichmolj
authored andcommitted
Allow unwritable PK when associating on create
Imagine updating a `Position`, changing the `Employee` it is associated with. Since we're justassociating, we don't need `Employee`s PK to be writable. But because our internals end up saving the `Employee` regardless - we just walk the tree, and `id` is part of the attributes considered - so we'd normally validate the PK, even though we really don't need to. We already avoided validating the PK when *updating* the `Position`, this PR also considers *creating* the `Position` and associating to a pre-existing `Employee`. We do this by looking at the `method` of each sidepost, instad of the overall action being called.
1 parent 19c75b5 commit 648cb28

File tree

2 files changed

+72
-19
lines changed

2 files changed

+72
-19
lines changed

lib/graphiti/request_validators/validator.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def validate
2525
end
2626
end
2727

28-
typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
28+
typecast_attributes(resource, deserialized_payload.attributes, @action, deserialized_payload.meta[:payload_path])
2929
process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
3030
else
3131
errors.add(:"data.type", :missing)
@@ -65,15 +65,20 @@ def process_relationships(resource, relationships, payload_path)
6565
next
6666
end
6767

68-
typecast_attributes(x[:resource], x[:attributes], x[:meta][:payload_path])
69-
process_relationships(x[:resource], x[:relationships], x[:meta][:payload_path])
68+
resource = x[:resource]
69+
attributes = x[:attributes]
70+
relationships = x[:relationships]
71+
payload_path = x[:meta][:payload_path]
72+
action = x[:meta][:method]
73+
typecast_attributes(resource, attributes, action, payload_path)
74+
process_relationships(resource, relationships, payload_path)
7075
end
7176
end
7277

73-
def typecast_attributes(resource, attributes, payload_path)
78+
def typecast_attributes(resource, attributes, action, payload_path)
7479
attributes.each_pair do |key, value|
7580
# Only validate id if create action, otherwise it's only used for lookup
76-
next if @action != :create &&
81+
next if action != :create &&
7782
key == :id &&
7883
resource.class.config[:attributes][:id][:writable] == false
7984

spec/integration/rails/persistence_spec.rb

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,6 @@ def self.model_name
404404
end
405405

406406
describe "non-writable foreign keys" do
407-
subject(:make_request) { do_update(payload) }
408-
409407
context "when belongs_to" do
410408
let(:klass) do
411409
Class.new(EmployeeResource) do
@@ -414,14 +412,16 @@ def self.model_name
414412
end
415413
end
416414

417-
let(:employee) { Employee.create!(first_name: "Jane") }
418-
let(:classification) { Classification.create!(description: "foo") }
415+
let!(:employee) { Employee.create!(first_name: "Jane") }
416+
let!(:classification) { Classification.create!(description: "foo") }
419417

420418
let(:payload) do
421419
{
422420
data: {
423421
type: "employees",
424-
id: employee.id.to_s,
422+
attributes: {
423+
first_name: "foo"
424+
},
425425
relationships: {
426426
classification: {
427427
data: {
@@ -436,12 +436,39 @@ def self.model_name
436436

437437
before do
438438
allow(controller).to receive(:resource) { klass }
439+
440+
classification_resource = Class.new(ClassificationResource) do
441+
def self.name
442+
"ClassificationResource"
443+
end
444+
attribute :id, :integer_id, writable: false
445+
end
446+
klass.belongs_to :classification, resource: classification_resource
439447
end
440448

441-
it "does not require the FK to be a writable attribute" do
442-
make_request
443-
employee = Employee.last
444-
expect(employee.classification_id).to eq(classification.id)
449+
context "and overall action is create" do
450+
subject(:make_request) { do_create(payload) }
451+
452+
it "does not require the FK to be a writable attribute" do
453+
expect {
454+
make_request
455+
}.to change { Employee.count }.by(1)
456+
employee = Employee.last
457+
expect(employee.classification_id).to eq(classification.id)
458+
end
459+
end
460+
461+
context "and overall action is update" do
462+
subject(:make_request) { do_update(payload) }
463+
464+
before do
465+
payload[:data][:id] = employee.id.to_s
466+
end
467+
468+
it "does not require the FK to be a writable attribute" do
469+
make_request
470+
expect(employee.reload.classification_id).to eq(classification.id)
471+
end
445472
end
446473
end
447474

@@ -465,7 +492,9 @@ def self.model_name
465492
{
466493
data: {
467494
type: "employees",
468-
id: employee.id.to_s,
495+
attributes: {
496+
first_name: "foo"
497+
},
469498
relationships: {
470499
positions: {
471500
data: [{
@@ -491,10 +520,29 @@ def self.model_name
491520
allow(controller).to receive(:resource) { klass }
492521
end
493522

494-
it "does not require the FK to be a writable attribute" do
495-
make_request
496-
employee = Employee.last
497-
expect(employee.positions.map(&:title)).to eq(["foo"])
523+
context "and the overall request is create" do
524+
subject(:make_request) { do_create(payload) }
525+
526+
it "does not require the FK to be a writable attribute" do
527+
expect {
528+
make_request
529+
}.to change { Employee.count }.by(1)
530+
employee = Employee.last
531+
expect(employee.positions.map(&:title)).to eq(["foo"])
532+
end
533+
end
534+
535+
context "and the overall request is update" do
536+
subject(:make_request) { do_update(payload) }
537+
538+
before do
539+
payload[:data][:id] = employee.id.to_s
540+
end
541+
542+
it "does not require the FK to be a writable attribute" do
543+
make_request
544+
expect(employee.reload.positions.map(&:title)).to eq(["foo"])
545+
end
498546
end
499547
end
500548
end

0 commit comments

Comments
 (0)