jsonapi-resources 0.10.6 → 0.11.0.beta2
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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +39 -2
- data/lib/generators/jsonapi/controller_generator.rb +2 -0
- data/lib/generators/jsonapi/resource_generator.rb +2 -0
- data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +3 -2
- data/lib/jsonapi/active_relation/join_manager.rb +30 -18
- data/lib/jsonapi/active_relation/join_manager_v10.rb +305 -0
- data/lib/jsonapi/active_relation_retrieval.rb +885 -0
- data/lib/jsonapi/active_relation_retrieval_v09.rb +715 -0
- data/lib/jsonapi/{active_relation_resource.rb → active_relation_retrieval_v10.rb} +113 -135
- data/lib/jsonapi/acts_as_resource_controller.rb +49 -49
- data/lib/jsonapi/cached_response_fragment.rb +4 -2
- data/lib/jsonapi/callbacks.rb +2 -0
- data/lib/jsonapi/compiled_json.rb +2 -0
- data/lib/jsonapi/configuration.rb +35 -15
- data/lib/jsonapi/error.rb +2 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +2 -0
- data/lib/jsonapi/formatter.rb +2 -0
- data/lib/jsonapi/include_directives.rb +77 -19
- data/lib/jsonapi/link_builder.rb +2 -0
- data/lib/jsonapi/mime_types.rb +6 -10
- data/lib/jsonapi/naive_cache.rb +2 -0
- data/lib/jsonapi/operation.rb +2 -0
- data/lib/jsonapi/operation_result.rb +2 -0
- data/lib/jsonapi/paginator.rb +2 -0
- data/lib/jsonapi/path.rb +2 -0
- data/lib/jsonapi/path_segment.rb +4 -2
- data/lib/jsonapi/processor.rb +95 -140
- data/lib/jsonapi/relationship.rb +89 -35
- data/lib/jsonapi/{request_parser.rb → request.rb} +157 -164
- data/lib/jsonapi/resource.rb +7 -2
- data/lib/jsonapi/{basic_resource.rb → resource_common.rb} +187 -88
- data/lib/jsonapi/resource_controller.rb +2 -0
- data/lib/jsonapi/resource_controller_metal.rb +2 -0
- data/lib/jsonapi/resource_fragment.rb +17 -15
- data/lib/jsonapi/resource_identity.rb +6 -0
- data/lib/jsonapi/resource_serializer.rb +20 -4
- data/lib/jsonapi/resource_set.rb +36 -16
- data/lib/jsonapi/resource_tree.rb +191 -0
- data/lib/jsonapi/resources/railtie.rb +3 -1
- data/lib/jsonapi/resources/version.rb +3 -1
- data/lib/jsonapi/response_document.rb +4 -2
- data/lib/jsonapi/routing_ext.rb +4 -2
- data/lib/jsonapi/simple_resource.rb +13 -0
- data/lib/jsonapi-resources.rb +10 -4
- data/lib/tasks/check_upgrade.rake +3 -1
- metadata +47 -15
- data/lib/jsonapi/resource_id_tree.rb +0 -112
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'csv'
|
2
4
|
|
3
5
|
module JSONAPI
|
@@ -13,7 +15,7 @@ module JSONAPI
|
|
13
15
|
:transaction
|
14
16
|
end
|
15
17
|
|
16
|
-
attr_reader :response_document
|
18
|
+
attr_reader :response_document, :jsonapi_request
|
17
19
|
|
18
20
|
def index
|
19
21
|
process_request
|
@@ -76,48 +78,54 @@ module JSONAPI
|
|
76
78
|
end
|
77
79
|
|
78
80
|
def process_request
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
begin
|
82
|
+
setup_response_document
|
83
|
+
verify_content_type_header
|
84
|
+
verify_accept_header
|
85
|
+
parse_request
|
86
|
+
execute_request
|
87
|
+
rescue => e
|
88
|
+
handle_exceptions(e)
|
84
89
|
end
|
90
|
+
render_response_document
|
91
|
+
end
|
85
92
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
key_formatter: key_formatter,
|
90
|
-
server_error_callbacks: (self.class.server_error_callbacks || []))
|
91
|
-
|
92
|
-
transactional = request_parser.transactional?
|
93
|
+
def setup_response_document
|
94
|
+
@response_document = create_response_document
|
95
|
+
end
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
op.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
97
|
+
def parse_request
|
98
|
+
@jsonapi_request = JSONAPI::Request.new(
|
99
|
+
params,
|
100
|
+
context: context,
|
101
|
+
key_formatter: key_formatter,
|
102
|
+
server_error_callbacks: (self.class.server_error_callbacks || []))
|
103
|
+
fail JSONAPI::Exceptions::Errors.new(@jsonapi_request.errors) if @jsonapi_request.errors.any?
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute_request
|
107
|
+
process_operations(jsonapi_request.transactional?) do
|
108
|
+
run_callbacks :process_operations do
|
109
|
+
jsonapi_request.operations.each do |op|
|
110
|
+
op.options[:serializer] = resource_serializer_klass.new(
|
111
|
+
op.resource_klass,
|
112
|
+
include_directives: op.options[:include_directives],
|
113
|
+
fields: op.options[:fields],
|
114
|
+
base_url: base_url,
|
115
|
+
key_formatter: key_formatter,
|
116
|
+
route_formatter: route_formatter,
|
117
|
+
serialization_options: serialization_options,
|
118
|
+
controller: self
|
119
|
+
)
|
120
|
+
op.options[:cache_serializer_output] = !JSONAPI.configuration.resource_cache.nil?
|
121
|
+
|
122
|
+
process_operation(op)
|
115
123
|
end
|
116
124
|
end
|
117
|
-
|
118
|
-
|
125
|
+
if response_document.has_errors?
|
126
|
+
raise ActiveRecord::Rollback
|
127
|
+
end
|
119
128
|
end
|
120
|
-
render_response_document
|
121
129
|
end
|
122
130
|
|
123
131
|
def process_operations(transactional)
|
@@ -161,24 +169,16 @@ module JSONAPI
|
|
161
169
|
|
162
170
|
def verify_content_type_header
|
163
171
|
if ['create', 'create_relationship', 'update_relationship', 'update'].include?(params[:action])
|
164
|
-
unless request.
|
165
|
-
fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.
|
172
|
+
unless request.media_type == JSONAPI::MEDIA_TYPE
|
173
|
+
fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.media_type)
|
166
174
|
end
|
167
175
|
end
|
168
|
-
true
|
169
|
-
rescue => e
|
170
|
-
handle_exceptions(e)
|
171
|
-
false
|
172
176
|
end
|
173
177
|
|
174
178
|
def verify_accept_header
|
175
179
|
unless valid_accept_media_type?
|
176
180
|
fail JSONAPI::Exceptions::NotAcceptableError.new(request.accept)
|
177
181
|
end
|
178
|
-
true
|
179
|
-
rescue => e
|
180
|
-
handle_exceptions(e)
|
181
|
-
false
|
182
182
|
end
|
183
183
|
|
184
184
|
def valid_accept_media_type?
|
@@ -273,7 +273,7 @@ module JSONAPI
|
|
273
273
|
when ActionController::ParameterMissing
|
274
274
|
errors = JSONAPI::Exceptions::ParameterMissing.new(e.param).errors
|
275
275
|
else
|
276
|
-
if JSONAPI.configuration.
|
276
|
+
if JSONAPI.configuration.exception_class_allowed?(e)
|
277
277
|
raise e
|
278
278
|
else
|
279
279
|
if self.class.server_error_callbacks
|
@@ -308,7 +308,7 @@ module JSONAPI
|
|
308
308
|
# caught that is not a JSONAPI::Exceptions::Error
|
309
309
|
# Useful for additional logging or notification configuration that
|
310
310
|
# would normally depend on rails catching and rendering an exception.
|
311
|
-
# Ignores
|
311
|
+
# Ignores allowlist exceptions from config
|
312
312
|
|
313
313
|
module ClassMethods
|
314
314
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
class CachedResponseFragment
|
3
5
|
|
@@ -51,8 +53,8 @@ module JSONAPI
|
|
51
53
|
@fetchable_fields = Set.new(fetchable_fields)
|
52
54
|
|
53
55
|
# Relationships left uncompiled because we'll often want to insert included ids on retrieval
|
54
|
-
|
55
|
-
|
56
|
+
@relationships = relationships
|
57
|
+
|
56
58
|
@links_json = CompiledJson.of(links_json)
|
57
59
|
@attributes_json = CompiledJson.of(attributes_json)
|
58
60
|
@meta_json = CompiledJson.of(meta_json)
|
data/lib/jsonapi/callbacks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'jsonapi/formatter'
|
2
4
|
require 'jsonapi/processor'
|
3
5
|
require 'concurrent'
|
@@ -28,8 +30,8 @@ module JSONAPI
|
|
28
30
|
:allow_transactions,
|
29
31
|
:include_backtraces_in_errors,
|
30
32
|
:include_application_backtraces_in_errors,
|
31
|
-
:
|
32
|
-
:
|
33
|
+
:exception_class_allowlist,
|
34
|
+
:allow_all_exceptions,
|
33
35
|
:always_include_to_one_linkage_data,
|
34
36
|
:always_include_to_many_linkage_data,
|
35
37
|
:cache_formatters,
|
@@ -40,6 +42,7 @@ module JSONAPI
|
|
40
42
|
:resource_cache_digest_function,
|
41
43
|
:resource_cache_usage_report_function,
|
42
44
|
:default_exclude_links,
|
45
|
+
:default_resource_retrieval_strategy,
|
43
46
|
:use_related_resource_records_for_joins
|
44
47
|
|
45
48
|
def initialize
|
@@ -96,12 +99,12 @@ module JSONAPI
|
|
96
99
|
# raise a Pundit::NotAuthorizedError at some point during operations
|
97
100
|
# processing. If you want to use Rails' `rescue_from` macro to
|
98
101
|
# catch this error and render a 403 status code, you should add
|
99
|
-
# the `Pundit::NotAuthorizedError` to the `
|
100
|
-
self.
|
102
|
+
# the `Pundit::NotAuthorizedError` to the `exception_class_allowlist`.
|
103
|
+
self.exception_class_allowlist = []
|
101
104
|
|
102
|
-
# If enabled, will override configuration option `
|
103
|
-
# and
|
104
|
-
self.
|
105
|
+
# If enabled, will override configuration option `exception_class_allowlist`
|
106
|
+
# and allow all exceptions.
|
107
|
+
self.allow_all_exceptions = false
|
105
108
|
|
106
109
|
# Resource Linkage
|
107
110
|
# Controls the serialization of resource linkage for non compound documents
|
@@ -160,9 +163,24 @@ module JSONAPI
|
|
160
163
|
# specific default links to exclude, which may be `:self` and `:related`.
|
161
164
|
self.default_exclude_links = :none
|
162
165
|
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
+
# Global configuration for resource retrieval strategy used by the Resource class.
|
167
|
+
# Selecting a default_resource_retrieval_strategy will affect all resources that derive from
|
168
|
+
# Resource. The default value is 'JSONAPI::ActiveRelationRetrieval'.
|
169
|
+
#
|
170
|
+
# To use multiple retrieval strategies in an app set this to :none and set a custom retrieval strategy
|
171
|
+
# per resource (or base resource) using the class method `load_resource_retrieval_strategy`.
|
172
|
+
#
|
173
|
+
# Available strategies:
|
174
|
+
# 'JSONAPI::ActiveRelationRetrieval'
|
175
|
+
# 'JSONAPI::ActiveRelationRetrievalV09'
|
176
|
+
# 'JSONAPI::ActiveRelationRetrievalV10'
|
177
|
+
# :none
|
178
|
+
# :self
|
179
|
+
self.default_resource_retrieval_strategy = 'JSONAPI::ActiveRelationRetrieval'
|
180
|
+
|
181
|
+
# For 'JSONAPI::ActiveRelationRetrievalV10': use a related resource's `records` when performing joins.
|
182
|
+
# This setting allows included resources to account for permission scopes. It can be overridden explicitly per
|
183
|
+
# relationship. Furthermore, specifying a `relation_name` on a relationship will cause this setting to be ignored.
|
166
184
|
self.use_related_resource_records_for_joins = true
|
167
185
|
end
|
168
186
|
|
@@ -225,9 +243,9 @@ module JSONAPI
|
|
225
243
|
return formatter
|
226
244
|
end
|
227
245
|
|
228
|
-
def
|
229
|
-
@
|
230
|
-
@
|
246
|
+
def exception_class_allowed?(e)
|
247
|
+
@allow_all_exceptions ||
|
248
|
+
@exception_class_allowlist.flatten.any? { |k| e.class.ancestors.map(&:to_s).include?(k.to_s) }
|
231
249
|
end
|
232
250
|
|
233
251
|
def default_processor_klass=(default_processor_klass)
|
@@ -276,9 +294,9 @@ module JSONAPI
|
|
276
294
|
|
277
295
|
attr_writer :include_application_backtraces_in_errors
|
278
296
|
|
279
|
-
attr_writer :
|
297
|
+
attr_writer :exception_class_allowlist
|
280
298
|
|
281
|
-
attr_writer :
|
299
|
+
attr_writer :allow_all_exceptions
|
282
300
|
|
283
301
|
attr_writer :always_include_to_one_linkage_data
|
284
302
|
|
@@ -306,6 +324,8 @@ module JSONAPI
|
|
306
324
|
|
307
325
|
attr_writer :default_exclude_links
|
308
326
|
|
327
|
+
attr_writer :default_resource_retrieval_strategy
|
328
|
+
|
309
329
|
attr_writer :use_related_resource_records_for_joins
|
310
330
|
end
|
311
331
|
|
data/lib/jsonapi/error.rb
CHANGED
data/lib/jsonapi/error_codes.rb
CHANGED
data/lib/jsonapi/exceptions.rb
CHANGED
data/lib/jsonapi/formatter.rb
CHANGED
@@ -1,24 +1,35 @@
|
|
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
9
|
+
# include_related: {
|
10
|
+
# posts: {
|
11
|
+
# include: true,
|
12
|
+
# include_related: {
|
13
|
+
# comments: {
|
14
|
+
# include: true,
|
15
|
+
# include_related: {
|
16
|
+
# tags: {
|
17
|
+
# include: true,
|
18
|
+
# include_related: {},
|
19
|
+
# include_in_join: true
|
20
|
+
# }
|
21
|
+
# },
|
22
|
+
# include_in_join: true
|
14
23
|
# }
|
15
|
-
# }
|
24
|
+
# },
|
25
|
+
# include_in_join: true
|
16
26
|
# }
|
17
27
|
# }
|
18
28
|
# }
|
19
29
|
|
20
|
-
def initialize(resource_klass, includes_array)
|
30
|
+
def initialize(resource_klass, includes_array, force_eager_load: false)
|
21
31
|
@resource_klass = resource_klass
|
32
|
+
@force_eager_load = force_eager_load
|
22
33
|
@include_directives_hash = { include_related: {} }
|
23
34
|
includes_array.each do |include|
|
24
35
|
parse_include(include)
|
@@ -29,21 +40,68 @@ module JSONAPI
|
|
29
40
|
@include_directives_hash
|
30
41
|
end
|
31
42
|
|
32
|
-
|
43
|
+
def [](name)
|
44
|
+
@include_directives_hash[name]
|
45
|
+
end
|
33
46
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
ensure_default_field: false,
|
38
|
-
parse_fields: false)
|
47
|
+
def model_includes
|
48
|
+
get_includes(@include_directives_hash)
|
49
|
+
end
|
39
50
|
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_related(current_path)
|
40
54
|
current = @include_directives_hash
|
55
|
+
current_resource_klass = @resource_klass
|
56
|
+
current_path.split('.').each do |fragment|
|
57
|
+
fragment = fragment.to_sym
|
58
|
+
|
59
|
+
if current_resource_klass
|
60
|
+
current_relationship = current_resource_klass._relationship(fragment)
|
61
|
+
current_resource_klass = current_relationship.try(:resource_klass)
|
62
|
+
else
|
63
|
+
raise JSONAPI::Exceptions::InvalidInclude.new(current_resource_klass, current_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
|
67
|
+
|
68
|
+
current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
|
69
|
+
current = current[:include_related][fragment]
|
70
|
+
end
|
71
|
+
current
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_includes(directive, only_joined_includes = true)
|
75
|
+
ir = directive[:include_related]
|
76
|
+
ir = ir.select { |_k,v| v[:include_in_join] } if only_joined_includes
|
77
|
+
|
78
|
+
ir.map do |name, sub_directive|
|
79
|
+
sub = get_includes(sub_directive, only_joined_includes)
|
80
|
+
sub.any? ? { name => sub } : name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_include(include)
|
85
|
+
parts = include.split('.')
|
86
|
+
local_path = ''
|
41
87
|
|
42
|
-
|
43
|
-
|
88
|
+
parts.each do |name|
|
89
|
+
local_path += local_path.length > 0 ? ".#{name}" : name
|
90
|
+
related = get_related(local_path)
|
91
|
+
related[:include] = true
|
92
|
+
end
|
93
|
+
end
|
44
94
|
|
45
|
-
|
46
|
-
|
95
|
+
def delve_paths(obj)
|
96
|
+
case obj
|
97
|
+
when Array
|
98
|
+
obj.map{|elem| delve_paths(elem)}.flatten(1)
|
99
|
+
when Hash
|
100
|
+
obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
|
101
|
+
when Symbol, String
|
102
|
+
[[obj]]
|
103
|
+
else
|
104
|
+
raise "delve_paths cannot descend into #{obj.class.name}"
|
47
105
|
end
|
48
106
|
|
49
107
|
rescue JSONAPI::Exceptions::InvalidRelationship => _e
|
data/lib/jsonapi/link_builder.rb
CHANGED
data/lib/jsonapi/mime_types.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
data/lib/jsonapi/naive_cache.rb
CHANGED
data/lib/jsonapi/operation.rb
CHANGED
data/lib/jsonapi/paginator.rb
CHANGED
data/lib/jsonapi/path.rb
CHANGED
data/lib/jsonapi/path_segment.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
class PathSegment
|
3
5
|
def self.parse(source_resource_klass:, segment_string:, parse_fields: true)
|
@@ -30,7 +32,7 @@ module JSONAPI
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def eql?(other)
|
33
|
-
other.is_a?(
|
35
|
+
other.is_a?(self.class) && relationship == other.relationship && resource_klass == other.resource_klass
|
34
36
|
end
|
35
37
|
|
36
38
|
def hash
|
@@ -59,7 +61,7 @@ module JSONAPI
|
|
59
61
|
end
|
60
62
|
|
61
63
|
def eql?(other)
|
62
|
-
other.is_a?(
|
64
|
+
other.is_a?(self.class) && field_name == other.field_name && resource_klass == other.resource_klass
|
63
65
|
end
|
64
66
|
|
65
67
|
def delegated_field_name
|