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