Skip to content
Draft
4 changes: 2 additions & 2 deletions graphiti.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.required_ruby_version = [">= 2.3", "< 3.1"]
spec.required_ruby_version = [">= 2.3", "< 3.3"]

spec.add_dependency "jsonapi-serializable", "~> 0.3.0"
spec.add_dependency "jsonapi-renderer", "~> 0.2", ">= 0.2.2"
Expand All @@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "kaminari", "~> 0.17"
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "standard", "0.4.7"
spec.add_development_dependency "standard", "~> 1.4.0"
spec.add_development_dependency "activemodel", ">= 4.1"
spec.add_development_dependency "graphiti_spec_helpers", "1.0.beta.4"
end
3 changes: 2 additions & 1 deletion lib/graphiti.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "json"
require "forwardable"
require "uri"
require "active_support/core_ext/string"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/class/attribute"
Expand Down Expand Up @@ -82,7 +83,7 @@ def self.logger=(val)
end

def self.log(msg, color = :white, bold = false)
colored = ActiveSupport::LogSubscriber.new.send(:color, msg, color, bold)
colored = ActiveSupport::LogSubscriber.new.send(:color, msg, color, bold: bold)
logger.debug(colored)
end

Expand Down
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
2 changes: 1 addition & 1 deletion lib/graphiti/util/serializer_relationships.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def validate_link_for_sideload!(sideload)
cache_key = :"#{@sideload.object_id}-#{action}"
return if self.class.validated_link_cache.include?(cache_key)
prc = Graphiti.config.context_for_endpoint
unless prc.call(sideload.resource.endpoint[:full_path], action)
unless prc.call(sideload.resource.endpoint[:full_path].to_s, action)
raise Errors::InvalidLink.new(@resource_class, sideload, action)
end
self.class.validated_link_cache << cache_key
Expand Down
2 changes: 1 addition & 1 deletion lib/graphiti/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Graphiti
VERSION = "1.2.31"
VERSION = "1.2.36"
end