graphiti-activegraph 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 788e448fa1ea2a64247650cb77402a7518ae543789ea8259240fdb60f7469340
4
+ data.tar.gz: 899272d03b3f1b78fa3567ae6fd02a0bb9386c39544ae8e2ff649c6de022b8ef
5
+ SHA512:
6
+ metadata.gz: 4c071b303b06a2d8bd78a601b212a2f9ca546be2012fb22f064eb8389232824598d0b8d2366df7e4496d3325360aa5071b8188f703e5ca2b72c7dfd9570163e7
7
+ data.tar.gz: c4a43e4604cb2f283a79f8bc42e9c43648f9f3ae6af52981bbec3365a1ba9b1f298d3a45ce0842dd09b0e1536d8c5573da3b0f6c3db73f07c2baa282176b1896
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "zeitwerk"
6
+
7
+ group :test do
8
+ gem "pry"
9
+ gem "appraisal"
10
+ gem "guard"
11
+ gem "guard-rspec"
12
+ end
@@ -0,0 +1,33 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "graphiti/active_graph/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "graphiti-activegraph"
7
+ spec.version = Graphiti::ActiveGraph::VERSION
8
+ spec.authors = ["Hardik Joshi"]
9
+ spec.email = ["hardikjoshi1991@gmail.com"]
10
+
11
+ spec.summary = "Easily build jsonapi.org-compatible APIs for GraphDB"
12
+ spec.homepage = "https://github.com/mrhardikjoshi/graphiti-activegraph"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+ spec.required_ruby_version = "~> 2.3"
20
+
21
+ spec.add_dependency "graphiti", "~> 1.2.0"
22
+ spec.add_dependency "concurrent-ruby", "~> 1.0"
23
+ spec.add_dependency "activesupport", ">= 4.1"
24
+ spec.add_dependency "zeitwerk"
25
+
26
+ spec.add_development_dependency "faraday", "~> 0.15"
27
+ spec.add_development_dependency "kaminari", "~> 0.17"
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "activemodel", ">= 4.1"
31
+ spec.add_development_dependency "graphiti_spec_helpers", "1.0.beta.4"
32
+ spec.add_development_dependency "standard"
33
+ end
@@ -0,0 +1,32 @@
1
+ require 'graphiti'
2
+ require 'zeitwerk'
3
+
4
+ # Workaround for missing zeitwerk support as of jruby-9.2.13.0
5
+ module Graphiti
6
+ module ActiveGraph
7
+ module Scoping
8
+ end
9
+ module Adapters
10
+ end
11
+ module Util
12
+ end
13
+ end
14
+ end
15
+ # End workaround
16
+
17
+ loader = Zeitwerk::Loader.for_gem
18
+ loader.inflector.inflect 'version' => 'VERSION'
19
+ loader.ignore(File.expand_path('graphiti-activegraph.rb', __dir__))
20
+ loader.setup
21
+
22
+ Graphiti::Resource::Persistence.prepend Graphiti::ActiveGraph::Resource::Persistence
23
+ Graphiti::Scoping::Filter.prepend Graphiti::ActiveGraph::Scoping::Filter
24
+ Graphiti::Scoping::Filterable.prepend Graphiti::ActiveGraph::Scoping::Filterable
25
+ Graphiti::Util::SerializerRelationship.prepend Graphiti::ActiveGraph::Util::SerializerRelationship
26
+ Graphiti::Deserializer.prepend Graphiti::ActiveGraph::Deserializer
27
+ Graphiti::Query.prepend Graphiti::ActiveGraph::Query
28
+ Graphiti::Resource.prepend Graphiti::ActiveGraph::ResourceInstanceMethods
29
+ Graphiti::Resource.extend Graphiti::ActiveGraph::Resource
30
+ Graphiti::ResourceProxy.prepend Graphiti::ActiveGraph::ResourceProxy
31
+ Graphiti::Runner.prepend Graphiti::ActiveGraph::Runner
32
+ Graphiti::Scope.prepend Graphiti::ActiveGraph::SideloadResolve
@@ -0,0 +1,169 @@
1
+ module Graphiti::ActiveGraph
2
+ module Adapters
3
+ class ActiveGraph < ::Graphiti::Adapters::Abstract
4
+ def self.sideloading_classes
5
+ {
6
+ has_many: Graphiti::ActiveGraph::Adapters::ActiveGraph::HasManySideload,
7
+ has_one: Graphiti::ActiveGraph::Adapters::ActiveGraph::HasOneSideload
8
+ }
9
+ end
10
+
11
+ def base_scope(model)
12
+ model.all
13
+ end
14
+
15
+ def assign_attributes(model_instance, attributes)
16
+ model_instance.before_assign_resource_attr if model_instance.respond_to?(:before_assign_resource_attr)
17
+
18
+ attributes.each_pair do |k, v|
19
+ model_instance.send(:"#{k}=", v) if model_instance.respond_to?(:"#{k}=")
20
+ end
21
+ end
22
+
23
+ def paginate(scope, current_page, per_page)
24
+ scope.skip((current_page - 1) * per_page).limit(per_page)
25
+ end
26
+
27
+ def transaction(_model_class)
28
+ ::ActiveGraph::Base.transaction do
29
+ yield
30
+ end
31
+ end
32
+
33
+ def order(scope, attribute, direction, extra_field = false)
34
+ if extra_field
35
+ scope.query.order("#{attribute} #{direction}").proxy_as(scope.model, scope.identity)
36
+ else
37
+ scope.order(attribute => direction)
38
+ end
39
+ end
40
+
41
+ def count(scope, _attr)
42
+ scope.skip(0).limit(nil).count
43
+ end
44
+
45
+ def save(model_instance)
46
+ model_instance.save
47
+ model_instance
48
+ end
49
+
50
+ def destroy(model_instance)
51
+ model_instance.destroy
52
+ model_instance
53
+ end
54
+
55
+ def resolve(scope)
56
+ scope.to_a
57
+ end
58
+
59
+ # def associate_all(parent, children, association_name, association_type)
60
+ # if association_type == :has_many
61
+ # if !parent.send(:"#{association_name}").present?
62
+ # parent.send(:"#{association_name}=", children)
63
+ # else
64
+ # parent.send(:"#{association_name}") << children
65
+ # end
66
+ # else
67
+ # parent.send(:"#{association_name}=", children)
68
+ # end
69
+ # end
70
+
71
+ def process_belongs_to(persistence, attributes)
72
+ []
73
+ end
74
+
75
+ def process_has_many(persistence, caller_model)
76
+ []
77
+ end
78
+
79
+ def persistence_attributes(persistence, attributes)
80
+ rel_attrs = {}
81
+ @persistence = persistence
82
+
83
+ del_empty_rels(rel_attrs) unless resource.relation_resource?
84
+ attributes_for_has_one(rel_attrs)
85
+ attributes_for_has_many(rel_attrs)
86
+
87
+ attributes.merge rel_attrs
88
+ end
89
+
90
+ def associate(parent, child, association_name, type)
91
+ end
92
+
93
+ def disassociate(parent, child, association_name, type)
94
+ parent.send(:"#{association_name}=", nil)
95
+ end
96
+
97
+ def clear_active_connections!
98
+ end
99
+
100
+ def filter_eq(scope, attribute, value)
101
+ scope.where(attribute => value)
102
+ end
103
+ alias filter_integer_eq filter_eq
104
+ alias filter_float_eq filter_eq
105
+ alias filter_big_decimal_eq filter_eq
106
+ alias filter_date_eq filter_eq
107
+ alias filter_boolean_eq filter_eq
108
+ alias filter_uuid_eq filter_eq
109
+ alias filter_enum_eq filter_eq
110
+
111
+ def filter_not_eq(scope, attribute, value)
112
+ scope.where_not(attribute => value)
113
+ end
114
+ alias filter_integer_not_eq filter_not_eq
115
+ alias filter_float_not_eq filter_not_eq
116
+ alias filter_big_decimal_not_eq filter_not_eq
117
+ alias filter_date_not_eq filter_not_eq
118
+ alias filter_boolean_not_eq filter_not_eq
119
+ alias filter_uuid_not_eq filter_not_eq
120
+ alias filter_enum_not_eq filter_not_eq
121
+
122
+ private
123
+
124
+ def del_empty_rels(rel_attrs)
125
+ relationships = @persistence.instance_variable_get(:@relationships)
126
+ relationships.each do |rel_name, rel_data|
127
+ rel_attrs[rel_name] = nil if rel_data.blank?
128
+ end
129
+ end
130
+
131
+ def attributes_for_has_one(rel_attrs)
132
+ @persistence.iterate(only: [:has_one]) do |x|
133
+ process_relationship_attrs(x, rel_attrs, false)
134
+ end
135
+ end
136
+
137
+ def attributes_for_has_many(rel_attrs)
138
+ @persistence.iterate(only: [:has_many]) do |x|
139
+ process_relationship_attrs(x, rel_attrs, true)
140
+ end
141
+ end
142
+
143
+ def process_relationship_attrs(x, rel_attrs, assign_multiple)
144
+ x[:object] = x[:resource]
145
+ .persist_with_relationships(x[:meta], x[:attributes], x[:relationships], self, x[:foreign_key])
146
+
147
+ resource = @persistence.instance_variable_get(:@resource)
148
+ meta = @persistence.instance_variable_get(:@meta)
149
+ # Relationship start/end nodes cannot be changed once persisted
150
+ unless meta[:method] == :update && resource.relation_resource?
151
+ if assign_multiple
152
+ rel_attrs[x[:foreign_key]] ||= []
153
+ rel_attrs[x[:foreign_key]] << resource_association_value(x)
154
+ else
155
+ rel_attrs[x[:foreign_key]] = resource_association_value(x)
156
+ end
157
+ end
158
+ end
159
+
160
+ def resource_association_value(rel_map)
161
+ if [:destroy, :disassociate].include?(rel_map[:meta][:method]) || rel_map[:attributes].blank?
162
+ nil
163
+ else
164
+ rel_map[:object]
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,15 @@
1
+ class Graphiti::ActiveGraph::Adapters::ActiveGraph::HasManySideload < Graphiti::Sideload::HasMany
2
+ include Graphiti::ActiveGraph::Adapters::ActiveGraph::Sideload
3
+
4
+ def default_base_scope
5
+ resource_class.model.all
6
+ end
7
+
8
+ def infer_foreign_key
9
+ association_name.to_sym
10
+ end
11
+
12
+ def default_value_when_empty
13
+ []
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ class Graphiti::ActiveGraph::Adapters::ActiveGraph::HasOneSideload < Graphiti::Sideload::HasOne
2
+ include Graphiti::ActiveGraph::Adapters::ActiveGraph::Sideload
3
+
4
+ def default_base_scope
5
+ resource_class.model.all
6
+ end
7
+
8
+ def infer_foreign_key
9
+ association_name.to_sym
10
+ end
11
+
12
+ def default_value_when_empty
13
+ nil
14
+ end
15
+ end
@@ -0,0 +1,68 @@
1
+ module Graphiti::ActiveGraph::Adapters::ActiveGraph::Sideload
2
+ def self.included(base)
3
+ base.class_eval do
4
+ class_attribute :sideload_scope
5
+ end
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def sideload_scope(&blk)
11
+ self.sideload_scope = blk
12
+ end
13
+ end
14
+
15
+ # def base_scope
16
+ # if @base_scope
17
+ # @base_scope.respond_to?(:call) ? @base_scope.call : @base_scope
18
+ # else
19
+ # resource.base_scope
20
+ # end
21
+ # end
22
+
23
+ # def load(parents, query, graph_parent)
24
+ # params, opts, proxy = nil, nil, nil
25
+
26
+ # with_error_handling Errors::SideloadParamsError do
27
+ # params = load_params(parents, query)
28
+ # params_proc&.call(params, parents, context)
29
+ # return [] if blank_query?(params)
30
+ # opts = load_options(parents, query)
31
+ # opts[:sideload] = self
32
+ # opts[:parent] = graph_parent
33
+ # end
34
+
35
+ # with_error_handling(Errors::SideloadQueryBuildingError) do
36
+ # proxy = resource.class._all(params, opts, base_scope)
37
+ # pre_load_proc&.call(proxy, parents)
38
+ # end
39
+
40
+ # proxy.to_a
41
+ # end
42
+
43
+ # def load(parents, _, _)
44
+ # child_arr = []
45
+ # @child_map = resource_with_parent_assoc(parents).each_with_object({}) do |arr, hash|
46
+ # child_obj = arr.first
47
+ # parent_uuid = arr.last
48
+ # hash[parent_uuid] ||= []
49
+ # hash[parent_uuid] << child_obj
50
+ # child_arr << child_obj
51
+ # end
52
+
53
+ # fire_assign(parents, child_arr)
54
+ # child_arr
55
+ # end
56
+
57
+ # def resource_with_parent_assoc(parents)
58
+ # parent_ids = parents.pluck(:id)
59
+ # proxy = parent_resource_class.model.as(:p).where(id: parent_ids)
60
+ # scope = self.class.sideload_scope
61
+
62
+ # if scope.present?
63
+ # scope.call(proxy)
64
+ # else
65
+ # proxy.send(association_name, :children)
66
+ # end.query.pluck(:children, p: :neo_id)
67
+ # end
68
+ end
@@ -0,0 +1,132 @@
1
+ module Graphiti::ActiveGraph
2
+ module Deserializer
3
+ class Conflict < StandardError
4
+ attr_reader :key, :path_value, :body_value
5
+
6
+ def initialize(key, path_value, body_value)
7
+ @key = key
8
+ @path_value = path_value
9
+ @body_value = body_value
10
+ end
11
+
12
+ def message
13
+ "Path parameter #{key} with value '#{path_value}' conflicts with payload value '#{body_value}'"
14
+ end
15
+ end
16
+
17
+ def initialize(payload, env=nil, model=nil, parent_map=nil)
18
+ super(payload)
19
+
20
+ if data.blank? && env && JsonApiNonParsableMiddleware.parsable_content?(env)
21
+ raise ArgumentError, "JSON API payload must contain the 'data' key"
22
+ end
23
+
24
+ @params = payload
25
+ @model = model
26
+ @parent_map = parent_map || {}
27
+ @env = env
28
+ end
29
+
30
+ def process_relationships(relationship_hash)
31
+ {}.tap do |hash|
32
+ relationship_hash.each_pair do |name, relationship_payload|
33
+ name = name.to_sym
34
+ data_payload = relationship_payload[:data]
35
+ hash[name] = data_payload.nil? ? process_nil_relationship(name) : process_relationship(relationship_payload[:data])
36
+ end
37
+ end
38
+ end
39
+
40
+ # change empty relationship as `disassociate` hash so they will be removed
41
+ def process_nil_relationship(name)
42
+ attributes = {}
43
+ method_name = :disassociate
44
+
45
+ {
46
+ meta: {
47
+ jsonapi_type: name.to_sym,
48
+ method: method_name
49
+ },
50
+ attributes: attributes,
51
+ relationships: {}
52
+ }
53
+ end
54
+
55
+ def meta(action: nil)
56
+ results = super
57
+ return results if action.present? || @env.nil?
58
+
59
+ action = case @env['REQUEST_METHOD']
60
+ when 'POST' then :create
61
+ when 'PUT', 'PATCH' then :update
62
+ when 'DELETE' then :destroy
63
+ end
64
+
65
+ results[:method] = action
66
+ results
67
+ end
68
+
69
+ def add_path_id_to_relationships!(params)
70
+ return params if path_relationships_updated?
71
+ detect_conflict(:id, @params[:id], attributes[:id])
72
+ path_map.each do |rel_name, path_value|
73
+ body_value = relationships.dig(rel_name, :attributes, :id)
74
+ if body_value
75
+ detect_conflict(rel_name, path_value.to_i, body_value&.to_i)
76
+ else
77
+ update_params(params, rel_name, path_value)
78
+ update_realationships(rel_name, path_value)
79
+ end
80
+ end
81
+ path_relationships_updated!
82
+ params
83
+ end
84
+
85
+ def path_relationships_updated!
86
+ @path_relationships_updated = true
87
+ end
88
+
89
+ def path_relationships_updated?
90
+ @path_relationships_updated.present?
91
+ end
92
+
93
+ def update_params(params, rel_name, path_value)
94
+ params[:data] ||= {}
95
+ params[:data][:relationships] ||= {}
96
+ params[:data][:relationships][rel_name] = {
97
+ data: {
98
+ type: rel_name.to_s,
99
+ id: path_value.to_i
100
+ }
101
+ }
102
+ end
103
+
104
+ def update_realationships(rel_name, path_value)
105
+ relationships[rel_name] = { meta: {}, attributes: { id: path_value.to_i } }
106
+ end
107
+
108
+ def path_map
109
+ map = @params.select { |key, _| key =~ /_id$/ }.permit!.to_h
110
+ map = filter_keys(map) { |key| key.gsub(/_id$/, '').to_sym }
111
+ map = filter_keys(map) { |key| @parent_map[key] || key }
112
+ map = filter_keys_presence(map) if @model < ActiveGraph::Node
113
+ map
114
+ end
115
+
116
+ def filter_keys_presence(map)
117
+ filter_keys(map) { |key| presence(key) || presence(key.to_s.pluralize.to_sym) }
118
+ end
119
+
120
+ def filter_keys(map)
121
+ map.map { |key, v| [yield(key), v] }.select(&:first).to_h
122
+ end
123
+
124
+ def presence(key)
125
+ key if @model.associations.include?(key)
126
+ end
127
+
128
+ def detect_conflict(key, path_value, body_value)
129
+ raise Conflict.new(key, path_value, body_value) if body_value && body_value != path_value
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,54 @@
1
+ module Graphiti::ActiveGraph
2
+ module Query
3
+ attr_reader :deep_sort
4
+
5
+ def filters
6
+ @filters ||= begin
7
+ {}.tap do |hash|
8
+ (@params[:filter] || {}).each_pair do |name, value|
9
+ name = name.to_sym
10
+
11
+ if legacy_nested?(name)
12
+ value.keys.each do |key|
13
+ filter_name = key.to_sym
14
+ filter_value = value[key]
15
+
16
+ if @resource.get_attr!(filter_name, :filterable, request: true)
17
+ hash[filter_name] = filter_value
18
+ end
19
+ end
20
+ elsif nested?(name)
21
+ name = name.to_s.split(".").last.to_sym
22
+ validate!(name, :filterable)
23
+ hash[name] = value
24
+ else
25
+ hash[name] = value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def sorts
33
+ return super unless (sort = params[:sort]) && sort.include?('.')
34
+
35
+ @deep_sort = sort_criteria(sort)
36
+ []
37
+ end
38
+
39
+ def parse_sort_criteria_hash(hash)
40
+ hash.map { |key, value| [key.to_s.split('.').map(&:to_sym), value] }.to_h
41
+ end
42
+
43
+ private
44
+
45
+ def sort_criteria(sort)
46
+ sort.split(',').map(&method(:sort_hash)).map(&method(:parse_sort_criteria_hash))
47
+ end
48
+
49
+ def update_include_hash(authorized_include_param)
50
+ @include_hash = authorized_include_param
51
+ @sideloads = nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,72 @@
1
+ module Graphiti
2
+ module ActiveGraph
3
+ module Resource
4
+ def relation_resource?
5
+ config[:relation_resource] || false
6
+ end
7
+
8
+ def relationship_resource=(value)
9
+ config[:relation_resource] = value
10
+ end
11
+
12
+ def with_preloaded_obj(obj, params)
13
+ id = params[:data].try(:[], :id) || params.delete(:id)
14
+ params[:filter] ||= {}
15
+ params[:filter][:id] = id if id
16
+
17
+ validate!(params)
18
+ runner = ::Graphiti::Runner.new(self, params)
19
+ runner.proxy(nil, single: true, raise_on_missing: false, preloaded: obj, bypass_required_filters: true)
20
+ end
21
+
22
+ def all_with_preloaded(obj_arr, params)
23
+ validate!(params)
24
+
25
+ runner = ::Graphiti::Runner.new(self, params)
26
+ runner.proxy(nil, raise_on_missing: false, preloaded: obj_arr)
27
+ end
28
+ end
29
+
30
+ module ResourceInstanceMethods
31
+ def relation_resource?
32
+ self.class.relation_resource?
33
+ end
34
+
35
+ def before_resolve(scope, query)
36
+ if query.params[:group_by].present?
37
+ scope.collect { |gp| query.params[:group_class].new(name: gp['name']) }
38
+ else
39
+ scope
40
+ end
41
+ # scope.with_associations(sideload_name_arr(query))
42
+ end
43
+
44
+ def sideload_name_arr(query)
45
+ query.sideloads.keys.map(&:to_sym)
46
+ end
47
+
48
+ def typecast(name, value, flag)
49
+ att = get_attr!(name, flag, request: true)
50
+
51
+ # in case of attribute is not declared on resource
52
+ # do not throw error, return original value without typecast
53
+ return value unless att
54
+
55
+ type_name = att[:type]
56
+ if flag == :filterable
57
+ type_name = filters[name][:type]
58
+ end
59
+ type = Graphiti::Types[type_name]
60
+ return if value.nil? && type[:kind] != "array"
61
+ begin
62
+ flag = :read if flag == :readable
63
+ flag = :write if flag == :writable
64
+ flag = :params if [:sortable, :filterable].include?(flag)
65
+ type[flag][value]
66
+ rescue => e
67
+ raise Errors::TypecastFailed.new(self, name, value, e, type_name)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ module Graphiti::ActiveGraph
2
+ module Resource
3
+ module Persistence
4
+ def update(update_params, meta = nil)
5
+ model_instance = nil
6
+ id = update_params[:id]
7
+ update_params = update_params.except(:id)
8
+
9
+ run_callbacks :persistence, :update, update_params, meta do
10
+ run_callbacks :attributes, :update, update_params, meta do |params|
11
+ model_instance = id ? model.find(id) : self.class._find(id: id).data
12
+ call_with_meta(:assign_attributes, model_instance, params, meta)
13
+ model_instance
14
+ end
15
+
16
+ run_callbacks :save, :update, model_instance, meta do
17
+ model_instance = call_with_meta(:save, model_instance, meta)
18
+ end
19
+ end
20
+
21
+ model_instance
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,84 @@
1
+ module Graphiti::ActiveGraph
2
+ module ResourceProxy
3
+ include Graphiti::ActiveGraph::SideloadResolve
4
+ attr_reader :preloaded
5
+
6
+ def initialize(resource, scope, query,
7
+ payload: nil,
8
+ single: false,
9
+ raise_on_missing: false,
10
+ preloaded: false)
11
+ @resource = resource
12
+ @scope = scope
13
+ @query = query
14
+ @payload = payload
15
+ @single = single
16
+ @raise_on_missing = raise_on_missing
17
+ @preloaded = preloaded
18
+ end
19
+
20
+ def data
21
+ return @data if @data
22
+
23
+ return super unless @preloaded
24
+
25
+ resolve_sideloads(@preloaded)
26
+ @single ? data_for_preloaded_record : data_for_preloaded_records
27
+ end
28
+
29
+ def data_for_preloaded_record
30
+ @preloaded = @preloaded.is_a?(Array) ? @preloaded[0] : @preloaded
31
+ @resource.decorate_record(@preloaded)
32
+ @data = @preloaded
33
+ end
34
+
35
+ def data_for_preloaded_records
36
+ @preloaded.each do |r|
37
+ @resource.decorate_record(r)
38
+ end
39
+ @data = @preloaded
40
+ end
41
+
42
+ def stats
43
+ @stats ||= if @query.hash[:stats] && !resource.relation_resource?
44
+ payload = ::Graphiti::Stats::Payload.new @resource,
45
+ @query,
46
+ @scope.unpaginated_object,
47
+ data
48
+ payload.generate
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ def save(action: :create)
55
+ # TODO: remove this. Only used for persisting many-to-many with AR
56
+ # (see activerecord adapter)
57
+ original = Graphiti.context[:namespace]
58
+ begin
59
+ Graphiti.context[:namespace] = action
60
+ ::Graphiti::RequestValidator.new(@resource, @payload.params).validate!
61
+ validator = persist {
62
+ @resource.persist_with_relationships \
63
+ @payload.meta(action: action),
64
+ @payload.attributes,
65
+ @payload.relationships
66
+ }
67
+ ensure
68
+ Graphiti.context[:namespace] = original
69
+ end
70
+ @data, success = validator.to_a
71
+
72
+ if success && !resource.relation_resource?
73
+ # If the context namespace is `update` or `create`, certain
74
+ # adapters will cause N+1 validation calls, so lets explicitly
75
+ # switch to a lookup context.
76
+ Graphiti.with_context(Graphiti.context[:object], :show) do
77
+ @scope.resolve_sideloads([@data])
78
+ end
79
+ end
80
+
81
+ success
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ module Graphiti::ActiveGraph
2
+ module Runner
3
+ def initialize(resource_class, params, query = nil, action = nil)
4
+ @resource_class = resource_class
5
+ @params = params
6
+ @query = query
7
+ @action = action
8
+
9
+ validator = ::Graphiti::RequestValidator.new(jsonapi_resource, params)
10
+
11
+ validator.validate! unless params[:skip_render_val]
12
+
13
+ @deserialized_payload = validator.deserialized_payload
14
+ end
15
+
16
+ def proxy(base = nil, opts = {})
17
+ base ||= jsonapi_resource.base_scope
18
+ scope_opts = opts.slice :sideload_parent_length,
19
+ :default_paginate,
20
+ :after_resolve,
21
+ :sideload,
22
+ :parent,
23
+ :params
24
+ scope = jsonapi_scope(base, scope_opts) unless jsonapi_resource.relation_resource?
25
+ preloaded = opts[:preloaded] || (jsonapi_resource.relation_resource? && jsonapi_resource.base_scope)
26
+ options = { payload: deserialized_payload,
27
+ single: opts[:single],
28
+ raise_on_missing: opts[:raise_on_missing],
29
+ preloaded: preloaded
30
+ }
31
+ ::Graphiti::ResourceProxy.new jsonapi_resource,
32
+ scope,
33
+ query,
34
+ options
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,38 @@
1
+ module Graphiti::ActiveGraph
2
+ module Scoping
3
+ module Filter
4
+ def each_filter
5
+ filter_param.each_pair do |param_name, param_value|
6
+ filter = find_filter!(param_name)
7
+
8
+ normalize_param(filter, param_value).each do |operator, value|
9
+ operator = operator.to_s.gsub("!", "not_").to_sym
10
+
11
+ # dynamic filters errors for validating and typecasting value below
12
+ # so they are skipped here without validation or typecast
13
+ filter_map = filter.values[0]
14
+ if filter_map[:dynamic_filter]
15
+ yield filter, operator, value
16
+ next
17
+ end
18
+ validate_operator(filter, operator)
19
+
20
+ type = ::Graphiti::Types[filter_map[:type]]
21
+ unless type[:canonical_name] == :hash || !value.is_a?(String)
22
+ value = parse_string_value(filter_map, value)
23
+ end
24
+
25
+ check_deny_empty_filters!(resource, filter, value)
26
+ value = parse_string_null(filter_map, value)
27
+ validate_singular(resource, filter, value)
28
+ value = coerce_types(filter_map, param_name.to_sym, value)
29
+ validate_allowlist(resource, filter, value)
30
+ validate_denylist(resource, filter, value)
31
+ value = value[0] if filter_map[:single]
32
+ yield filter, operator, value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module Graphiti::ActiveGraph
2
+ module Scoping
3
+ module Filterable
4
+ def find_filter!(name)
5
+ val = resource.filters[name] || {
6
+ operators: {}, type: :string, single: false, dynamic_filter: true
7
+ }
8
+ {name => val}
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Graphiti::ActiveGraph
2
+ module SideloadResolve
3
+ def resolve_sideloads(parents)
4
+ @query.sideloads.each_pair do |name, q|
5
+ sideload = @resource.class.sideload(name)
6
+ next if sideload.nil? || sideload.shared_remote?
7
+
8
+ if sideload.assign_each_proc
9
+ Array.wrap(parents).each do |parent|
10
+ children = sideload.assign_each_proc.call(parent) || sideload.default_value_when_empty
11
+
12
+ # currently there is no possible way to assign association on activegraph without triggering save
13
+ # https://github.com/neo4jrb/activegraph/issues/1445
14
+ # as a workaround we are using instance variable here to store and retrive associations
15
+ # once above issue is fixed use that fix to assign the association here
16
+ # and also remove 1) this code and 2) SerializerRelationship#data_proc
17
+ parent.instance_variable_set("@graphiti_render_#{name}", { data: children })
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Graphiti::ActiveGraph
2
+ module Util
3
+ module SerializerRelationship
4
+ def data_proc
5
+ sideload_ref = @sideload
6
+ ->(_) {
7
+ # use custom assigned sideload if it is specified via "assign_each_proc"
8
+ # otherwise retrieve sideload using normal getter on parent object
9
+ custom_assigned_sideload = @object.instance_variable_get("@graphiti_render_#{sideload_ref.association_name}")
10
+ records = if custom_assigned_sideload.blank?
11
+ @object.public_send(sideload_ref.association_name)
12
+ else
13
+ custom_assigned_sideload[:data]
14
+ end
15
+
16
+ if records
17
+ if records.respond_to?(:to_ary)
18
+ records.each { |r| sideload_ref.resource.decorate_record(r) }
19
+ else
20
+ sideload_ref.resource.decorate_record(records)
21
+ end
22
+
23
+ records
24
+ end
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Graphiti
2
+ module ActiveGraph
3
+ VERSION = '0.1.2'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,217 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphiti-activegraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Hardik Joshi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphiti
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '4.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '4.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: zeitwerk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.15'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.15'
83
+ - !ruby/object:Gem::Dependency
84
+ name: kaminari
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.17'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: activemodel
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '4.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '4.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: graphiti_spec_helpers
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 1.0.beta.4
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 1.0.beta.4
153
+ - !ruby/object:Gem::Dependency
154
+ name: standard
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description:
168
+ email:
169
+ - hardikjoshi1991@gmail.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".gitignore"
175
+ - Gemfile
176
+ - graphiti-activegraph.gemspec
177
+ - lib/graphiti-activegraph.rb
178
+ - lib/graphiti/active_graph/adapters/active_graph.rb
179
+ - lib/graphiti/active_graph/adapters/active_graph/has_many_sideload.rb
180
+ - lib/graphiti/active_graph/adapters/active_graph/has_one_sideload.rb
181
+ - lib/graphiti/active_graph/adapters/active_graph/sideload.rb
182
+ - lib/graphiti/active_graph/deserializer.rb
183
+ - lib/graphiti/active_graph/query.rb
184
+ - lib/graphiti/active_graph/resource.rb
185
+ - lib/graphiti/active_graph/resource/persistence.rb
186
+ - lib/graphiti/active_graph/resource_proxy.rb
187
+ - lib/graphiti/active_graph/runner.rb
188
+ - lib/graphiti/active_graph/scoping/filter.rb
189
+ - lib/graphiti/active_graph/scoping/filterable.rb
190
+ - lib/graphiti/active_graph/sideload_resolve.rb
191
+ - lib/graphiti/active_graph/util/serializer_relationship.rb
192
+ - lib/graphiti/active_graph/version.rb
193
+ homepage: https://github.com/mrhardikjoshi/graphiti-activegraph
194
+ licenses:
195
+ - MIT
196
+ metadata: {}
197
+ post_install_message:
198
+ rdoc_options: []
199
+ require_paths:
200
+ - lib
201
+ required_ruby_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - "~>"
204
+ - !ruby/object:Gem::Version
205
+ version: '2.3'
206
+ required_rubygems_version: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: '0'
211
+ requirements: []
212
+ rubyforge_project:
213
+ rubygems_version: 2.7.6
214
+ signing_key:
215
+ specification_version: 4
216
+ summary: Easily build jsonapi.org-compatible APIs for GraphDB
217
+ test_files: []