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/42] 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 53c8bfa..b4ad5e4 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/42] 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 b4ad5e4..849af1f 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/42] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 849af1f..b6a3cb0 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/42] Update Readme Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6a3cb0..632efce 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/42] 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 c9c4c7a..12442c1 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/42] [#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 b8a2418..e767135 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -86,7 +86,7 @@ module FastJsonapi 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 ef755cc..74bb2f4 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -473,6 +473,16 @@ describe FastJsonapi::ObjectSerializer do 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/42] 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 200af9b..845aee7 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -44,6 +44,8 @@ module FastJsonapi 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 @@ module FastJsonapi 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 913ba83..697f209 100644 --- a/spec/lib/object_serializer_fields_spec.rb +++ b/spec/lib/object_serializer_fields_spec.rb @@ -22,6 +22,18 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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/42] Fix error on defining anonymous serializer class, fixes #353 --- lib/fast_jsonapi/object_serializer.rb | 2 +- spec/lib/object_serializer_spec.rb | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index e767135..d142a04 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -130,7 +130,7 @@ module FastJsonapi 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 74bb2f4..c86ce7c 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -314,13 +314,6 @@ describe FastJsonapi::ObjectSerializer do expect(BlahBlahSerializer.record_type).to be :blah_blah end - it 'shouldnt set default_type for a serializer that doesnt follow convention' do - class BlahBlahSerializerBuilder - include FastJsonapi::ObjectSerializer - end - expect(BlahBlahSerializerBuilder.record_type).to be_nil - end - it 'should set default_type for a namespaced serializer' do module V1 class BlahSerializer @@ -329,6 +322,20 @@ describe FastJsonapi::ObjectSerializer do 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 + end + expect(BlahBlahSerializerBuilder.record_type).to be_nil + end + + it 'shouldnt set default_type for an anonymous serializer' do + serializer_class = Class.new do + include FastJsonapi::ObjectSerializer + end + expect(serializer_class.record_type).to be_nil + end end context 'when serializing included, serialize any links' do From 3668625882ee55f59870270a19cd0278041da401 Mon Sep 17 00:00:00 2001 From: Daniel Duke Date: Fri, 4 Jan 2019 09:12:17 -0800 Subject: [PATCH 09/42] 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 d142a04..c161fb6 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -299,7 +299,7 @@ module FastJsonapi 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/42] 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 c86ce7c..42db8f0 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -142,12 +142,25 @@ describe FastJsonapi::ObjectSerializer do 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/42] 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 632efce..cbc615e 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 6912ed4..de144de 100644 --- a/spec/lib/instrumentation/as_notifications_negative_spec.rb +++ b/spec/lib/instrumentation/as_notifications_negative_spec.rb @@ -10,7 +10,8 @@ describe FastJsonapi::ObjectSerializer do 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 f7769ff..e4993bb 100644 --- a/spec/lib/instrumentation/as_notifications_spec.rb +++ b/spec/lib/instrumentation/as_notifications_spec.rb @@ -24,7 +24,8 @@ describe FastJsonapi::ObjectSerializer do 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 8e455d4..2f58cd1 100644 --- a/spec/lib/object_serializer_caching_spec.rb +++ b/spec/lib/object_serializer_caching_spec.rb @@ -16,7 +16,8 @@ describe FastJsonapi::ObjectSerializer do 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 205f645..6fe4718 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -213,7 +213,7 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 42db8f0..dc846e6 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -10,7 +10,8 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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/42] 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 cbc615e..f36a22a 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 dc846e6..724795c 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -4,13 +4,14 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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] From d64b1b5f4f193b37aa108b1bf555cb7d934bfb07 Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Fri, 4 Oct 2019 14:42:12 +0200 Subject: [PATCH 13/42] Test against Ruby 2.6 and fix Travis (#3) * Test against Ruby 2.6 * Enforce bundler 1.17.3 --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 00b98f7..7025fd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,12 @@ rvm: - 2.3.6 - 2.4.3 - 2.5.0 + - 2.6 + +before_install: + - "travis_retry gem update --system 2.7.9" + - "travis_retry gem install bundler -v '1.17.3'" +install: BUNDLER_VERSION=1.17.3 bundle install --path=vendor/bundle --retry=3 --jobs=3 + script: - bundle exec rspec From 1373eb436c90d4e26d6b1d3777ac2b8a7233c5ae Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:09:25 +0200 Subject: [PATCH 14/42] Compute remaining_items once --- lib/fast_jsonapi/serialization_core.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 845aee7..924b5da 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -119,6 +119,8 @@ module FastJsonapi includes_list.sort.each_with_object([]) do |include_item, included_records| items = parse_include_item(include_item) + remaining_items = remaining_items(items) + items.each do |item| next unless relationships_to_serialize && relationships_to_serialize[item] relationship_item = relationships_to_serialize[item] @@ -139,8 +141,8 @@ module FastJsonapi serializer = self.compute_serializer_name(inc_obj.class.name.demodulize.to_sym).to_s.constantize end - if remaining_items(items) - serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params) + if remaining_items.present? + serializer_records = serializer.get_included_records(inc_obj, remaining_items, known_included_objects, fieldsets, params) included_records.concat(serializer_records) unless serializer_records.empty? end From 2b6c81692fb8a9d78bfd315ebc4b283d34ebe3f2 Mon Sep 17 00:00:00 2001 From: Jo Potts Date: Fri, 4 Oct 2019 17:39:34 +0100 Subject: [PATCH 15/42] Allow relationship links to be declared as object method (#2) * Allow relationship links to be declared as object method * Relationship links note added to readme --- README.md | 6 +++++ lib/fast_jsonapi/relationship.rb | 8 +++++-- ...ject_serializer_relationship_links_spec.rb | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f36a22a..ff42cac 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,12 @@ class MovieSerializer end ``` +Relationship links can also be configured to be defined as a method on the object. + +```ruby + has_many :actors, links: :actor_relationship_links +``` + This will create a `self` reference for the relationship, and a `related` link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the `lazy_load_data` option: ```ruby diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index 7a038de..f8a9bfd 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -104,8 +104,12 @@ module FastJsonapi end def add_links_hash(record, params, output_hash) - output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash| - Link.new(key: key, method: method).serialize(record, params, hash)\ + if links.is_a?(Symbol) + output_hash[key][:links] = record.public_send(links) + else + output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash| + Link.new(key: key, method: method).serialize(record, params, hash)\ + end end end diff --git a/spec/lib/object_serializer_relationship_links_spec.rb b/spec/lib/object_serializer_relationship_links_spec.rb index 8c2f272..e0d4848 100644 --- a/spec/lib/object_serializer_relationship_links_spec.rb +++ b/spec/lib/object_serializer_relationship_links_spec.rb @@ -67,5 +67,28 @@ describe FastJsonapi::ObjectSerializer do expect(actor_hash).not_to have_key(:data) end end + + context "relationship links defined by a method on the object" do + before(:context) do + class Movie + def relationship_links + { self: "http://movies.com/#{id}/relationships/actors" } + end + end + + class LinksPassingMovieSerializer < MovieSerializer + has_many :actors, links: :relationship_links + end + end + + let(:serializer) { LinksPassingMovieSerializer.new(movie) } + let(:links) { hash[:data][:relationships][:actors][:links] } + let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" } + + it "generates relationship links in the object" do + expect(links).to be_present + expect(links[:self]).to eq(relationship_url) + end + end end end From 9ec89d4cf5c4c3c825ee737ccc6af2c79f2bd9ac Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:11:56 +0200 Subject: [PATCH 16/42] Map split include_item in-place --- lib/fast_jsonapi/serialization_core.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 924b5da..55994a0 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -102,7 +102,8 @@ module FastJsonapi def parse_include_item(include_item) return [include_item.to_sym] unless include_item.to_s.include?('.') - include_item.to_s.split('.').map { |item| item.to_sym } + + include_item.to_s.split('.').map!(&:to_sym) end def remaining_items(items) From dce1faf1e2a16950f102bc27e004a4bacf4d6304 Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:09:55 +0200 Subject: [PATCH 17/42] Move transforms mapping to constant --- lib/fast_jsonapi/object_serializer.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index c161fb6..c6861cd 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -17,6 +17,12 @@ module FastJsonapi SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash' SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json' + TRANSFORMS_MAPPING = { + camel: :camelize, + camel_lower: [:camelize, :lower], + dash: :dasherize, + underscore: :underscore + }.freeze included do # Set record_type based on the name of the serializer class @@ -137,13 +143,7 @@ module FastJsonapi end def set_key_transform(transform_name) - mapping = { - camel: :camelize, - camel_lower: [:camelize, :lower], - dash: :dasherize, - underscore: :underscore - } - self.transform_method = mapping[transform_name.to_sym] + self.transform_method = TRANSFORMS_MAPPING[transform_name.to_sym] # ensure that the record type is correctly transformed if record_type From 9e83c1e0a5a70c134f50fb7c79ca4db469ceb45d Mon Sep 17 00:00:00 2001 From: "Jessie A. Young" Date: Thu, 21 Mar 2019 15:55:36 -0700 Subject: [PATCH 18/42] 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 df86d28..a8b8799 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 f0142d948c62b2552a92907597224a34d2471525 Mon Sep 17 00:00:00 2001 From: Csaba Apagyi Date: Mon, 4 Mar 2019 13:18:03 +0100 Subject: [PATCH 19/42] 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 a8b8799..3c4f846 100644 --- a/README.md +++ b/README.md @@ -522,7 +522,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 209c925723fd14689399def9774f1ee5ecfbd485 Mon Sep 17 00:00:00 2001 From: Charalampos Aristomenopoulos Date: Fri, 22 Feb 2019 12:08:36 +0200 Subject: [PATCH 20/42] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c4f846..6a056dc 100644 --- a/README.md +++ b/README.md @@ -328,7 +328,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 021db276050defe00a3d36b5911d0426ec5491f8 Mon Sep 17 00:00:00 2001 From: Maxime Orefice Date: Mon, 18 Feb 2019 19:32:30 -0500 Subject: [PATCH 21/42] Update Readme Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a056dc..5c5c77b 100644 --- a/README.md +++ b/README.md @@ -545,7 +545,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 1a407c00304595693374e03f5dc2aa197e1ed9e6 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Fri, 21 Dec 2018 18:41:55 +0300 Subject: [PATCH 22/42] [#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 b8a2418..e767135 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -86,7 +86,7 @@ module FastJsonapi 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 ef755cc..74bb2f4 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -473,6 +473,16 @@ describe FastJsonapi::ObjectSerializer do 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 21ae4aaa0a0e707d11643b9da67a022f49dcbc3e Mon Sep 17 00:00:00 2001 From: Matt Eddy Date: Mon, 8 Oct 2018 11:45:46 -0700 Subject: [PATCH 23/42] 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 200af9b..845aee7 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -44,6 +44,8 @@ module FastJsonapi 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 @@ module FastJsonapi 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 913ba83..697f209 100644 --- a/spec/lib/object_serializer_fields_spec.rb +++ b/spec/lib/object_serializer_fields_spec.rb @@ -22,6 +22,18 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 8d8e5c305912a32a5e0a077950783e4c4d1c04e3 Mon Sep 17 00:00:00 2001 From: Daniel Illi Date: Thu, 22 Nov 2018 10:58:42 +0100 Subject: [PATCH 24/42] Fix error on defining anonymous serializer class, fixes #353 --- lib/fast_jsonapi/object_serializer.rb | 2 +- spec/lib/object_serializer_spec.rb | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index e767135..d142a04 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -130,7 +130,7 @@ module FastJsonapi 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 74bb2f4..c86ce7c 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -314,13 +314,6 @@ describe FastJsonapi::ObjectSerializer do expect(BlahBlahSerializer.record_type).to be :blah_blah end - it 'shouldnt set default_type for a serializer that doesnt follow convention' do - class BlahBlahSerializerBuilder - include FastJsonapi::ObjectSerializer - end - expect(BlahBlahSerializerBuilder.record_type).to be_nil - end - it 'should set default_type for a namespaced serializer' do module V1 class BlahSerializer @@ -329,6 +322,20 @@ describe FastJsonapi::ObjectSerializer do 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 + end + expect(BlahBlahSerializerBuilder.record_type).to be_nil + end + + it 'shouldnt set default_type for an anonymous serializer' do + serializer_class = Class.new do + include FastJsonapi::ObjectSerializer + end + expect(serializer_class.record_type).to be_nil + end end context 'when serializing included, serialize any links' do From 267b706366f5b6cf8e05efebda09d5c426ec6bc9 Mon Sep 17 00:00:00 2001 From: Daniel Duke Date: Fri, 4 Jan 2019 09:12:17 -0800 Subject: [PATCH 25/42] 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 d142a04..c161fb6 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -299,7 +299,7 @@ module FastJsonapi 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 5767664c8a7890a3bdb2e80d0437e8c042e0985f Mon Sep 17 00:00:00 2001 From: Daniel Duke Date: Fri, 4 Jan 2019 09:12:58 -0800 Subject: [PATCH 26/42] 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 c86ce7c..42db8f0 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -142,12 +142,25 @@ describe FastJsonapi::ObjectSerializer do 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 b9a86a002acabfde9fcea76c5586924e2f5fc3c4 Mon Sep 17 00:00:00 2001 From: iwsksky Date: Fri, 21 Dec 2018 03:44:42 +0900 Subject: [PATCH 27/42] 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 5c5c77b..ec817c4 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,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 @@ -320,7 +320,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 6912ed4..de144de 100644 --- a/spec/lib/instrumentation/as_notifications_negative_spec.rb +++ b/spec/lib/instrumentation/as_notifications_negative_spec.rb @@ -10,7 +10,8 @@ describe FastJsonapi::ObjectSerializer do 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 f7769ff..e4993bb 100644 --- a/spec/lib/instrumentation/as_notifications_spec.rb +++ b/spec/lib/instrumentation/as_notifications_spec.rb @@ -24,7 +24,8 @@ describe FastJsonapi::ObjectSerializer do 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 8e455d4..2f58cd1 100644 --- a/spec/lib/object_serializer_caching_spec.rb +++ b/spec/lib/object_serializer_caching_spec.rb @@ -16,7 +16,8 @@ describe FastJsonapi::ObjectSerializer do 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 205f645..6fe4718 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -213,7 +213,7 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 42db8f0..dc846e6 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -10,7 +10,8 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 e68dbee80660b8fd548f2b0ed6bacf2e25cd6e76 Mon Sep 17 00:00:00 2001 From: iwsksky Date: Tue, 22 Jan 2019 01:30:21 +0900 Subject: [PATCH 28/42] 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 ec817c4..461cf2b 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,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 @@ -321,7 +332,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 dc846e6..724795c 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -4,13 +4,14 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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 @@ describe FastJsonapi::ObjectSerializer do 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] From b24af1f912f171b37c3919c0ddb9d4da8bc4ca7e Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Fri, 4 Oct 2019 14:42:12 +0200 Subject: [PATCH 29/42] Test against Ruby 2.6 and fix Travis (#3) * Test against Ruby 2.6 * Enforce bundler 1.17.3 --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 00b98f7..7025fd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,12 @@ rvm: - 2.3.6 - 2.4.3 - 2.5.0 + - 2.6 + +before_install: + - "travis_retry gem update --system 2.7.9" + - "travis_retry gem install bundler -v '1.17.3'" +install: BUNDLER_VERSION=1.17.3 bundle install --path=vendor/bundle --retry=3 --jobs=3 + script: - bundle exec rspec From f04abfd2fe5d0f2040a65678041c5f815dc50d70 Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:09:25 +0200 Subject: [PATCH 30/42] Compute remaining_items once --- lib/fast_jsonapi/serialization_core.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 845aee7..924b5da 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -119,6 +119,8 @@ module FastJsonapi includes_list.sort.each_with_object([]) do |include_item, included_records| items = parse_include_item(include_item) + remaining_items = remaining_items(items) + items.each do |item| next unless relationships_to_serialize && relationships_to_serialize[item] relationship_item = relationships_to_serialize[item] @@ -139,8 +141,8 @@ module FastJsonapi serializer = self.compute_serializer_name(inc_obj.class.name.demodulize.to_sym).to_s.constantize end - if remaining_items(items) - serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params) + if remaining_items.present? + serializer_records = serializer.get_included_records(inc_obj, remaining_items, known_included_objects, fieldsets, params) included_records.concat(serializer_records) unless serializer_records.empty? end From 83e99b2923081b343ca80fe32506c0f60ed320f3 Mon Sep 17 00:00:00 2001 From: Jo Potts Date: Fri, 4 Oct 2019 17:39:34 +0100 Subject: [PATCH 31/42] Allow relationship links to be declared as object method (#2) * Allow relationship links to be declared as object method * Relationship links note added to readme --- README.md | 6 +++++ lib/fast_jsonapi/relationship.rb | 8 +++++-- ...ject_serializer_relationship_links_spec.rb | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 461cf2b..b18d055 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,12 @@ class MovieSerializer end ``` +Relationship links can also be configured to be defined as a method on the object. + +```ruby + has_many :actors, links: :actor_relationship_links +``` + This will create a `self` reference for the relationship, and a `related` link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the `lazy_load_data` option: ```ruby diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index 7a038de..f8a9bfd 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -104,8 +104,12 @@ module FastJsonapi end def add_links_hash(record, params, output_hash) - output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash| - Link.new(key: key, method: method).serialize(record, params, hash)\ + if links.is_a?(Symbol) + output_hash[key][:links] = record.public_send(links) + else + output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash| + Link.new(key: key, method: method).serialize(record, params, hash)\ + end end end diff --git a/spec/lib/object_serializer_relationship_links_spec.rb b/spec/lib/object_serializer_relationship_links_spec.rb index 8c2f272..e0d4848 100644 --- a/spec/lib/object_serializer_relationship_links_spec.rb +++ b/spec/lib/object_serializer_relationship_links_spec.rb @@ -67,5 +67,28 @@ describe FastJsonapi::ObjectSerializer do expect(actor_hash).not_to have_key(:data) end end + + context "relationship links defined by a method on the object" do + before(:context) do + class Movie + def relationship_links + { self: "http://movies.com/#{id}/relationships/actors" } + end + end + + class LinksPassingMovieSerializer < MovieSerializer + has_many :actors, links: :relationship_links + end + end + + let(:serializer) { LinksPassingMovieSerializer.new(movie) } + let(:links) { hash[:data][:relationships][:actors][:links] } + let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" } + + it "generates relationship links in the object" do + expect(links).to be_present + expect(links[:self]).to eq(relationship_url) + end + end end end From fd17386b51da9cb04f059b919700263999049993 Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:11:56 +0200 Subject: [PATCH 32/42] Map split include_item in-place --- lib/fast_jsonapi/serialization_core.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 924b5da..55994a0 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -102,7 +102,8 @@ module FastJsonapi def parse_include_item(include_item) return [include_item.to_sym] unless include_item.to_s.include?('.') - include_item.to_s.split('.').map { |item| item.to_sym } + + include_item.to_s.split('.').map!(&:to_sym) end def remaining_items(items) From e4c65a2567da81029a090c190cd5b5f55db94f9d Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:09:55 +0200 Subject: [PATCH 33/42] Move transforms mapping to constant --- lib/fast_jsonapi/object_serializer.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index c161fb6..c6861cd 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -17,6 +17,12 @@ module FastJsonapi SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash' SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json' + TRANSFORMS_MAPPING = { + camel: :camelize, + camel_lower: [:camelize, :lower], + dash: :dasherize, + underscore: :underscore + }.freeze included do # Set record_type based on the name of the serializer class @@ -137,13 +143,7 @@ module FastJsonapi end def set_key_transform(transform_name) - mapping = { - camel: :camelize, - camel_lower: [:camelize, :lower], - dash: :dasherize, - underscore: :underscore - } - self.transform_method = mapping[transform_name.to_sym] + self.transform_method = TRANSFORMS_MAPPING[transform_name.to_sym] # ensure that the record type is correctly transformed if record_type From 44a896dda5145c2eef59255c2d6d35796e2e305e Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:12:31 +0200 Subject: [PATCH 34/42] Take items of original array instead of dup and delete --- lib/fast_jsonapi/serialization_core.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 55994a0..7dd1b76 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -109,9 +109,7 @@ module FastJsonapi def remaining_items(items) return unless items.size > 1 - items_copy = items.dup - items_copy.delete_at(0) - [items_copy.join('.').to_sym] + [items[1..-1].join('.').to_sym] end # includes handler From f2a1934b766b58b0b689a64185d1934e51a6a1b9 Mon Sep 17 00:00:00 2001 From: Krzysztof Rybka Date: Thu, 2 May 2019 15:10:52 +0200 Subject: [PATCH 35/42] Use each_with_object instead of Hash[map] --- lib/fast_jsonapi/object_serializer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index c6861cd..c9476e9 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -99,9 +99,9 @@ module FastJsonapi def deep_symbolize(collection) if collection.is_a? Hash - Hash[collection.map do |k, v| - [k.to_sym, deep_symbolize(v)] - end] + collection.each_with_object({}) do |(k, v), hsh| + hsh[k.to_sym] = deep_symbolize(v) + end elsif collection.is_a? Array collection.map { |i| deep_symbolize(i) } else From 37206ddf0b6df04f94663cd7ddb81d66c041dcd7 Mon Sep 17 00:00:00 2001 From: Kevin Pheasey Date: Fri, 4 Oct 2019 19:45:43 -0400 Subject: [PATCH 36/42] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b8b37..97809b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,5 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimize SerializationCore.get_included_records calculates remaining_items only once ([#4](https://github.com/fast-jsonapi/fast_jsonapi/pull/4)) - Optimize SerializtionCore.parse_include_item by mapping in place ([#5](https://github.com/fast-jsonapi/fast_jsonapi/pull/5)) - Define ObjectSerializer.set_key_transform mapping as a constant ([#7](https://github.com/fast-jsonapi/fast_jsonapi/pull/7)) +- Optimize SerializtionCore.remaining_items by taking from original array ([#9](https://github.com/fast-jsonapi/fast_jsonapi/pull/9)) +- Optimize ObjectSerializer.deep_symbolize by using each_with_object instead of Hash[map] ([#6](https://github.com/fast-jsonapi/fast_jsonapi/pull/6)) [Unreleased]: https://github.com/fast-jsonapi/fast_jsonapi/compare/dev...HEAD From 8e2383128ed6e64f221f2348e7d9108c6bf33b26 Mon Sep 17 00:00:00 2001 From: David Pickart Date: Tue, 8 Oct 2019 07:30:21 -0500 Subject: [PATCH 37/42] Include `data` key when lazy-loaded relationships are specified with `includes` (#10) --- lib/fast_jsonapi/object_serializer.rb | 4 ++-- lib/fast_jsonapi/relationship.rb | 4 ++-- lib/fast_jsonapi/serialization_core.rb | 17 +++++++++-------- ...bject_serializer_relationship_links_spec.rb | 18 ++++++++++++++++++ spec/lib/serialization_core_spec.rb | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index c9476e9..06fcbf6 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -49,7 +49,7 @@ module FastJsonapi return serializable_hash unless @resource - serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params) + serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @includes, @params) serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? serializable_hash end @@ -61,7 +61,7 @@ module FastJsonapi included = [] fieldset = @fieldsets[self.class.record_type.to_sym] @resource.each do |record| - data << self.class.record_hash(record, fieldset, @params) + data << self.class.record_hash(record, fieldset, @includes, @params) included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? end diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index f8a9bfd..5f82843 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -34,12 +34,12 @@ module FastJsonapi @lazy_load_data = lazy_load_data end - def serialize(record, serialization_params, output_hash) + def serialize(record, included, serialization_params, output_hash) if include_relationship?(record, serialization_params) empty_case = relationship_type == :has_many ? [] : nil output_hash[key] = {} - unless lazy_load_data + unless (lazy_load_data && !included) output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case end add_links_hash(record, serialization_params, output_hash) if links.present? diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 7dd1b76..1c9a3bc 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -51,13 +51,14 @@ module FastJsonapi end end - def relationships_hash(record, relationships = nil, fieldset = nil, params = {}) + def relationships_hash(record, relationships = nil, fieldset = nil, includes_list = 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) + relationships.each_with_object({}) do |(key, relationship), hash| + included = includes_list.present? && includes_list.include?(key) + relationship.serialize(record, included, params, hash) end end @@ -65,23 +66,23 @@ module FastJsonapi meta_to_serialize.call(record, params) end - def record_hash(record, fieldset, params = {}) + def record_hash(record, fieldset, includes_list, params = {}) if cached record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do temp_hash = id_hash(id_from_record(record), record_type, true) temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present? temp_hash[:relationships] = {} - temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, params) if cachable_relationships_to_serialize.present? + temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, includes_list, params) if cachable_relationships_to_serialize.present? temp_hash[:links] = links_hash(record, params) if data_links.present? temp_hash end - record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, params)) if uncachable_relationships_to_serialize.present? + record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, includes_list, params)) if uncachable_relationships_to_serialize.present? record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present? record_hash else record_hash = id_hash(id_from_record(record), record_type, true) record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present? - record_hash[:relationships] = relationships_hash(record, nil, fieldset, params) if relationships_to_serialize.present? + record_hash[:relationships] = relationships_hash(record, nil, fieldset, includes_list, params) if relationships_to_serialize.present? record_hash[:links] = links_hash(record, params) if data_links.present? record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present? record_hash @@ -150,7 +151,7 @@ module FastJsonapi known_included_objects[code] = inc_obj - included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], params) + included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], includes_list, params) end end end diff --git a/spec/lib/object_serializer_relationship_links_spec.rb b/spec/lib/object_serializer_relationship_links_spec.rb index e0d4848..e45f9d7 100644 --- a/spec/lib/object_serializer_relationship_links_spec.rb +++ b/spec/lib/object_serializer_relationship_links_spec.rb @@ -68,6 +68,24 @@ describe FastJsonapi::ObjectSerializer do end end + context "including lazy loaded relationships" do + before(:context) do + class LazyLoadingMovieSerializer < MovieSerializer + has_many :actors, lazy_load_data: true, links: { + related: :actors_relationship_url + } + end + end + + let(:serializer) { LazyLoadingMovieSerializer.new(movie, include: [:actors]) } + let(:actor_hash) { hash[:data][:relationships][:actors] } + + it "includes the :data key" do + expect(actor_hash).to be_present + expect(actor_hash).to have_key(:data) + end + end + context "relationship links defined by a method on the object" do before(:context) do class Movie diff --git a/spec/lib/serialization_core_spec.rb b/spec/lib/serialization_core_spec.rb index adf33df..0d8d011 100644 --- a/spec/lib/serialization_core_spec.rb +++ b/spec/lib/serialization_core_spec.rb @@ -52,7 +52,7 @@ describe FastJsonapi::ObjectSerializer do end it 'returns correct hash when record_hash is called' do - record_hash = MovieSerializer.send(:record_hash, movie, nil) + record_hash = MovieSerializer.send(:record_hash, movie, nil, nil) expect(record_hash[:id]).to eq movie.id.to_s expect(record_hash[:type]).to eq MovieSerializer.record_type expect(record_hash).to have_key(:attributes) if MovieSerializer.attributes_to_serialize.present? From 145b4ce23a5b2dd89eac004185fed01ba461d008 Mon Sep 17 00:00:00 2001 From: Kevin Pheasey Date: Tue, 8 Oct 2019 08:31:25 -0400 Subject: [PATCH 38/42] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97809b4..3931338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow relationship links to be delcared as a method ([#2](https://github.com/fast-jsonapi/fast_jsonapi/pull/2)) - Test against Ruby 2.6 ([#1](https://github.com/fast-jsonapi/fast_jsonapi/pull/1)) +- Include `data` key when lazy-loaded relationships are included ([#10](https://github.com/fast-jsonapi/fast_jsonapi/pull/10)) ### Changed - Optimize SerializationCore.get_included_records calculates remaining_items only once ([#4](https://github.com/fast-jsonapi/fast_jsonapi/pull/4)) - Optimize SerializtionCore.parse_include_item by mapping in place ([#5](https://github.com/fast-jsonapi/fast_jsonapi/pull/5)) From 587fb2c5fe3f221c1732ef5b10627b82eda10edd Mon Sep 17 00:00:00 2001 From: Henning Vogt Date: Tue, 15 Oct 2019 20:45:46 +0200 Subject: [PATCH 39/42] Add params to set_id block (#16) * Add params to set_id block arguments Pull request #331 added a block to the ObjectSerializer.set_id class method, which allows passing a block to the set_id method. Currently this block takes only one argument `record`: ``` set_id do |record| "#{record.name.downcase}-#{record.id}" end ``` This PR adds another argument `params` to the block: ``` set id do |record, params| params[:admin] ? record.id : "#{record.name.downcase}-#{record.id}" end ``` This customization can be useful in situation where we serve different clients that may need different IDs. One nice side effect is also that the `set_id` method has the same method signature as the `attribute` method. * Update the README --- README.md | 12 +++++++++--- lib/fast_jsonapi/serialization_core.rb | 10 +++++----- spec/lib/object_serializer_class_methods_spec.rb | 11 +++++++++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b18d055..b9cd186 100644 --- a/README.md +++ b/README.md @@ -381,13 +381,19 @@ related to a current authenticated user. The `options[:params]` value covers the cases by allowing you to pass in a hash of additional parameters necessary for your use case. -Leveraging the new params is easy, when you define a custom attribute or relationship with a -block you opt-in to using params by adding it as a block parameter. +Leveraging the new params is easy, when you define a custom id, attribute or +relationship with a block you opt-in to using params by adding it as a block +parameter. ```ruby class MovieSerializer include FastJsonapi::ObjectSerializer + set_id do |movie, params| + # in here, params is a hash containing the `:admin` key + params[:admin] ? movie.owner_id : "movie-#{movie.id}" + end + attributes :name, :year attribute :can_view_early do |movie, params| # in here, params is a hash containing the `:current_user` key @@ -539,7 +545,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, params\| params[:admin] ? record.id : "#{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 ``` diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 1c9a3bc..73d3234 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -69,7 +69,7 @@ module FastJsonapi def record_hash(record, fieldset, includes_list, params = {}) if cached record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do - temp_hash = id_hash(id_from_record(record), record_type, true) + temp_hash = id_hash(id_from_record(record, params), record_type, true) temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present? temp_hash[:relationships] = {} temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, includes_list, params) if cachable_relationships_to_serialize.present? @@ -80,7 +80,7 @@ module FastJsonapi record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present? record_hash else - record_hash = id_hash(id_from_record(record), record_type, true) + record_hash = id_hash(id_from_record(record, params), record_type, true) record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present? record_hash[:relationships] = relationships_hash(record, nil, fieldset, includes_list, params) if relationships_to_serialize.present? record_hash[:links] = links_hash(record, params) if data_links.present? @@ -89,8 +89,8 @@ module FastJsonapi end end - def id_from_record(record) - return record_id.call(record) if record_id.is_a?(Proc) + def id_from_record(record, params) + return record_id.call(record, params) if record_id.is_a?(Proc) return record.send(record_id) if record_id raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id) record.id @@ -146,7 +146,7 @@ module FastJsonapi included_records.concat(serializer_records) unless serializer_records.empty? end - code = "#{record_type}_#{serializer.id_from_record(inc_obj)}" + code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}" next if known_included_objects.key?(code) known_included_objects[code] = inc_obj diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 6fe4718..796628b 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -193,7 +193,10 @@ describe FastJsonapi::ObjectSerializer do end describe '#set_id' do - subject(:serializable_hash) { MovieSerializer.new(resource).serializable_hash } + let(:params) { {} } + subject(:serializable_hash) do + MovieSerializer.new(resource, { params: params }).serializable_hash + end context 'method name' do before do @@ -223,8 +226,12 @@ describe FastJsonapi::ObjectSerializer do end context 'with block' do + let(:params) { { prefix: 'movie' } } + before do - MovieSerializer.set_id { |record| "movie-#{record.owner_id}" } + MovieSerializer.set_id do |record, params| + "#{params[:prefix]}-#{record.owner_id}" + end end after do From ddc261d8dc7325d35f8f275ed642b45af3ebba03 Mon Sep 17 00:00:00 2001 From: Brad Grzesiak Date: Fri, 25 Oct 2019 08:32:56 -0500 Subject: [PATCH 40/42] Allow "if: Proc..." on link (#15) --- lib/fast_jsonapi/attribute.rb | 30 ++----------------- lib/fast_jsonapi/link.rb | 19 ++---------- lib/fast_jsonapi/object_serializer.rb | 11 +++++-- lib/fast_jsonapi/scalar.rb | 29 ++++++++++++++++++ .../object_serializer_class_methods_spec.rb | 20 +++++++++++++ spec/shared/contexts/movie_context.rb | 7 +++++ 6 files changed, 70 insertions(+), 46 deletions(-) create mode 100644 lib/fast_jsonapi/scalar.rb diff --git a/lib/fast_jsonapi/attribute.rb b/lib/fast_jsonapi/attribute.rb index 9acc3e0..20bbc5e 100644 --- a/lib/fast_jsonapi/attribute.rb +++ b/lib/fast_jsonapi/attribute.rb @@ -1,29 +1,5 @@ +require 'fast_jsonapi/scalar' + module FastJsonapi - class Attribute - attr_reader :key, :method, :conditional_proc - - def initialize(key:, method:, options: {}) - @key = key - @method = method - @conditional_proc = options[:if] - end - - def serialize(record, serialization_params, output_hash) - if include_attribute?(record, serialization_params) - output_hash[key] = if method.is_a?(Proc) - method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params) - else - record.public_send(method) - end - end - end - - def include_attribute?(record, serialization_params) - if conditional_proc.present? - conditional_proc.call(record, serialization_params) - else - true - end - end - end + class Attribute < Scalar; end end diff --git a/lib/fast_jsonapi/link.rb b/lib/fast_jsonapi/link.rb index 41f84c2..fa4fff1 100644 --- a/lib/fast_jsonapi/link.rb +++ b/lib/fast_jsonapi/link.rb @@ -1,18 +1,5 @@ +require 'fast_jsonapi/scalar' + module FastJsonapi - class Link - attr_reader :key, :method - - def initialize(key:, method:) - @key = key - @method = method - end - - def serialize(record, serialization_params, output_hash) - output_hash[key] = if method.is_a?(Proc) - method.arity == 1 ? method.call(record) : method.call(record, serialization_params) - else - record.public_send(method) - end - end - end + class Link < Scalar; end end diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 06fcbf6..afa6bb2 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -285,14 +285,19 @@ module FastJsonapi {} end - def link(link_name, link_method_name = nil, &block) + # def link(link_name, link_method_name = nil, &block) + def link(*params, &block) self.data_links = {} if self.data_links.nil? - link_method_name = link_name if link_method_name.nil? + + options = params.last.is_a?(Hash) ? params.pop : {} + link_name = params.first + link_method_name = params[-1] key = run_key_transform(link_name) self.data_links[key] = Link.new( key: key, - method: block || link_method_name + method: block || link_method_name, + options: options ) end diff --git a/lib/fast_jsonapi/scalar.rb b/lib/fast_jsonapi/scalar.rb new file mode 100644 index 0000000..27d081c --- /dev/null +++ b/lib/fast_jsonapi/scalar.rb @@ -0,0 +1,29 @@ +module FastJsonapi + class Scalar + attr_reader :key, :method, :conditional_proc + + def initialize(key:, method:, options: {}) + @key = key + @method = method + @conditional_proc = options[:if] + end + + def serialize(record, serialization_params, output_hash) + if conditionally_allowed?(record, serialization_params) + output_hash[key] = if method.is_a?(Proc) + method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params) + else + record.public_send(method) + end + end + end + + def conditionally_allowed?(record, serialization_params) + if conditional_proc.present? + conditional_proc.call(record, serialization_params) + else + true + end + end + end +end diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 796628b..b50d7b4 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -407,6 +407,26 @@ describe FastJsonapi::ObjectSerializer do expect(action_serializable_hash[:data][:links][:url]).to eq "/action-movie/#{movie.id}" end end + + describe 'optional links' do + subject(:downloadable_serializable_hash) { OptionalDownloadableMovieSerializer.new(movie, params).serializable_hash } + + context 'when the link is provided' do + let(:params) { { params: { signed_url: signed_url } } } + let(:signed_url) { 'http://example.com/download_link?signature=abcdef' } + + it 'includes the link' do + expect(downloadable_serializable_hash[:data][:links][:download]).to eq signed_url + end + end + + context 'when the link is not provided' do + let(:params) { { params: {} } } + it 'does not include the link' do + expect(downloadable_serializable_hash[:data][:links]).to_not have_key(:download) + end + end + end end describe '#key_transform' do diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index df0c395..9b3a4f6 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -197,6 +197,12 @@ RSpec.shared_context 'movie class' do link(:url) { |object| "/horror-movie/#{object.id}" } end + class OptionalDownloadableMovieSerializer < MovieSerializer + link(:download, if: Proc.new { |record, params| params && params[:signed_url] }) do |movie, params| + params[:signed_url] + end + end + class MovieWithoutIdStructSerializer include FastJsonapi::ObjectSerializer attributes :name, :release_year @@ -385,6 +391,7 @@ RSpec.shared_context 'movie class' do ActionMovieSerializer GenreMovieSerializer HorrorMovieSerializer + OptionalDownloadableMovieSerializer Movie MovieSerializer Actor From 8fe376c8a3d193c500985b41d0f14117c72d4f94 Mon Sep 17 00:00:00 2001 From: Kevin Pheasey Date: Fri, 25 Oct 2019 09:36:02 -0400 Subject: [PATCH 41/42] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3931338..e3d2167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow relationship links to be delcared as a method ([#2](https://github.com/fast-jsonapi/fast_jsonapi/pull/2)) - Test against Ruby 2.6 ([#1](https://github.com/fast-jsonapi/fast_jsonapi/pull/1)) - Include `data` key when lazy-loaded relationships are included ([#10](https://github.com/fast-jsonapi/fast_jsonapi/pull/10)) +- Conditional links [#15](https://github.com/fast-jsonapi/fast_jsonapi/pull/15) +- Include params on set_id block [#16](https://github.com/fast-jsonapi/fast_jsonapi/pull/16) ### Changed - Optimize SerializationCore.get_included_records calculates remaining_items only once ([#4](https://github.com/fast-jsonapi/fast_jsonapi/pull/4)) - Optimize SerializtionCore.parse_include_item by mapping in place ([#5](https://github.com/fast-jsonapi/fast_jsonapi/pull/5)) From 1cd3f974855cfea3f59775ef33a851cdd1e6dbf0 Mon Sep 17 00:00:00 2001 From: Kevin Pheasey Date: Mon, 4 Nov 2019 17:31:23 -0500 Subject: [PATCH 42/42] 1.6.0 --- CHANGELOG.md | 3 ++- lib/fast_jsonapi/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d2167..812eb2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [1.6.0] - 2019-11-04 ### Added - Allow relationship links to be delcared as a method ([#2](https://github.com/fast-jsonapi/fast_jsonapi/pull/2)) - Test against Ruby 2.6 ([#1](https://github.com/fast-jsonapi/fast_jsonapi/pull/1)) @@ -19,3 +19,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimize ObjectSerializer.deep_symbolize by using each_with_object instead of Hash[map] ([#6](https://github.com/fast-jsonapi/fast_jsonapi/pull/6)) [Unreleased]: https://github.com/fast-jsonapi/fast_jsonapi/compare/dev...HEAD +[1.6.0]: https://github.com/fast-jsonapi/fast_jsonapi/compare/1.5...1.6.0 \ No newline at end of file diff --git a/lib/fast_jsonapi/version.rb b/lib/fast_jsonapi/version.rb index 12442c1..3456d92 100644 --- a/lib/fast_jsonapi/version.rb +++ b/lib/fast_jsonapi/version.rb @@ -1,3 +1,3 @@ module FastJsonapi - VERSION = "1.5" + VERSION = '1.6.0' end