jsonapi-resources 0.9.3 → 0.10.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +34 -11
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
- data/lib/jsonapi/active_relation/join_manager.rb +297 -0
- data/lib/jsonapi/active_relation_resource.rb +879 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
- data/lib/jsonapi/basic_resource.rb +1162 -0
- data/lib/jsonapi/cached_response_fragment.rb +127 -0
- data/lib/jsonapi/compiled_json.rb +11 -1
- data/lib/jsonapi/configuration.rb +63 -8
- data/lib/jsonapi/error.rb +27 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +63 -40
- data/lib/jsonapi/formatter.rb +3 -3
- data/lib/jsonapi/include_directives.rb +18 -65
- data/lib/jsonapi/link_builder.rb +74 -80
- data/lib/jsonapi/operation.rb +16 -5
- data/lib/jsonapi/operation_result.rb +74 -16
- data/lib/jsonapi/path.rb +43 -0
- data/lib/jsonapi/path_segment.rb +76 -0
- data/lib/jsonapi/processor.rb +237 -110
- data/lib/jsonapi/relationship.rb +144 -15
- data/lib/jsonapi/request_parser.rb +412 -357
- data/lib/jsonapi/resource.rb +3 -1263
- data/lib/jsonapi/resource_controller_metal.rb +5 -2
- data/lib/jsonapi/resource_fragment.rb +47 -0
- data/lib/jsonapi/resource_id_tree.rb +112 -0
- data/lib/jsonapi/resource_identity.rb +42 -0
- data/lib/jsonapi/resource_serializer.rb +143 -285
- data/lib/jsonapi/resource_set.rb +176 -0
- data/lib/jsonapi/resources/railtie.rb +9 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +105 -83
- data/lib/jsonapi/routing_ext.rb +48 -26
- data/lib/jsonapi-resources.rb +20 -4
- data/lib/tasks/check_upgrade.rake +52 -0
- metadata +47 -17
- data/lib/jsonapi/cached_resource_fragment.rb +0 -127
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -0,0 +1,127 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class CachedResponseFragment
|
3
|
+
|
4
|
+
Lookup = Struct.new(:resource_klass, :serializer_config_key, :context, :context_key, :cache_ids) do
|
5
|
+
|
6
|
+
def type
|
7
|
+
resource_klass._type
|
8
|
+
end
|
9
|
+
|
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
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
|
43
|
+
:links_json, :attributes_json, :meta_json
|
44
|
+
|
45
|
+
def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
|
46
|
+
links_json, attributes_json, meta_json)
|
47
|
+
@resource_klass = resource_klass
|
48
|
+
@id = id
|
49
|
+
@type = type
|
50
|
+
@context = context
|
51
|
+
@fetchable_fields = Set.new(fetchable_fields)
|
52
|
+
|
53
|
+
# Relationships left uncompiled because we'll often want to insert included ids on retrieval
|
54
|
+
@relationships = relationships
|
55
|
+
|
56
|
+
@links_json = CompiledJson.of(links_json)
|
57
|
+
@attributes_json = CompiledJson.of(attributes_json)
|
58
|
+
@meta_json = CompiledJson.of(meta_json)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_cache_value
|
62
|
+
{
|
63
|
+
id: id,
|
64
|
+
type: type,
|
65
|
+
fetchable: fetchable_fields,
|
66
|
+
rels: relationships,
|
67
|
+
links: links_json.try(:to_s),
|
68
|
+
attrs: attributes_json.try(:to_s),
|
69
|
+
meta: meta_json.try(:to_s)
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
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
|
77
|
+
|
78
|
+
keys = lookups.map(&:keys).flatten(1)
|
79
|
+
|
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] ||= {}
|
86
|
+
|
87
|
+
if hits.has_key?(key)
|
88
|
+
hash[resource_klass][id] = self.from_cache_value(resource_klass, context, hits[key])
|
89
|
+
else
|
90
|
+
hash[resource_klass][id] = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
hash
|
94
|
+
end
|
95
|
+
end
|
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
|
+
|
113
|
+
def self.from_cache_value(resource_klass, context, h)
|
114
|
+
new(
|
115
|
+
resource_klass,
|
116
|
+
h.fetch(:id),
|
117
|
+
h.fetch(:type),
|
118
|
+
context,
|
119
|
+
h.fetch(:fetchable),
|
120
|
+
h.fetch(:rels, nil),
|
121
|
+
h.fetch(:links, nil),
|
122
|
+
h.fetch(:attrs, nil),
|
123
|
+
h.fetch(:meta, nil)
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -5,6 +5,7 @@ module JSONAPI
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.of(obj)
|
8
|
+
# :nocov:
|
8
9
|
case obj
|
9
10
|
when NilClass then nil
|
10
11
|
when CompiledJson then obj
|
@@ -12,6 +13,7 @@ module JSONAPI
|
|
12
13
|
when Hash then CompiledJson.compile(obj)
|
13
14
|
else raise "Can't figure out how to turn #{obj.inspect} into CompiledJson"
|
14
15
|
end
|
16
|
+
# :nocov:
|
15
17
|
end
|
16
18
|
|
17
19
|
def initialize(json, h = nil)
|
@@ -19,7 +21,7 @@ module JSONAPI
|
|
19
21
|
@h = h
|
20
22
|
end
|
21
23
|
|
22
|
-
def to_json(*
|
24
|
+
def to_json(*_args)
|
23
25
|
@json
|
24
26
|
end
|
25
27
|
|
@@ -27,9 +29,17 @@ module JSONAPI
|
|
27
29
|
@json
|
28
30
|
end
|
29
31
|
|
32
|
+
# :nocov:
|
30
33
|
def to_h
|
31
34
|
@h ||= JSON.parse(@json)
|
32
35
|
end
|
36
|
+
# :nocov:
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
# :nocov:
|
40
|
+
to_h[key]
|
41
|
+
# :nocov:
|
42
|
+
end
|
33
43
|
|
34
44
|
undef_method :as_json
|
35
45
|
end
|
@@ -8,13 +8,17 @@ module JSONAPI
|
|
8
8
|
:resource_key_type,
|
9
9
|
:route_format,
|
10
10
|
:raise_if_parameters_not_allowed,
|
11
|
-
:
|
11
|
+
:warn_on_route_setup_issues,
|
12
|
+
:warn_on_missing_routes,
|
13
|
+
:warn_on_performance_issues,
|
14
|
+
:default_allow_include_to_one,
|
15
|
+
:default_allow_include_to_many,
|
12
16
|
:allow_sort,
|
13
17
|
:allow_filter,
|
14
18
|
:default_paginator,
|
15
19
|
:default_page_size,
|
16
20
|
:maximum_page_size,
|
17
|
-
:
|
21
|
+
:default_processor_klass_name,
|
18
22
|
:use_text_errors,
|
19
23
|
:top_level_links_include_pagination,
|
20
24
|
:top_level_meta_include_record_count,
|
@@ -23,6 +27,7 @@ module JSONAPI
|
|
23
27
|
:top_level_meta_page_count_key,
|
24
28
|
:allow_transactions,
|
25
29
|
:include_backtraces_in_errors,
|
30
|
+
:include_application_backtraces_in_errors,
|
26
31
|
:exception_class_whitelist,
|
27
32
|
:whitelist_all_exceptions,
|
28
33
|
:always_include_to_one_linkage_data,
|
@@ -30,9 +35,11 @@ module JSONAPI
|
|
30
35
|
:cache_formatters,
|
31
36
|
:use_relationship_reflection,
|
32
37
|
:resource_cache,
|
38
|
+
:default_caching,
|
33
39
|
:default_resource_cache_field,
|
34
40
|
:resource_cache_digest_function,
|
35
|
-
:resource_cache_usage_report_function
|
41
|
+
:resource_cache_usage_report_function,
|
42
|
+
:default_exclude_links
|
36
43
|
|
37
44
|
def initialize
|
38
45
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
@@ -45,12 +52,17 @@ module JSONAPI
|
|
45
52
|
self.resource_key_type = :integer
|
46
53
|
|
47
54
|
# optional request features
|
48
|
-
self.
|
55
|
+
self.default_allow_include_to_one = true
|
56
|
+
self.default_allow_include_to_many = true
|
49
57
|
self.allow_sort = true
|
50
58
|
self.allow_filter = true
|
51
59
|
|
52
60
|
self.raise_if_parameters_not_allowed = true
|
53
61
|
|
62
|
+
self.warn_on_route_setup_issues = true
|
63
|
+
self.warn_on_missing_routes = true
|
64
|
+
self.warn_on_performance_issues = true
|
65
|
+
|
54
66
|
# :none, :offset, :paged, or a custom paginator name
|
55
67
|
self.default_paginator = :none
|
56
68
|
|
@@ -71,8 +83,12 @@ module JSONAPI
|
|
71
83
|
self.use_text_errors = false
|
72
84
|
|
73
85
|
# Whether or not to include exception backtraces in JSONAPI error
|
74
|
-
# responses. Defaults to `false` in
|
75
|
-
self.include_backtraces_in_errors =
|
86
|
+
# responses. Defaults to `false` in anything other than development or test.
|
87
|
+
self.include_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
|
88
|
+
|
89
|
+
# Whether or not to include exception application backtraces in JSONAPI error
|
90
|
+
# responses. Defaults to `false` in anything other than development or test.
|
91
|
+
self.include_application_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
|
76
92
|
|
77
93
|
# List of classes that should not be rescued by the operations processor.
|
78
94
|
# For example, if you use Pundit for authorization, you might
|
@@ -94,7 +110,7 @@ module JSONAPI
|
|
94
110
|
|
95
111
|
# The default Operation Processor to use if one is not defined specifically
|
96
112
|
# for a Resource.
|
97
|
-
self.
|
113
|
+
self.default_processor_klass_name = 'JSONAPI::Processor'
|
98
114
|
|
99
115
|
# Allows transactions for creating and updating records
|
100
116
|
# Set this to false if your backend does not support transactions (e.g. Mongodb)
|
@@ -117,6 +133,11 @@ module JSONAPI
|
|
117
133
|
# Rails cache store.
|
118
134
|
self.resource_cache = nil
|
119
135
|
|
136
|
+
# Cache resources by default
|
137
|
+
# Cache resources by default. Individual resources can be excluded from caching by calling:
|
138
|
+
# `caching false`
|
139
|
+
self.default_caching = false
|
140
|
+
|
120
141
|
# Default resource cache field
|
121
142
|
# On Resources with caching enabled, this field will be used to check for out-of-date
|
122
143
|
# cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
|
@@ -131,6 +152,12 @@ module JSONAPI
|
|
131
152
|
# Optionally provide a callable which JSONAPI will call with information about cache
|
132
153
|
# performance. Should accept three arguments: resource name, hits count, misses count.
|
133
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
|
134
161
|
end
|
135
162
|
|
136
163
|
def cache_formatters=(bool)
|
@@ -198,10 +225,26 @@ module JSONAPI
|
|
198
225
|
end
|
199
226
|
|
200
227
|
def default_processor_klass=(default_processor_klass)
|
228
|
+
ActiveSupport::Deprecation.warn('`default_processor_klass` has been replaced by `default_processor_klass_name`.')
|
201
229
|
@default_processor_klass = default_processor_klass
|
202
230
|
end
|
203
231
|
|
204
|
-
|
232
|
+
def default_processor_klass
|
233
|
+
@default_processor_klass ||= default_processor_klass_name.safe_constantize
|
234
|
+
end
|
235
|
+
|
236
|
+
def default_processor_klass_name=(default_processor_klass_name)
|
237
|
+
@default_processor_klass = nil
|
238
|
+
@default_processor_klass_name = default_processor_klass_name
|
239
|
+
end
|
240
|
+
|
241
|
+
def allow_include=(allow_include)
|
242
|
+
ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
|
243
|
+
@default_allow_include_to_one = allow_include
|
244
|
+
@default_allow_include_to_many = allow_include
|
245
|
+
end
|
246
|
+
|
247
|
+
attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
|
205
248
|
|
206
249
|
attr_writer :default_paginator
|
207
250
|
|
@@ -225,6 +268,8 @@ module JSONAPI
|
|
225
268
|
|
226
269
|
attr_writer :include_backtraces_in_errors
|
227
270
|
|
271
|
+
attr_writer :include_application_backtraces_in_errors
|
272
|
+
|
228
273
|
attr_writer :exception_class_whitelist
|
229
274
|
|
230
275
|
attr_writer :whitelist_all_exceptions
|
@@ -235,15 +280,25 @@ module JSONAPI
|
|
235
280
|
|
236
281
|
attr_writer :raise_if_parameters_not_allowed
|
237
282
|
|
283
|
+
attr_writer :warn_on_route_setup_issues
|
284
|
+
|
285
|
+
attr_writer :warn_on_missing_routes
|
286
|
+
|
287
|
+
attr_writer :warn_on_performance_issues
|
288
|
+
|
238
289
|
attr_writer :use_relationship_reflection
|
239
290
|
|
240
291
|
attr_writer :resource_cache
|
241
292
|
|
293
|
+
attr_writer :default_caching
|
294
|
+
|
242
295
|
attr_writer :default_resource_cache_field
|
243
296
|
|
244
297
|
attr_writer :resource_cache_digest_function
|
245
298
|
|
246
299
|
attr_writer :resource_cache_usage_report_function
|
300
|
+
|
301
|
+
attr_writer :default_exclude_links
|
247
302
|
end
|
248
303
|
|
249
304
|
class << self
|
data/lib/jsonapi/error.rb
CHANGED
@@ -24,6 +24,33 @@ module JSONAPI
|
|
24
24
|
instance_variables.each {|var| hash[var.to_s.delete('@')] = instance_variable_get(var) unless instance_variable_get(var).nil? }
|
25
25
|
hash
|
26
26
|
end
|
27
|
+
|
28
|
+
def update_with_overrides(error_object_overrides)
|
29
|
+
@title = error_object_overrides[:title] || @title
|
30
|
+
@detail = error_object_overrides[:detail] || @detail
|
31
|
+
@id = error_object_overrides[:id] || @id
|
32
|
+
@href = error_object_overrides[:href] || href
|
33
|
+
|
34
|
+
if error_object_overrides[:code]
|
35
|
+
# :nocov:
|
36
|
+
@code = if JSONAPI.configuration.use_text_errors
|
37
|
+
TEXT_ERRORS[error_object_overrides[:code]]
|
38
|
+
else
|
39
|
+
error_object_overrides[:code]
|
40
|
+
end
|
41
|
+
# :nocov:
|
42
|
+
end
|
43
|
+
|
44
|
+
@source = error_object_overrides[:source] || @source
|
45
|
+
@links = error_object_overrides[:links] || @links
|
46
|
+
|
47
|
+
if error_object_overrides[:status]
|
48
|
+
# :nocov:
|
49
|
+
@status = Rack::Utils::SYMBOL_TO_STATUS_CODE[error_object_overrides[:status]].to_s
|
50
|
+
# :nocov:
|
51
|
+
end
|
52
|
+
@meta = error_object_overrides[:meta] || @meta
|
53
|
+
end
|
27
54
|
end
|
28
55
|
|
29
56
|
class Warning
|
data/lib/jsonapi/error_codes.rb
CHANGED
@@ -20,6 +20,7 @@ module JSONAPI
|
|
20
20
|
INVALID_FILTERS_SYNTAX = '120'
|
21
21
|
SAVE_FAILED = '121'
|
22
22
|
INVALID_DATA_FORMAT = '122'
|
23
|
+
INVALID_RELATIONSHIP = '123'
|
23
24
|
BAD_REQUEST = '400'
|
24
25
|
FORBIDDEN = '403'
|
25
26
|
RECORD_NOT_FOUND = '404'
|
@@ -50,6 +51,7 @@ module JSONAPI
|
|
50
51
|
INVALID_FILTERS_SYNTAX => 'INVALID_FILTERS_SYNTAX',
|
51
52
|
SAVE_FAILED => 'SAVE_FAILED',
|
52
53
|
INVALID_DATA_FORMAT => 'INVALID_DATA_FORMAT',
|
54
|
+
INVALID_RELATIONSHIP => 'INVALID_RELATIONSHIP',
|
53
55
|
FORBIDDEN => 'FORBIDDEN',
|
54
56
|
RECORD_NOT_FOUND => 'RECORD_NOT_FOUND',
|
55
57
|
NOT_ACCEPTABLE => 'NOT_ACCEPTABLE',
|
data/lib/jsonapi/exceptions.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Exceptions
|
3
3
|
class Error < RuntimeError
|
4
|
-
|
4
|
+
attr_reader :error_object_overrides
|
5
5
|
|
6
6
|
def initialize(error_object_overrides = {})
|
7
7
|
@error_object_overrides = error_object_overrides
|
@@ -18,6 +18,22 @@ module JSONAPI
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class Errors < Error
|
22
|
+
def initialize(errors, error_object_overrides = {})
|
23
|
+
@errors = errors
|
24
|
+
|
25
|
+
@errors.each do |error|
|
26
|
+
error.update_with_overrides(error_object_overrides)
|
27
|
+
end
|
28
|
+
|
29
|
+
super(error_object_overrides)
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors
|
33
|
+
@errors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
21
37
|
class InternalServerError < Error
|
22
38
|
attr_accessor :exception
|
23
39
|
|
@@ -33,6 +49,12 @@ module JSONAPI
|
|
33
49
|
meta[:backtrace] = exception.backtrace
|
34
50
|
end
|
35
51
|
|
52
|
+
if JSONAPI.configuration.include_application_backtraces_in_errors
|
53
|
+
meta ||= Hash.new
|
54
|
+
meta[:exception] ||= exception.message
|
55
|
+
meta[:application_backtrace] = exception.backtrace.select{|line| line =~ /#{Rails.root}/}
|
56
|
+
end
|
57
|
+
|
36
58
|
[create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR,
|
37
59
|
status: :internal_server_error,
|
38
60
|
title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title',
|
@@ -119,49 +141,30 @@ module JSONAPI
|
|
119
141
|
end
|
120
142
|
end
|
121
143
|
|
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
144
|
class BadRequest < Error
|
143
|
-
def initialize(exception)
|
145
|
+
def initialize(exception, error_object_overrides = {})
|
144
146
|
@exception = exception
|
147
|
+
super(error_object_overrides)
|
145
148
|
end
|
146
149
|
|
147
150
|
def errors
|
148
|
-
[
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
[create_error_object(code: JSONAPI::BAD_REQUEST,
|
152
|
+
status: :bad_request,
|
153
|
+
title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
|
154
|
+
default: 'Bad Request'),
|
155
|
+
detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
|
156
|
+
default: @exception))]
|
154
157
|
end
|
155
158
|
end
|
156
159
|
|
157
160
|
class InvalidRequestFormat < Error
|
158
161
|
def errors
|
159
|
-
[
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
162
|
+
[create_error_object(code: JSONAPI::BAD_REQUEST,
|
163
|
+
status: :bad_request,
|
164
|
+
title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
|
165
|
+
default: 'Bad Request'),
|
166
|
+
detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
|
167
|
+
default: 'Request must be a hash'))]
|
165
168
|
end
|
166
169
|
end
|
167
170
|
|
@@ -324,6 +327,26 @@ module JSONAPI
|
|
324
327
|
end
|
325
328
|
end
|
326
329
|
|
330
|
+
class InvalidRelationship < Error
|
331
|
+
attr_accessor :relationship_name, :type
|
332
|
+
|
333
|
+
def initialize(type, relationship_name, error_object_overrides = {})
|
334
|
+
@relationship_name = relationship_name
|
335
|
+
@type = type
|
336
|
+
super(error_object_overrides)
|
337
|
+
end
|
338
|
+
|
339
|
+
def errors
|
340
|
+
[create_error_object(code: JSONAPI::INVALID_RELATIONSHIP,
|
341
|
+
status: :bad_request,
|
342
|
+
title: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.title',
|
343
|
+
default: 'Invalid relationship'),
|
344
|
+
detail: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.detail',
|
345
|
+
default: "#{relationship_name} is not a valid field for #{type}.",
|
346
|
+
relationship_name: relationship_name, type: type))]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
327
350
|
class InvalidInclude < Error
|
328
351
|
attr_accessor :relationship, :resource
|
329
352
|
|
@@ -339,7 +362,7 @@ module JSONAPI
|
|
339
362
|
title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
|
340
363
|
default: 'Invalid field'),
|
341
364
|
detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
|
342
|
-
default: "#{relationship} is not a valid relationship of #{resource}",
|
365
|
+
default: "#{relationship} is not a valid includable relationship of #{resource}",
|
343
366
|
relationship: relationship, resource: resource))]
|
344
367
|
end
|
345
368
|
end
|
@@ -374,11 +397,11 @@ module JSONAPI
|
|
374
397
|
|
375
398
|
def errors
|
376
399
|
[create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
400
|
+
status: :bad_request,
|
401
|
+
title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
|
402
|
+
default: 'Param not allowed'),
|
403
|
+
detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
|
404
|
+
default: "#{param} is not allowed.", param: param))]
|
382
405
|
end
|
383
406
|
end
|
384
407
|
|
data/lib/jsonapi/formatter.rb
CHANGED
@@ -108,7 +108,7 @@ end
|
|
108
108
|
|
109
109
|
class DasherizedKeyFormatter < JSONAPI::KeyFormatter
|
110
110
|
class << self
|
111
|
-
def format(
|
111
|
+
def format(_key)
|
112
112
|
super.underscore.dasherize
|
113
113
|
end
|
114
114
|
|
@@ -146,7 +146,7 @@ end
|
|
146
146
|
|
147
147
|
class CamelizedRouteFormatter < JSONAPI::RouteFormatter
|
148
148
|
class << self
|
149
|
-
def format(
|
149
|
+
def format(_route)
|
150
150
|
super.camelize(:lower)
|
151
151
|
end
|
152
152
|
|
@@ -158,7 +158,7 @@ end
|
|
158
158
|
|
159
159
|
class DasherizedRouteFormatter < JSONAPI::RouteFormatter
|
160
160
|
class << self
|
161
|
-
def format(
|
161
|
+
def format(_route)
|
162
162
|
super.dasherize
|
163
163
|
end
|
164
164
|
|
@@ -4,14 +4,12 @@ module JSONAPI
|
|
4
4
|
# For example ['posts.comments.tags']
|
5
5
|
# will transform into =>
|
6
6
|
# {
|
7
|
-
# posts:{
|
8
|
-
#
|
9
|
-
# include_related:{
|
7
|
+
# posts: {
|
8
|
+
# include_related: {
|
10
9
|
# comments:{
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# include:true
|
10
|
+
# include_related: {
|
11
|
+
# tags: {
|
12
|
+
# include_related: {}
|
15
13
|
# }
|
16
14
|
# }
|
17
15
|
# }
|
@@ -19,9 +17,8 @@ module JSONAPI
|
|
19
17
|
# }
|
20
18
|
# }
|
21
19
|
|
22
|
-
def initialize(resource_klass, includes_array
|
20
|
+
def initialize(resource_klass, includes_array)
|
23
21
|
@resource_klass = resource_klass
|
24
|
-
@force_eager_load = force_eager_load
|
25
22
|
@include_directives_hash = { include_related: {} }
|
26
23
|
includes_array.each do |include|
|
27
24
|
parse_include(include)
|
@@ -32,69 +29,25 @@ module JSONAPI
|
|
32
29
|
@include_directives_hash
|
33
30
|
end
|
34
31
|
|
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))
|
41
|
-
end
|
42
|
-
|
43
32
|
private
|
44
33
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
raise JSONAPI::Exceptions::InvalidInclude.new(current_resource_klass, 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
|
34
|
+
def parse_include(include)
|
35
|
+
path = JSONAPI::Path.new(resource_klass: @resource_klass,
|
36
|
+
path_string: include,
|
37
|
+
ensure_default_field: false,
|
38
|
+
parse_fields: false)
|
69
39
|
|
70
|
-
|
71
|
-
sub = get_includes(sub_directive, only_joined_includes)
|
72
|
-
sub.any? ? { name => sub } : name
|
73
|
-
end
|
74
|
-
end
|
40
|
+
current = @include_directives_hash
|
75
41
|
|
76
|
-
|
77
|
-
|
78
|
-
local_path = ''
|
42
|
+
path.segments.each do |segment|
|
43
|
+
relationship_name = segment.relationship.name.to_sym
|
79
44
|
|
80
|
-
|
81
|
-
|
82
|
-
related = get_related(local_path)
|
83
|
-
related[:include] = true
|
45
|
+
current[:include_related][relationship_name] ||= { include_related: {} }
|
46
|
+
current = current[:include_related][relationship_name]
|
84
47
|
end
|
85
|
-
end
|
86
48
|
|
87
|
-
|
88
|
-
|
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
|
49
|
+
rescue JSONAPI::Exceptions::InvalidRelationship => _e
|
50
|
+
raise JSONAPI::Exceptions::InvalidInclude.new(@resource_klass, include)
|
98
51
|
end
|
99
52
|
end
|
100
53
|
end
|