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

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
  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