jsonapi-resources 0.9.0 → 0.9.12
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/lib/jsonapi/acts_as_resource_controller.rb +3 -2
- data/lib/jsonapi/configuration.rb +15 -1
- data/lib/jsonapi/exceptions.rb +22 -15
- data/lib/jsonapi/include_directives.rb +11 -1
- data/lib/jsonapi/link_builder.rb +81 -80
- data/lib/jsonapi/operation_result.rb +1 -1
- data/lib/jsonapi/processor.rb +8 -7
- data/lib/jsonapi/relationship.rb +47 -2
- data/lib/jsonapi/relationship_builder.rb +4 -4
- data/lib/jsonapi/request_parser.rb +130 -54
- data/lib/jsonapi/resource.rb +134 -11
- data/lib/jsonapi/resource_controller_metal.rb +2 -2
- data/lib/jsonapi/resource_serializer.rb +64 -44
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +14 -9
- data/lib/jsonapi/routing_ext.rb +40 -16
- metadata +35 -8
@@ -52,6 +52,7 @@ module JSONAPI
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def setup_get_related_resource_action(params)
|
55
|
+
resolve_singleton_id(params)
|
55
56
|
initialize_source(params)
|
56
57
|
parse_fields(params[:fields])
|
57
58
|
parse_include_directives(params[:include])
|
@@ -63,6 +64,7 @@ module JSONAPI
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def setup_get_related_resources_action(params)
|
67
|
+
resolve_singleton_id(params)
|
66
68
|
initialize_source(params)
|
67
69
|
parse_fields(params[:fields])
|
68
70
|
parse_include_directives(params[:include])
|
@@ -74,13 +76,17 @@ module JSONAPI
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def setup_show_action(params)
|
79
|
+
resolve_singleton_id(params)
|
77
80
|
parse_fields(params[:fields])
|
78
81
|
parse_include_directives(params[:include])
|
82
|
+
parse_filters(params[:filter])
|
83
|
+
|
79
84
|
@id = params[:id]
|
80
85
|
add_show_operation
|
81
86
|
end
|
82
87
|
|
83
88
|
def setup_show_relationship_action(params)
|
89
|
+
resolve_singleton_id(params)
|
84
90
|
add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
|
85
91
|
end
|
86
92
|
|
@@ -91,24 +97,29 @@ module JSONAPI
|
|
91
97
|
end
|
92
98
|
|
93
99
|
def setup_create_relationship_action(params)
|
100
|
+
resolve_singleton_id(params)
|
94
101
|
parse_modify_relationship_action(params, :add)
|
95
102
|
end
|
96
103
|
|
97
104
|
def setup_update_relationship_action(params)
|
105
|
+
resolve_singleton_id(params)
|
98
106
|
parse_modify_relationship_action(params, :update)
|
99
107
|
end
|
100
108
|
|
101
109
|
def setup_update_action(params)
|
110
|
+
resolve_singleton_id(params)
|
102
111
|
parse_fields(params[:fields])
|
103
112
|
parse_include_directives(params[:include])
|
104
113
|
parse_replace_operation(params.require(:data), params[:id])
|
105
114
|
end
|
106
115
|
|
107
116
|
def setup_destroy_action(params)
|
117
|
+
resolve_singleton_id(params)
|
108
118
|
parse_remove_operation(params)
|
109
119
|
end
|
110
120
|
|
111
121
|
def setup_destroy_relationship_action(params)
|
122
|
+
resolve_singleton_id(params)
|
112
123
|
parse_modify_relationship_action(params, :remove)
|
113
124
|
end
|
114
125
|
|
@@ -170,13 +181,11 @@ module JSONAPI
|
|
170
181
|
end
|
171
182
|
type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
|
172
183
|
rescue NameError
|
173
|
-
|
174
|
-
rescue JSONAPI::Exceptions::InvalidResource => e
|
175
|
-
@errors.concat(e.errors)
|
184
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
176
185
|
end
|
177
186
|
|
178
187
|
if type_resource.nil?
|
179
|
-
|
188
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
180
189
|
else
|
181
190
|
unless values.nil?
|
182
191
|
valid_fields = type_resource.fields.collect { |key| format_key(key) }
|
@@ -184,11 +193,11 @@ module JSONAPI
|
|
184
193
|
if valid_fields.include?(field)
|
185
194
|
extracted_fields[type].push unformat_key(field)
|
186
195
|
else
|
187
|
-
|
196
|
+
fail JSONAPI::Exceptions::InvalidField.new(type, field)
|
188
197
|
end
|
189
198
|
end
|
190
199
|
else
|
191
|
-
|
200
|
+
fail JSONAPI::Exceptions::InvalidField.new(type, 'nil')
|
192
201
|
end
|
193
202
|
end
|
194
203
|
end
|
@@ -205,8 +214,7 @@ module JSONAPI
|
|
205
214
|
check_include(Resource.resource_for(resource_klass.module_path + relationship.class_name.to_s.underscore), include_parts.last.partition('.'))
|
206
215
|
end
|
207
216
|
else
|
208
|
-
|
209
|
-
include_parts.first).errors)
|
217
|
+
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
|
210
218
|
end
|
211
219
|
end
|
212
220
|
|
@@ -214,31 +222,36 @@ module JSONAPI
|
|
214
222
|
return unless raw_include
|
215
223
|
|
216
224
|
unless JSONAPI.configuration.allow_include
|
217
|
-
fail JSONAPI::Exceptions::
|
225
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:include)
|
218
226
|
end
|
219
227
|
|
220
228
|
included_resources = []
|
221
229
|
begin
|
222
|
-
included_resources += CSV.parse_line(raw_include)
|
230
|
+
included_resources += Array(CSV.parse_line(raw_include))
|
223
231
|
rescue CSV::MalformedCSVError
|
224
232
|
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(@resource_klass._type), raw_include)
|
225
233
|
end
|
226
234
|
|
227
235
|
return if included_resources.empty?
|
228
236
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
237
|
+
begin
|
238
|
+
result = included_resources.compact.map do |included_resource|
|
239
|
+
check_include(@resource_klass, included_resource.partition('.'))
|
240
|
+
unformat_key(included_resource).to_s
|
241
|
+
end
|
233
242
|
|
234
|
-
|
243
|
+
@include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, result)
|
244
|
+
rescue JSONAPI::Exceptions::InvalidInclude => e
|
245
|
+
@errors.concat(e.errors)
|
246
|
+
@include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, [])
|
247
|
+
end
|
235
248
|
end
|
236
249
|
|
237
250
|
def parse_filters(filters)
|
238
251
|
return unless filters
|
239
252
|
|
240
253
|
unless JSONAPI.configuration.allow_filter
|
241
|
-
fail JSONAPI::Exceptions::
|
254
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
|
242
255
|
end
|
243
256
|
|
244
257
|
unless filters.class.method_defined?(:each)
|
@@ -247,12 +260,41 @@ module JSONAPI
|
|
247
260
|
end
|
248
261
|
|
249
262
|
filters.each do |key, value|
|
250
|
-
|
251
|
-
|
252
|
-
|
263
|
+
|
264
|
+
unformatted_key = unformat_key(key)
|
265
|
+
if resource_klass._allowed_filter?(unformatted_key)
|
266
|
+
@filters[unformatted_key] = value
|
267
|
+
elsif unformatted_key.to_s.include?('.')
|
268
|
+
parse_relationship_filter(unformatted_key, value)
|
253
269
|
else
|
254
|
-
@errors.concat(
|
270
|
+
return @errors.concat(Exceptions::FilterNotAllowed.new(unformatted_key).errors)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def parse_relationship_filter(key, value)
|
276
|
+
included_resource_name, filter_method = key.to_s.split('.')
|
277
|
+
filter_method = filter_method.to_sym if filter_method.present?
|
278
|
+
|
279
|
+
if included_resource_name
|
280
|
+
relationship = resource_klass._relationship(included_resource_name || '')
|
281
|
+
|
282
|
+
unless relationship
|
283
|
+
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
284
|
+
end
|
285
|
+
|
286
|
+
unless relationship.resource_klass._allowed_filter?(filter_method)
|
287
|
+
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
288
|
+
end
|
289
|
+
|
290
|
+
unless @include_directives.try(:include_config, relationship.name.to_sym).present?
|
291
|
+
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
255
292
|
end
|
293
|
+
|
294
|
+
verified_filter = relationship.resource_klass.verify_filters({ filter_method => value }, @context)
|
295
|
+
@include_directives.merge_filter(relationship.name, verified_filter)
|
296
|
+
else
|
297
|
+
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
256
298
|
end
|
257
299
|
end
|
258
300
|
|
@@ -267,12 +309,12 @@ module JSONAPI
|
|
267
309
|
return unless sort_criteria.present?
|
268
310
|
|
269
311
|
unless JSONAPI.configuration.allow_sort
|
270
|
-
fail JSONAPI::Exceptions::
|
312
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort)
|
271
313
|
end
|
272
314
|
|
273
315
|
sorts = []
|
274
316
|
begin
|
275
|
-
raw = URI.unescape(sort_criteria)
|
317
|
+
raw = URI::DEFAULT_PARSER.unescape(sort_criteria)
|
276
318
|
sorts += CSV.parse_line(raw)
|
277
319
|
rescue CSV::MalformedCSVError
|
278
320
|
fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(@resource_klass._type), raw)
|
@@ -296,9 +338,8 @@ module JSONAPI
|
|
296
338
|
sort_field = sort_criteria[:field]
|
297
339
|
sortable_fields = resource_klass.sortable_fields(context)
|
298
340
|
|
299
|
-
unless sortable_fields.include?
|
300
|
-
|
301
|
-
.new(format_key(resource_klass._type), sort_field).errors)
|
341
|
+
unless sortable_fields.include?sort_field.to_sym
|
342
|
+
fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field)
|
302
343
|
end
|
303
344
|
end
|
304
345
|
|
@@ -352,7 +393,7 @@ module JSONAPI
|
|
352
393
|
relationship_type: relationship_type,
|
353
394
|
source_klass: @source_klass,
|
354
395
|
source_id: @source_id,
|
355
|
-
filters: @
|
396
|
+
filters: @filters,
|
356
397
|
sort_criteria: @sort_criteria,
|
357
398
|
paginator: @paginator,
|
358
399
|
fields: @fields,
|
@@ -473,7 +514,7 @@ module JSONAPI
|
|
473
514
|
|
474
515
|
unless links_object[:id].nil?
|
475
516
|
resource = self.resource_klass || Resource
|
476
|
-
relationship_resource = resource.resource_for(unformat_key(links_object[:type]).to_s)
|
517
|
+
relationship_resource = resource.resource_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
|
477
518
|
relationship_id = relationship_resource.verify_key(links_object[:id], @context)
|
478
519
|
if relationship.polymorphic?
|
479
520
|
{ id: relationship_id, type: unformat_key(links_object[:type].to_s) }
|
@@ -496,20 +537,40 @@ module JSONAPI
|
|
496
537
|
|
497
538
|
links_object = parse_to_many_links_object(linkage)
|
498
539
|
|
499
|
-
# Since we do not yet support polymorphic to_many relationships we will raise an error if the type does not match the
|
500
|
-
# relationship's type.
|
501
|
-
# ToDo: Support Polymorphic relationships
|
502
|
-
|
503
540
|
if links_object.length == 0
|
504
541
|
add_result.call([])
|
505
542
|
else
|
506
|
-
if
|
507
|
-
|
508
|
-
|
543
|
+
if relationship.polymorphic?
|
544
|
+
polymorphic_results = []
|
545
|
+
|
546
|
+
links_object.each_pair do |type, keys|
|
547
|
+
resource = self.resource_klass || Resource
|
548
|
+
type_name = unformat_key(type).to_s
|
549
|
+
|
550
|
+
relationship_resource_klass = resource.resource_for(relationship.class_name)
|
551
|
+
relationship_klass = relationship_resource_klass._model_class
|
552
|
+
|
553
|
+
linkage_object_resource_klass = resource.resource_for(type_name)
|
554
|
+
linkage_object_klass = linkage_object_resource_klass._model_class
|
555
|
+
|
556
|
+
unless linkage_object_klass == relationship_klass || linkage_object_klass.in?(relationship_klass.subclasses)
|
557
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(type_name)
|
558
|
+
end
|
509
559
|
|
510
|
-
|
511
|
-
|
512
|
-
|
560
|
+
relationship_ids = relationship_resource_klass.verify_keys(keys, @context)
|
561
|
+
polymorphic_results << { type: type, ids: relationship_ids }
|
562
|
+
end
|
563
|
+
|
564
|
+
add_result.call polymorphic_results
|
565
|
+
else
|
566
|
+
relationship_type = unformat_key(relationship.type).to_s
|
567
|
+
|
568
|
+
if links_object.length > 1 || !links_object.has_key?(relationship_type)
|
569
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
570
|
+
end
|
571
|
+
|
572
|
+
relationship_resource = Resource.resource_for(@resource_klass.module_path + relationship_type)
|
573
|
+
add_result.call relationship_resource.verify_keys(links_object[relationship_type], @context)
|
513
574
|
end
|
514
575
|
end
|
515
576
|
end
|
@@ -528,8 +589,10 @@ module JSONAPI
|
|
528
589
|
when 'relationships'
|
529
590
|
value.keys.each do |links_key|
|
530
591
|
unless formatted_allowed_fields.include?(links_key.to_sym)
|
531
|
-
|
532
|
-
|
592
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
593
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key)
|
594
|
+
else
|
595
|
+
params_not_allowed.push(links_key)
|
533
596
|
value.delete links_key
|
534
597
|
end
|
535
598
|
end
|
@@ -537,8 +600,10 @@ module JSONAPI
|
|
537
600
|
when 'attributes'
|
538
601
|
value.each do |attr_key, attr_value|
|
539
602
|
unless formatted_allowed_fields.include?(attr_key.to_sym)
|
540
|
-
|
541
|
-
|
603
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
604
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key)
|
605
|
+
else
|
606
|
+
params_not_allowed.push(attr_key)
|
542
607
|
value.delete attr_key
|
543
608
|
end
|
544
609
|
end
|
@@ -546,27 +611,30 @@ module JSONAPI
|
|
546
611
|
when 'type'
|
547
612
|
when 'id'
|
548
613
|
unless formatted_allowed_fields.include?(:id)
|
549
|
-
|
550
|
-
|
614
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
615
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id)
|
616
|
+
else
|
617
|
+
params_not_allowed.push(:id)
|
551
618
|
params.delete :id
|
552
619
|
end
|
553
620
|
end
|
554
621
|
else
|
555
|
-
|
622
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
623
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(key)
|
624
|
+
else
|
625
|
+
params_not_allowed.push(key)
|
626
|
+
params.delete key
|
627
|
+
end
|
556
628
|
end
|
557
629
|
end
|
558
630
|
|
559
631
|
if params_not_allowed.length > 0
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
JSONAPI::Warning.new(code: JSONAPI::PARAM_NOT_ALLOWED,
|
565
|
-
title: 'Param not allowed',
|
566
|
-
detail: "#{key} is not allowed.")
|
567
|
-
end
|
568
|
-
self.warnings.concat(params_not_allowed_warnings)
|
632
|
+
params_not_allowed_warnings = params_not_allowed.map do |param|
|
633
|
+
JSONAPI::Warning.new(code: JSONAPI::PARAM_NOT_ALLOWED,
|
634
|
+
title: 'Param not allowed',
|
635
|
+
detail: "#{param} is not allowed.")
|
569
636
|
end
|
637
|
+
self.warnings.concat(params_not_allowed_warnings)
|
570
638
|
end
|
571
639
|
end
|
572
640
|
|
@@ -635,7 +703,8 @@ module JSONAPI
|
|
635
703
|
end
|
636
704
|
|
637
705
|
def parse_replace_operation(data, keys)
|
638
|
-
parse_single_replace_operation(data, [keys],
|
706
|
+
parse_single_replace_operation(data, [keys],
|
707
|
+
id_key_presence_check_required: keys.present? && !@resource_klass.singleton?)
|
639
708
|
rescue JSONAPI::Exceptions::Error => e
|
640
709
|
@errors.concat(e.errors)
|
641
710
|
end
|
@@ -666,6 +735,13 @@ module JSONAPI
|
|
666
735
|
end
|
667
736
|
end
|
668
737
|
|
738
|
+
def resolve_singleton_id(params)
|
739
|
+
if @resource_klass.singleton? && params[:id].nil?
|
740
|
+
key = @resource_klass.singleton_key(context)
|
741
|
+
params[:id] = key
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
669
745
|
def format_key(key)
|
670
746
|
@key_formatter.format(key)
|
671
747
|
end
|
data/lib/jsonapi/resource.rb
CHANGED
@@ -5,6 +5,9 @@ module JSONAPI
|
|
5
5
|
class Resource
|
6
6
|
include Callbacks
|
7
7
|
|
8
|
+
DEFAULT_ATTRIBUTE_OPTIONS = { format: :default }.freeze
|
9
|
+
MODULE_PATH_REGEXP = /::[^:]+\Z/.freeze
|
10
|
+
|
8
11
|
attr_reader :context
|
9
12
|
|
10
13
|
define_jsonapi_resources_callbacks :create,
|
@@ -302,6 +305,26 @@ module JSONAPI
|
|
302
305
|
to_add = relationship_key_values - (relationship_key_values & existing)
|
303
306
|
_create_to_many_links(relationship_type, to_add, {})
|
304
307
|
|
308
|
+
@reload_needed = true
|
309
|
+
elsif relationship.polymorphic?
|
310
|
+
relationship_key_values.each do |relationship_key_value|
|
311
|
+
relationship_resource_klass = self.class.resource_for(relationship_key_value[:type])
|
312
|
+
ids = relationship_key_value[:ids]
|
313
|
+
|
314
|
+
related_records = relationship_resource_klass
|
315
|
+
.records(options)
|
316
|
+
.where({relationship_resource_klass._primary_key => ids})
|
317
|
+
|
318
|
+
missed_ids = ids - related_records.pluck(relationship_resource_klass._primary_key)
|
319
|
+
|
320
|
+
if missed_ids.present?
|
321
|
+
fail JSONAPI::Exceptions::RecordNotFound.new(missed_ids)
|
322
|
+
end
|
323
|
+
|
324
|
+
relation_name = relationship.relation_name(context: @context)
|
325
|
+
@model.send("#{relation_name}") << related_records
|
326
|
+
end
|
327
|
+
|
305
328
|
@reload_needed = true
|
306
329
|
else
|
307
330
|
send("#{relationship.foreign_key}=", relationship_key_values)
|
@@ -415,6 +438,8 @@ module JSONAPI
|
|
415
438
|
subclass.abstract(false)
|
416
439
|
subclass.immutable(false)
|
417
440
|
subclass.caching(false)
|
441
|
+
subclass.singleton(singleton?, (_singleton_options.dup || {}))
|
442
|
+
subclass.exclude_links(_exclude_links)
|
418
443
|
subclass._attributes = (_attributes || {}).dup
|
419
444
|
|
420
445
|
subclass._model_hints = (_model_hints || {}).dup
|
@@ -435,6 +460,9 @@ module JSONAPI
|
|
435
460
|
end
|
436
461
|
|
437
462
|
check_reserved_resource_name(subclass._type, subclass.name)
|
463
|
+
|
464
|
+
subclass._routed = false
|
465
|
+
subclass._warned_missing_route = false
|
438
466
|
end
|
439
467
|
|
440
468
|
def rebuild_relationships(relationships)
|
@@ -453,7 +481,7 @@ module JSONAPI
|
|
453
481
|
|
454
482
|
def resource_for(type)
|
455
483
|
type = type.underscore
|
456
|
-
type_with_module = type.
|
484
|
+
type_with_module = type.start_with?(module_path) ? type : module_path + type
|
457
485
|
|
458
486
|
resource_name = _resource_name_from_type(type_with_module)
|
459
487
|
resource = resource_name.safe_constantize if resource_name
|
@@ -480,7 +508,7 @@ module JSONAPI
|
|
480
508
|
end
|
481
509
|
end
|
482
510
|
|
483
|
-
attr_accessor :_attributes, :_relationships, :_type, :_model_hints
|
511
|
+
attr_accessor :_attributes, :_relationships, :_type, :_model_hints, :_routed, :_warned_missing_route
|
484
512
|
attr_writer :_allowed_filters, :_paginator
|
485
513
|
|
486
514
|
def create(context)
|
@@ -507,10 +535,12 @@ module JSONAPI
|
|
507
535
|
end
|
508
536
|
end
|
509
537
|
|
510
|
-
def attribute(
|
538
|
+
def attribute(attribute_name, options = {})
|
539
|
+
attr = attribute_name.to_sym
|
540
|
+
|
511
541
|
check_reserved_attribute_name(attr)
|
512
542
|
|
513
|
-
if (attr
|
543
|
+
if (attr == :id) && (options[:format].nil?)
|
514
544
|
ActiveSupport::Deprecation.warn('Id without format is no longer supported. Please remove ids from attributes, or specify a format.')
|
515
545
|
end
|
516
546
|
|
@@ -528,7 +558,7 @@ module JSONAPI
|
|
528
558
|
end
|
529
559
|
|
530
560
|
def default_attribute_options
|
531
|
-
|
561
|
+
DEFAULT_ATTRIBUTE_OPTIONS
|
532
562
|
end
|
533
563
|
|
534
564
|
def relationship(*attrs)
|
@@ -563,7 +593,14 @@ module JSONAPI
|
|
563
593
|
_add_relationship(Relationship::ToMany, *attrs)
|
564
594
|
end
|
565
595
|
|
596
|
+
# @model_class is inherited from superclass, and this causes some issues:
|
597
|
+
# ```
|
598
|
+
# CarResource._model_class #=> Vehicle # it should be Car
|
599
|
+
# ```
|
600
|
+
# so in order to invoke the right class from subclasses,
|
601
|
+
# we should call this method to override it.
|
566
602
|
def model_name(model, options = {})
|
603
|
+
@model_class = nil
|
567
604
|
@_model_name = model.to_sym
|
568
605
|
|
569
606
|
model_hint(model: @_model_name, resource: self) unless options[:add_model_hint] == false
|
@@ -577,6 +614,19 @@ module JSONAPI
|
|
577
614
|
_model_hints[model.to_s.gsub('::', '/').underscore] = resource_type.to_s
|
578
615
|
end
|
579
616
|
|
617
|
+
def singleton(*attrs)
|
618
|
+
@_singleton = (!!attrs[0] == attrs[0]) ? attrs[0] : true
|
619
|
+
@_singleton_options = attrs.extract_options!
|
620
|
+
end
|
621
|
+
|
622
|
+
def _singleton_options
|
623
|
+
@_singleton_options ||= {}
|
624
|
+
end
|
625
|
+
|
626
|
+
def singleton?
|
627
|
+
@_singleton ||= false
|
628
|
+
end
|
629
|
+
|
580
630
|
def filters(*attrs)
|
581
631
|
@_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h })
|
582
632
|
end
|
@@ -636,7 +686,7 @@ module JSONAPI
|
|
636
686
|
include_directives = options[:include_directives]
|
637
687
|
if include_directives
|
638
688
|
model_includes = resolve_relationship_names_to_relations(self, include_directives.model_includes, options)
|
639
|
-
records = records.includes(model_includes)
|
689
|
+
records = records.includes(model_includes) if model_includes.present?
|
640
690
|
end
|
641
691
|
|
642
692
|
records
|
@@ -730,9 +780,30 @@ module JSONAPI
|
|
730
780
|
records
|
731
781
|
end
|
732
782
|
|
783
|
+
def apply_included_resources_filters(records, options = {})
|
784
|
+
include_directives = options[:include_directives]
|
785
|
+
return records unless include_directives
|
786
|
+
related_directives = include_directives.include_directives.fetch(:include_related)
|
787
|
+
related_directives.reduce(records) do |memo, (relationship_name, config)|
|
788
|
+
relationship = _relationship(relationship_name)
|
789
|
+
next memo unless relationship && relationship.is_a?(JSONAPI::Relationship::ToMany)
|
790
|
+
filtering_resource = relationship.resource_klass
|
791
|
+
|
792
|
+
# Don't try to merge where clauses when relation isn't already being joined to query.
|
793
|
+
next memo unless config[:include_in_join]
|
794
|
+
|
795
|
+
filters = config[:include_filters]
|
796
|
+
next memo unless filters
|
797
|
+
|
798
|
+
rel_records = filtering_resource.apply_filters(filtering_resource.records(options), filters, options).references(relationship_name)
|
799
|
+
memo.merge(rel_records)
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
733
803
|
def filter_records(filters, options, records = records(options))
|
734
804
|
records = apply_filters(records, filters, options)
|
735
|
-
apply_includes(records, options)
|
805
|
+
records = apply_includes(records, options)
|
806
|
+
apply_included_resources_filters(records, options)
|
736
807
|
end
|
737
808
|
|
738
809
|
def sort_records(records, order_options, context = {})
|
@@ -808,6 +879,9 @@ module JSONAPI
|
|
808
879
|
|
809
880
|
def verify_filters(filters, context = nil)
|
810
881
|
verified_filters = {}
|
882
|
+
|
883
|
+
return verified_filters if filters.nil?
|
884
|
+
|
811
885
|
filters.each do |filter, raw_value|
|
812
886
|
verified_filter = verify_filter(filter, raw_value, context)
|
813
887
|
verified_filters[verified_filter[0]] = verified_filter[1]
|
@@ -855,6 +929,24 @@ module JSONAPI
|
|
855
929
|
@_resource_key_type ||= JSONAPI.configuration.resource_key_type
|
856
930
|
end
|
857
931
|
|
932
|
+
# override to all resolution of masked ids to actual ids. Because singleton routes do not specify the id this
|
933
|
+
# will be needed to allow lookup of singleton resources. Alternately singleton resources can override
|
934
|
+
# `verify_key`
|
935
|
+
def singleton_key(context)
|
936
|
+
if @_singleton_options && @_singleton_options[:singleton_key]
|
937
|
+
strategy = @_singleton_options[:singleton_key]
|
938
|
+
case strategy
|
939
|
+
when Proc
|
940
|
+
key = strategy.call(context)
|
941
|
+
when Symbol, String
|
942
|
+
key = send(strategy, context)
|
943
|
+
else
|
944
|
+
raise "singleton_key must be a proc or function name"
|
945
|
+
end
|
946
|
+
end
|
947
|
+
key
|
948
|
+
end
|
949
|
+
|
858
950
|
def verify_key(key, context = nil)
|
859
951
|
key_type = resource_key_type
|
860
952
|
|
@@ -906,6 +998,10 @@ module JSONAPI
|
|
906
998
|
default_attribute_options.merge(@_attributes[attr])
|
907
999
|
end
|
908
1000
|
|
1001
|
+
def _has_attribute?(attr)
|
1002
|
+
@_attributes.keys.include?(attr.to_sym)
|
1003
|
+
end
|
1004
|
+
|
909
1005
|
def _updatable_relationships
|
910
1006
|
@_relationships.map { |key, _relationship| key }
|
911
1007
|
end
|
@@ -975,6 +1071,31 @@ module JSONAPI
|
|
975
1071
|
!@immutable
|
976
1072
|
end
|
977
1073
|
|
1074
|
+
def exclude_links(exclude)
|
1075
|
+
_resolve_exclude_links(exclude)
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
def _exclude_links
|
1079
|
+
@_exclude_links ||= _resolve_exclude_links(JSONAPI.configuration.default_exclude_links)
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def exclude_link?(link)
|
1083
|
+
_exclude_links.include?(link.to_sym)
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def _resolve_exclude_links(exclude)
|
1087
|
+
case exclude
|
1088
|
+
when :default, "default"
|
1089
|
+
@_exclude_links = [:self]
|
1090
|
+
when :none, "none"
|
1091
|
+
@_exclude_links = []
|
1092
|
+
when Array
|
1093
|
+
@_exclude_links = exclude.collect {|link| link.to_sym}
|
1094
|
+
else
|
1095
|
+
fail "Invalid exclude_links"
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
|
978
1099
|
def caching(val = true)
|
979
1100
|
@caching = val
|
980
1101
|
end
|
@@ -1015,7 +1136,7 @@ module JSONAPI
|
|
1015
1136
|
if name == 'JSONAPI::Resource'
|
1016
1137
|
''
|
1017
1138
|
else
|
1018
|
-
name =~
|
1139
|
+
name =~ MODULE_PATH_REGEXP ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
|
1019
1140
|
end
|
1020
1141
|
end
|
1021
1142
|
|
@@ -1038,7 +1159,8 @@ module JSONAPI
|
|
1038
1159
|
options = attrs.extract_options!
|
1039
1160
|
options[:parent_resource] = self
|
1040
1161
|
|
1041
|
-
attrs.each do |
|
1162
|
+
attrs.each do |name|
|
1163
|
+
relationship_name = name.to_sym
|
1042
1164
|
check_reserved_relationship_name(relationship_name)
|
1043
1165
|
check_duplicate_relationship_name(relationship_name)
|
1044
1166
|
|
@@ -1232,7 +1354,8 @@ module JSONAPI
|
|
1232
1354
|
rel_id = row[index+1]
|
1233
1355
|
assoc_rels = res.preloaded_fragments[rel_name]
|
1234
1356
|
if index == path.length - 1
|
1235
|
-
|
1357
|
+
association_res = target_resources[klass.name].fetch(rel_id, nil)
|
1358
|
+
assoc_rels[rel_id] = association_res if association_res
|
1236
1359
|
else
|
1237
1360
|
res = assoc_rels[rel_id]
|
1238
1361
|
end
|
@@ -1246,7 +1369,7 @@ module JSONAPI
|
|
1246
1369
|
quoted_attrs = attrs.map do |attr|
|
1247
1370
|
quoted_table = conn.quote_table_name(attr.relation.table_alias || attr.relation.name)
|
1248
1371
|
quoted_column = conn.quote_column_name(attr.name)
|
1249
|
-
"#{quoted_table}.#{quoted_column}"
|
1372
|
+
Arel.sql("#{quoted_table}.#{quoted_column}")
|
1250
1373
|
end
|
1251
1374
|
relation.pluck(*quoted_attrs)
|
1252
1375
|
end
|
@@ -5,10 +5,10 @@ module JSONAPI
|
|
5
5
|
ActionController::Rendering,
|
6
6
|
ActionController::Renderers::All,
|
7
7
|
ActionController::StrongParameters,
|
8
|
-
ActionController::ForceSSL,
|
8
|
+
Gem::Requirement.new('< 6.1').satisfied_by?(ActionPack.gem_version) ? ActionController::ForceSSL : nil,
|
9
9
|
ActionController::Instrumentation,
|
10
10
|
JSONAPI::ActsAsResourceController
|
11
|
-
].freeze
|
11
|
+
].compact.freeze
|
12
12
|
|
13
13
|
MODULES.each do |mod|
|
14
14
|
include mod
|