From ecb92f07f57cf8a0a4371f3e7e9afa6c12f48a91 Mon Sep 17 00:00:00 2001 From: Oleksiy Babich Date: Wed, 4 Jul 2018 05:35:06 +0300 Subject: [PATCH] add is_collection parameter to force corresponding serialization (#239) * add is_collection parameter to force corresponding serialization * add documentation for is_collection purpose, behavior and notes re. default autodetect logic --- README.md | 20 ++++++++++++ lib/fast_jsonapi/object_serializer.rb | 9 ++++-- spec/lib/object_serializer_spec.rb | 46 +++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index af57e7a..f4333d1 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,26 @@ hash = MovieSerializer.new([movie, movie], options).serializable_hash json_string = MovieSerializer.new([movie, movie], options).serialized_json ``` +#### Control Over Collection Serialization + +You can use `is_collection` option to have better control over collection serialization. + +If this option is not provided or `nil` autedetect logic is used to try understand +if provided resource is a single object or collection. + +Autodetect logic is compatible with most DB toolkits (ActiveRecord, Sequel, etc.) but +**cannot** guarantee that single vs collection will be always detected properly. + +```ruby +options[:is_collection] +``` + +was introduced to be able to have precise control this behavior + +- `nil` or not provided: will try to autodetect single vs collection (please, see notes above) +- `true` will always treat input resource as *collection* +- `false` will always treat input resource as *single object* + ### Caching Requires a `cache_key` method be defined on model: diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 7cc3634..90771b4 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -28,7 +28,7 @@ module FastJsonapi end def serializable_hash - return hash_for_collection if is_collection?(@resource) + return hash_for_collection if is_collection?(@resource, @is_collection) hash_for_one_record end @@ -75,6 +75,7 @@ module FastJsonapi @known_included_objects = {} @meta = options[:meta] @links = options[:links] + @is_collection = options[:is_collection] @params = options[:params] || {} raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash) @@ -84,8 +85,10 @@ module FastJsonapi end end - def is_collection?(resource) - resource.respond_to?(:each) && !resource.respond_to?(:each_pair) + def is_collection?(resource, force_is_collection = nil) + return force_is_collection unless force_is_collection.nil? + + resource.respond_to?(:size) && !resource.respond_to?(:each_pair) end class_methods do diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index ed770af..07cbbef 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -310,6 +310,52 @@ describe FastJsonapi::ObjectSerializer do end end + context 'when is_collection option present' do + subject { MovieSerializer.new(resource, is_collection_options).serializable_hash } + + context 'autodetect' do + let(:is_collection_options) { {} } + + context 'collection if no option present' do + let(:resource) { [movie] } + it { expect(subject[:data]).to be_a(Array) } + end + + context 'single if no option present' do + let(:resource) { movie } + it { expect(subject[:data]).to be_a(Hash) } + end + end + + context 'force is_collection to true' do + let(:is_collection_options) { { is_collection: true } } + + context 'collection will pass' do + let(:resource) { [movie] } + it { expect(subject[:data]).to be_a(Array) } + end + + context 'single will raise error' do + let(:resource) { movie } + it { expect { subject }.to raise_error(NoMethodError, /method(.*)each/) } + end + end + + context 'force is_collection to false' do + let(:is_collection_options) { { is_collection: false } } + + context 'collection will fail without id' do + let(:resource) { [movie] } + it { expect { subject }.to raise_error(FastJsonapi::MandatoryField, /id is a mandatory field/) } + end + + context 'single will pass' do + let(:resource) { movie } + it { expect(subject[:data]).to be_a(Hash) } + end + end + end + context 'when optional attributes are determined by record data' do it 'returns optional attribute when attribute is included' do movie.release_year = 2001