jsonapi-resources 0.9.12 → 0.10.0.beta1

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 (36) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  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-resources.rb +8 -3
  7. data/lib/jsonapi/active_relation_resource_finder.rb +640 -0
  8. data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +126 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +121 -106
  10. data/lib/jsonapi/{cached_resource_fragment.rb → cached_response_fragment.rb} +13 -30
  11. data/lib/jsonapi/compiled_json.rb +11 -1
  12. data/lib/jsonapi/configuration.rb +44 -18
  13. data/lib/jsonapi/error.rb +27 -0
  14. data/lib/jsonapi/exceptions.rb +43 -40
  15. data/lib/jsonapi/formatter.rb +3 -3
  16. data/lib/jsonapi/include_directives.rb +2 -45
  17. data/lib/jsonapi/link_builder.rb +87 -80
  18. data/lib/jsonapi/operation.rb +16 -5
  19. data/lib/jsonapi/operation_result.rb +74 -16
  20. data/lib/jsonapi/processor.rb +233 -112
  21. data/lib/jsonapi/relationship.rb +77 -53
  22. data/lib/jsonapi/request_parser.rb +378 -423
  23. data/lib/jsonapi/resource.rb +224 -524
  24. data/lib/jsonapi/resource_controller_metal.rb +2 -2
  25. data/lib/jsonapi/resource_fragment.rb +47 -0
  26. data/lib/jsonapi/resource_id_tree.rb +112 -0
  27. data/lib/jsonapi/resource_identity.rb +42 -0
  28. data/lib/jsonapi/resource_serializer.rb +133 -301
  29. data/lib/jsonapi/resource_set.rb +108 -0
  30. data/lib/jsonapi/resources/version.rb +1 -1
  31. data/lib/jsonapi/response_document.rb +100 -88
  32. data/lib/jsonapi/routing_ext.rb +21 -43
  33. metadata +29 -45
  34. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  35. data/lib/jsonapi/operation_results.rb +0 -35
  36. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -1,37 +1,26 @@
1
1
  module JSONAPI
2
- class CachedResourceFragment
3
- def self.fetch_fragments(resource_klass, serializer, context, cache_ids)
4
- serializer_config_key = serializer.config_key(resource_klass).gsub("/", "_")
2
+ class CachedResponseFragment
3
+ def self.fetch_cached_fragments(resource_klass, serializer_config_key, cache_ids, context)
5
4
  context_json = resource_klass.attribute_caching_context(context).to_json
6
5
  context_b64 = JSONAPI.configuration.resource_cache_digest_function.call(context_json)
7
6
  context_key = "ATTR-CTX-#{context_b64.gsub("/", "_")}"
8
7
 
9
8
  results = self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
10
9
 
11
- miss_ids = results.select{|k,v| v.nil? }.keys
12
- unless miss_ids.empty?
13
- find_filters = {resource_klass._primary_key => miss_ids.uniq}
14
- find_options = {context: context}
15
- resource_klass.find(find_filters, find_options).each do |resource|
16
- (id, cr) = write(resource_klass, resource, serializer, serializer_config_key, context, context_key)
17
- results[id] = cr
18
- end
19
- end
20
-
21
10
  if JSONAPI.configuration.resource_cache_usage_report_function
11
+ miss_ids = results.select{|_k,v| v.nil? }.keys
22
12
  JSONAPI.configuration.resource_cache_usage_report_function.call(
23
- resource_klass.name,
24
- cache_ids.size - miss_ids.size,
25
- miss_ids.size
13
+ resource_klass.name,
14
+ cache_ids.size - miss_ids.size,
15
+ miss_ids.size
26
16
  )
27
17
  end
28
18
 
29
- return results
19
+ results
30
20
  end
31
21
 
32
22
  attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
33
- :links_json, :attributes_json, :meta_json,
34
- :preloaded_fragments
23
+ :links_json, :attributes_json, :meta_json
35
24
 
36
25
  def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
37
26
  links_json, attributes_json, meta_json)
@@ -47,9 +36,6 @@ module JSONAPI
47
36
  @links_json = CompiledJson.of(links_json)
48
37
  @attributes_json = CompiledJson.of(attributes_json)
49
38
  @meta_json = CompiledJson.of(meta_json)
50
-
51
- # A hash of hashes
52
- @preloaded_fragments ||= Hash.new
53
39
  end
54
40
 
55
41
  def to_cache_value
@@ -64,11 +50,6 @@ module JSONAPI
64
50
  }
65
51
  end
66
52
 
67
- def to_real_resource
68
- rs = Resource.resource_for(self.type).find_by_keys([self.id], {context: self.context})
69
- return rs.try(:first)
70
- end
71
-
72
53
  private
73
54
 
74
55
  def self.lookup(resource_klass, serializer_config_key, context, context_key, cache_ids)
@@ -103,12 +84,14 @@ module JSONAPI
103
84
  )
104
85
  end
105
86
 
106
- def self.write(resource_klass, resource, serializer, serializer_config_key, context, context_key)
87
+ def self.write(resource_klass, resource, serializer, serializer_config_key, context, context_key, relationship_data )
107
88
  (id, cache_key) = resource.cache_id
108
- json = serializer.object_hash(resource) # No inclusions passed to object_hash
89
+
90
+ json = serializer.object_hash(resource, relationship_data)
91
+
109
92
  cr = self.new(
110
93
  resource_klass,
111
- json['id'],
94
+ id,
112
95
  json['type'],
113
96
  context,
114
97
  resource.fetchable_fields,
@@ -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(*args)
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
@@ -1,5 +1,6 @@
1
1
  require 'jsonapi/formatter'
2
2
  require 'jsonapi/processor'
3
+ require 'jsonapi/active_relation_resource_finder'
3
4
  require 'concurrent'
4
5
 
5
6
  module JSONAPI
@@ -8,13 +9,15 @@ module JSONAPI
8
9
  :resource_key_type,
9
10
  :route_format,
10
11
  :raise_if_parameters_not_allowed,
11
- :warn_on_missing_routes,
12
- :allow_include,
12
+ :warn_on_route_setup_issues,
13
+ :default_allow_include_to_one,
14
+ :default_allow_include_to_many,
13
15
  :allow_sort,
14
16
  :allow_filter,
15
17
  :default_paginator,
16
18
  :default_page_size,
17
19
  :maximum_page_size,
20
+ :resource_finder,
18
21
  :default_processor_klass,
19
22
  :use_text_errors,
20
23
  :top_level_links_include_pagination,
@@ -24,6 +27,7 @@ module JSONAPI
24
27
  :top_level_meta_page_count_key,
25
28
  :allow_transactions,
26
29
  :include_backtraces_in_errors,
30
+ :include_application_backtraces_in_errors,
27
31
  :exception_class_whitelist,
28
32
  :whitelist_all_exceptions,
29
33
  :always_include_to_one_linkage_data,
@@ -31,10 +35,10 @@ module JSONAPI
31
35
  :cache_formatters,
32
36
  :use_relationship_reflection,
33
37
  :resource_cache,
38
+ :default_caching,
34
39
  :default_resource_cache_field,
35
40
  :resource_cache_digest_function,
36
- :resource_cache_usage_report_function,
37
- :default_exclude_links
41
+ :resource_cache_usage_report_function
38
42
 
39
43
  def initialize
40
44
  #:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -47,13 +51,14 @@ module JSONAPI
47
51
  self.resource_key_type = :integer
48
52
 
49
53
  # optional request features
50
- self.allow_include = true
54
+ self.default_allow_include_to_one = true
55
+ self.default_allow_include_to_many = true
51
56
  self.allow_sort = true
52
57
  self.allow_filter = true
53
58
 
54
59
  self.raise_if_parameters_not_allowed = true
55
60
 
56
- self.warn_on_missing_routes = true
61
+ self.warn_on_route_setup_issues = true
57
62
 
58
63
  # :none, :offset, :paged, or a custom paginator name
59
64
  self.default_paginator = :none
@@ -75,8 +80,12 @@ module JSONAPI
75
80
  self.use_text_errors = false
76
81
 
77
82
  # Whether or not to include exception backtraces in JSONAPI error
78
- # responses. Defaults to `false` in production, and `true` otherwise.
79
- self.include_backtraces_in_errors = !Rails.env.production?
83
+ # responses. Defaults to `false` in anything other than development or test.
84
+ self.include_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
85
+
86
+ # Whether or not to include exception application backtraces in JSONAPI error
87
+ # responses. Defaults to `false` in anything other than development or test.
88
+ self.include_application_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
80
89
 
81
90
  # List of classes that should not be rescued by the operations processor.
82
91
  # For example, if you use Pundit for authorization, you might
@@ -96,6 +105,12 @@ module JSONAPI
96
105
  self.always_include_to_one_linkage_data = false
97
106
  self.always_include_to_many_linkage_data = false
98
107
 
108
+ # ResourceFinder Mixin
109
+ # The default ResourceFinder is the ActiveRelationResourceFinder which provides
110
+ # access to ActiveRelation backed models. Custom ResourceFinders can be specified
111
+ # in order to support other ORMs.
112
+ self.resource_finder = JSONAPI::ActiveRelationResourceFinder
113
+
99
114
  # The default Operation Processor to use if one is not defined specifically
100
115
  # for a Resource.
101
116
  self.default_processor_klass = JSONAPI::Processor
@@ -121,6 +136,11 @@ module JSONAPI
121
136
  # Rails cache store.
122
137
  self.resource_cache = nil
123
138
 
139
+ # Cache resources by default
140
+ # Cache resources by default. Individual resources can be excluded from caching by calling:
141
+ # `caching false`
142
+ self.default_caching = false
143
+
124
144
  # Default resource cache field
125
145
  # On Resources with caching enabled, this field will be used to check for out-of-date
126
146
  # cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
@@ -135,12 +155,6 @@ module JSONAPI
135
155
  # Optionally provide a callable which JSONAPI will call with information about cache
136
156
  # performance. Should accept three arguments: resource name, hits count, misses count.
137
157
  self.resource_cache_usage_report_function = nil
138
-
139
- # Global configuration for links exclusion
140
- # Controls whether to generate links like `self`, `related` with all the resources
141
- # and relationships. Accepts either `:default`, `:none`, or array containing the
142
- # specific default links to exclude, which may be `:self` and `:related`.
143
- self.default_exclude_links = :none
144
158
  end
145
159
 
146
160
  def cache_formatters=(bool)
@@ -211,7 +225,17 @@ module JSONAPI
211
225
  @default_processor_klass = default_processor_klass
212
226
  end
213
227
 
214
- attr_writer :allow_include, :allow_sort, :allow_filter
228
+ def resource_finder=(resource_finder)
229
+ @resource_finder = resource_finder
230
+ end
231
+
232
+ def allow_include=(allow_include)
233
+ ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
234
+ @default_allow_include_to_one = allow_include
235
+ @default_allow_include_to_many = allow_include
236
+ end
237
+
238
+ attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
215
239
 
216
240
  attr_writer :default_paginator
217
241
 
@@ -235,6 +259,8 @@ module JSONAPI
235
259
 
236
260
  attr_writer :include_backtraces_in_errors
237
261
 
262
+ attr_writer :include_application_backtraces_in_errors
263
+
238
264
  attr_writer :exception_class_whitelist
239
265
 
240
266
  attr_writer :whitelist_all_exceptions
@@ -245,19 +271,19 @@ module JSONAPI
245
271
 
246
272
  attr_writer :raise_if_parameters_not_allowed
247
273
 
248
- attr_writer :warn_on_missing_routes
274
+ attr_writer :warn_on_route_setup_issues
249
275
 
250
276
  attr_writer :use_relationship_reflection
251
277
 
252
278
  attr_writer :resource_cache
253
279
 
280
+ attr_writer :default_caching
281
+
254
282
  attr_writer :default_resource_cache_field
255
283
 
256
284
  attr_writer :resource_cache_digest_function
257
285
 
258
286
  attr_writer :resource_cache_usage_report_function
259
-
260
- attr_writer :default_exclude_links
261
287
  end
262
288
 
263
289
  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
@@ -1,7 +1,7 @@
1
1
  module JSONAPI
2
2
  module Exceptions
3
3
  class Error < RuntimeError
4
- attr :error_object_overrides
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
- [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))]
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
- [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'))]
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
 
@@ -339,7 +342,7 @@ module JSONAPI
339
342
  title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
340
343
  default: 'Invalid field'),
341
344
  detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
342
- default: "#{relationship} is not a valid relationship of #{resource}",
345
+ default: "#{relationship} is not a valid includable relationship of #{resource}",
343
346
  relationship: relationship, resource: resource))]
344
347
  end
345
348
  end
@@ -374,11 +377,11 @@ module JSONAPI
374
377
 
375
378
  def errors
376
379
  [create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
377
- status: :bad_request,
378
- title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
379
- default: 'Param not allowed'),
380
- detail: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.detail',
381
- default: "#{param} is not allowed.", param: param))]
380
+ status: :bad_request,
381
+ title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
382
+ default: 'Param not allowed'),
383
+ detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
384
+ default: "#{param} is not allowed.", param: param))]
382
385
  end
383
386
  end
384
387