jsonapi-resources 0.7.1.beta1 → 0.7.1.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/README.md +248 -74
- data/lib/jsonapi-resources.rb +5 -3
- data/lib/jsonapi/acts_as_resource_controller.rb +77 -14
- data/lib/jsonapi/configuration.rb +77 -16
- data/lib/jsonapi/error.rb +12 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +29 -9
- data/lib/jsonapi/formatter.rb +29 -4
- data/lib/jsonapi/link_builder.rb +18 -18
- data/lib/jsonapi/mime_types.rb +25 -6
- data/lib/jsonapi/naive_cache.rb +30 -0
- data/lib/jsonapi/operation.rb +10 -342
- data/lib/jsonapi/operation_dispatcher.rb +87 -0
- data/lib/jsonapi/operation_result.rb +2 -1
- data/lib/jsonapi/paginator.rb +6 -2
- data/lib/jsonapi/processor.rb +283 -0
- data/lib/jsonapi/relationship.rb +6 -4
- data/lib/jsonapi/{request.rb → request_parser.rb} +46 -35
- data/lib/jsonapi/resource.rb +88 -13
- data/lib/jsonapi/resource_controller.rb +2 -14
- data/lib/jsonapi/resource_controller_metal.rb +17 -0
- data/lib/jsonapi/resource_serializer.rb +62 -47
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +13 -2
- data/lib/jsonapi/routing_ext.rb +49 -11
- metadata +37 -129
- data/.gitignore +0 -22
- data/.travis.yml +0 -9
- data/Gemfile +0 -23
- data/Rakefile +0 -20
- data/jsonapi-resources.gemspec +0 -29
- data/lib/jsonapi/active_record_operations_processor.rb +0 -35
- data/lib/jsonapi/operations_processor.rb +0 -120
- data/locales/en.yml +0 -80
- data/test/config/database.yml +0 -5
- data/test/controllers/controller_test.rb +0 -3312
- data/test/fixtures/active_record.rb +0 -1486
- data/test/fixtures/author_details.yml +0 -9
- data/test/fixtures/book_authors.yml +0 -3
- data/test/fixtures/book_comments.yml +0 -12
- data/test/fixtures/books.yml +0 -7
- data/test/fixtures/categories.yml +0 -35
- data/test/fixtures/comments.yml +0 -21
- data/test/fixtures/comments_tags.yml +0 -20
- data/test/fixtures/companies.yml +0 -4
- data/test/fixtures/craters.yml +0 -9
- data/test/fixtures/customers.yml +0 -11
- data/test/fixtures/documents.yml +0 -3
- data/test/fixtures/expense_entries.yml +0 -13
- data/test/fixtures/facts.yml +0 -11
- data/test/fixtures/hair_cuts.yml +0 -3
- data/test/fixtures/iso_currencies.yml +0 -17
- data/test/fixtures/line_items.yml +0 -37
- data/test/fixtures/makes.yml +0 -2
- data/test/fixtures/moons.yml +0 -6
- data/test/fixtures/numeros_telefone.yml +0 -3
- data/test/fixtures/order_flags.yml +0 -7
- data/test/fixtures/people.yml +0 -31
- data/test/fixtures/pictures.yml +0 -15
- data/test/fixtures/planet_types.yml +0 -19
- data/test/fixtures/planets.yml +0 -47
- data/test/fixtures/posts.yml +0 -102
- data/test/fixtures/posts_tags.yml +0 -59
- data/test/fixtures/preferences.yml +0 -14
- data/test/fixtures/products.yml +0 -3
- data/test/fixtures/purchase_orders.yml +0 -23
- data/test/fixtures/sections.yml +0 -8
- data/test/fixtures/tags.yml +0 -39
- data/test/fixtures/vehicles.yml +0 -17
- data/test/fixtures/web_pages.yml +0 -3
- data/test/helpers/assertions.rb +0 -13
- data/test/helpers/functional_helpers.rb +0 -59
- data/test/helpers/value_matchers.rb +0 -60
- data/test/helpers/value_matchers_test.rb +0 -40
- data/test/integration/requests/namespaced_model_test.rb +0 -13
- data/test/integration/requests/request_test.rb +0 -932
- data/test/integration/routes/routes_test.rb +0 -218
- data/test/integration/sti_fields_test.rb +0 -18
- data/test/lib/generators/jsonapi/controller_generator_test.rb +0 -25
- data/test/lib/generators/jsonapi/resource_generator_test.rb +0 -30
- data/test/test_helper.rb +0 -342
- data/test/unit/formatters/dasherized_key_formatter_test.rb +0 -8
- data/test/unit/jsonapi_request/jsonapi_request_test.rb +0 -199
- data/test/unit/operation/operations_processor_test.rb +0 -528
- data/test/unit/pagination/offset_paginator_test.rb +0 -245
- data/test/unit/pagination/paged_paginator_test.rb +0 -242
- data/test/unit/resource/resource_test.rb +0 -560
- data/test/unit/serializer/include_directives_test.rb +0 -113
- data/test/unit/serializer/link_builder_test.rb +0 -244
- data/test/unit/serializer/polymorphic_serializer_test.rb +0 -383
- data/test/unit/serializer/response_document_test.rb +0 -61
- data/test/unit/serializer/serializer_test.rb +0 -1939
@@ -2,7 +2,7 @@ require 'jsonapi/operation'
|
|
2
2
|
require 'jsonapi/paginator'
|
3
3
|
|
4
4
|
module JSONAPI
|
5
|
-
class
|
5
|
+
class RequestParser
|
6
6
|
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
|
7
7
|
:resource_klass, :context, :paginator, :source_klass, :source_id,
|
8
8
|
:include_directives, :params, :warnings, :server_error_callbacks
|
@@ -289,27 +289,29 @@ module JSONAPI
|
|
289
289
|
end
|
290
290
|
|
291
291
|
def add_find_operation
|
292
|
-
@operations.push JSONAPI::
|
292
|
+
@operations.push JSONAPI::Operation.new(:find,
|
293
293
|
@resource_klass,
|
294
294
|
context: @context,
|
295
295
|
filters: @filters,
|
296
296
|
include_directives: @include_directives,
|
297
297
|
sort_criteria: @sort_criteria,
|
298
|
-
paginator: @paginator
|
298
|
+
paginator: @paginator,
|
299
|
+
fields: @fields
|
299
300
|
)
|
300
301
|
end
|
301
302
|
|
302
303
|
def add_show_operation
|
303
|
-
@operations.push JSONAPI::
|
304
|
+
@operations.push JSONAPI::Operation.new(:show,
|
304
305
|
@resource_klass,
|
305
306
|
context: @context,
|
306
307
|
id: @id,
|
307
|
-
include_directives: @include_directives
|
308
|
+
include_directives: @include_directives,
|
309
|
+
fields: @fields
|
308
310
|
)
|
309
311
|
end
|
310
312
|
|
311
313
|
def add_show_relationship_operation(relationship_type, parent_key)
|
312
|
-
@operations.push JSONAPI::
|
314
|
+
@operations.push JSONAPI::Operation.new(:show_relationship,
|
313
315
|
@resource_klass,
|
314
316
|
context: @context,
|
315
317
|
relationship_type: relationship_type,
|
@@ -318,17 +320,18 @@ module JSONAPI
|
|
318
320
|
end
|
319
321
|
|
320
322
|
def add_show_related_resource_operation(relationship_type)
|
321
|
-
@operations.push JSONAPI::
|
323
|
+
@operations.push JSONAPI::Operation.new(:show_related_resource,
|
322
324
|
@resource_klass,
|
323
325
|
context: @context,
|
324
326
|
relationship_type: relationship_type,
|
325
327
|
source_klass: @source_klass,
|
326
|
-
source_id: @source_id
|
328
|
+
source_id: @source_id,
|
329
|
+
fields: @fields
|
327
330
|
)
|
328
331
|
end
|
329
332
|
|
330
333
|
def add_show_related_resources_operation(relationship_type)
|
331
|
-
@operations.push JSONAPI::
|
334
|
+
@operations.push JSONAPI::Operation.new(:show_related_resources,
|
332
335
|
@resource_klass,
|
333
336
|
context: @context,
|
334
337
|
relationship_type: relationship_type,
|
@@ -336,7 +339,8 @@ module JSONAPI
|
|
336
339
|
source_id: @source_id,
|
337
340
|
filters: @source_klass.verify_filters(@filters, @context),
|
338
341
|
sort_criteria: @sort_criteria,
|
339
|
-
paginator: @paginator
|
342
|
+
paginator: @paginator,
|
343
|
+
fields: @fields
|
340
344
|
)
|
341
345
|
end
|
342
346
|
|
@@ -356,10 +360,11 @@ module JSONAPI
|
|
356
360
|
verify_type(params[:type])
|
357
361
|
|
358
362
|
data = parse_params(params, creatable_fields)
|
359
|
-
@operations.push JSONAPI::
|
363
|
+
@operations.push JSONAPI::Operation.new(:create_resource,
|
360
364
|
@resource_klass,
|
361
365
|
context: @context,
|
362
|
-
data: data
|
366
|
+
data: data,
|
367
|
+
fields: @fields
|
363
368
|
)
|
364
369
|
end
|
365
370
|
rescue JSONAPI::Exceptions::Error => e
|
@@ -382,7 +387,8 @@ module JSONAPI
|
|
382
387
|
}
|
383
388
|
end
|
384
389
|
|
385
|
-
if !raw.is_a?(Hash) || raw.
|
390
|
+
if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) ||
|
391
|
+
raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
|
386
392
|
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
387
393
|
end
|
388
394
|
|
@@ -476,7 +482,7 @@ module JSONAPI
|
|
476
482
|
def parse_to_many_relationship(link_value, relationship, &add_result)
|
477
483
|
if link_value.is_a?(Array) && link_value.length == 0
|
478
484
|
linkage = []
|
479
|
-
elsif link_value.is_a?(Hash)
|
485
|
+
elsif (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
|
480
486
|
linkage = link_value[:data]
|
481
487
|
else
|
482
488
|
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
@@ -531,7 +537,14 @@ module JSONAPI
|
|
531
537
|
end
|
532
538
|
end
|
533
539
|
end
|
534
|
-
when 'type'
|
540
|
+
when 'type'
|
541
|
+
when 'id'
|
542
|
+
unless formatted_allowed_fields.include?(:id)
|
543
|
+
params_not_allowed.push(:id)
|
544
|
+
unless JSONAPI.configuration.raise_if_parameters_not_allowed
|
545
|
+
params.delete :id
|
546
|
+
end
|
547
|
+
end
|
535
548
|
else
|
536
549
|
params_not_allowed.push(key)
|
537
550
|
end
|
@@ -564,7 +577,7 @@ module JSONAPI
|
|
564
577
|
|
565
578
|
def parse_add_relationship_operation(verified_params, relationship, parent_key)
|
566
579
|
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
567
|
-
@operations.push JSONAPI::
|
580
|
+
@operations.push JSONAPI::Operation.new(:create_to_many_relationship,
|
568
581
|
resource_klass,
|
569
582
|
context: @context,
|
570
583
|
resource_id: parent_key,
|
@@ -575,40 +588,37 @@ module JSONAPI
|
|
575
588
|
end
|
576
589
|
|
577
590
|
def parse_update_relationship_operation(verified_params, relationship, parent_key)
|
578
|
-
|
591
|
+
options = {
|
579
592
|
context: @context,
|
580
593
|
resource_id: parent_key,
|
581
594
|
relationship_type: relationship.name
|
582
|
-
|
595
|
+
}
|
583
596
|
|
584
597
|
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
585
598
|
if relationship.polymorphic?
|
586
|
-
|
587
|
-
|
588
|
-
key_type: verified_params[:to_one].values[0][:type]
|
589
|
-
)
|
599
|
+
options[:key_value] = verified_params[:to_one].values[0][:id]
|
600
|
+
options[:key_type] = verified_params[:to_one].values[0][:type]
|
590
601
|
|
591
|
-
|
602
|
+
operation_type = :replace_polymorphic_to_one_relationship
|
592
603
|
else
|
593
|
-
|
594
|
-
|
604
|
+
options[:key_value] = verified_params[:to_one].values[0]
|
605
|
+
operation_type = :replace_to_one_relationship
|
595
606
|
end
|
596
607
|
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
597
608
|
unless relationship.acts_as_set
|
598
609
|
fail JSONAPI::Exceptions::ToManySetReplacementForbidden.new
|
599
610
|
end
|
600
|
-
|
601
|
-
|
602
|
-
operation_klass = JSONAPI::ReplaceToManyRelationshipOperation
|
611
|
+
options[:data] = verified_params[:to_many].values[0]
|
612
|
+
operation_type = :replace_to_many_relationship
|
603
613
|
end
|
604
614
|
|
605
|
-
@operations.push
|
615
|
+
@operations.push JSONAPI::Operation.new(operation_type, resource_klass, options)
|
606
616
|
end
|
607
617
|
|
608
618
|
def parse_single_replace_operation(data, keys, id_key_presence_check_required: true)
|
609
619
|
fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
|
610
620
|
|
611
|
-
key = data[:id]
|
621
|
+
key = data[:id].to_s
|
612
622
|
if id_key_presence_check_required && !keys.include?(key)
|
613
623
|
fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
|
614
624
|
end
|
@@ -617,11 +627,12 @@ module JSONAPI
|
|
617
627
|
|
618
628
|
verify_type(data[:type])
|
619
629
|
|
620
|
-
@operations.push JSONAPI::
|
630
|
+
@operations.push JSONAPI::Operation.new(:replace_fields,
|
621
631
|
@resource_klass,
|
622
632
|
context: @context,
|
623
633
|
resource_id: key,
|
624
|
-
data: parse_params(data, updatable_fields)
|
634
|
+
data: parse_params(data, updatable_fields),
|
635
|
+
fields: @fields
|
625
636
|
)
|
626
637
|
end
|
627
638
|
|
@@ -645,7 +656,7 @@ module JSONAPI
|
|
645
656
|
keys = parse_key_array(params.require(:id))
|
646
657
|
|
647
658
|
keys.each do |key|
|
648
|
-
@operations.push JSONAPI::
|
659
|
+
@operations.push JSONAPI::Operation.new(:remove_resource,
|
649
660
|
@resource_klass,
|
650
661
|
context: @context,
|
651
662
|
resource_id: key
|
@@ -667,12 +678,12 @@ module JSONAPI
|
|
667
678
|
keys.each do |key|
|
668
679
|
operation_args = operation_base_args.dup
|
669
680
|
operation_args[1] = operation_args[1].merge(associated_key: key)
|
670
|
-
@operations.push JSONAPI::
|
681
|
+
@operations.push JSONAPI::Operation.new(:remove_to_many_relationship,
|
671
682
|
*operation_args
|
672
683
|
)
|
673
684
|
end
|
674
685
|
else
|
675
|
-
@operations.push JSONAPI::
|
686
|
+
@operations.push JSONAPI::Operation.new(:remove_to_one_relationship,
|
676
687
|
*operation_base_args
|
677
688
|
)
|
678
689
|
end
|
data/lib/jsonapi/resource.rb
CHANGED
@@ -152,6 +152,15 @@ module JSONAPI
|
|
152
152
|
{}
|
153
153
|
end
|
154
154
|
|
155
|
+
# Override this to return custom links
|
156
|
+
# must return a hash, which will be merged with the default { self: 'self-url' } links hash
|
157
|
+
# links keys will be not be formatted with the key formatter for the serializer by default.
|
158
|
+
# They can however use the serializer's format_key and format_value methods if desired
|
159
|
+
# the _options hash will contain the serializer and the serialization_options
|
160
|
+
def custom_links(_options)
|
161
|
+
{}
|
162
|
+
end
|
163
|
+
|
155
164
|
private
|
156
165
|
|
157
166
|
def save
|
@@ -173,8 +182,8 @@ module JSONAPI
|
|
173
182
|
# return :accepted
|
174
183
|
# end
|
175
184
|
# ```
|
176
|
-
def _save
|
177
|
-
unless @model.valid?
|
185
|
+
def _save(validation_context = nil)
|
186
|
+
unless @model.valid?(validation_context)
|
178
187
|
fail JSONAPI::Exceptions::ValidationErrors.new(self)
|
179
188
|
end
|
180
189
|
|
@@ -201,6 +210,9 @@ module JSONAPI
|
|
201
210
|
fail JSONAPI::Exceptions::ValidationErrors.new(self)
|
202
211
|
end
|
203
212
|
:completed
|
213
|
+
|
214
|
+
rescue ActiveRecord::DeleteRestrictionError => e
|
215
|
+
fail JSONAPI::Exceptions::RecordLocked.new(e.message)
|
204
216
|
end
|
205
217
|
|
206
218
|
def _create_to_many_links(relationship_type, relationship_key_values)
|
@@ -256,6 +268,11 @@ module JSONAPI
|
|
256
268
|
@model.public_send(relation_name).delete(key)
|
257
269
|
|
258
270
|
:completed
|
271
|
+
|
272
|
+
rescue ActiveRecord::DeleteRestrictionError => e
|
273
|
+
fail JSONAPI::Exceptions::RecordLocked.new(e.message)
|
274
|
+
rescue ActiveRecord::RecordNotFound
|
275
|
+
fail JSONAPI::Exceptions::RecordNotFound.new(key)
|
259
276
|
end
|
260
277
|
|
261
278
|
def _remove_to_one_link(relationship_type)
|
@@ -390,11 +407,11 @@ module JSONAPI
|
|
390
407
|
@_attributes ||= {}
|
391
408
|
@_attributes[attr] = options
|
392
409
|
define_method attr do
|
393
|
-
@model.public_send(attr)
|
410
|
+
@model.public_send(options[:delegate] ? options[:delegate].to_sym : attr)
|
394
411
|
end unless method_defined?(attr)
|
395
412
|
|
396
413
|
define_method "#{attr}=" do |value|
|
397
|
-
@model.public_send
|
414
|
+
@model.public_send("#{options[:delegate] ? options[:delegate].to_sym : attr}=", value)
|
398
415
|
end unless method_defined?("#{attr}=")
|
399
416
|
end
|
400
417
|
|
@@ -521,17 +538,57 @@ module JSONAPI
|
|
521
538
|
|
522
539
|
def apply_sort(records, order_options, _context = {})
|
523
540
|
if order_options.any?
|
524
|
-
|
525
|
-
|
526
|
-
|
541
|
+
order_options.each_pair do |field, direction|
|
542
|
+
if field.to_s.include?(".")
|
543
|
+
*model_names, column_name = field.split(".")
|
544
|
+
|
545
|
+
associations = _lookup_association_chain([records.model.to_s, *model_names])
|
546
|
+
joins_query = _build_joins([records.model, *associations])
|
547
|
+
|
548
|
+
# _sorting is appended to avoid name clashes with manual joins eg. overriden filters
|
549
|
+
order_by_query = "#{associations.last.name}_sorting.#{column_name} #{direction}"
|
550
|
+
records = records.joins(joins_query).order(order_by_query)
|
551
|
+
else
|
552
|
+
records = records.order(field => direction)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
records
|
558
|
+
end
|
559
|
+
|
560
|
+
def _lookup_association_chain(model_names)
|
561
|
+
associations = []
|
562
|
+
model_names.inject do |prev, current|
|
563
|
+
association = prev.classify.constantize.reflect_on_all_associations.detect do |assoc|
|
564
|
+
assoc.name.to_s.downcase == current.downcase
|
565
|
+
end
|
566
|
+
associations << association
|
567
|
+
association.class_name
|
568
|
+
end
|
569
|
+
|
570
|
+
associations
|
571
|
+
end
|
572
|
+
|
573
|
+
def _build_joins(associations)
|
574
|
+
joins = []
|
575
|
+
|
576
|
+
associations.inject do |prev, current|
|
577
|
+
joins << "LEFT JOIN #{current.table_name} AS #{current.name}_sorting ON #{current.name}_sorting.id = #{prev.table_name}.#{current.foreign_key}"
|
578
|
+
current
|
527
579
|
end
|
580
|
+
joins.join("\n")
|
528
581
|
end
|
529
582
|
|
530
583
|
def apply_filter(records, filter, value, options = {})
|
531
584
|
strategy = _allowed_filters.fetch(filter.to_sym, Hash.new)[:apply]
|
532
585
|
|
533
586
|
if strategy
|
534
|
-
strategy.
|
587
|
+
if strategy.is_a?(Symbol) || strategy.is_a?(String)
|
588
|
+
send(strategy, records, value, options)
|
589
|
+
else
|
590
|
+
strategy.call(records, value, options)
|
591
|
+
end
|
535
592
|
else
|
536
593
|
records.where(filter => value)
|
537
594
|
end
|
@@ -571,8 +628,13 @@ module JSONAPI
|
|
571
628
|
apply_sort(records, order_options, context)
|
572
629
|
end
|
573
630
|
|
631
|
+
# Assumes ActiveRecord's counting. Override if you need a different counting method
|
632
|
+
def count_records(records)
|
633
|
+
records.count(:all)
|
634
|
+
end
|
635
|
+
|
574
636
|
def find_count(filters, options = {})
|
575
|
-
filter_records(filters, options)
|
637
|
+
count_records(filter_records(filters, options))
|
576
638
|
end
|
577
639
|
|
578
640
|
# Override this method if you have more complex requirements than this basic find method provides
|
@@ -587,9 +649,15 @@ module JSONAPI
|
|
587
649
|
|
588
650
|
records = apply_pagination(records, options[:paginator], order_options)
|
589
651
|
|
652
|
+
resources_for(records, context)
|
653
|
+
end
|
654
|
+
|
655
|
+
def resources_for(records, context)
|
590
656
|
resources = []
|
657
|
+
resource_classes = {}
|
591
658
|
records.each do |model|
|
592
|
-
|
659
|
+
resource_class = resource_classes[model.class] ||= self.resource_for_model(model)
|
660
|
+
resources.push resource_class.new(model, context)
|
593
661
|
end
|
594
662
|
|
595
663
|
resources
|
@@ -625,12 +693,19 @@ module JSONAPI
|
|
625
693
|
|
626
694
|
def verify_filter(filter, raw, context = nil)
|
627
695
|
filter_values = []
|
628
|
-
|
696
|
+
if raw.present?
|
697
|
+
filter_values += raw.is_a?(String) ? CSV.parse_line(raw) : [raw]
|
698
|
+
end
|
629
699
|
|
630
700
|
strategy = _allowed_filters.fetch(filter, Hash.new)[:verify]
|
631
701
|
|
632
702
|
if strategy
|
633
|
-
|
703
|
+
if strategy.is_a?(Symbol) || strategy.is_a?(String)
|
704
|
+
values = send(strategy, filter_values, context)
|
705
|
+
else
|
706
|
+
values = strategy.call(filter_values, context)
|
707
|
+
end
|
708
|
+
[filter, values]
|
634
709
|
else
|
635
710
|
if is_filter_relationship?(filter)
|
636
711
|
verify_relationship_filter(filter, filter_values, context)
|
@@ -645,7 +720,7 @@ module JSONAPI
|
|
645
720
|
end
|
646
721
|
|
647
722
|
def resource_key_type
|
648
|
-
@_resource_key_type
|
723
|
+
@_resource_key_type ||= JSONAPI.configuration.resource_key_type
|
649
724
|
end
|
650
725
|
|
651
726
|
def verify_key(key, context = nil)
|
@@ -1,17 +1,5 @@
|
|
1
1
|
module JSONAPI
|
2
|
-
class ResourceController < ActionController::
|
3
|
-
|
4
|
-
AbstractController::Rendering,
|
5
|
-
ActionController::Rendering,
|
6
|
-
ActionController::Renderers::All,
|
7
|
-
ActionController::StrongParameters,
|
8
|
-
ActionController::ForceSSL,
|
9
|
-
ActionController::Instrumentation,
|
10
|
-
JSONAPI::ActsAsResourceController
|
11
|
-
].freeze
|
12
|
-
|
13
|
-
MODULES.each do |mod|
|
14
|
-
include mod
|
15
|
-
end
|
2
|
+
class ResourceController < ActionController::Base
|
3
|
+
include JSONAPI::ActsAsResourceController
|
16
4
|
end
|
17
5
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class ResourceControllerMetal < ActionController::Metal
|
3
|
+
MODULES = [
|
4
|
+
AbstractController::Rendering,
|
5
|
+
ActionController::Rendering,
|
6
|
+
ActionController::Renderers::All,
|
7
|
+
ActionController::StrongParameters,
|
8
|
+
ActionController::ForceSSL,
|
9
|
+
ActionController::Instrumentation,
|
10
|
+
JSONAPI::ActsAsResourceController
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
MODULES.each do |mod|
|
14
|
+
include mod
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -12,7 +12,7 @@ module JSONAPI
|
|
12
12
|
# Purpose: determines which fields are serialized for a resource type. This encompasses both attributes and
|
13
13
|
# relationship ids in the links section for a resource. Fields are global for a resource type.
|
14
14
|
# Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
|
15
|
-
# key_formatter: KeyFormatter
|
15
|
+
# key_formatter: KeyFormatter instance to override the default configuration
|
16
16
|
# serializer_options: additional options that will be passed to resource meta and links lambdas
|
17
17
|
|
18
18
|
def initialize(primary_resource_klass, options = {})
|
@@ -21,16 +21,23 @@ module JSONAPI
|
|
21
21
|
@include = options.fetch(:include, [])
|
22
22
|
@include_directives = options[:include_directives]
|
23
23
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
24
|
+
@id_formatter = ValueFormatter.value_formatter_for(:id)
|
24
25
|
@link_builder = generate_link_builder(primary_resource_klass, options)
|
25
26
|
@always_include_to_one_linkage_data = options.fetch(:always_include_to_one_linkage_data,
|
26
27
|
JSONAPI.configuration.always_include_to_one_linkage_data)
|
27
28
|
@always_include_to_many_linkage_data = options.fetch(:always_include_to_many_linkage_data,
|
28
29
|
JSONAPI.configuration.always_include_to_many_linkage_data)
|
29
30
|
@serialization_options = options.fetch(:serialization_options, {})
|
31
|
+
|
32
|
+
# Warning: This makes ResourceSerializer non-thread-safe. That's not a problem with the
|
33
|
+
# request-specific way it's currently used, though.
|
34
|
+
@value_formatter_type_cache = NaiveCache.new{|arg| ValueFormatter.value_formatter_for(arg) }
|
30
35
|
end
|
31
36
|
|
32
37
|
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
|
33
38
|
def serialize_to_hash(source)
|
39
|
+
@top_level_sources = Set.new([source].flatten.compact.map {|s| top_level_source_key(s) })
|
40
|
+
|
34
41
|
is_resource_collection = source.respond_to?(:to_ary)
|
35
42
|
|
36
43
|
@included_objects = {}
|
@@ -81,8 +88,7 @@ module JSONAPI
|
|
81
88
|
end
|
82
89
|
|
83
90
|
def format_value(value, format)
|
84
|
-
|
85
|
-
value_formatter.format(value)
|
91
|
+
@value_formatter_type_cache.get(format).format(value)
|
86
92
|
end
|
87
93
|
|
88
94
|
private
|
@@ -114,19 +120,18 @@ module JSONAPI
|
|
114
120
|
|
115
121
|
obj_hash['type'] = format_key(source.class._type.to_s)
|
116
122
|
|
117
|
-
links =
|
123
|
+
links = links_hash(source)
|
118
124
|
obj_hash['links'] = links unless links.empty?
|
119
125
|
|
120
|
-
attributes =
|
126
|
+
attributes = attributes_hash(source)
|
121
127
|
obj_hash['attributes'] = attributes unless attributes.empty?
|
122
128
|
|
123
|
-
relationships =
|
129
|
+
relationships = relationships_hash(source, include_directives)
|
124
130
|
obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
|
125
131
|
|
126
|
-
meta = source
|
127
|
-
|
128
|
-
|
129
|
-
end
|
132
|
+
meta = meta_hash(source)
|
133
|
+
obj_hash['meta'] = meta unless meta.empty?
|
134
|
+
|
130
135
|
obj_hash
|
131
136
|
end
|
132
137
|
|
@@ -139,7 +144,7 @@ module JSONAPI
|
|
139
144
|
end
|
140
145
|
end
|
141
146
|
|
142
|
-
def
|
147
|
+
def attributes_hash(source)
|
143
148
|
requested = requested_fields(source.class)
|
144
149
|
fields = source.fetchable_fields & source.class._attributes.keys.to_a
|
145
150
|
fields = requested & fields unless requested.nil?
|
@@ -159,7 +164,31 @@ module JSONAPI
|
|
159
164
|
}
|
160
165
|
end
|
161
166
|
|
162
|
-
def
|
167
|
+
def meta_hash(source)
|
168
|
+
meta = source.meta(custom_generation_options)
|
169
|
+
(meta.is_a?(Hash) && meta) || {}
|
170
|
+
end
|
171
|
+
|
172
|
+
def links_hash(source)
|
173
|
+
{
|
174
|
+
self: link_builder.self_link(source)
|
175
|
+
}.merge(custom_links_hash(source)).compact
|
176
|
+
end
|
177
|
+
|
178
|
+
def custom_links_hash(source)
|
179
|
+
custom_links = source.custom_links(custom_generation_options)
|
180
|
+
(custom_links.is_a?(Hash) && custom_links) || {}
|
181
|
+
end
|
182
|
+
|
183
|
+
def top_level_source_key(source)
|
184
|
+
"#{source.class}_#{source.id}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def self_referential_and_already_in_source(resource)
|
188
|
+
resource && @top_level_sources.include?(top_level_source_key(resource))
|
189
|
+
end
|
190
|
+
|
191
|
+
def relationships_hash(source, include_directives)
|
163
192
|
relationships = source.class._relationships
|
164
193
|
requested = requested_fields(source.class)
|
165
194
|
fields = relationships.keys
|
@@ -177,39 +206,24 @@ module JSONAPI
|
|
177
206
|
|
178
207
|
include_linkage = ia && ia[:include]
|
179
208
|
include_linked_children = ia && !ia[:include_related].empty?
|
209
|
+
resources = (include_linkage || include_linked_children) && [source.public_send(name)].flatten.compact
|
180
210
|
|
181
211
|
if field_set.include?(name)
|
182
212
|
hash[format_key(name)] = link_object(source, relationship, include_linkage)
|
183
213
|
end
|
184
214
|
|
185
|
-
type = relationship.type
|
186
|
-
|
187
215
|
# If the object has been serialized once it will be in the related objects list,
|
188
216
|
# but it's possible all children won't have been captured. So we must still go
|
189
217
|
# through the relationships.
|
190
218
|
if include_linkage || include_linked_children
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
elsif include_linked_children || relationships_only
|
200
|
-
relationship_data(resource, ia)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
204
|
-
resources = source.public_send(name)
|
205
|
-
resources.each do |resource|
|
206
|
-
id = resource.id
|
207
|
-
relationships_only = already_serialized?(type, id)
|
208
|
-
if include_linkage && !relationships_only
|
209
|
-
add_included_object(id, object_hash(resource, ia))
|
210
|
-
elsif include_linked_children || relationships_only
|
211
|
-
relationship_data(resource, ia)
|
212
|
-
end
|
219
|
+
resources.each do |resource|
|
220
|
+
next if self_referential_and_already_in_source(resource)
|
221
|
+
id = resource.id
|
222
|
+
relationships_only = already_serialized?(relationship.type, id)
|
223
|
+
if include_linkage && !relationships_only
|
224
|
+
add_included_object(id, object_hash(resource, ia))
|
225
|
+
elsif include_linked_children || relationships_only
|
226
|
+
relationships_hash(resource, ia)
|
213
227
|
end
|
214
228
|
end
|
215
229
|
end
|
@@ -217,13 +231,6 @@ module JSONAPI
|
|
217
231
|
end
|
218
232
|
end
|
219
233
|
|
220
|
-
def relationship_links(source)
|
221
|
-
links = {}
|
222
|
-
links[:self] = link_builder.self_link(source)
|
223
|
-
|
224
|
-
links
|
225
|
-
end
|
226
|
-
|
227
234
|
def already_serialized?(type, id)
|
228
235
|
type = format_key(type)
|
229
236
|
@included_objects.key?(type) && @included_objects[type].key?(id)
|
@@ -292,18 +299,26 @@ module JSONAPI
|
|
292
299
|
def foreign_key_value(source, relationship)
|
293
300
|
foreign_key = relationship.foreign_key
|
294
301
|
value = source.public_send(foreign_key)
|
295
|
-
|
302
|
+
@id_formatter.format(value)
|
296
303
|
end
|
297
304
|
|
298
305
|
def foreign_key_types_and_values(source, relationship)
|
299
306
|
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
300
307
|
if relationship.polymorphic?
|
301
|
-
source._model.public_send(relationship.name)
|
302
|
-
|
308
|
+
assoc = source._model.public_send(relationship.name)
|
309
|
+
# Avoid hitting the database again for values already pre-loaded
|
310
|
+
if assoc.respond_to?(:loaded?) and assoc.loaded?
|
311
|
+
assoc.map do |obj|
|
312
|
+
[obj.type.underscore.pluralize, @id_formatter.format(obj.id)]
|
313
|
+
end
|
314
|
+
else
|
315
|
+
assoc.pluck(:type, :id).map do |type, id|
|
316
|
+
[type.underscore.pluralize, @id_formatter.format(id)]
|
317
|
+
end
|
303
318
|
end
|
304
319
|
else
|
305
320
|
source.public_send(relationship.foreign_key).map do |value|
|
306
|
-
[relationship.type,
|
321
|
+
[relationship.type, @id_formatter.format(value)]
|
307
322
|
end
|
308
323
|
end
|
309
324
|
end
|