sanger-jsonapi-resources 0.1.1 → 0.2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +35 -12
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +26 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +898 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +130 -113
  10. data/lib/jsonapi/basic_resource.rb +1164 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +129 -0
  12. data/lib/jsonapi/callbacks.rb +2 -0
  13. data/lib/jsonapi/compatibility_helper.rb +29 -0
  14. data/lib/jsonapi/compiled_json.rb +13 -1
  15. data/lib/jsonapi/configuration.rb +88 -21
  16. data/lib/jsonapi/error.rb +29 -0
  17. data/lib/jsonapi/error_codes.rb +4 -0
  18. data/lib/jsonapi/exceptions.rb +82 -50
  19. data/lib/jsonapi/formatter.rb +5 -3
  20. data/lib/jsonapi/include_directives.rb +22 -67
  21. data/lib/jsonapi/link_builder.rb +76 -80
  22. data/lib/jsonapi/mime_types.rb +6 -10
  23. data/lib/jsonapi/naive_cache.rb +2 -0
  24. data/lib/jsonapi/operation.rb +18 -5
  25. data/lib/jsonapi/operation_result.rb +76 -16
  26. data/lib/jsonapi/paginator.rb +2 -0
  27. data/lib/jsonapi/path.rb +45 -0
  28. data/lib/jsonapi/path_segment.rb +78 -0
  29. data/lib/jsonapi/processor.rb +193 -115
  30. data/lib/jsonapi/relationship.rb +145 -14
  31. data/lib/jsonapi/request.rb +734 -0
  32. data/lib/jsonapi/resource.rb +3 -1251
  33. data/lib/jsonapi/resource_controller.rb +2 -0
  34. data/lib/jsonapi/resource_controller_metal.rb +7 -1
  35. data/lib/jsonapi/resource_fragment.rb +56 -0
  36. data/lib/jsonapi/resource_identity.rb +44 -0
  37. data/lib/jsonapi/resource_serializer.rb +158 -284
  38. data/lib/jsonapi/resource_set.rb +196 -0
  39. data/lib/jsonapi/resource_tree.rb +236 -0
  40. data/lib/jsonapi/resources/railtie.rb +9 -0
  41. data/lib/jsonapi/resources/version.rb +1 -1
  42. data/lib/jsonapi/response_document.rb +107 -83
  43. data/lib/jsonapi/routing_ext.rb +50 -26
  44. data/lib/jsonapi-resources.rb +23 -5
  45. data/lib/tasks/check_upgrade.rake +52 -0
  46. metadata +43 -31
  47. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  48. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  49. data/lib/jsonapi/operation_results.rb +0 -35
  50. data/lib/jsonapi/relationship_builder.rb +0 -167
  51. data/lib/jsonapi/request_parser.rb +0 -678
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Exceptions
3
5
  class Error < RuntimeError
4
- attr :error_object_overrides
6
+ attr_reader :error_object_overrides
5
7
 
6
8
  def initialize(error_object_overrides = {})
7
9
  @error_object_overrides = error_object_overrides
@@ -18,6 +20,22 @@ module JSONAPI
18
20
  end
19
21
  end
20
22
 
23
+ class Errors < Error
24
+ def initialize(errors, error_object_overrides = {})
25
+ @errors = errors
26
+
27
+ @errors.each do |error|
28
+ error.update_with_overrides(error_object_overrides)
29
+ end
30
+
31
+ super(error_object_overrides)
32
+ end
33
+
34
+ def errors
35
+ @errors
36
+ end
37
+ end
38
+
21
39
  class InternalServerError < Error
22
40
  attr_accessor :exception
23
41
 
@@ -33,6 +51,12 @@ module JSONAPI
33
51
  meta[:backtrace] = exception.backtrace
34
52
  end
35
53
 
54
+ if JSONAPI.configuration.include_application_backtraces_in_errors
55
+ meta ||= Hash.new
56
+ meta[:exception] ||= exception.message
57
+ meta[:application_backtrace] = exception.backtrace.select{|line| line =~ /#{Rails.root}/}
58
+ end
59
+
36
60
  [create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR,
37
61
  status: :internal_server_error,
38
62
  title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title',
@@ -119,49 +143,30 @@ module JSONAPI
119
143
  end
120
144
  end
121
145
 
122
-
123
- class HasManyRelationExists < Error
124
- attr_accessor :id
125
-
126
- def initialize(id, error_object_overrides = {})
127
- @id = id
128
- super(error_object_overrides)
129
- end
130
-
131
- def errors
132
- [create_error_object(code: JSONAPI::RELATION_EXISTS,
133
- status: :bad_request,
134
- title: I18n.translate('jsonapi-resources.exceptions.has_many_relation.title',
135
- default: 'Relation exists'),
136
- detail: I18n.translate('jsonapi-resources.exceptions.has_many_relation.detail',
137
- default: "The relation to #{id} already exists.",
138
- id: id))]
139
- end
140
- end
141
-
142
146
  class BadRequest < Error
143
- def initialize(exception)
147
+ def initialize(exception, error_object_overrides = {})
144
148
  @exception = exception
149
+ super(error_object_overrides)
145
150
  end
146
151
 
147
152
  def errors
148
- [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST,
149
- status: :bad_request,
150
- title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
151
- default: 'Bad Request'),
152
- detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
153
- default: @exception))]
153
+ [create_error_object(code: JSONAPI::BAD_REQUEST,
154
+ status: :bad_request,
155
+ title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
156
+ default: 'Bad Request'),
157
+ detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
158
+ default: @exception))]
154
159
  end
155
160
  end
156
161
 
157
162
  class InvalidRequestFormat < Error
158
163
  def errors
159
- [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST,
160
- status: :bad_request,
161
- title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
162
- default: 'Bad Request'),
163
- detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
164
- default: 'Request must be a hash'))]
164
+ [create_error_object(code: JSONAPI::BAD_REQUEST,
165
+ status: :bad_request,
166
+ title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
167
+ default: 'Bad Request'),
168
+ detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
169
+ default: 'Request must be a hash'))]
165
170
  end
166
171
  end
167
172
 
@@ -324,6 +329,26 @@ module JSONAPI
324
329
  end
325
330
  end
326
331
 
332
+ class InvalidRelationship < Error
333
+ attr_accessor :relationship_name, :type
334
+
335
+ def initialize(type, relationship_name, error_object_overrides = {})
336
+ @relationship_name = relationship_name
337
+ @type = type
338
+ super(error_object_overrides)
339
+ end
340
+
341
+ def errors
342
+ [create_error_object(code: JSONAPI::INVALID_RELATIONSHIP,
343
+ status: :bad_request,
344
+ title: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.title',
345
+ default: 'Invalid relationship'),
346
+ detail: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.detail',
347
+ default: "#{relationship_name} is not a valid field for #{type}.",
348
+ relationship_name: relationship_name, type: type))]
349
+ end
350
+ end
351
+
327
352
  class InvalidInclude < Error
328
353
  attr_accessor :relationship, :resource
329
354
 
@@ -339,7 +364,7 @@ module JSONAPI
339
364
  title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
340
365
  default: 'Invalid field'),
341
366
  detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
342
- default: "#{relationship} is not a valid relationship of #{resource}",
367
+ default: "#{relationship} is not a valid includable relationship of #{resource}",
343
368
  relationship: relationship, resource: resource))]
344
369
  end
345
370
  end
@@ -364,24 +389,21 @@ module JSONAPI
364
389
  end
365
390
  end
366
391
 
367
- class ParametersNotAllowed < Error
368
- attr_accessor :params
392
+ class ParameterNotAllowed < Error
393
+ attr_accessor :param
369
394
 
370
- def initialize(params, error_object_overrides = {})
371
- @params = params
395
+ def initialize(param, error_object_overrides = {})
396
+ @param = param
372
397
  super(error_object_overrides)
373
398
  end
374
399
 
375
400
  def errors
376
- params.collect do |param|
377
- create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
378
- status: :bad_request,
379
- title: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.title',
380
- default: 'Param not allowed'),
381
- detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
382
- default: "#{param} is not allowed.", param: param))
383
-
384
- end
401
+ [create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
402
+ status: :bad_request,
403
+ title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
404
+ default: 'Param not allowed'),
405
+ detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
406
+ default: "#{param} is not allowed.", param: param))]
385
407
  end
386
408
  end
387
409
 
@@ -451,11 +473,12 @@ module JSONAPI
451
473
  end
452
474
 
453
475
  class ValidationErrors < Error
454
- attr_reader :error_messages, :error_metadata, :resource_relationships
476
+ attr_reader :error_messages, :error_metadata, :resource_relationships, :resource_class
455
477
 
456
478
  def initialize(resource, error_object_overrides = {})
457
479
  @error_messages = resource.model_error_messages
458
480
  @error_metadata = resource.validation_error_metadata
481
+ @resource_class = resource.class
459
482
  @resource_relationships = resource.class._relationships.keys
460
483
  @key_formatter = JSONAPI.configuration.key_formatter
461
484
  super(error_object_overrides)
@@ -477,7 +500,7 @@ module JSONAPI
477
500
  create_error_object(code: JSONAPI::VALIDATION_ERROR,
478
501
  status: :unprocessable_entity,
479
502
  title: message,
480
- detail: "#{format_key(attr_key)} - #{message}",
503
+ detail: detail(attr_key, message),
481
504
  source: { pointer: pointer(attr_key) },
482
505
  meta: metadata_for(attr_key, message))
483
506
  end
@@ -487,7 +510,12 @@ module JSONAPI
487
510
  error_metadata[attr_key] ? error_metadata[attr_key][message] : nil
488
511
  end
489
512
 
513
+ def detail(attr_key, message)
514
+ general_error?(attr_key) ? message : "#{format_key(attr_key)} - #{message}"
515
+ end
516
+
490
517
  def pointer(attr_or_relationship_name)
518
+ return '/data' if general_error?(attr_or_relationship_name)
491
519
  formatted_attr_or_relationship_name = format_key(attr_or_relationship_name)
492
520
  if resource_relationships.include?(attr_or_relationship_name)
493
521
  "/data/relationships/#{formatted_attr_or_relationship_name}"
@@ -495,6 +523,10 @@ module JSONAPI
495
523
  "/data/attributes/#{formatted_attr_or_relationship_name}"
496
524
  end
497
525
  end
526
+
527
+ def general_error?(attr_key)
528
+ attr_key.to_sym == :base && !resource_class._has_attribute?(attr_key)
529
+ end
498
530
  end
499
531
 
500
532
  class SaveFailed < Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class Formatter
3
5
  class << self
@@ -108,7 +110,7 @@ end
108
110
 
109
111
  class DasherizedKeyFormatter < JSONAPI::KeyFormatter
110
112
  class << self
111
- def format(key)
113
+ def format(_key)
112
114
  super.underscore.dasherize
113
115
  end
114
116
 
@@ -146,7 +148,7 @@ end
146
148
 
147
149
  class CamelizedRouteFormatter < JSONAPI::RouteFormatter
148
150
  class << self
149
- def format(route)
151
+ def format(_route)
150
152
  super.camelize(:lower)
151
153
  end
152
154
 
@@ -158,7 +160,7 @@ end
158
160
 
159
161
  class DasherizedRouteFormatter < JSONAPI::RouteFormatter
160
162
  class << self
161
- def format(route)
163
+ def format(_route)
162
164
  super.dasherize
163
165
  end
164
166
 
@@ -1,17 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class IncludeDirectives
3
5
  # Construct an IncludeDirectives Hash from an array of dot separated include strings.
4
6
  # For example ['posts.comments.tags']
5
7
  # will transform into =>
6
8
  # {
7
- # posts:{
8
- # include:true,
9
- # include_related:{
9
+ # posts: {
10
+ # include_related: {
10
11
  # comments:{
11
- # include:true,
12
- # include_related:{
13
- # tags:{
14
- # include:true
12
+ # include_related: {
13
+ # tags: {
14
+ # include_related: {}
15
15
  # }
16
16
  # }
17
17
  # }
@@ -19,82 +19,37 @@ module JSONAPI
19
19
  # }
20
20
  # }
21
21
 
22
- def initialize(resource_klass, includes_array, force_eager_load: false)
22
+ def initialize(resource_klass, includes_array)
23
23
  @resource_klass = resource_klass
24
- @force_eager_load = force_eager_load
25
24
  @include_directives_hash = { include_related: {} }
26
25
  includes_array.each do |include|
27
26
  parse_include(include)
28
27
  end
29
28
  end
30
29
 
31
- def include_directives
32
- @include_directives_hash
33
- end
34
-
35
- def model_includes
36
- get_includes(@include_directives_hash)
37
- end
38
-
39
- def paths
40
- delve_paths(get_includes(@include_directives_hash, false))
30
+ def [](name)
31
+ @include_directives_hash[name]
41
32
  end
42
33
 
43
34
  private
44
35
 
45
- def get_related(current_path)
46
- current = @include_directives_hash
47
- current_resource_klass = @resource_klass
48
- current_path.split('.').each do |fragment|
49
- fragment = fragment.to_sym
50
-
51
- if current_resource_klass
52
- current_relationship = current_resource_klass._relationships[fragment]
53
- current_resource_klass = current_relationship.try(:resource_klass)
54
- else
55
- warn "[RELATIONSHIP NOT FOUND] Relationship could not be found for #{current_path}."
56
- end
57
-
58
- include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
59
-
60
- current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
61
- current = current[:include_related][fragment]
62
- end
63
- current
64
- end
65
-
66
- def get_includes(directive, only_joined_includes = true)
67
- ir = directive[:include_related]
68
- ir = ir.select { |k,v| v[:include_in_join] } if only_joined_includes
36
+ def parse_include(include)
37
+ path = JSONAPI::Path.new(resource_klass: @resource_klass,
38
+ path_string: include,
39
+ ensure_default_field: false,
40
+ parse_fields: false)
69
41
 
70
- ir.map do |name, sub_directive|
71
- sub = get_includes(sub_directive, only_joined_includes)
72
- sub.any? ? { name => sub } : name
73
- end
74
- end
42
+ current = @include_directives_hash
75
43
 
76
- def parse_include(include)
77
- parts = include.split('.')
78
- local_path = ''
44
+ path.segments.each do |segment|
45
+ relationship_name = segment.relationship.name.to_sym
79
46
 
80
- parts.each do |name|
81
- local_path += local_path.length > 0 ? ".#{name}" : name
82
- related = get_related(local_path)
83
- related[:include] = true
47
+ current[:include_related][relationship_name] ||= { include_related: {} }
48
+ current = current[:include_related][relationship_name]
84
49
  end
85
- end
86
50
 
87
- def delve_paths(obj)
88
- case obj
89
- when Array
90
- obj.map{|elem| delve_paths(elem)}.flatten(1)
91
- when Hash
92
- obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
93
- when Symbol, String
94
- [[obj]]
95
- else
96
- raise "delve_paths cannot descend into #{obj.class.name}"
97
- end
51
+ rescue JSONAPI::Exceptions::InvalidRelationship => _e
52
+ raise JSONAPI::Exceptions::InvalidInclude.new(@resource_klass, include)
98
53
  end
99
54
  end
100
55
  end
@@ -1,109 +1,104 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class LinkBuilder
3
5
  attr_reader :base_url,
4
6
  :primary_resource_klass,
5
7
  :route_formatter,
6
- :engine_name
8
+ :engine,
9
+ :engine_mount_point,
10
+ :url_helpers
11
+
12
+ @@url_helper_methods = {}
7
13
 
8
14
  def initialize(config = {})
9
- @base_url = config[:base_url]
15
+ @base_url = config[:base_url]
10
16
  @primary_resource_klass = config[:primary_resource_klass]
11
- @route_formatter = config[:route_formatter]
12
- @engine_name = build_engine_name
17
+ @route_formatter = config[:route_formatter]
18
+ @engine = build_engine
19
+ @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""
13
20
 
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
21
+ # url_helpers may be either a controller which has the route helper methods, or the application router's
22
+ # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
23
+ # singleton, and it's expensive to generate the module, the controller is preferred.
24
+ @url_helpers = config[:url_helpers]
19
25
  end
20
26
 
21
27
  def engine?
22
- !!@engine_name
28
+ !!@engine
23
29
  end
24
30
 
25
31
  def primary_resources_url
26
- if engine?
27
- engine_primary_resources_url
32
+ if @primary_resource_klass._routed
33
+ primary_resources_path = resources_path(primary_resource_klass)
34
+ @primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }"
28
35
  else
29
- regular_primary_resources_url
36
+ if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route
37
+ warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
38
+ @primary_resource_klass._warned_missing_route = true
39
+ end
40
+ nil
30
41
  end
31
42
  end
32
43
 
33
44
  def query_link(query_params)
34
- "#{ primary_resources_url }?#{ query_params.to_query }"
45
+ url = primary_resources_url
46
+ return url if url.nil?
47
+ "#{ url }?#{ query_params.to_query }"
35
48
  end
36
49
 
37
50
  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
51
+ if relationship._routed
52
+ url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
53
+ url = "#{ url }?#{ query_params.to_query }" if query_params.present?
54
+ url
55
+ else
56
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
57
+ warn "related_link for #{relationship} could not be generated"
58
+ relationship._warned_missing_route = true
59
+ end
60
+ nil
61
+ end
41
62
  end
42
63
 
43
64
  def relationships_self_link(source, relationship)
44
- "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
65
+ if relationship._routed
66
+ "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
67
+ else
68
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
69
+ warn "self_link for #{relationship} could not be generated"
70
+ relationship._warned_missing_route = true
71
+ end
72
+ nil
73
+ end
45
74
  end
46
75
 
47
76
  def self_link(source)
48
- if engine?
49
- engine_resource_url(source)
77
+ if source.class._routed
78
+ resource_url(source)
50
79
  else
51
- regular_resource_url(source)
80
+ if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route
81
+ warn "self_link for #{source.class} could not be generated"
82
+ source.class._warned_missing_route = true
83
+ end
84
+ nil
52
85
  end
53
86
  end
54
87
 
55
88
  private
56
89
 
57
- def build_engine_name
90
+ def build_engine
58
91
  scopes = module_scopes_from_class(primary_resource_klass)
59
92
 
60
93
  begin
61
94
  unless scopes.empty?
62
95
  "#{ scopes.first.to_s.camelize }::Engine".safe_constantize
63
96
  end
64
- rescue LoadError => e
65
- nil
66
- end
67
- end
68
-
69
- def engine_path_from_resource_class(klass)
70
- path_name = engine_resources_path_name_from_class(klass)
71
- engine_name.routes.url_helpers.public_send(path_name)
72
- end
73
97
 
74
- def engine_primary_resources_path
75
- engine_path_from_resource_class(primary_resource_klass)
76
- end
77
-
78
- def engine_primary_resources_url
79
- "#{ base_url }#{ engine_primary_resources_path }"
80
- end
81
-
82
- def engine_resource_path(source)
83
- resource_path_name = engine_resource_path_name_from_source(source)
84
- engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
85
- end
86
-
87
- def engine_resource_path_name_from_source(source)
88
- scopes = module_scopes_from_class(source.class)[1..-1]
89
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
90
- end_path_name = source.class._type.to_s.singularize
91
- [base_path_name, end_path_name, "path"].reject(&:blank?).join("_")
92
- end
93
-
94
- def engine_resource_url(source)
95
- "#{ base_url }#{ engine_resource_path(source) }"
96
- end
97
-
98
- def engine_resources_path_name_from_class(klass)
99
- scopes = module_scopes_from_class(klass)[1..-1]
100
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
101
- end_path_name = klass._type.to_s
102
-
103
- if base_path_name.blank?
104
- "#{ end_path_name }_path"
105
- else
106
- "#{ base_path_name }_#{ end_path_name }_path"
98
+ # :nocov:
99
+ rescue LoadError => _e
100
+ nil
101
+ # :nocov:
107
102
  end
108
103
  end
109
104
 
@@ -112,10 +107,14 @@ module JSONAPI
112
107
  end
113
108
 
114
109
  def formatted_module_path_from_class(klass)
115
- scopes = module_scopes_from_class(klass)
110
+ scopes = if @engine
111
+ module_scopes_from_class(klass)[1..-1]
112
+ else
113
+ module_scopes_from_class(klass)
114
+ end
116
115
 
117
116
  unless scopes.empty?
118
- "/#{ scopes.map{ |scope| format_route(scope.to_s.underscore) }.join('/') }/"
117
+ "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/"
119
118
  else
120
119
  "/"
121
120
  end
@@ -125,24 +124,21 @@ module JSONAPI
125
124
  klass.name.to_s.split("::")[0...-1]
126
125
  end
127
126
 
128
- def regular_resources_path(source_klass)
129
- @resources_path_cache.get(source_klass)
130
- end
131
-
132
- def regular_primary_resources_path
133
- regular_resources_path(primary_resource_klass)
127
+ def resources_path(source_klass)
128
+ @_resources_path ||= {}
129
+ @_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
134
130
  end
135
131
 
136
- def regular_primary_resources_url
137
- "#{ base_url }#{ regular_primary_resources_path }"
138
- end
139
-
140
- def regular_resource_path(source)
141
- "#{regular_resources_path(source.class)}/#{source.id}"
132
+ def resource_path(source)
133
+ if source.class.singleton?
134
+ resources_path(source.class)
135
+ else
136
+ "#{resources_path(source.class)}/#{source.id}"
137
+ end
142
138
  end
143
139
 
144
- def regular_resource_url(source)
145
- "#{ base_url }#{ regular_resource_path(source) }"
140
+ def resource_url(source)
141
+ "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
146
142
  end
147
143
 
148
144
  def route_for_relationship(relationship)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
 
3
5
  module JSONAPI
@@ -7,16 +9,10 @@ module JSONAPI
7
9
  def self.install
8
10
  Mime::Type.register JSONAPI::MEDIA_TYPE, :api_json
9
11
 
10
- # :nocov:
11
- if Rails::VERSION::MAJOR >= 5
12
- parsers = ActionDispatch::Request.parameter_parsers.merge(
13
- Mime::Type.lookup(JSONAPI::MEDIA_TYPE).symbol => parser
14
- )
15
- ActionDispatch::Request.parameter_parsers = parsers
16
- else
17
- ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)] = parser
18
- end
19
- # :nocov:
12
+ parsers = ActionDispatch::Request.parameter_parsers.merge(
13
+ Mime::Type.lookup(JSONAPI::MEDIA_TYPE).symbol => parser
14
+ )
15
+ ActionDispatch::Request.parameter_parsers = parsers
20
16
  end
21
17
 
22
18
  def self.parser
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
 
3
5
  # Cache which memoizes the given block.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class Operation
3
5
  attr_reader :resource_klass, :operation_type, :options
@@ -8,17 +10,28 @@ module JSONAPI
8
10
  @options = options
9
11
  end
10
12
 
11
- def transactional?
12
- JSONAPI::Processor._processor_from_resource_type(resource_klass).transactional_operation_type?(operation_type)
13
- end
14
-
15
13
  def process
16
14
  processor.process
17
15
  end
18
16
 
19
17
  private
20
18
  def processor
21
- JSONAPI::Processor.processor_instance_for(resource_klass, operation_type, options)
19
+ self.class.processor_instance_for(resource_klass, operation_type, options)
20
+ end
21
+
22
+ class << self
23
+ def processor_instance_for(resource_klass, operation_type, params)
24
+ _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
25
+ end
26
+
27
+ def _processor_from_resource_type(resource_klass)
28
+ processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
29
+ if processor.nil?
30
+ processor = JSONAPI.configuration.default_processor_klass
31
+ end
32
+
33
+ return processor
34
+ end
22
35
  end
23
36
  end
24
37
  end