graphiti 1.0.beta.5 → 1.0.beta.6

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
  SHA1:
3
- metadata.gz: 3fc7ed80afd6a4d3805e1243f5dcf1ab6c39cec1
4
- data.tar.gz: 5f9d051ad124fc3e452cbf51bb6f31e70ea9b36f
3
+ metadata.gz: ebbb1dbc7d5e33d3fe8411ea7f184cb1c1a42b72
4
+ data.tar.gz: 5d94a4b97e8aa526abcc342adfe12ae124df549c
5
5
  SHA512:
6
- metadata.gz: 523c8d39c68fcdaca2a52e49fb34161e7dcb08b94a524fa2eca22a3a85a679eb1312e2f2603e14ee6534a2a1ab4a856cdce6c9a50a8aacd7a7262b5e843ec25e
7
- data.tar.gz: ee10e629146235de49f01d8c86e7700a2afce5e64baf6fb85ed07b05d01d5e103c061072acba09963c66acf648f9773e21019c4c62ee485d2396834db40f424e
6
+ metadata.gz: 60cd65ff706feace9b10d691b13f55cf716c0fdef0563b75ce9ad8a34579d553824a3417eba67bfd6ac84ff02e70356537a5f63001e71bf1a54bcf16cba6d216
7
+ data.tar.gz: 15055313e91f05705ef372721e720c34556c7a4b82e11e122b167d16fca063feac398513a5b89141e3a8c8618c8f9e056037d5101a49de29cbb5355a13ae7bf5
data/graphiti.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  # Pinning this version until backwards-incompatibility is addressed
22
22
  spec.add_dependency 'jsonapi-serializable', '~> 0.3.0'
23
23
  spec.add_dependency 'dry-types', '~> 0.13'
24
- spec.add_dependency 'graphiti_errors', '~> 1.0.alpha.2'
24
+ spec.add_dependency 'graphiti_errors', '~> 1.0.beta.1'
25
25
  spec.add_dependency 'concurrent-ruby', '~> 1.0'
26
26
  spec.add_dependency 'activesupport', ['>= 4.1', '< 6']
27
27
 
@@ -32,5 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency "sqlite3"
33
33
  spec.add_development_dependency "database_cleaner"
34
34
  spec.add_development_dependency "activemodel", ['>= 4.1', '< 6']
35
- spec.add_development_dependency "graphiti_spec_helpers", '>= 1.0.alpha.1'
35
+ spec.add_development_dependency "graphiti_spec_helpers", '>= 1.0.beta.3'
36
36
  end
data/lib/graphiti.rb CHANGED
@@ -68,6 +68,7 @@ require "graphiti/extensions/extra_attribute"
68
68
  require "graphiti/extensions/boolean_attribute"
69
69
  require "graphiti/extensions/temp_id"
70
70
  require "graphiti/serializer"
71
+ require "graphiti/debugger"
71
72
 
72
73
  if defined?(ActiveRecord)
73
74
  require 'graphiti/adapters/active_record'
@@ -127,6 +128,34 @@ module Graphiti
127
128
  @resources ||= []
128
129
  end
129
130
 
131
+ def self.broadcast(name, payload)
132
+ name = "graphiti.#{name}"
133
+ ActiveSupport::Notifications.instrument(name, payload) do
134
+ yield payload if block_given?
135
+ end
136
+ end
137
+
138
+ def self.logger
139
+ @logger ||= stdout_logger
140
+ end
141
+
142
+ def self.stdout_logger
143
+ logger = Logger.new($stdout)
144
+ logger.formatter = proc do |severity, datetime, progname, msg|
145
+ "#{msg}\n"
146
+ end
147
+ logger
148
+ end
149
+
150
+ def self.logger=(val)
151
+ @logger = val
152
+ end
153
+
154
+ def self.log(msg, color = :white, bold = false)
155
+ colored = ActiveSupport::LogSubscriber.new.send(:color, msg, color, bold)
156
+ logger.debug(colored)
157
+ end
158
+
130
159
  # When we add a sideload, we need to do configuration, such as
131
160
  # adding the relationship to the Resource's serializer.
132
161
  # However, the sideload's Resource class may not be loaded yet.
data/lib/graphiti/base.rb CHANGED
@@ -57,7 +57,11 @@ module Graphiti
57
57
  def proxy(base = nil, opts = {})
58
58
  base ||= jsonapi_resource.base_scope
59
59
  scope_opts = opts.slice :sideload_parent_length,
60
- :default_paginate, :after_resolve
60
+ :default_paginate,
61
+ :after_resolve,
62
+ :sideload,
63
+ :parent,
64
+ :params
61
65
  scope = jsonapi_scope(base, scope_opts)
62
66
  ResourceProxy.new jsonapi_resource,
63
67
  scope,
@@ -13,6 +13,8 @@ module Graphiti
13
13
  attr_accessor :schema_path
14
14
  attr_accessor :links_on_demand
15
15
  attr_accessor :typecast_reads
16
+ attr_accessor :debug
17
+ attr_accessor :debug_models
16
18
 
17
19
  # Set defaults
18
20
  # @api private
@@ -22,14 +24,28 @@ module Graphiti
22
24
  @respond_to = [:json, :jsonapi, :xml]
23
25
  @links_on_demand = false
24
26
  @typecast_reads = true
27
+ self.debug = ENV.fetch('GRAPHITI_DEBUG', true)
28
+ self.debug_models = ENV.fetch('GRAPHITI_DEBUG_MODELS', false)
25
29
 
26
30
  if defined?(::Rails)
27
31
  @schema_path = "#{::Rails.root}/public/schema.json"
32
+ self.debug = ::Rails.logger.level.zero?
33
+ Graphiti.logger = ::Rails.logger
28
34
  end
29
35
  end
30
36
 
31
37
  def schema_path
32
38
  @schema_path ||= raise('No schema_path defined! Set Graphiti.config.schema_path to save your schema.')
33
39
  end
40
+
41
+ def debug=(val)
42
+ @debug = val
43
+ Debugger.enabled = val
44
+ end
45
+
46
+ def debug_models=(val)
47
+ @debug_models = val
48
+ Debugger.debug_models = val
49
+ end
34
50
  end
35
51
  end
@@ -0,0 +1,194 @@
1
+ # This could definitely use some refactoring love, but I have no time ATM
2
+ # The code is pretty self-contained; we're just listening to notifications
3
+ # and taking action.
4
+ module Graphiti
5
+ class Debugger
6
+ class << self
7
+ attr_accessor :enabled, :chunks, :debug_models, :preserve, :pry
8
+ end
9
+ self.chunks = []
10
+
11
+ class << self
12
+ def on_data(name, start, stop, id, payload)
13
+ took = ((stop-start)*1000.0).round(2)
14
+ params = scrub_params(payload[:params])
15
+
16
+ if payload[:exception]
17
+ on_data_exception(payload, params)
18
+ else
19
+ if payload[:sideload]
20
+ on_sideload_data(payload, params, took)
21
+ else
22
+ on_primary_data(payload, params, took)
23
+ end
24
+ end
25
+ end
26
+
27
+ def on_data_exception(payload, params)
28
+ unless payload[:exception_object].instance_variable_get(:@__graphiti_debug)
29
+ add_chunk do |logs, json|
30
+ logs << ["\n=== Graphiti Debug ERROR", :red, true]
31
+ if sideload = payload[:sideload]
32
+ logs << ["#{sideload.parent_resource.class}: Sideload \"#{sideload.name}\"", :red, true]
33
+ json[:parent_resource] = sideload.parent_resource.class.name
34
+ json[:sideload] = sideload.name
35
+ end
36
+ if params
37
+ query = "#{payload[:resource].class.name}.all(#{JSON.pretty_generate(params)}).data"
38
+ logs << [query, :cyan, true]
39
+ logs << ["The error occurred when running the above query. Copy/paste it into a rake task or Rails console session to reproduce. Keep in mind you may have to set context.", :yellow, true]
40
+ json[:query] = query
41
+ else
42
+ query = "This sideload is done manually via .scope - no debug information available."
43
+ logs << [query, :cyan, true]
44
+ json[:query] = query
45
+ end
46
+ logs << "\n\n"
47
+ payload[:exception_object].instance_variable_set(:@__graphiti_debug, json)
48
+ end
49
+ end
50
+ end
51
+
52
+ def results(raw_results)
53
+ raw_results.map { |r| "[#{r.class.name}, #{r.id.inspect}]" }.join(', ')
54
+ end
55
+
56
+ def on_sideload_data(payload, params, took)
57
+ sideload = payload[:sideload]
58
+ results = results(payload[:results])
59
+ add_chunk(payload[:resource], payload[:parent]) do |logs, json|
60
+ #logs << [" \\_ #{sideload.parent_resource_class} > \"#{sideload.name}\":", :yellow, true]
61
+ logs << [" \\_ #{sideload.name}", :yellow, true]
62
+ json[:name] = sideload.name
63
+ query = "#{payload[:resource].class.name}.all(#{params.inspect})"
64
+ unless params
65
+ query = "#{payload[:resource].class.name}: Manual sideload via .scope"
66
+ end
67
+ logs << [" #{query}", :cyan, true]
68
+ json[:query] = query
69
+ logs << [" Returned Models: #{results}"] if debug_models
70
+ logs << [" Took: #{took}ms", :magenta, true]
71
+ json[:took] = took
72
+ end
73
+ end
74
+
75
+ def on_primary_data(payload, params, took)
76
+ add_chunk(payload[:resource], payload[:parent]) do |logs, json|
77
+ logs << [""]
78
+ logs << ["=== Graphiti Debug", :green, true]
79
+ title = "Top Level Data Retrieval (+ sideloads):"
80
+ logs << [title, :green, true]
81
+ json[:title] = title
82
+ query = "#{payload[:resource].class.name}.all(#{params.inspect})"
83
+ logs << [query, :cyan, true]
84
+ json[:query] = query
85
+ logs << ["Returned Models: #{results}"] if debug_models
86
+ logs << ["Took: #{took}ms", :magenta, true]
87
+ json[:took] = took
88
+ end
89
+ end
90
+
91
+ def on_render(name, start, stop, id, payload)
92
+ add_chunk do |logs|
93
+ took = ((stop-start)*1000.0).round(2)
94
+ logs << [""]
95
+ logs << ["=== Graphiti Debug", :green, true]
96
+ logs << ["Rendering:", :green, true]
97
+ logs << ["Took: #{took}ms", :magenta, true]
98
+ end
99
+ end
100
+
101
+ def debug
102
+ if enabled
103
+ begin
104
+ self.chunks = []
105
+ yield
106
+ ensure
107
+ flush
108
+ self.chunks = [] unless self.preserve
109
+ end
110
+ else
111
+ yield
112
+ end
113
+ end
114
+
115
+ def to_a
116
+ debugs = []
117
+ graph_statements.each do |chunk|
118
+ debugs << chunk_to_hash(chunk)
119
+ end
120
+ debugs
121
+ end
122
+
123
+ def flush
124
+ Graphiti.broadcast('debug.flush', {}) do |payload|
125
+ payload[:chunks] = chunks
126
+ graph_statements.each do |chunk|
127
+ flush_chunk(chunk)
128
+ end
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def scrub_params(params)
135
+ params ||= {}
136
+ params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
137
+ params.reject! { |k,v| [:controller, :action, :format, :debug].include?(k.to_sym) }
138
+ params.reject! { |k,v| k.to_sym == :include }
139
+ params.deep_symbolize_keys
140
+ end
141
+
142
+ def add_chunk(resource = nil, parent = nil)
143
+ logs, json = [], {}
144
+ yield(logs, json)
145
+ self.chunks << {
146
+ resource: resource,
147
+ parent: parent,
148
+ logs: logs,
149
+ json: json,
150
+ children: []
151
+ }
152
+ end
153
+
154
+ def graph_statements
155
+ @chunks.each do |chunk|
156
+ if parent = chunk[:parent]
157
+ relevant = chunks.find { |c| c[:resource] == parent }
158
+ relevant[:children].unshift(chunk)
159
+ end
160
+ end
161
+ @chunks.reject! { |c| !!c[:parent] }
162
+ @chunks
163
+ end
164
+
165
+ def chunk_to_hash(chunk)
166
+ hash = {}
167
+ hash.merge!(chunk[:json])
168
+ sideloads = []
169
+ chunk[:children].each do |child_chunk|
170
+ sideloads << chunk_to_hash(child_chunk)
171
+ end
172
+ hash[:sideloads] = sideloads
173
+ hash
174
+ end
175
+
176
+ def flush_chunk(chunk, depth = 0)
177
+ chunk[:logs].each do |args|
178
+ indent = ' ' * depth
179
+ args[0] = "#{indent}#{args[0]}"
180
+ Graphiti.log(*args)
181
+ end
182
+
183
+ chunk[:children].each do |child_chunk|
184
+ flush_chunk(child_chunk, depth + 1)
185
+ end
186
+ end
187
+ end
188
+
189
+ ActiveSupport::Notifications.subscribe \
190
+ 'graphiti.data', method(:on_data)
191
+ ActiveSupport::Notifications.subscribe \
192
+ 'graphiti.render', method(:on_render)
193
+ end
194
+ end
@@ -22,6 +22,7 @@ module Graphiti
22
22
  end
23
23
 
24
24
  def links?
25
+ return false if [:json, :xml, 'json', 'xml'].include?(params[:format])
25
26
  if Graphiti.config.links_on_demand
26
27
  [true, 'true'].include?(@params[:links])
27
28
  else
@@ -29,6 +30,10 @@ module Graphiti
29
30
  end
30
31
  end
31
32
 
33
+ def debug_requested?
34
+ !!@params[:debug]
35
+ end
36
+
32
37
  def hash
33
38
  @hash ||= {}.tap do |h|
34
39
  h[:filter] = filters unless filters.empty?
@@ -12,6 +12,7 @@ module Graphiti
12
12
  include Graphiti::Context
13
13
  include GraphitiErrors
14
14
  around_action :wrap_context
15
+ around_action :debug
15
16
  end
16
17
  end
17
18
 
@@ -21,6 +22,12 @@ module Graphiti
21
22
  end
22
23
  end
23
24
 
25
+ def debug
26
+ Debugger.debug do
27
+ yield
28
+ end
29
+ end
30
+
24
31
  def jsonapi_context
25
32
  self
26
33
  end
@@ -1,5 +1,10 @@
1
1
  module Graphiti
2
2
  class Railtie < ::Rails::Railtie
3
+ rake_tasks do
4
+ path = File.expand_path(__dir__)
5
+ load "#{path}/tasks.rb"
6
+ end
7
+
3
8
  initializer "graphiti.require_activerecord_adapter" do
4
9
  config.after_initialize do |app|
5
10
  ActiveSupport.on_load(:active_record) do
@@ -38,7 +38,7 @@ module Graphiti
38
38
  private
39
39
 
40
40
  def render(renderer)
41
- notify do
41
+ Graphiti.broadcast(:render, records: records, options: options) do
42
42
  options[:fields] = proxy.fields
43
43
  options[:expose] ||= {}
44
44
  options[:expose][:extra_fields] = proxy.extra_fields
@@ -46,25 +46,21 @@ module Graphiti
46
46
  options[:include] = proxy.include_hash
47
47
  options[:meta] ||= {}
48
48
  options[:meta].merge!(stats: proxy.stats) unless proxy.stats.empty?
49
+ options[:meta][:debug] = Debugger.to_a if debug_json?
50
+
49
51
  renderer.render(records, options)
50
52
  end
51
53
  end
52
54
 
53
- # TODO: more generic notification pattern
54
- # Likely comes out of debugger work
55
- def notify
56
- if defined?(ActiveSupport::Notifications)
57
- opts = [
58
- 'render.graphiti',
59
- records: records,
60
- options: options
61
- ]
62
- ActiveSupport::Notifications.instrument(*opts) do
63
- yield
55
+ def debug_json?
56
+ debug = false
57
+ if Debugger.enabled && proxy.debug_requested?
58
+ context = proxy.resource.context
59
+ if context.respond_to?(:allow_graphiti_debug_json?)
60
+ debug = context.allow_graphiti_debug_json?
64
61
  end
65
- else
66
- yield
67
62
  end
63
+ debug
68
64
  end
69
65
  end
70
66
  end
@@ -12,6 +12,7 @@ module Graphiti
12
12
  # @api private
13
13
  def _all(params, opts, base_scope)
14
14
  runner = Runner.new(self, params, opts.delete(:query))
15
+ opts[:params] = params
15
16
  runner.proxy(base_scope, opts)
16
17
  end
17
18
 
@@ -4,13 +4,16 @@ module Graphiti
4
4
 
5
5
  attr_reader :resource, :query, :scope, :payload
6
6
 
7
- def initialize(resource, scope, query, payload: nil, single: false, raise_on_missing: false)
8
- @resource = resource
9
- @scope = scope
10
- @query = query
11
- @payload = payload
12
- @single = single
13
- @raise_on_missing = raise_on_missing
7
+ def initialize(resource, scope, query,
8
+ payload: nil,
9
+ single: false,
10
+ raise_on_missing: false)
11
+ @resource = resource
12
+ @scope = scope
13
+ @query = query
14
+ @payload = payload
15
+ @single = single
16
+ @raise_on_missing = raise_on_missing
14
17
  end
15
18
 
16
19
  def single?
@@ -122,6 +125,10 @@ module Graphiti
122
125
  query.extra_fields
123
126
  end
124
127
 
128
+ def debug_requested?
129
+ query.debug_requested?
130
+ end
131
+
125
132
  private
126
133
 
127
134
  def persist
@@ -17,7 +17,11 @@ module Graphiti
17
17
  if @query.zero_results?
18
18
  []
19
19
  else
20
- resolved = @resource.resolve(@object)
20
+ resolved = broadcast_data do |payload|
21
+ payload[:results] = @resource.resolve(@object)
22
+ payload[:results]
23
+ end
24
+ resolved.compact!
21
25
  assign_serializer(resolved)
22
26
  yield resolved if block_given?
23
27
  if @opts[:after_resolve]
@@ -30,6 +34,18 @@ module Graphiti
30
34
 
31
35
  private
32
36
 
37
+ def broadcast_data
38
+ opts = {
39
+ resource: @resource,
40
+ params: @opts[:params],
41
+ sideload: @opts[:sideload],
42
+ parent: @opts[:parent]
43
+ }
44
+ Graphiti.broadcast("data", opts) do |payload|
45
+ yield payload
46
+ end
47
+ end
48
+
33
49
  # Used to ensure the resource's serializer is used
34
50
  # Not one derived through the usual jsonapi-rb logic
35
51
  def assign_serializer(records)
@@ -46,8 +62,9 @@ module Graphiti
46
62
 
47
63
  @query.sideloads.each_pair do |name, q|
48
64
  sideload = @resource.class.sideload(name)
65
+ _parent = @resource
49
66
  resolve_sideload = -> {
50
- sideload.resolve(results, q)
67
+ sideload.resolve(results, q, _parent)
51
68
  if concurrent && defined?(ActiveRecord)
52
69
  ActiveRecord::Base.clear_active_connections!
53
70
  end
@@ -141,10 +141,12 @@ module Graphiti
141
141
  end
142
142
  end
143
143
 
144
- def load(parents, query)
144
+ def load(parents, query, graph_parent)
145
145
  params = load_params(parents, query)
146
146
  params_proc.call(params, parents) if params_proc
147
147
  opts = load_options(parents, query)
148
+ opts[:sideload] = self
149
+ opts[:parent] = graph_parent
148
150
  proxy = resource.class._all(params, opts, base_scope)
149
151
  pre_load_proc.call(proxy, parents) if pre_load_proc
150
152
  proxy.to_a
@@ -191,23 +193,28 @@ module Graphiti
191
193
  children.replace(associated) if track_associated
192
194
  end
193
195
 
194
- def resolve(parents, query)
196
+ def resolve(parents, query, graph_parent)
195
197
  if single? && parents.length > 1
196
198
  raise Errors::SingularSideload.new(self, parents.length)
197
199
  end
198
200
 
199
201
  if self.class.scope_proc
200
- sideload_scope = fire_scope(parents)
201
- sideload_scope = Scope.new sideload_scope,
202
- resource,
203
- query,
204
- sideload_parent_length: parents.length,
205
- default_paginate: false
206
- sideload_scope.resolve do |sideload_results|
207
- fire_assign(parents, sideload_results)
202
+ Graphiti.broadcast("data", resource_class: resource.class, sideload: self) do |payload|
203
+ sideload_scope = fire_scope(parents)
204
+ sideload_scope = Scope.new sideload_scope,
205
+ resource,
206
+ query,
207
+ parent: graph_parent,
208
+ sideload: self,
209
+ sideload_parent_length: parents.length,
210
+ default_paginate: false
211
+ sideload_scope.resolve do |sideload_results|
212
+ payload[:results] = sideload_results
213
+ fire_assign(parents, sideload_results)
214
+ end
208
215
  end
209
216
  else
210
- load(parents, query)
217
+ load(parents, query, graph_parent)
211
218
  end
212
219
  end
213
220
 
@@ -10,7 +10,7 @@ class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
10
10
  end
11
11
  end
12
12
 
13
- def load(parents, query)
13
+ def load(parents, query, graph_parent)
14
14
  if ids_for_parents(parents).empty?
15
15
  []
16
16
  else
@@ -78,14 +78,14 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
78
78
  end
79
79
  end
80
80
 
81
- def resolve(parents, query)
81
+ def resolve(parents, query, graph_parent)
82
82
  parents.group_by(&grouper.field_name).each_pair do |group_name, group|
83
83
  next if group_name.nil?
84
84
 
85
85
  match = ->(c) { c.group_name == group_name.to_sym }
86
86
  if sideload = children.values.find(&match)
87
87
  query = remove_invalid_sideloads(sideload.resource, query)
88
- sideload.resolve(group, query)
88
+ sideload.resolve(group, query, graph_parent)
89
89
  else
90
90
  err = ::Graphiti::Errors::PolymorphicSideloadChildNotFound
91
91
  raise err.new(self, group_name)
@@ -0,0 +1,54 @@
1
+ namespace :graphiti do
2
+ include GraphitiSpecHelpers::Helpers
3
+
4
+ def response
5
+ session.response
6
+ end
7
+
8
+ def session
9
+ @session ||= ActionDispatch::Integration::Session.new(Rails.application)
10
+ end
11
+
12
+ def setup_rails!
13
+ Rails.application.eager_load!
14
+ Rails.application.config.cache_classes = true
15
+ Rails.application.config.action_controller.perform_caching = false
16
+ end
17
+
18
+ def make_request(path, debug = false)
19
+ if path.split('/').length == 2
20
+ path = "#{ApplicationResource.endpoint_namespace}#{path}"
21
+ end
22
+ if path.include?('?')
23
+ path << '&cache=bust'
24
+ else
25
+ path << '?cache=bust'
26
+ end
27
+ path = "#{path}&debug=true" if debug
28
+ session.get("#{path}")
29
+ end
30
+
31
+ desc "Execute request without web server."
32
+ task :request, [:path,:debug] => [:environment] do |_, args|
33
+ setup_rails!
34
+ Graphiti.logger = Graphiti.stdout_logger
35
+ Graphiti::Debugger.preserve = true
36
+ require 'pp'
37
+ path, debug = args[:path], args[:debug]
38
+ puts "Graphiti Request: #{path}"
39
+ make_request(path, debug)
40
+ pp json
41
+ Graphiti::Debugger.flush if debug
42
+ end
43
+
44
+ desc "Execute benchmark without web server."
45
+ task :benchmark, [:path,:requests] => [:environment] do |_, args|
46
+ setup_rails!
47
+ took = Benchmark.ms do
48
+ args[:requests].to_i.times do
49
+ make_request(args[:path])
50
+ end
51
+ end
52
+ puts "Took: #{(took / args[:requests].to_f).round(2)}ms"
53
+ end
54
+ end
@@ -35,6 +35,12 @@ module Graphiti
35
35
  @serializer.relationship(@sideload.name, &block)
36
36
  end
37
37
 
38
+ # If we can't eagerly validate links on app boot, we do it at runtime
39
+ # To avoid any performance confusion, this caches that lookup
40
+ def self.validated_link_cache
41
+ @validated_link_cache ||= []
42
+ end
43
+
38
44
  private
39
45
 
40
46
  def block
@@ -49,11 +55,13 @@ module Graphiti
49
55
  data { instance_eval(&_data_proc) }
50
56
 
51
57
  if _link
52
- _self.send(:validate_link!) unless _self.send(:eagerly_validate_links?)
58
+ if _links = @proxy.query.links?
59
+ _self.send(:validate_link!) unless _self.send(:eagerly_validate_links?)
53
60
 
54
- if @proxy.query.links?
55
61
  link(:related) do
56
- ::Graphiti::Util::Link.new(_sl, @object).generate
62
+ if _links
63
+ ::Graphiti::Util::Link.new(_sl, @object).generate
64
+ end
57
65
  end
58
66
  end
59
67
  end
@@ -103,10 +111,13 @@ module Graphiti
103
111
 
104
112
  def _validate_link!(sideload)
105
113
  action = sideload.type == :belongs_to ? :show : :index
114
+ cache_key = :"#{@sideload.object_id}-#{action}"
115
+ return if self.class.validated_link_cache.include?(cache_key)
106
116
  prc = Graphiti.config.context_for_endpoint
107
117
  unless prc.call(sideload.resource.endpoint[:full_path], action)
108
118
  raise Errors::InvalidLink.new(@resource_class, sideload, action)
109
119
  end
120
+ self.class.validated_link_cache << cache_key
110
121
  end
111
122
 
112
123
  def link?
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.0.beta.5"
2
+ VERSION = "1.0.beta.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.beta.5
4
+ version: 1.0.beta.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-23 00:00:00.000000000 Z
11
+ date: 2018-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.0.alpha.2
47
+ version: 1.0.beta.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.0.alpha.2
54
+ version: 1.0.beta.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: concurrent-ruby
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -202,14 +202,14 @@ dependencies:
202
202
  requirements:
203
203
  - - ">="
204
204
  - !ruby/object:Gem::Version
205
- version: 1.0.alpha.1
205
+ version: 1.0.beta.3
206
206
  type: :development
207
207
  prerelease: false
208
208
  version_requirements: !ruby/object:Gem::Requirement
209
209
  requirements:
210
210
  - - ">="
211
211
  - !ruby/object:Gem::Version
212
- version: 1.0.alpha.1
212
+ version: 1.0.beta.3
213
213
  description:
214
214
  email:
215
215
  - richmolj@gmail.com
@@ -265,6 +265,7 @@ files:
265
265
  - lib/graphiti/cli.rb
266
266
  - lib/graphiti/configuration.rb
267
267
  - lib/graphiti/context.rb
268
+ - lib/graphiti/debugger.rb
268
269
  - lib/graphiti/deserializer.rb
269
270
  - lib/graphiti/errors.rb
270
271
  - lib/graphiti/extensions/boolean_attribute.rb
@@ -306,6 +307,7 @@ files:
306
307
  - lib/graphiti/sideload/polymorphic_belongs_to.rb
307
308
  - lib/graphiti/stats/dsl.rb
308
309
  - lib/graphiti/stats/payload.rb
310
+ - lib/graphiti/tasks.rb
309
311
  - lib/graphiti/types.rb
310
312
  - lib/graphiti/util/attribute_check.rb
311
313
  - lib/graphiti/util/class.rb