jsonapi-resources 0.10.0.beta2 → 0.10.0

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.
@@ -1,22 +1,42 @@
1
1
  module JSONAPI
2
2
  class CachedResponseFragment
3
- def self.fetch_cached_fragments(resource_klass, serializer_config_key, cache_ids, context)
4
- context_json = resource_klass.attribute_caching_context(context).to_json
5
- context_b64 = JSONAPI.configuration.resource_cache_digest_function.call(context_json)
6
- context_key = "ATTR-CTX-#{context_b64.gsub("/", "_")}"
7
-
8
- results = self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
9
-
10
- if JSONAPI.configuration.resource_cache_usage_report_function
11
- miss_ids = results.select{|_k,v| v.nil? }.keys
12
- JSONAPI.configuration.resource_cache_usage_report_function.call(
13
- resource_klass.name,
14
- cache_ids.size - miss_ids.size,
15
- miss_ids.size
16
- )
3
+
4
+ Lookup = Struct.new(:resource_klass, :serializer_config_key, :context, :context_key, :cache_ids) do
5
+
6
+ def type
7
+ resource_klass._type
17
8
  end
18
9
 
19
- results
10
+ def keys
11
+ cache_ids.map do |(id, cache_key)|
12
+ [type, id, cache_key, serializer_config_key, context_key]
13
+ end
14
+ end
15
+ end
16
+
17
+ Write = Struct.new(:resource_klass, :resource, :serializer, :serializer_config_key, :context, :context_key, :relationship_data) do
18
+ def to_key_value
19
+
20
+ (id, cache_key) = resource.cache_id
21
+
22
+ json = serializer.object_hash(resource, relationship_data)
23
+
24
+ cr = CachedResponseFragment.new(
25
+ resource_klass,
26
+ id,
27
+ json['type'],
28
+ context,
29
+ resource.fetchable_fields,
30
+ json['relationships'],
31
+ json['links'],
32
+ json['attributes'],
33
+ json['meta']
34
+ )
35
+
36
+ key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
37
+
38
+ [key, cr]
39
+ end
20
40
  end
21
41
 
22
42
  attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
@@ -50,26 +70,46 @@ module JSONAPI
50
70
  }
51
71
  end
52
72
 
53
- private
73
+ # @param [Lookup[]] lookups
74
+ # @return [Hash<Class<Resource>, Hash<ID, CachedResourceFragment>>]
75
+ def self.lookup(lookups, context)
76
+ type_to_klass = lookups.map {|l| [l.type, l.resource_klass]}.to_h
54
77
 
55
- def self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
56
- type = resource_klass._type
78
+ keys = lookups.map(&:keys).flatten(1)
57
79
 
58
- keys = cache_ids.map do |(id, cache_key)|
59
- [type, id, cache_key, serializer_config_key, context_key]
60
- end
80
+ hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject {|_, v| v.nil?}
81
+
82
+ return keys.inject({}) do |hash, key|
83
+ (type, id, _, _) = key
84
+ resource_klass = type_to_klass[type]
85
+ hash[resource_klass] ||= {}
61
86
 
62
- hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject{|_,v| v.nil? }
63
- return keys.each_with_object({}) do |key, hash|
64
- (_, id, _, _) = key
65
87
  if hits.has_key?(key)
66
- hash[id] = self.from_cache_value(resource_klass, context, hits[key])
88
+ hash[resource_klass][id] = self.from_cache_value(resource_klass, context, hits[key])
67
89
  else
68
- hash[id] = nil
90
+ hash[resource_klass][id] = nil
69
91
  end
92
+
93
+ hash
70
94
  end
71
95
  end
72
96
 
97
+ # @param [Write[]] lookups
98
+ def self.write(writes)
99
+ key_values = writes.map(&:to_key_value)
100
+
101
+ to_write = key_values.map {|(k, v)| [k, v.to_cache_value]}.to_h
102
+
103
+ if JSONAPI.configuration.resource_cache.respond_to? :write_multi
104
+ JSONAPI.configuration.resource_cache.write_multi(to_write)
105
+ else
106
+ to_write.each do |key, value|
107
+ JSONAPI.configuration.resource_cache.write(key, value)
108
+ end
109
+ end
110
+
111
+ end
112
+
73
113
  def self.from_cache_value(resource_klass, context, h)
74
114
  new(
75
115
  resource_klass,
@@ -83,28 +123,5 @@ module JSONAPI
83
123
  h.fetch(:meta, nil)
84
124
  )
85
125
  end
86
-
87
- def self.write(resource_klass, resource, serializer, serializer_config_key, context, context_key, relationship_data )
88
- (id, cache_key) = resource.cache_id
89
-
90
- json = serializer.object_hash(resource, relationship_data)
91
-
92
- cr = self.new(
93
- resource_klass,
94
- id,
95
- json['type'],
96
- context,
97
- resource.fetchable_fields,
98
- json['relationships'],
99
- json['links'],
100
- json['attributes'],
101
- json['meta']
102
- )
103
-
104
- key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
105
- JSONAPI.configuration.resource_cache.write(key, cr.to_cache_value)
106
- return [id, cr]
107
- end
108
-
109
126
  end
110
127
  end
@@ -1,6 +1,5 @@
1
1
  require 'jsonapi/formatter'
2
2
  require 'jsonapi/processor'
3
- require 'jsonapi/active_relation_resource_finder'
4
3
  require 'concurrent'
5
4
 
6
5
  module JSONAPI
@@ -10,6 +9,8 @@ module JSONAPI
10
9
  :route_format,
11
10
  :raise_if_parameters_not_allowed,
12
11
  :warn_on_route_setup_issues,
12
+ :warn_on_missing_routes,
13
+ :warn_on_performance_issues,
13
14
  :default_allow_include_to_one,
14
15
  :default_allow_include_to_many,
15
16
  :allow_sort,
@@ -17,7 +18,6 @@ module JSONAPI
17
18
  :default_paginator,
18
19
  :default_page_size,
19
20
  :maximum_page_size,
20
- :resource_finder,
21
21
  :default_processor_klass,
22
22
  :use_text_errors,
23
23
  :top_level_links_include_pagination,
@@ -38,7 +38,8 @@ module JSONAPI
38
38
  :default_caching,
39
39
  :default_resource_cache_field,
40
40
  :resource_cache_digest_function,
41
- :resource_cache_usage_report_function
41
+ :resource_cache_usage_report_function,
42
+ :default_exclude_links
42
43
 
43
44
  def initialize
44
45
  #:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -59,6 +60,8 @@ module JSONAPI
59
60
  self.raise_if_parameters_not_allowed = true
60
61
 
61
62
  self.warn_on_route_setup_issues = true
63
+ self.warn_on_missing_routes = true
64
+ self.warn_on_performance_issues = true
62
65
 
63
66
  # :none, :offset, :paged, or a custom paginator name
64
67
  self.default_paginator = :none
@@ -105,12 +108,6 @@ module JSONAPI
105
108
  self.always_include_to_one_linkage_data = false
106
109
  self.always_include_to_many_linkage_data = false
107
110
 
108
- # ResourceFinder Mixin
109
- # The default ResourceFinder is the ActiveRelationResourceFinder which provides
110
- # access to ActiveRelation backed models. Custom ResourceFinders can be specified
111
- # in order to support other ORMs.
112
- self.resource_finder = JSONAPI::ActiveRelationResourceFinder
113
-
114
111
  # The default Operation Processor to use if one is not defined specifically
115
112
  # for a Resource.
116
113
  self.default_processor_klass = JSONAPI::Processor
@@ -155,6 +152,12 @@ module JSONAPI
155
152
  # Optionally provide a callable which JSONAPI will call with information about cache
156
153
  # performance. Should accept three arguments: resource name, hits count, misses count.
157
154
  self.resource_cache_usage_report_function = nil
155
+
156
+ # Global configuration for links exclusion
157
+ # Controls whether to generate links like `self`, `related` with all the resources
158
+ # and relationships. Accepts either `:default`, `:none`, or array containing the
159
+ # specific default links to exclude, which may be `:self` and `:related`.
160
+ self.default_exclude_links = :none
158
161
  end
159
162
 
160
163
  def cache_formatters=(bool)
@@ -225,10 +228,6 @@ module JSONAPI
225
228
  @default_processor_klass = default_processor_klass
226
229
  end
227
230
 
228
- def resource_finder=(resource_finder)
229
- @resource_finder = resource_finder
230
- end
231
-
232
231
  def allow_include=(allow_include)
233
232
  ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
234
233
  @default_allow_include_to_one = allow_include
@@ -273,6 +272,10 @@ module JSONAPI
273
272
 
274
273
  attr_writer :warn_on_route_setup_issues
275
274
 
275
+ attr_writer :warn_on_missing_routes
276
+
277
+ attr_writer :warn_on_performance_issues
278
+
276
279
  attr_writer :use_relationship_reflection
277
280
 
278
281
  attr_writer :resource_cache
@@ -284,6 +287,8 @@ module JSONAPI
284
287
  attr_writer :resource_cache_digest_function
285
288
 
286
289
  attr_writer :resource_cache_usage_report_function
290
+
291
+ attr_writer :default_exclude_links
287
292
  end
288
293
 
289
294
  class << self
@@ -4,14 +4,12 @@ module JSONAPI
4
4
  # For example ['posts.comments.tags']
5
5
  # will transform into =>
6
6
  # {
7
- # posts:{
8
- # include:true,
9
- # include_related:{
7
+ # posts: {
8
+ # include_related: {
10
9
  # comments:{
11
- # include:true,
12
- # include_related:{
13
- # tags:{
14
- # include:true
10
+ # include_related: {
11
+ # tags: {
12
+ # include_related: {}
15
13
  # }
16
14
  # }
17
15
  # }
@@ -44,7 +42,7 @@ module JSONAPI
44
42
  path.segments.each do |segment|
45
43
  relationship_name = segment.relationship.name.to_sym
46
44
 
47
- current[:include_related][relationship_name] ||= { include: true, include_related: {} }
45
+ current[:include_related][relationship_name] ||= { include_related: {} }
48
46
  current = current[:include_related][relationship_name]
49
47
  end
50
48
 
@@ -3,109 +3,100 @@ module JSONAPI
3
3
  attr_reader :base_url,
4
4
  :primary_resource_klass,
5
5
  :route_formatter,
6
- :engine_name
6
+ :engine,
7
+ :engine_mount_point,
8
+ :url_helpers
9
+
10
+ @@url_helper_methods = {}
7
11
 
8
12
  def initialize(config = {})
9
- @base_url = config[:base_url]
13
+ @base_url = config[:base_url]
10
14
  @primary_resource_klass = config[:primary_resource_klass]
11
- @route_formatter = config[:route_formatter]
12
- @engine_name = build_engine_name
15
+ @route_formatter = config[:route_formatter]
16
+ @engine = build_engine
17
+ @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""
13
18
 
14
- # Warning: These make LinkBuilder non-thread-safe. That's not a problem with the
15
- # request-specific way it's currently used, though.
16
- @resources_path_cache = JSONAPI::NaiveCache.new do |source_klass|
17
- formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
18
- end
19
+ # url_helpers may be either a controller which has the route helper methods, or the application router's
20
+ # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
21
+ # singleton, and it's expensive to generate the module, the controller is preferred.
22
+ @url_helpers = config[:url_helpers]
19
23
  end
20
24
 
21
25
  def engine?
22
- !!@engine_name
26
+ !!@engine
23
27
  end
24
28
 
25
29
  def primary_resources_url
26
- if engine?
27
- engine_primary_resources_url
30
+ if @primary_resource_klass._routed
31
+ primary_resources_path = resources_path(primary_resource_klass)
32
+ @primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }"
28
33
  else
29
- regular_primary_resources_url
34
+ if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route
35
+ warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
36
+ @primary_resource_klass._warned_missing_route = true
37
+ end
38
+ nil
30
39
  end
31
40
  end
32
41
 
33
42
  def query_link(query_params)
34
- "#{ primary_resources_url }?#{ query_params.to_query }"
43
+ url = primary_resources_url
44
+ return url if url.nil?
45
+ "#{ url }?#{ query_params.to_query }"
35
46
  end
36
47
 
37
48
  def relationships_related_link(source, relationship, query_params = {})
38
- url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
39
- url = "#{ url }?#{ query_params.to_query }" if query_params.present?
40
- url
49
+ if relationship._routed
50
+ url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
51
+ url = "#{ url }?#{ query_params.to_query }" if query_params.present?
52
+ url
53
+ else
54
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
55
+ warn "related_link for #{relationship} could not be generated"
56
+ relationship._warned_missing_route = true
57
+ end
58
+ nil
59
+ end
41
60
  end
42
61
 
43
62
  def relationships_self_link(source, relationship)
44
- "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
63
+ if relationship._routed
64
+ "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
65
+ else
66
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
67
+ warn "self_link for #{relationship} could not be generated"
68
+ relationship._warned_missing_route = true
69
+ end
70
+ nil
71
+ end
45
72
  end
46
73
 
47
74
  def self_link(source)
48
- if engine?
49
- engine_resource_url(source)
75
+ if source.class._routed
76
+ resource_url(source)
50
77
  else
51
- regular_resource_url(source)
78
+ if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route
79
+ warn "self_link for #{source.class} could not be generated"
80
+ source.class._warned_missing_route = true
81
+ end
82
+ nil
52
83
  end
53
84
  end
54
85
 
55
86
  private
56
87
 
57
- def build_engine_name
88
+ def build_engine
58
89
  scopes = module_scopes_from_class(primary_resource_klass)
59
90
 
60
91
  begin
61
92
  unless scopes.empty?
62
93
  "#{ scopes.first.to_s.camelize }::Engine".safe_constantize
63
94
  end
64
- # :nocov:
95
+
96
+ # :nocov:
65
97
  rescue LoadError => _e
66
98
  nil
67
- # :nocov:
68
- end
69
- end
70
-
71
- def engine_path_from_resource_class(klass)
72
- path_name = engine_resources_path_name_from_class(klass)
73
- engine_name.routes.url_helpers.public_send(path_name)
74
- end
75
-
76
- def engine_primary_resources_path
77
- engine_path_from_resource_class(primary_resource_klass)
78
- end
79
-
80
- def engine_primary_resources_url
81
- "#{ base_url }#{ engine_primary_resources_path }"
82
- end
83
-
84
- def engine_resource_path(source)
85
- resource_path_name = engine_resource_path_name_from_source(source)
86
- engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
87
- end
88
-
89
- def engine_resource_path_name_from_source(source)
90
- scopes = module_scopes_from_class(source.class)[1..-1]
91
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
92
- end_path_name = source.class._type.to_s.singularize
93
- [base_path_name, end_path_name, "path"].reject(&:blank?).join("_")
94
- end
95
-
96
- def engine_resource_url(source)
97
- "#{ base_url }#{ engine_resource_path(source) }"
98
- end
99
-
100
- def engine_resources_path_name_from_class(klass)
101
- scopes = module_scopes_from_class(klass)[1..-1]
102
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
103
- end_path_name = klass._type.to_s
104
-
105
- if base_path_name.blank?
106
- "#{ end_path_name }_path"
107
- else
108
- "#{ base_path_name }_#{ end_path_name }_path"
99
+ # :nocov:
109
100
  end
110
101
  end
111
102
 
@@ -114,10 +105,14 @@ module JSONAPI
114
105
  end
115
106
 
116
107
  def formatted_module_path_from_class(klass)
117
- scopes = module_scopes_from_class(klass)
108
+ scopes = if @engine
109
+ module_scopes_from_class(klass)[1..-1]
110
+ else
111
+ module_scopes_from_class(klass)
112
+ end
118
113
 
119
114
  unless scopes.empty?
120
- "/#{ scopes.map{ |scope| format_route(scope.to_s.underscore) }.compact.join('/') }/"
115
+ "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/"
121
116
  else
122
117
  "/"
123
118
  end
@@ -127,30 +122,21 @@ module JSONAPI
127
122
  klass.name.to_s.split("::")[0...-1]
128
123
  end
129
124
 
130
- def regular_resources_path(source_klass)
131
- @resources_path_cache.get(source_klass)
132
- end
133
-
134
- def regular_primary_resources_path
135
- regular_resources_path(primary_resource_klass)
125
+ def resources_path(source_klass)
126
+ @_resources_path ||= {}
127
+ @_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
136
128
  end
137
129
 
138
- def regular_primary_resources_url
139
- "#{ base_url }#{ regular_primary_resources_path }"
140
- end
141
-
142
- def regular_resource_path(source)
143
- if source.is_a?(JSONAPI::CachedResponseFragment)
144
- # :nocov:
145
- "#{regular_resources_path(source.resource_klass)}/#{source.id}"
146
- # :nocov:
130
+ def resource_path(source)
131
+ if source.class.singleton?
132
+ resources_path(source.class)
147
133
  else
148
- "#{regular_resources_path(source.class)}/#{source.id}"
134
+ "#{resources_path(source.class)}/#{source.id}"
149
135
  end
150
136
  end
151
137
 
152
- def regular_resource_url(source)
153
- "#{ base_url }#{ regular_resource_path(source) }"
138
+ def resource_url(source)
139
+ "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
154
140
  end
155
141
 
156
142
  def route_for_relationship(relationship)
@@ -100,7 +100,7 @@ module JSONAPI
100
100
  end
101
101
  end
102
102
 
103
- class LinksObjectOperationResult < OperationResult
103
+ class RelationshipOperationResult < OperationResult
104
104
  attr_accessor :parent_resource, :relationship, :resource_ids
105
105
 
106
106
  def initialize(code, parent_resource, relationship, resource_ids, options = {})
@@ -112,7 +112,7 @@ module JSONAPI
112
112
 
113
113
  def to_hash(serializer = nil)
114
114
  if serializer
115
- serializer.serialize_to_links_hash(parent_resource, relationship, resource_ids)
115
+ serializer.serialize_to_relationship_hash(parent_resource, relationship, resource_ids)
116
116
  else
117
117
  # :nocov:
118
118
  {}
data/lib/jsonapi/path.rb CHANGED
@@ -31,5 +31,13 @@ module JSONAPI
31
31
  def relationship_path_string
32
32
  relationship_segments.collect(&:to_s).join('.')
33
33
  end
34
+
35
+ def last_relationship
36
+ if @segments.last.is_a?(PathSegment::Relationship)
37
+ @segments.last
38
+ else
39
+ @segments[-2]
40
+ end
41
+ end
34
42
  end
35
43
  end
@@ -22,19 +22,27 @@ module JSONAPI
22
22
  end
23
23
 
24
24
  class Relationship
25
- attr_reader :relationship
25
+ attr_reader :relationship, :resource_klass
26
26
 
27
- def initialize(relationship:, resource_klass:)
27
+ def initialize(relationship:, resource_klass: nil)
28
28
  @relationship = relationship
29
29
  @resource_klass = resource_klass
30
30
  end
31
31
 
32
+ def eql?(other)
33
+ relationship == other.relationship && resource_klass == other.resource_klass
34
+ end
35
+
36
+ def hash
37
+ [relationship, resource_klass].hash
38
+ end
39
+
32
40
  def to_s
33
- @resource_klass ? "#{relationship.name}##{resource_klass._type}" : "#{relationship.name}"
41
+ @resource_klass ? "#{relationship.parent_resource_klass._type}.#{relationship.name}##{resource_klass._type}" : "#{resource_klass._type}.#{relationship.name}"
34
42
  end
35
43
 
36
44
  def resource_klass
37
- @resource_klass || @relationship.resource_klass
45
+ @resource_klass || relationship.resource_klass
38
46
  end
39
47
 
40
48
  def path_specified_resource_klass?
@@ -50,13 +58,17 @@ module JSONAPI
50
58
  @field_name = field_name
51
59
  end
52
60
 
61
+ def eql?(other)
62
+ field_name == other.field_name && resource_klass == other.resource_klass
63
+ end
64
+
53
65
  def delegated_field_name
54
66
  resource_klass._attribute_delegated_name(field_name)
55
67
  end
56
68
 
57
69
  def to_s
58
70
  # :nocov:
59
- field_name.to_s
71
+ "#{resource_klass._type}.#{field_name.to_s}"
60
72
  # :nocov:
61
73
  end
62
74
  end
@@ -130,11 +130,11 @@ module JSONAPI
130
130
  find_options,
131
131
  nil)
132
132
 
133
- return JSONAPI::LinksObjectOperationResult.new(:ok,
134
- parent_resource,
135
- resource_klass._relationship(relationship_type),
136
- resource_id_tree.fragments.keys,
137
- result_options)
133
+ return JSONAPI::RelationshipOperationResult.new(:ok,
134
+ parent_resource,
135
+ resource_klass._relationship(relationship_type),
136
+ resource_id_tree.fragments.keys,
137
+ result_options)
138
138
  end
139
139
 
140
140
  def show_related_resource
@@ -429,8 +429,7 @@ module JSONAPI
429
429
  def load_included(resource_klass, source_resource_id_tree, include_related, options)
430
430
  source_rids = source_resource_id_tree.fragments.keys
431
431
 
432
- include_related.try(:each_pair) do |key, value|
433
- next unless value[:include]
432
+ include_related.try(:each_key) do |key|
434
433
  relationship = resource_klass._relationship(key)
435
434
  relationship_name = relationship.name.to_sym
436
435
 
@@ -7,6 +7,8 @@ module JSONAPI
7
7
 
8
8
  attr_writer :allow_include
9
9
 
10
+ attr_accessor :_routed, :_warned_missing_route
11
+
10
12
  def initialize(name, options = {})
11
13
  @name = name.to_s
12
14
  @options = options
@@ -27,11 +29,17 @@ module JSONAPI
27
29
  @class_name = nil
28
30
  @inverse_relationship = nil
29
31
 
30
- # Custom methods are reserved for use in resource finders. Not used in the default ActiveRelationResourceFinder
32
+ @_routed = false
33
+ @_warned_missing_route = false
34
+
35
+ exclude_links(options.fetch(:exclude_links, JSONAPI.configuration.default_exclude_links))
36
+
37
+ # Custom methods are reserved for future use
31
38
  @custom_methods = options.fetch(:custom_methods, {})
32
39
  end
33
40
 
34
41
  alias_method :polymorphic?, :polymorphic
42
+ alias_method :parent_resource_klass, :parent_resource
35
43
 
36
44
  def primary_key
37
45
  # :nocov:
@@ -98,6 +106,27 @@ module JSONAPI
98
106
  @options[:readonly]
99
107
  end
100
108
 
109
+ def exclude_links(exclude)
110
+ case exclude
111
+ when :default, "default"
112
+ @_exclude_links = [:self, :related]
113
+ when :none, "none"
114
+ @_exclude_links = []
115
+ when Array
116
+ @_exclude_links = exclude.collect {|link| link.to_sym}
117
+ else
118
+ fail "Invalid exclude_links"
119
+ end
120
+ end
121
+
122
+ def _exclude_links
123
+ @_exclude_links ||= []
124
+ end
125
+
126
+ def exclude_link?(link)
127
+ _exclude_links.include?(link.to_sym)
128
+ end
129
+
101
130
  class ToOne < Relationship
102
131
  attr_reader :foreign_key_on
103
132
 
@@ -113,7 +142,7 @@ module JSONAPI
113
142
 
114
143
  def to_s
115
144
  # :nocov: useful for debugging
116
- "#{parent_resource._type}.#{name} => (#{belongs_to? ? 'ToOne' : 'BelongsToOne'}) #{resource_klass._type}"
145
+ "#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
117
146
  # :nocov:
118
147
  end
119
148
 
@@ -163,7 +192,7 @@ module JSONAPI
163
192
 
164
193
  def to_s
165
194
  # :nocov: useful for debugging
166
- "#{parent_resource._type}.#{name} => (ToMany) #{resource_klass._type}"
195
+ "#{parent_resource}.#{name}(ToMany)"
167
196
  # :nocov:
168
197
  end
169
198