jsonapi-resources 0.9.3 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|