graphiti-activegraph 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f43302a065e6583232921abe819bcea82bce10d85893b1cc80d132dd169ebbc0
4
- data.tar.gz: 4aa5cd9c5486ac50d4d0f8844433e931ce55998777aa740014e1a9281b797471
3
+ metadata.gz: 4e2e06c991422f868705fa49e812c51af9c537366fac6246ebfc882969e7bdae
4
+ data.tar.gz: 7fc80649e1455f255450e00dcf6c66fe0ff889d1d74e16ebe123dc70362e8266
5
5
  SHA512:
6
- metadata.gz: 5604a91acd31eeeecbe45bd7defa41fd2b24612be053463dec11e48d61cc81ed52005050ebb077d5ba9172e1bb012e2bf6d8354a1d27385aeb4276f66ce92283
7
- data.tar.gz: 37ed8852db96fd2dc357afce65069c96ddd664b3c3d33628ab50dfa83aa43d82a5336d779d98d5df59a0f968c20ee7667c4593ce691f95724d548bc146805eba
6
+ metadata.gz: 3db8cee492d4ac415bddc3a44cf755cfa4f32a0b3865f90821c62a681838776d48c6e12dff4fd30fafce88f204c56884c37bc016bc4b71166fe572ec725ab1df
7
+ data.tar.gz: 8d7f27a547460a725247b4b28ea629fec384f20e0255f29771c6f805e096063f8bfb5138e576f7ef7d6c7eb1acfe4cfca735940d2852dee41fee3042462ca604
data/.gitignore CHANGED
@@ -45,7 +45,7 @@ build-iPhoneSimulator/
45
45
 
46
46
  # for a library or gem, you might want to ignore these files since the code is
47
47
  # intended to run in multiple environments; otherwise, check them in:
48
- # Gemfile.lock
48
+ /Gemfile.lock
49
49
  # .ruby-version
50
50
  # .ruby-gemset
51
51
 
data/CHANGELOG.md CHANGED
@@ -56,6 +56,12 @@ Features:
56
56
 
57
57
  - Added preliminary support for Sideload backed by function instead of model association
58
58
 
59
+ ## 0.1.25 (04-12-2024)
60
+
61
+ Features:
62
+
63
+ - Added support to preload extra_fields for the main resource, replacing N+1 queries with a single query. This does not apply to sideloaded resources.
64
+
59
65
  <!-- ### [version (DD-MM-YYYY)] -->
60
66
  <!-- Breaking changes:-->
61
67
  <!-- Features:-->
@@ -25,6 +25,9 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "faraday", "~> 0.15"
26
26
  spec.add_development_dependency "kaminari", "~> 0.17"
27
27
  spec.add_development_dependency "bundler"
28
+ # dependency conflict introduced during runtime
29
+ # jar-dependencies is a default gem in JRuby and also a dependency in neo4j-ruby-driver
30
+ spec.add_development_dependency "jar-dependencies", "~> 0.4.1"
28
31
  spec.add_development_dependency "rake", "~> 10.0"
29
32
  spec.add_development_dependency "graphiti_spec_helpers", "1.0.beta.4"
30
33
  spec.add_development_dependency "standard"
@@ -0,0 +1,44 @@
1
+ module Graphiti::ActiveGraph::Concerns
2
+ module PathRelationships
3
+ def add_path_id_to_relationships!(params)
4
+ return params if path_relationships_updated?
5
+ detect_conflict(:id, @params[:id]&.to_s, attributes[:id]&.to_s)
6
+ path_map.each do |rel_name, path_value|
7
+ body_value = relationships.dig(rel_name, :attributes, :id)
8
+ if body_value
9
+ detect_conflict(rel_name, path_value&.to_s, body_value&.to_s)
10
+ else
11
+ update_params(params, rel_name, path_value)
12
+ update_realationships(rel_name, path_value)
13
+ end
14
+ end
15
+ path_relationships_updated!
16
+ params
17
+ end
18
+
19
+ private
20
+
21
+ def path_relationships_updated!
22
+ @path_relationships_updated = true
23
+ end
24
+
25
+ def path_relationships_updated?
26
+ @path_relationships_updated.present?
27
+ end
28
+
29
+ def update_params(params, rel_name, path_value)
30
+ params[:data] ||= {}
31
+ params[:data][:relationships] ||= {}
32
+ params[:data][:relationships][rel_name] = {
33
+ data: {
34
+ type: derive_resource_type(rel_name),
35
+ id: path_value
36
+ }
37
+ }
38
+ end
39
+
40
+ def update_realationships(rel_name, path_value)
41
+ relationships[rel_name] = { meta: {}, attributes: { id: path_value } }
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
1
  module Graphiti::ActiveGraph
2
- module Deserializer
2
+ class Deserializer < Graphiti::Deserializer
3
+ include Concerns::PathRelationships
4
+
3
5
  class Conflict < StandardError
4
6
  attr_reader :key, :path_value, :body_value
5
7
 
@@ -14,13 +16,33 @@ module Graphiti::ActiveGraph
14
16
  end
15
17
  end
16
18
 
17
- def initialize(payload, env=nil, model=nil, parent_map=nil)
19
+ def initialize(payload, env = nil, model = nil, parent_map = nil)
18
20
  super(payload)
19
21
 
20
22
  @params = payload
21
23
  @model = model
22
24
  @parent_map = parent_map || {}
23
25
  @env = env
26
+
27
+ return unless data.blank? && env && parsable_content?
28
+
29
+ raise ArgumentError, "JSON API payload must contain the 'data' key"
30
+ end
31
+
32
+ def process_relationship_datum(datum)
33
+ {
34
+ meta: {
35
+ jsonapi_type: datum[:type],
36
+ temp_id: datum[:'temp-id'],
37
+ method: datum[:method]&.to_sym
38
+ },
39
+ attributes: datum[:id] ? { id: datum[:id] } : {},
40
+ relationships: {}
41
+ }
42
+ end
43
+
44
+ def meta_params
45
+ data[:meta] || {}
24
46
  end
25
47
 
26
48
  def process_relationships(relationship_hash)
@@ -33,6 +55,10 @@ module Graphiti::ActiveGraph
33
55
  end
34
56
  end
35
57
 
58
+ def relationship?(name)
59
+ relationships[name.to_sym].present?
60
+ end
61
+
36
62
  # change empty relationship as `disassociate` hash so they will be removed
37
63
  def process_nil_relationship(name)
38
64
  attributes = {}
@@ -62,45 +88,6 @@ module Graphiti::ActiveGraph
62
88
  results
63
89
  end
64
90
 
65
- def add_path_id_to_relationships!(params)
66
- return params if path_relationships_updated?
67
- detect_conflict(:id, @params[:id]&.to_s, attributes[:id]&.to_s)
68
- path_map.each do |rel_name, path_value|
69
- body_value = relationships.dig(rel_name, :attributes, :id)
70
- if body_value
71
- detect_conflict(rel_name, path_value&.to_s, body_value&.to_s)
72
- else
73
- update_params(params, rel_name, path_value)
74
- update_realationships(rel_name, path_value)
75
- end
76
- end
77
- path_relationships_updated!
78
- params
79
- end
80
-
81
- def path_relationships_updated!
82
- @path_relationships_updated = true
83
- end
84
-
85
- def path_relationships_updated?
86
- @path_relationships_updated.present?
87
- end
88
-
89
- def update_params(params, rel_name, path_value)
90
- params[:data] ||= {}
91
- params[:data][:relationships] ||= {}
92
- params[:data][:relationships][rel_name] = {
93
- data: {
94
- type: derive_resource_type(rel_name),
95
- id: path_value
96
- }
97
- }
98
- end
99
-
100
- def update_realationships(rel_name, path_value)
101
- relationships[rel_name] = { meta: {}, attributes: { id: path_value } }
102
- end
103
-
104
91
  def path_map
105
92
  map = @params.select { |key, _| key =~ /_id$/ }.permit!.to_h
106
93
  map = filter_keys(map) { |key| key.gsub(/_id$/, '').to_sym }
@@ -127,6 +114,10 @@ module Graphiti::ActiveGraph
127
114
 
128
115
  private
129
116
 
117
+ def parsable_content?
118
+ true
119
+ end
120
+
130
121
  def derive_resource_type(rel_name)
131
122
  if @model.include?(ActiveGraph::Node)
132
123
  @model.associations[rel_name].target_class.model_name.plural.to_s
@@ -0,0 +1,52 @@
1
+ module Graphiti::ActiveGraph::Extensions::Grouping
2
+ class Params
3
+ attr_reader :params, :grouping_criteria_list, :resource_class
4
+ def initialize(params, resource_class)
5
+ @params = params
6
+ @grouping_criteria_list = params.fetch(:group_by, nil)&.split(',') || []
7
+ @resource_class = resource_class
8
+ end
9
+
10
+ def single_grouping_criteria?
11
+ grouping_criteria_list.size < 2
12
+ end
13
+
14
+ def grouping_criteria_on_attribute?
15
+ grouping_criteria_list.any? { |criteria| ends_with_attribute?(resource_class.model, criteria) }
16
+ end
17
+
18
+ def empty?
19
+ grouping_criteria_list.empty?
20
+ end
21
+
22
+ def ends_with_attribute?(model, criteria)
23
+ return false if criteria.blank?
24
+
25
+ last_segment_attribute?(model, criteria.split('.'))
26
+ end
27
+
28
+ private
29
+
30
+ def last_segment_attribute?(model, segments)
31
+ last_segment = segments.last
32
+ intermediate_model = traverse_to_last_associated_model(model, segments[0...-1])
33
+
34
+ intermediate_model && attribute?(intermediate_model, last_segment)
35
+ end
36
+
37
+ def traverse_to_last_associated_model(model, intermediate_segments)
38
+ intermediate_segments.each do |segment|
39
+ return false unless(model = associated_model(model, segment))
40
+ end
41
+ model
42
+ end
43
+
44
+ def attribute?(model, segment)
45
+ model.attribute_names.include?(segment)
46
+ end
47
+
48
+ def associated_model(model, segment)
49
+ model.associations[segment.to_sym]&.target_class
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ module Graphiti::ActiveGraph::Extensions
2
+ class QueryParams
3
+ attr_reader :params, :grouping_extra_params, :resource_class
4
+
5
+ def initialize(params, resource_class, grouping_extra_params: {})
6
+ @params = params
7
+ @resource_class = resource_class
8
+ @grouping_extra_params = grouping_extra_params
9
+ end
10
+
11
+ def group_by
12
+ Grouping::Params.new(params, resource_class)
13
+ end
14
+
15
+ def group_by_params
16
+ group_by_params_hash unless group_by.empty?
17
+ end
18
+
19
+ def group_by_params_hash
20
+ { group_by: group_by.grouping_criteria_list }.merge(grouping_extra_params)
21
+ end
22
+
23
+ def extra_field?(type, name)
24
+ params.dig(:extra_fields, type)&.include?(name.to_s)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module Graphiti::ActiveGraph
2
+ module RequestValidators
3
+ module Validator
4
+ def deserialized_payload
5
+ @deserialized_payload ||= Graphiti::ActiveGraph::Deserializer.new(@params)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -23,6 +23,10 @@ module Graphiti
23
23
 
24
24
  def guard_nil_id!(params)
25
25
  end
26
+
27
+ def extra_attribute?(name)
28
+ extra_attributes.has_key?(name)
29
+ end
26
30
  end
27
31
 
28
32
  module ResourceInstanceMethods
@@ -30,6 +34,10 @@ module Graphiti
30
34
  self.class.relation_resource?
31
35
  end
32
36
 
37
+ def extra_attribute?(name)
38
+ self.class.extra_attribute?(name)
39
+ end
40
+
33
41
  def sideload_name_arr(query)
34
42
  query.sideloads.keys.map(&:to_sym)
35
43
  end
@@ -1,5 +1,7 @@
1
1
  module Graphiti::ActiveGraph
2
2
  module SideloadResolve
3
+ PRELOAD_METHOD_PREFIX = 'preload_'.freeze
4
+
3
5
  def initialize(object, resource, query, opts = {})
4
6
  @object = object
5
7
  @resource = resource
@@ -15,5 +17,40 @@ module Graphiti::ActiveGraph
15
17
 
16
18
  def resolve_sideloads(parents)
17
19
  end
20
+
21
+ def resolve
22
+ super.tap { |results| preload_extra_fields(results) }
23
+ end
24
+
25
+ private
26
+
27
+ def preload_extra_fields(results)
28
+ requested_extra_fields.each do |extra_field_name|
29
+ next unless preload_extra_field?(extra_field_name)
30
+
31
+ result_map = fetch_preloaded_data(extra_field_name, results)
32
+ assign_preloaded_data(results, extra_field_name, result_map)
33
+ end
34
+ end
35
+
36
+ def requested_extra_fields
37
+ @query.extra_fields[@resource.type] || []
38
+ end
39
+
40
+ def fetch_preloaded_data(extra_field_name, results)
41
+ @resource.model.public_send(default_preload_method(extra_field_name), results.pluck(:id))
42
+ end
43
+
44
+ def assign_preloaded_data(results, extra_field_name, result_map)
45
+ results.each { |r| r.public_send("#{extra_field_name}=", result_map[r.id]) }
46
+ end
47
+
48
+ def preload_extra_field?(extra_field_name)
49
+ @resource.extra_attribute?(extra_field_name) && @resource.model.respond_to?(default_preload_method(extra_field_name))
50
+ end
51
+
52
+ def default_preload_method(extra_field_name)
53
+ "#{PRELOAD_METHOD_PREFIX}#{extra_field_name}"
54
+ end
18
55
  end
19
56
  end
@@ -1,5 +1,5 @@
1
1
  module Graphiti
2
2
  module ActiveGraph
3
- VERSION = '0.1.24'
3
+ VERSION = '0.1.26'
4
4
  end
5
5
  end
@@ -27,7 +27,6 @@ Graphiti::Scoping::Filter.prepend Graphiti::ActiveGraph::Scoping::Filter
27
27
  Graphiti::Util::SerializerRelationship.prepend Graphiti::ActiveGraph::Util::SerializerRelationship
28
28
  Graphiti::Util::SerializerAttribute.prepend Graphiti::ActiveGraph::Util::SerializerAttribute
29
29
  Graphiti::Util::RelationshipPayload.prepend Graphiti::ActiveGraph::Util::RelationshipPayload
30
- Graphiti::Deserializer.prepend Graphiti::ActiveGraph::Deserializer
31
30
  Graphiti::Query.prepend Graphiti::ActiveGraph::Query
32
31
  Graphiti::Resource.prepend Graphiti::ActiveGraph::ResourceInstanceMethods
33
32
  Graphiti::Resource.extend Graphiti::ActiveGraph::Resource
@@ -35,6 +34,7 @@ Graphiti::ResourceProxy.prepend Graphiti::ActiveGraph::ResourceProxy
35
34
  Graphiti::Runner.prepend Graphiti::ActiveGraph::Runner
36
35
  Graphiti::Scope.prepend Graphiti::ActiveGraph::SideloadResolve
37
36
  Graphiti::Configuration.include Graphiti::SidepostConfiguration
37
+ Graphiti::RequestValidators::Validator.prepend Graphiti::ActiveGraph::RequestValidators::Validator
38
38
 
39
39
  # JSONAPI extensions
40
40
  JSONAPI::Serializable::Resource.prepend Graphiti::ActiveGraph::JsonapiExt::Serializable::ResourceExt
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti-activegraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.24
4
+ version: 0.1.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hardik Joshi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-18 00:00:00.000000000 Z
11
+ date: 2025-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 0.4.1
117
+ name: jar-dependencies
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.4.1
111
125
  - !ruby/object:Gem::Dependency
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
@@ -199,9 +213,13 @@ files:
199
213
  - lib/graphiti/active_graph/adapters/active_graph/has_one_sideload.rb
200
214
  - lib/graphiti/active_graph/adapters/active_graph/polymorphic_belongs_to.rb
201
215
  - lib/graphiti/active_graph/adapters/active_graph/sideload.rb
216
+ - lib/graphiti/active_graph/concerns/path_relationships.rb
202
217
  - lib/graphiti/active_graph/deserializer.rb
218
+ - lib/graphiti/active_graph/extensions/grouping/params.rb
219
+ - lib/graphiti/active_graph/extensions/query_params.rb
203
220
  - lib/graphiti/active_graph/jsonapi_ext/serializable/resource_ext.rb
204
221
  - lib/graphiti/active_graph/query.rb
222
+ - lib/graphiti/active_graph/request_validators/validator.rb
205
223
  - lib/graphiti/active_graph/resource.rb
206
224
  - lib/graphiti/active_graph/resource/interface.rb
207
225
  - lib/graphiti/active_graph/resource/persistence.rb