From 268499bf400df6bad30f218f296905abdf8c9095 Mon Sep 17 00:00:00 2001 From: Samuel J Clopton Date: Fri, 18 Dec 2020 11:22:14 -0800 Subject: [PATCH 01/14] Initial implemention of `{ : { data: null } }` nullification This commit updates graphiti to be able to nullify relations only if `null` is specified in the assocation payload. This will still need feedback from the library maintainers, and after approval of design tests will need written to test this feature. --- .../adapters/persistence/associations.rb | 25 +++++++++-- lib/graphiti/deserializer.rb | 8 ++++ lib/graphiti/util/persistence.rb | 4 +- lib/graphiti/util/relationship_payload.rb | 44 +++++++++++++++---- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/lib/graphiti/adapters/persistence/associations.rb b/lib/graphiti/adapters/persistence/associations.rb index 444c8344..8166cdb0 100644 --- a/lib/graphiti/adapters/persistence/associations.rb +++ b/lib/graphiti/adapters/persistence/associations.rb @@ -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] @@ -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] @@ -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 diff --git a/lib/graphiti/deserializer.rb b/lib/graphiti/deserializer.rb index dc7ce8c2..adea3da1 100644 --- a/lib/graphiti/deserializer.rb +++ b/lib/graphiti/deserializer.rb @@ -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 diff --git a/lib/graphiti/util/persistence.rb b/lib/graphiti/util/persistence.rb index 05e63723..40197f7d 100644 --- a/lib/graphiti/util/persistence.rb +++ b/lib/graphiti/util/persistence.rb @@ -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) @@ -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 @@ -68,7 +70,7 @@ def run persisted end - def iterate(only: [], except: []) + def iterate(only: {}, except: {}) opts = { resource: @resource, relationships: @relationships diff --git a/lib/graphiti/util/relationship_payload.rb b/lib/graphiti/util/relationship_payload.rb index 3fcb3bbe..b8d7dfc0 100644 --- a/lib/graphiti/util/relationship_payload.rb +++ b/lib/graphiti/util/relationship_payload.rb @@ -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 @@ -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 @@ -37,10 +41,32 @@ 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) From 10cde1e6fc93d40b88fe02e5ea85e69f7df6ceb4 Mon Sep 17 00:00:00 2001 From: Samuel J Clopton Date: Fri, 18 Dec 2020 13:25:02 -0800 Subject: [PATCH 02/14] ... --- lib/graphiti/util/relationship_payload.rb | 59 ++++++++++++++--------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/lib/graphiti/util/relationship_payload.rb b/lib/graphiti/util/relationship_payload.rb index b8d7dfc0..eb869161 100644 --- a/lib/graphiti/util/relationship_payload.rb +++ b/lib/graphiti/util/relationship_payload.rb @@ -70,31 +70,46 @@ def should_yield_relationship_type?(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 From d267384307e5fc38fc7577a515bd00d992a11e12 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Thu, 27 Feb 2025 11:39:45 -0800 Subject: [PATCH 03/14] Update graphiti.gemspec for Ruby 3.1 --- graphiti.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphiti.gemspec b/graphiti.gemspec index 83b6ab4d..a43992b5 100644 --- a/graphiti.gemspec +++ b/graphiti.gemspec @@ -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.1"] spec.add_dependency "jsonapi-serializable", "~> 0.3.0" spec.add_dependency "jsonapi-renderer", "~> 0.2", ">= 0.2.2" @@ -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 From a64ea9f0e8489e9217d34b06cb4500e6a97a8631 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Thu, 27 Feb 2025 11:41:39 -0800 Subject: [PATCH 04/14] Update version.rb --- lib/graphiti/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti/version.rb b/lib/graphiti/version.rb index 3079d73f..f43a07b6 100644 --- a/lib/graphiti/version.rb +++ b/lib/graphiti/version.rb @@ -1,3 +1,3 @@ module Graphiti - VERSION = "1.2.31" + VERSION = "1.2.32" end From 0eeabf8c8418dc7c321f77138eed7c87256812f3 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Fri, 28 Feb 2025 10:28:12 -0800 Subject: [PATCH 05/14] Update graphiti.gemspec --- graphiti.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphiti.gemspec b/graphiti.gemspec index a43992b5..ed456ea7 100644 --- a/graphiti.gemspec +++ b/graphiti.gemspec @@ -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.2"] spec.add_dependency "jsonapi-serializable", "~> 0.3.0" spec.add_dependency "jsonapi-renderer", "~> 0.2", ">= 0.2.2" From 5c11b130b30855b09fc38770aba24d98564061a0 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Fri, 28 Feb 2025 10:29:18 -0800 Subject: [PATCH 06/14] Update version.rb --- lib/graphiti/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti/version.rb b/lib/graphiti/version.rb index f43a07b6..9c978c56 100644 --- a/lib/graphiti/version.rb +++ b/lib/graphiti/version.rb @@ -1,3 +1,3 @@ module Graphiti - VERSION = "1.2.32" + VERSION = "1.2.33" end From 68d01e6e2732c0beccae03a026aad624b8552a86 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Thu, 6 Mar 2025 11:54:07 -0800 Subject: [PATCH 07/14] Update serializer_relationships.rb --- lib/graphiti/util/serializer_relationships.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti/util/serializer_relationships.rb b/lib/graphiti/util/serializer_relationships.rb index 5ab52b2e..70bf886d 100644 --- a/lib/graphiti/util/serializer_relationships.rb +++ b/lib/graphiti/util/serializer_relationships.rb @@ -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 From 4e0b9c57bea8ee019640ec8e1c36de75c922af71 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Thu, 6 Mar 2025 11:54:52 -0800 Subject: [PATCH 08/14] Update graphiti.rb --- lib/graphiti.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/graphiti.rb b/lib/graphiti.rb index fd3971aa..ec7c9f6b 100644 --- a/lib/graphiti.rb +++ b/lib/graphiti.rb @@ -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" From 173603a3ff3d348b910180dae6c501c4b4363352 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Thu, 6 Mar 2025 11:55:09 -0800 Subject: [PATCH 09/14] Update version.rb --- lib/graphiti/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti/version.rb b/lib/graphiti/version.rb index 9c978c56..117f462b 100644 --- a/lib/graphiti/version.rb +++ b/lib/graphiti/version.rb @@ -1,3 +1,3 @@ module Graphiti - VERSION = "1.2.33" + VERSION = "1.2.34" end From a24da97d0a3212e719fd971927d371ec95d74ef7 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Fri, 10 Oct 2025 12:25:34 -0700 Subject: [PATCH 10/14] Samsinite patch ruby 3 2 (#2) * Update to allow Ruby 3.2 * Update version.rb --- graphiti.gemspec | 2 +- lib/graphiti/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphiti.gemspec b/graphiti.gemspec index ed456ea7..599284ba 100644 --- a/graphiti.gemspec +++ b/graphiti.gemspec @@ -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.2"] + spec.required_ruby_version = [">= 2.3", "<= 3.2"] spec.add_dependency "jsonapi-serializable", "~> 0.3.0" spec.add_dependency "jsonapi-renderer", "~> 0.2", ">= 0.2.2" diff --git a/lib/graphiti/version.rb b/lib/graphiti/version.rb index 117f462b..183ee9c0 100644 --- a/lib/graphiti/version.rb +++ b/lib/graphiti/version.rb @@ -1,3 +1,3 @@ module Graphiti - VERSION = "1.2.34" + VERSION = "1.2.35" end From 3e60aecaefed77a244c63c37ef295372adc4196e Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Fri, 10 Oct 2025 12:26:59 -0700 Subject: [PATCH 11/14] Update graphiti.gemspec --- graphiti.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphiti.gemspec b/graphiti.gemspec index 599284ba..13b65095 100644 --- a/graphiti.gemspec +++ b/graphiti.gemspec @@ -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.2"] + 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" From bae3a108f7a22b497c9920ac5e109d94bec2766a Mon Sep 17 00:00:00 2001 From: Joe Weakley Date: Fri, 31 Oct 2025 10:56:06 -0700 Subject: [PATCH 12/14] Bold Parameter in color method (#3) Fixes issue where bold parameter was not being passed correctly to color method in ActiveSupport::LogSubscriber. --- lib/graphiti.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti.rb b/lib/graphiti.rb index ec7c9f6b..119f2012 100644 --- a/lib/graphiti.rb +++ b/lib/graphiti.rb @@ -83,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 From 09186edcae4d2c6d8338545b849a89439b096005 Mon Sep 17 00:00:00 2001 From: Sam Clopton Date: Fri, 31 Oct 2025 10:57:06 -0700 Subject: [PATCH 13/14] Version bump to 1.2.36 --- lib/graphiti/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphiti/version.rb b/lib/graphiti/version.rb index 183ee9c0..55d1215d 100644 --- a/lib/graphiti/version.rb +++ b/lib/graphiti/version.rb @@ -1,3 +1,3 @@ module Graphiti - VERSION = "1.2.35" + VERSION = "1.2.36" end From d704951450d6a69cc47f827f0b884b827e54a6d9 Mon Sep 17 00:00:00 2001 From: Joe Weakley Date: Fri, 5 Dec 2025 14:24:23 -0800 Subject: [PATCH 14/14] Ruby 3.3 Compatibility (#4) Ruby 3.3 Compatibility Ensure the codebase is compatible with Ruby 3.3 by updating dependencies and fixing deprecation warnings. --- graphiti.gemspec | 2 +- mise.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 mise.toml diff --git a/graphiti.gemspec b/graphiti.gemspec index 13b65095..b60fcf2a 100644 --- a/graphiti.gemspec +++ b/graphiti.gemspec @@ -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.3"] + 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" diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..408a7171 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +ruby = "3.3.10"