Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implemention of { <relationship>: { data: null } } nullification #306

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions lib/graphiti/adapters/persistence/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,28 @@ module Graphiti
module Adapters
module Persistence
module Associations
def process_nullified_belongs_to_associations(persistence, attributes)
persistence.iterate(only: { relationship_types: [:polymorphic_belongs_to, :belongs_to], method_types: [:nullify] }) do |x|
update_foreign_key(persistence, attributes, x)
end
end

def process_nullified_has_many_associations(persistence, caller_model)
[].tap do |processed|
persistence.iterate(only: { method_types: [:nullify] }, except: { relationship_types: [:polymorphic_belongs_to, :belongs_to] }) do |x|
update_foreign_key(caller_model, x[:attributes], x)

x[:object] = x[:resource]
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships], caller_model, x[:foreign_key])

processed << x
end
end
end

def process_belongs_to(persistence, attributes)
parents = [].tap do |processed|
persistence.iterate(only: [:polymorphic_belongs_to, :belongs_to]) do |x|
persistence.iterate(only: { relationship_types: [:polymorphic_belongs_to, :belongs_to] }, except: { method_types: [:nullify] }) do |x|
begin
id = x.dig(:attributes, :id)
x[:object] = x[:resource]
Expand All @@ -23,7 +42,7 @@ def process_belongs_to(persistence, attributes)

def process_has_many(persistence, caller_model)
[].tap do |processed|
persistence.iterate(except: [:polymorphic_belongs_to, :belongs_to]) do |x|
persistence.iterate(except: { relationship_types: [:polymorphic_belongs_to, :belongs_to], method_types: [:nullify] }) do |x|
update_foreign_key(caller_model, x[:attributes], x)

x[:object] = x[:resource]
Expand All @@ -47,7 +66,7 @@ def update_foreign_key_for_parents(parents, attributes)
def update_foreign_key(parent_object, attrs, x)
return if x[:sideload].type == :many_to_many

if [:destroy, :disassociate].include?(x[:meta][:method])
if [:destroy, :disassociate, :nullify].include?(x[:meta][:method])
if x[:sideload].polymorphic_has_one? || x[:sideload].polymorphic_has_many?
attrs[:"#{x[:sideload].polymorphic_as}_type"] = nil
end
Expand Down
8 changes: 8 additions & 0 deletions lib/graphiti/deserializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ def process_relationships(relationship_hash)

if relationship_payload[:data]
hash[name] = process_relationship(relationship_payload[:data])
elsif relationship_payload.key?(:data) && relationship_payload[:data] == nil
hash[name] = {
meta: {
method: :nullify
},
attributes: {},
relationships: {}
}
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion lib/graphiti/util/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def initialize(resource, meta, attributes, relationships, caller_model, foreign_
# @return a model instance
def run
attributes = @adapter.persistence_attributes(self, @attributes)
@adapter.process_nullified_belongs_to_associations(self, attributes)

parents = @adapter.process_belongs_to(self, attributes)
persisted = persist_object(@meta[:method], attributes)
Expand All @@ -53,6 +54,7 @@ def run

associate_parents(persisted, parents)

@adapter.process_nullified_has_many_associations(self, persisted)
children = @adapter.process_has_many(self, persisted)

associate_children(persisted, children) unless @meta[:method] == :destroy
Expand All @@ -68,7 +70,7 @@ def run
persisted
end

def iterate(only: [], except: [])
def iterate(only: {}, except: {})
opts = {
resource: @resource,
relationships: @relationships
Expand Down
103 changes: 72 additions & 31 deletions lib/graphiti/util/relationship_payload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ module Util
class RelationshipPayload
attr_reader :resource, :payload

def self.iterate(resource:, relationships: {}, only: [], except: [])
def self.iterate(resource:, relationships: {}, only: {}, except: {})
instance = new(resource, relationships, only: only, except: except)
instance.iterate do |sideload, relationship_data, sub_relationships|
yield sideload, relationship_data, sub_relationships
end
end

def initialize(resource, payload, only: [], except: [])
def initialize(resource, payload, only: {}, except: {})
@resource = resource
@payload = payload
@only = only
Expand All @@ -22,13 +22,17 @@ def initialize(resource, payload, only: [], except: [])
def iterate
payload.each_pair do |relationship_name, relationship_payload|
if (sl = resource.class.sideload(relationship_name.to_sym))
if should_yield?(sl.type)
if should_yield_relationship_type?(sl.type)
if relationship_payload.is_a?(Array)
relationship_payload.each do |rp|
yield payload_for(sl, rp)
if should_yield_method_type?(rp.fetch(:meta, {})[:method] || :update)
yield payload_for(sl, rp)
end
end
else
yield payload_for(sl, relationship_payload)
if should_yield_method_type?(relationship_payload.fetch(:meta, {})[:method] || :update)
yield payload_for(sl, relationship_payload)
end
end
end
end
Expand All @@ -37,38 +41,75 @@ def iterate

private

def should_yield?(type)
(@only.length == 0 && @except.length == 0) ||
(@only.length > 0 && @only.include?(type)) ||
(@except.length > 0 && [email protected]?(type))
def only_relationship_types
@only[:relationship_types] || []
end

def except_relationship_types
@except[:relationship_types] || []
end

def only_method_types
@only[:method_types] || []
end

def except_method_types
@except[:method_types] || []
end

def should_yield_method_type?(type)
(only_method_types.length == 0 && except_method_types.length == 0) ||
(only_method_types.length > 0 && only_method_types.include?(type)) ||
(except_method_types.length > 0 && !except_method_types.include?(type))
end

def should_yield_relationship_type?(type)
(only_relationship_types.length == 0 && except_relationship_types.length == 0) ||
(only_relationship_types.length > 0 && only_relationship_types.include?(type)) ||
(except_relationship_types.length > 0 && !except_relationship_types.include?(type))
end

def payload_for(sideload, relationship_payload)
type = relationship_payload[:meta][:jsonapi_type].to_sym
if relationship_payload[:meta][:method] == :nullify
resource = sideload.resource

# For polymorphic *sideloads*, grab the correct child sideload
if sideload.resource.type != type && sideload.type == :polymorphic_belongs_to
sideload = sideload.child_for_type!(type)
end
{
resource: resource,
sideload: sideload,
is_polymorphic: sideload.polymorphic_child?,
primary_key: sideload.primary_key,
foreign_key: sideload.foreign_key,
attributes: relationship_payload[:attributes],
meta: relationship_payload[:meta],
relationships: relationship_payload[:relationships]
}
else
type = relationship_payload[:meta][:jsonapi_type].to_sym

# For polymorphic *resources*, grab the correct child resource
resource = sideload.resource
if resource.type != type && resource.polymorphic?
resource = resource.class.resource_for_type(type).new
end
# For polymorphic *sideloads*, grab the correct child sideload
if sideload.resource.type != type && sideload.type == :polymorphic_belongs_to
sideload = sideload.child_for_type!(type)
end

relationship_payload[:meta][:method] ||= :update

{
resource: resource,
sideload: sideload,
is_polymorphic: sideload.polymorphic_child?,
primary_key: sideload.primary_key,
foreign_key: sideload.foreign_key,
attributes: relationship_payload[:attributes],
meta: relationship_payload[:meta],
relationships: relationship_payload[:relationships]
}
# For polymorphic *resources*, grab the correct child resource
resource = sideload.resource
if resource.type != type && resource.polymorphic?
resource = resource.class.resource_for_type(type).new
end

relationship_payload[:meta][:method] ||= :update

{
resource: resource,
sideload: sideload,
is_polymorphic: sideload.polymorphic_child?,
primary_key: sideload.primary_key,
foreign_key: sideload.foreign_key,
attributes: relationship_payload[:attributes],
meta: relationship_payload[:meta],
relationships: relationship_payload[:relationships]
}
end
end
end
end
Expand Down