Skip to content
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
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.4"]

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
6 changes: 5 additions & 1 deletion lib/graphiti/adapters/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,11 @@ def destroy(model_instance)
end

def close
::ActiveRecord::Base.clear_active_connections!
if ::ActiveRecord.version > Gem::Version.new("7.1")
::ActiveRecord::Base.connection_handler.clear_active_connections!
else
::ActiveRecord::Base.clear_active_connections!
end
end

private
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 && !@except.include?(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.37"
end
2 changes: 2 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
ruby = "3.3.10"