graphiti-activegraph 0.1.24 → 0.1.26

Sign up to get free protection for your applications and to get access to all the features.
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