From fcecda7dd81e8d31a119e4ab1f77f61147e2e983 Mon Sep 17 00:00:00 2001 From: "Jessie A. Young" Date: Thu, 21 Mar 2019 15:55:36 -0700 Subject: [PATCH 01/12] Highlight that this is for JSON:API spec only * The link was already there but I skipped over it on my first read. This update makes the fact more prominent. * I was testing to see if we wanted to move from AM Serializers to fast_jsonapi but our API is not written according to the JSON:API spec so, after converting one serializer over, I learned that this would not work for me. * This update might save someone in my position the ~30 mins or so it takes to bundle and write a serializer in the future. :) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 53c8bfa7..b4ad5e4d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ A lightning fast [JSON:API](http://jsonapi.org/) serializer for Ruby Objects. +Note: this gem deals only with implementing the JSON:API spec. If your API +responses are not formatted according to the JSON:API spec, this library will +not work for you. + # Performance Comparison We compare serialization times with Active Model Serializer as part of RSpec performance tests included on this library. We want to ensure that with every change on this library, serialization time is at least `25 times` faster than Active Model Serializers on up to current benchmark of 1000 records. Please read the [performance document](https://github.com/Netflix/fast_jsonapi/blob/master/performance_methodology.md) for any questions related to methodology. From bf7a7e35c8ea284279eccd61ae7139689b6be639 Mon Sep 17 00:00:00 2001 From: Csaba Apagyi Date: Mon, 4 Mar 2019 13:18:03 +0100 Subject: [PATCH 02/12] Fix formatting of `set_id` example in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4ad5e4d..849af1f2 100644 --- a/README.md +++ b/README.md @@ -454,7 +454,7 @@ Option | Purpose | Example ------------ | ------------- | ------------- set_type | Type name of Object | ```set_type :movie ``` key | Key of Object | ```belongs_to :owner, key: :user ``` -set_id | ID of Object | ```set_id :owner_id ``` or ```set_id { |record| "#{record.name.downcase}-#{record.id}" }``` +set_id | ID of Object | ```set_id :owner_id ``` or ```set_id { \|record\| "#{record.name.downcase}-#{record.id}" }``` cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours, race_condition_ttl: 10.seconds``` id_method_name | Set custom method name to get ID of an object (If block is provided for the relationship, `id_method_name` is invoked on the return value of the block instead of the resource object) | ```has_many :locations, id_method_name: :place_ids ``` object_method_name | Set custom method name to get related objects | ```has_many :locations, object_method_name: :places ``` From 513eaca3dc731ccc55c8d4aeeda9985bf640a156 Mon Sep 17 00:00:00 2001 From: Charalampos Aristomenopoulos Date: Fri, 22 Feb 2019 12:08:36 +0200 Subject: [PATCH 03/12] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 849af1f2..b6a3cb0c 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,7 @@ json_string = MovieSerializer.new([movie, movie], options).serialized_json 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 this option is not provided or `nil` autodetect 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 From 91e2beec8e21fb2bfaa2587fd08d956e2bcab9f5 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Mon, 18 Feb 2019 19:32:30 -0500 Subject: [PATCH 04/12] Update Readme Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6a3cb0c..632efceb 100644 --- a/README.md +++ b/README.md @@ -477,7 +477,7 @@ Skylight relies on `ActiveSupport::Notifications` to track these two core method require 'fast_jsonapi/instrumentation' ``` -The two instrumented notifcations are supplied by these two constants: +The two instrumented notifications are supplied by these two constants: * `FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION` * `FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION` From 6aefeb755678573a794e7a7cd45493d4977b7337 Mon Sep 17 00:00:00 2001 From: Shishir Kakaraddi Date: Sat, 3 Nov 2018 12:11:56 -0700 Subject: [PATCH 05/12] bump up version to 1.5 --- lib/fast_jsonapi/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/version.rb b/lib/fast_jsonapi/version.rb index c9c4c7ab..12442c14 100644 --- a/lib/fast_jsonapi/version.rb +++ b/lib/fast_jsonapi/version.rb @@ -1,3 +1,3 @@ module FastJsonapi - VERSION = "1.4" + VERSION = "1.5" end From 36b8ea2dfc609cc772451fd455775202dd3abb42 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Fri, 21 Dec 2018 18:41:55 +0300 Subject: [PATCH 06/12] [#365] Support frozen array in option --- lib/fast_jsonapi/object_serializer.rb | 2 +- spec/lib/object_serializer_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index b8a24183..e767135d 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -86,7 +86,7 @@ def process_options(options) raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash) if options[:include].present? - @includes = options[:include].delete_if(&:blank?).map(&:to_sym) + @includes = options[:include].reject(&:blank?).map(&:to_sym) self.class.validate_includes!(@includes) end end diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index ef755ccc..74bb2f41 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -473,6 +473,16 @@ class BlahSerializer options[:include] = [:actors] expect(serializable_hash['included']).to be_blank end + + end + end + + context 'when include has frozen array' do + let(:options) { { include: [:actors].freeze }} + let(:json) { MovieOptionalRelationshipSerializer.new(movie, options).serialized_json } + + it 'does not raise and error' do + expect(json['included']).to_not be_blank end end From ae93b851037f453ee8e6cc4b4d8fb4dcb62a7469 Mon Sep 17 00:00:00 2001 From: Matt Eddy Date: Mon, 8 Oct 2018 11:45:46 -0700 Subject: [PATCH 07/12] Allow fieldsets to specify no attributes/relationships --- lib/fast_jsonapi/serialization_core.rb | 3 +++ spec/lib/object_serializer_fields_spec.rb | 33 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 200af9b3..845aee75 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -44,6 +44,8 @@ def links_hash(record, params = {}) def attributes_hash(record, fieldset = nil, params = {}) attributes = attributes_to_serialize attributes = attributes.slice(*fieldset) if fieldset.present? + attributes = {} if fieldset == [] + attributes.each_with_object({}) do |(_k, attribute), hash| attribute.serialize(record, params, hash) end @@ -52,6 +54,7 @@ def attributes_hash(record, fieldset = nil, params = {}) def relationships_hash(record, relationships = nil, fieldset = nil, params = {}) relationships = relationships_to_serialize if relationships.nil? relationships = relationships.slice(*fieldset) if fieldset.present? + relationships = {} if fieldset == [] relationships.each_with_object({}) do |(_k, relationship), hash| relationship.serialize(record, params, hash) diff --git a/spec/lib/object_serializer_fields_spec.rb b/spec/lib/object_serializer_fields_spec.rb index 913ba83b..697f209d 100644 --- a/spec/lib/object_serializer_fields_spec.rb +++ b/spec/lib/object_serializer_fields_spec.rb @@ -22,6 +22,18 @@ expect(hash[:data][:relationships].keys.sort).to eq %i[actors advertising_campaign] end + it 'returns no fields when none are specified' do + hash = MovieSerializer.new(movie, fields: { movie: [] }).serializable_hash + + expect(hash[:data][:attributes].keys).to eq [] + end + + it 'returns no relationships when none are specified' do + hash = MovieSerializer.new(movie, fields: { movie: [] }).serializable_hash + + expect(hash[:data][:relationships].keys).to eq [] + end + it 'only returns specified fields for included relationships' do hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash @@ -45,4 +57,25 @@ expect(hash[:included][3][:relationships].keys.sort).to eq %i[movie] end + + context 'with no included fields specified' do + let(:fields) do + { + movie: %i[name actors advertising_campaign], + actor: [] + } + end + + it 'returns no fields for included relationships when none are specified' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash + + expect(hash[:included][2][:attributes].keys).to eq [] + end + + it 'returns no relationships when none are specified' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash + + expect(hash[:included][2][:relationships].keys).to eq [] + end + end end From dd379a02ca4d4d65a867644afd695e3d6234c07d Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 22 Nov 2018 10:58:42 +0100 Subject: [PATCH 08/12] Fix error on defining anonymous serializer class, fixes #353 --- lib/fast_jsonapi/object_serializer.rb | 2 +- spec/lib/object_serializer_spec.rb | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index e767135d..d142a04a 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -130,7 +130,7 @@ def reflected_record_type return @reflected_record_type if defined?(@reflected_record_type) @reflected_record_type ||= begin - if self.name.end_with?('Serializer') + if self.name && self.name.end_with?('Serializer') self.name.split('::').last.chomp('Serializer').underscore.to_sym end end diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index 74bb2f41..c86ce7c9 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -314,6 +314,15 @@ class BlahBlahSerializer expect(BlahBlahSerializer.record_type).to be :blah_blah end + it 'should set default_type for a namespaced serializer' do + module V1 + class BlahSerializer + include FastJsonapi::ObjectSerializer + end + end + expect(V1::BlahSerializer.record_type).to be :blah + end + it 'shouldnt set default_type for a serializer that doesnt follow convention' do class BlahBlahSerializerBuilder include FastJsonapi::ObjectSerializer @@ -321,13 +330,11 @@ class BlahBlahSerializerBuilder expect(BlahBlahSerializerBuilder.record_type).to be_nil end - it 'should set default_type for a namespaced serializer' do - module V1 - class BlahSerializer - include FastJsonapi::ObjectSerializer - end + it 'shouldnt set default_type for an anonymous serializer' do + serializer_class = Class.new do + include FastJsonapi::ObjectSerializer end - expect(V1::BlahSerializer.record_type).to be :blah + expect(serializer_class.record_type).to be_nil end end From 3668625882ee55f59870270a19cd0278041da401 Mon Sep 17 00:00:00 2001 From: Daniel Duke Date: Fri, 4 Jan 2019 09:12:17 -0800 Subject: [PATCH 09/12] validate all include items instead of just the first --- lib/fast_jsonapi/object_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index d142a04a..c161fb6b 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -299,7 +299,7 @@ def link(link_name, link_method_name = nil, &block) def validate_includes!(includes) return if includes.blank? - includes.detect do |include_item| + includes.each do |include_item| klass = self parse_include_item(include_item).each do |parsed_include| relationships_to_serialize = klass.relationships_to_serialize || {} From 9f0608d4c920149149446949459802e182e27a1b Mon Sep 17 00:00:00 2001 From: Daniel Duke Date: Fri, 4 Jan 2019 09:12:58 -0800 Subject: [PATCH 10/12] add specs for multiple include options --- spec/lib/object_serializer_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index c86ce7c9..42db8f00 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -142,12 +142,25 @@ expect { MovieSerializer.new([movie, movie], options).serializable_hash }.to raise_error(ArgumentError) end + it 'returns errors when serializing with non-existent and existent includes keys' do + options = {} + options[:meta] = { total: 2 } + options[:include] = [:actors, :blah_blah] + expect { MovieSerializer.new([movie, movie], options).serializable_hash }.to raise_error(ArgumentError) + end + it 'does not throw an error with non-empty string array includes key' do options = {} options[:include] = ['actors'] expect { MovieSerializer.new(movie, options) }.not_to raise_error end + it 'does not throw an error with non-empty string array includes keys' do + options = {} + options[:include] = ['actors', 'owner'] + expect { MovieSerializer.new(movie, options) }.not_to raise_error + end + it 'returns keys when serializing with empty string/nil array includes key' do options = {} options[:meta] = { total: 2 } From 4077a23c4523abfdb075b9d4b5a7688195e81ab6 Mon Sep 17 00:00:00 2001 From: iwsksky Date: Fri, 21 Dec 2018 03:44:42 +0900 Subject: [PATCH 11/12] pass array of unique movies to serializer --- README.md | 4 ++-- .../as_notifications_negative_spec.rb | 3 ++- .../instrumentation/as_notifications_spec.rb | 3 ++- spec/lib/object_serializer_caching_spec.rb | 3 ++- .../lib/object_serializer_class_methods_spec.rb | 6 +++--- spec/lib/object_serializer_spec.rb | 17 +++++++++++------ 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 632efceb..cbc615ee 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,7 @@ options[:links] = { prev: '...' } options[:include] = [:actors, :'actors.agency', :'actors.agency.state'] -MovieSerializer.new([movie, movie], options).serialized_json +MovieSerializer.new(movies, options).serialized_json ``` ### Collection Serialization @@ -314,7 +314,7 @@ options[:links] = { next: '...', prev: '...' } -hash = MovieSerializer.new([movie, movie], options).serializable_hash +hash = MovieSerializer.new(movies, options).serializable_hash json_string = MovieSerializer.new([movie, movie], options).serialized_json ``` diff --git a/spec/lib/instrumentation/as_notifications_negative_spec.rb b/spec/lib/instrumentation/as_notifications_negative_spec.rb index 6912ed49..de144dea 100644 --- a/spec/lib/instrumentation/as_notifications_negative_spec.rb +++ b/spec/lib/instrumentation/as_notifications_negative_spec.rb @@ -10,7 +10,8 @@ options[:meta] = { total: 2 } options[:include] = [:actors] - @serializer = MovieSerializer.new([movie, movie], options) + movies = build_movies(2) + @serializer = MovieSerializer.new(movies, options) end context 'serializable_hash' do diff --git a/spec/lib/instrumentation/as_notifications_spec.rb b/spec/lib/instrumentation/as_notifications_spec.rb index f7769ffe..e4993bb6 100644 --- a/spec/lib/instrumentation/as_notifications_spec.rb +++ b/spec/lib/instrumentation/as_notifications_spec.rb @@ -24,7 +24,8 @@ options[:meta] = { total: 2 } options[:include] = [:actors] - @serializer = MovieSerializer.new([movie, movie], options) + movies = build_movies(2) + @serializer = MovieSerializer.new(movies, options) end context 'serializable_hash' do diff --git a/spec/lib/object_serializer_caching_spec.rb b/spec/lib/object_serializer_caching_spec.rb index 8e455d49..2f58cd16 100644 --- a/spec/lib/object_serializer_caching_spec.rb +++ b/spec/lib/object_serializer_caching_spec.rb @@ -16,7 +16,8 @@ options[:links] = { self: 'self' } options[:include] = [:actors] - serializable_hash = CachingMovieSerializer.new([movie, movie], options).serializable_hash + movies = build_movies(2) + serializable_hash = CachingMovieSerializer.new(movies, options).serializable_hash expect(serializable_hash[:data].length).to eq 2 expect(serializable_hash[:data][0][:relationships].length).to eq 3 diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 205f6452..6fe4718b 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -213,7 +213,7 @@ end context 'when an array of records is given' do - let(:resource) { [movie, movie] } + let(:resource) { build_movies(2) } it 'returns correct hash which id equals owner_id' do expect(serializable_hash[:data][0][:id].to_i).to eq movie.owner_id @@ -240,7 +240,7 @@ end context 'when an array of records is given' do - let(:resource) { [movie, movie] } + let(:resource) { build_movies(2) } it 'returns correct hash which id equals movie-id' do expect(serializable_hash[:data][0][:id]).to eq "movie-#{movie.owner_id}" @@ -403,7 +403,7 @@ def year_since_release_calculator(release_year) end describe '#key_transform' do - subject(:hash) { movie_serializer_class.new([movie, movie], include: [:movie_type]).serializable_hash } + subject(:hash) { movie_serializer_class.new(build_movies(2), include: [:movie_type]).serializable_hash } let(:movie_serializer_class) { "#{key_transform}_movie_serializer".classify.constantize } diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index 42db8f00..dc846e6c 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -10,7 +10,8 @@ options[:meta] = { total: 2 } options[:links] = { self: 'self' } options[:include] = [:actors] - serializable_hash = MovieSerializer.new([movie, movie], options).serializable_hash + movies = build_movies(2) + serializable_hash = MovieSerializer.new(movies, options).serializable_hash expect(serializable_hash[:data].length).to eq 2 expect(serializable_hash[:data][0][:relationships].length).to eq 4 @@ -58,7 +59,8 @@ it 'returns correct number of records when serialized_json is called for an array' do options = {} options[:meta] = { total: 2 } - json = MovieSerializer.new([movie, movie], options).serialized_json + movies = build_movies(2) + json = MovieSerializer.new(movies, options).serialized_json serializable_hash = JSON.parse(json) expect(serializable_hash['data'].length).to eq 2 expect(serializable_hash['meta']).to be_instance_of(Hash) @@ -124,7 +126,8 @@ end it 'returns multiple records' do - json_hash = MovieSerializer.new([movie, movie]).as_json + movies = build_movies(2) + json_hash = MovieSerializer.new(movies).as_json expect(json_hash['data'].length).to eq 2 end @@ -139,7 +142,8 @@ options = {} options[:meta] = { total: 2 } options[:include] = [:blah_blah] - expect { MovieSerializer.new([movie, movie], options).serializable_hash }.to raise_error(ArgumentError) + movies = build_movies(2) + expect { MovieSerializer.new(movies, options).serializable_hash }.to raise_error(ArgumentError) end it 'returns errors when serializing with non-existent and existent includes keys' do @@ -165,9 +169,10 @@ options = {} options[:meta] = { total: 2 } options[:include] = [''] - expect(MovieSerializer.new([movie, movie], options).serializable_hash.keys).to eq [:data, :meta] + movies = build_movies(2) + expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta] options[:include] = [nil] - expect(MovieSerializer.new([movie, movie], options).serializable_hash.keys).to eq [:data, :meta] + expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta] end end From 83e7fb62f92a93d3bd705e77b38ded9598982e1a Mon Sep 17 00:00:00 2001 From: iwsksky Date: Tue, 22 Jan 2019 01:30:21 +0900 Subject: [PATCH 12/12] update document/use let statement --- README.md | 13 ++++++++++++- spec/lib/object_serializer_spec.rb | 7 ++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cbc615ee..f36a22a3 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,17 @@ movie.actor_ids = [1, 2, 3] movie.owner_id = 3 movie.movie_type_id = 1 movie + +movies = + 2.times.map do |i| + m = Movie.new + m.id = i + 1 + m.name = "test movie #{i}" + m.actor_ids = [1, 2, 3] + m.owner_id = 3 + m.movie_type_id = 1 + m + end ``` ### Object Serialization @@ -315,7 +326,7 @@ options[:links] = { prev: '...' } hash = MovieSerializer.new(movies, options).serializable_hash -json_string = MovieSerializer.new([movie, movie], options).serialized_json +json_string = MovieSerializer.new(movies, options).serialized_json ``` #### Control Over Collection Serialization diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index dc846e6c..724795c7 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -4,13 +4,14 @@ include_context 'movie class' include_context 'group class' + let(:movies) { build_movies(2) } + context 'when testing instance methods of object serializer' do it 'returns correct hash when serializable_hash is called' do options = {} options[:meta] = { total: 2 } options[:links] = { self: 'self' } options[:include] = [:actors] - movies = build_movies(2) serializable_hash = MovieSerializer.new(movies, options).serializable_hash expect(serializable_hash[:data].length).to eq 2 @@ -59,7 +60,6 @@ it 'returns correct number of records when serialized_json is called for an array' do options = {} options[:meta] = { total: 2 } - movies = build_movies(2) json = MovieSerializer.new(movies, options).serialized_json serializable_hash = JSON.parse(json) expect(serializable_hash['data'].length).to eq 2 @@ -126,7 +126,6 @@ end it 'returns multiple records' do - movies = build_movies(2) json_hash = MovieSerializer.new(movies).as_json expect(json_hash['data'].length).to eq 2 end @@ -142,7 +141,6 @@ options = {} options[:meta] = { total: 2 } options[:include] = [:blah_blah] - movies = build_movies(2) expect { MovieSerializer.new(movies, options).serializable_hash }.to raise_error(ArgumentError) end @@ -169,7 +167,6 @@ options = {} options[:meta] = { total: 2 } options[:include] = [''] - movies = build_movies(2) expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta] options[:include] = [nil] expect(MovieSerializer.new(movies, options).serializable_hash.keys).to eq [:data, :meta]