graphiti-activegraph 0.1.2

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.
@@ -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: []