jsonapi-resources 0.10.6 → 0.11.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|