From 77a3a0bb5bbcd4e4545a1c0b38b9f1ef57b1cc55 Mon Sep 17 00:00:00 2001 From: Erol Date: Fri, 13 Jul 2018 12:35:43 +0800 Subject: [PATCH 01/15] Allow passing procs with variable arguments when declaring an attribute --- lib/fast_jsonapi/attribute.rb | 4 ++-- spec/lib/object_serializer_class_methods_spec.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/fast_jsonapi/attribute.rb b/lib/fast_jsonapi/attribute.rb index c26bf19..9acc3e0 100644 --- a/lib/fast_jsonapi/attribute.rb +++ b/lib/fast_jsonapi/attribute.rb @@ -11,7 +11,7 @@ module FastJsonapi def serialize(record, serialization_params, output_hash) if include_attribute?(record, serialization_params) output_hash[key] = if method.is_a?(Proc) - method.arity == 1 ? method.call(record) : method.call(record, serialization_params) + method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params) else record.public_send(method) end @@ -26,4 +26,4 @@ module FastJsonapi end end end -end \ No newline at end of file +end diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 85346f0..761900e 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -230,6 +230,22 @@ describe FastJsonapi::ObjectSerializer do expect(serializable_hash[:data][:attributes][:title_with_year]).to eq "#{movie.name} (#{movie.release_year})" end end + + context 'with &:proc' do + before do + movie.release_year = 2008 + MovieSerializer.attribute :released_in_year, &:release_year + end + + after do + MovieSerializer.attributes_to_serialize.delete(:released_in_year) + end + + it 'returns correct hash when serializable_hash is called' do + expect(serializable_hash[:data][:attributes][:name]).to eq movie.name + expect(serializable_hash[:data][:attributes][:released_in_year]).to eq movie.release_year + end + end end describe '#link' do From 5c8e9358f261baceda9033fea911a32182453bac Mon Sep 17 00:00:00 2001 From: Erol Date: Sat, 14 Jul 2018 17:11:24 +0800 Subject: [PATCH 02/15] Add spec for proc methods with optional arguments --- spec/lib/object_serializer_class_methods_spec.rb | 3 ++- spec/shared/contexts/movie_context.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 761900e..3c477c3 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -235,6 +235,7 @@ describe FastJsonapi::ObjectSerializer do before do movie.release_year = 2008 MovieSerializer.attribute :released_in_year, &:release_year + MovieSerializer.attribute :name, &:local_name end after do @@ -242,7 +243,7 @@ describe FastJsonapi::ObjectSerializer do end it 'returns correct hash when serializable_hash is called' do - expect(serializable_hash[:data][:attributes][:name]).to eq movie.name + expect(serializable_hash[:data][:attributes][:name]).to eq "english #{movie.name}" expect(serializable_hash[:data][:attributes][:released_in_year]).to eq movie.release_year end end diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index bbd89a9..9061226 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -54,6 +54,10 @@ RSpec.shared_context 'movie class' do "#{id}" end + def local_name(locale = :english) + "#{locale} #{name}" + end + def url "http://movies.com/#{id}" end From abc830b41eac925dfca350c4b0834775564b909f Mon Sep 17 00:00:00 2001 From: Erol Date: Mon, 16 Jul 2018 06:19:54 +0800 Subject: [PATCH 03/15] Add proc shortcut use case to README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f4333d1..c812eac 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,18 @@ class MovieSerializer end ``` +Attributes can also use a different name by passing the original method or accessor with a proc shortcut: + +```ruby +class MovieSerializer + include FastJsonapi::ObjectSerializer + + attributes :name + + attribute :released_in_year, &:year +end +``` + ### Links Per Object Links are defined in FastJsonapi using the `link` method. By default, link are read directly from the model property of the same name.In this example, `public_url` is expected to be a property of the object being serialized. From 7b44620018822435dcf7c63818f11c8ad90bf9a0 Mon Sep 17 00:00:00 2001 From: Erol Date: Mon, 16 Jul 2018 17:34:42 +0800 Subject: [PATCH 04/15] Allow the serializer to return sparse fieldsets --- lib/fast_jsonapi/fieldset.rb | 7 +++++ lib/fast_jsonapi/object_serializer.rb | 25 +++++++++++++--- lib/fast_jsonapi/serialization_core.rb | 29 ++++++++++-------- spec/lib/object_serializer_fields_spec.rb | 36 +++++++++++++++++++++++ spec/lib/serialization_core_spec.rb | 2 +- 5 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 lib/fast_jsonapi/fieldset.rb create mode 100644 spec/lib/object_serializer_fields_spec.rb diff --git a/lib/fast_jsonapi/fieldset.rb b/lib/fast_jsonapi/fieldset.rb new file mode 100644 index 0000000..0788a67 --- /dev/null +++ b/lib/fast_jsonapi/fieldset.rb @@ -0,0 +1,7 @@ +module FastJsonapi + class Fieldset + def initialize(fields) + @fields = fields + end + end +end diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 90771b4..cbadf40 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -7,6 +7,7 @@ require 'fast_jsonapi/attribute' require 'fast_jsonapi/relationship' require 'fast_jsonapi/link' require 'fast_jsonapi/serialization_core' +require 'fast_jsonapi/fieldset' module FastJsonapi module ObjectSerializer @@ -41,8 +42,8 @@ module FastJsonapi return serializable_hash unless @resource - serializable_hash[:data] = self.class.record_hash(@resource, @params) - serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @params) if @includes.present? + serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.reflected_record_type.to_sym], @params) + serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? serializable_hash end @@ -51,9 +52,10 @@ module FastJsonapi data = [] included = [] + fieldset = @fieldsets[self.class.reflected_record_type.to_sym] @resource.each do |record| - data << self.class.record_hash(record, @params) - included.concat self.class.get_included_records(record, @includes, @known_included_objects, @params) if @includes.present? + data << self.class.record_hash(record, fieldset, @params) + included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? end serializable_hash[:data] = data @@ -70,6 +72,8 @@ module FastJsonapi private def process_options(options) + @fieldsets = deep_symbolize(options[:fields].presence || {}) + return if options.blank? @known_included_objects = {} @@ -85,6 +89,18 @@ module FastJsonapi end end + def deep_symbolize(collection) + if collection.is_a? Hash + Hash[collection.map do |k, v| + [k.to_sym, deep_symbolize(v)] + end] + elsif collection.is_a? Array + collection.map { |i| deep_symbolize(i) } + else + collection.to_sym + end + end + def is_collection?(resource, force_is_collection = nil) return force_is_collection unless force_is_collection.nil? @@ -104,6 +120,7 @@ module FastJsonapi subclass.race_condition_ttl = race_condition_ttl subclass.data_links = data_links subclass.cached = cached + subclass.fieldset = fieldset.dup if fieldset.present? end def reflected_record_type diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 11257aa..03df974 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -21,7 +21,8 @@ module FastJsonapi :cache_length, :race_condition_ttl, :cached, - :data_links + :data_links, + :fieldset end end @@ -40,27 +41,30 @@ module FastJsonapi end end - def attributes_hash(record, params = {}) - attributes_to_serialize.each_with_object({}) do |(_k, attribute), hash| + def attributes_hash(record, fieldset = nil, params = {}) + attributes = attributes_to_serialize + attributes = attributes.slice(*fieldset) if fieldset.present? + attributes.each_with_object({}) do |(_k, attribute), hash| attribute.serialize(record, params, hash) end end - def relationships_hash(record, relationships = 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.each_with_object({}) do |(_k, relationship), hash| relationship.serialize(record, params, hash) end end - def record_hash(record, params = {}) + def record_hash(record, fieldset, 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, params) if attributes_to_serialize.present? + 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, params) if cachable_relationships_to_serialize.present? + temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, params) if cachable_relationships_to_serialize.present? temp_hash[:links] = links_hash(record, params) if data_links.present? temp_hash end @@ -68,8 +72,8 @@ module FastJsonapi record_hash else record_hash = id_hash(id_from_record(record), record_type, true) - record_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present? - record_hash[:relationships] = relationships_hash(record, nil, params) if relationships_to_serialize.present? + 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[:links] = links_hash(record, params) if data_links.present? record_hash end @@ -100,7 +104,7 @@ module FastJsonapi end # includes handler - def get_included_records(record, includes_list, known_included_objects, params = {}) + def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {}) return unless includes_list.present? includes_list.sort.each_with_object([]) do |include_item, included_records| @@ -120,7 +124,7 @@ module FastJsonapi included_objects.each do |inc_obj| if remaining_items(items) - serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects) + serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets) included_records.concat(serializer_records) unless serializer_records.empty? end @@ -128,7 +132,8 @@ module FastJsonapi next if known_included_objects.key?(code) known_included_objects[code] = inc_obj - included_records << serializer.record_hash(inc_obj, params) + + included_records << serializer.record_hash(inc_obj, fieldsets[serializer.reflected_record_type], params) end end end diff --git a/spec/lib/object_serializer_fields_spec.rb b/spec/lib/object_serializer_fields_spec.rb new file mode 100644 index 0000000..f44ab2e --- /dev/null +++ b/spec/lib/object_serializer_fields_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe FastJsonapi::ObjectSerializer do + include_context 'movie class' + + let(:fields) do + { + movie: %i[name actors], + actor: %i[name agency] + } + end + + it 'only returns specified fields' do + hash = MovieSerializer.new(movie, fields: fields).serializable_hash + + expect(hash[:data][:attributes].keys.sort).to eq %i[name] + end + + it 'only returns specified relationships' do + hash = MovieSerializer.new(movie, fields: fields).serializable_hash + + expect(hash[:data][:relationships].keys.sort).to eq %i[actors] + end + + it 'only returns specified fields for included relationships' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash + + expect(hash[:included].first[:attributes].keys.sort).to eq %i[name] + end + + it 'only returns specified relationships for included relationships' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash + + expect(hash[:included].first[:relationships].keys.sort).to eq %i[agency] + end +end diff --git a/spec/lib/serialization_core_spec.rb b/spec/lib/serialization_core_spec.rb index d161e3f..adf33df 100644 --- a/spec/lib/serialization_core_spec.rb +++ b/spec/lib/serialization_core_spec.rb @@ -64,7 +64,7 @@ describe FastJsonapi::ObjectSerializer do known_included_objects = {} included_records = [] [movie, movie].each do |record| - included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, nil) + included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, {}, nil) end expect(included_records.size).to eq 3 end From d427a157eeefa41634125b6e0865196a1359d3a3 Mon Sep 17 00:00:00 2001 From: Erol Date: Mon, 16 Jul 2018 17:59:54 +0800 Subject: [PATCH 05/15] Update spec with included documents with no explicitly given fields --- spec/lib/object_serializer_fields_spec.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/spec/lib/object_serializer_fields_spec.rb b/spec/lib/object_serializer_fields_spec.rb index f44ab2e..913ba83 100644 --- a/spec/lib/object_serializer_fields_spec.rb +++ b/spec/lib/object_serializer_fields_spec.rb @@ -5,7 +5,7 @@ describe FastJsonapi::ObjectSerializer do let(:fields) do { - movie: %i[name actors], + movie: %i[name actors advertising_campaign], actor: %i[name agency] } end @@ -19,7 +19,7 @@ describe FastJsonapi::ObjectSerializer do it 'only returns specified relationships' do hash = MovieSerializer.new(movie, fields: fields).serializable_hash - expect(hash[:data][:relationships].keys.sort).to eq %i[actors] + expect(hash[:data][:relationships].keys.sort).to eq %i[actors advertising_campaign] end it 'only returns specified fields for included relationships' do @@ -29,8 +29,20 @@ describe FastJsonapi::ObjectSerializer do end it 'only returns specified relationships for included relationships' do - hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash expect(hash[:included].first[:relationships].keys.sort).to eq %i[agency] end + + it 'returns all fields for included relationships when no explicit fields have been specified' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash + + expect(hash[:included][3][:attributes].keys.sort).to eq %i[id name] + end + + it 'returns all fields for included relationships when no explicit fields have been specified' do + hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash + + expect(hash[:included][3][:relationships].keys.sort).to eq %i[movie] + end end From 5aa5dc511c460c76b43b2b94f23894043d76ea42 Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 17 Jul 2018 11:01:33 +0800 Subject: [PATCH 06/15] Remove unused code --- lib/fast_jsonapi/fieldset.rb | 7 ------- lib/fast_jsonapi/object_serializer.rb | 2 -- lib/fast_jsonapi/serialization_core.rb | 3 +-- 3 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 lib/fast_jsonapi/fieldset.rb diff --git a/lib/fast_jsonapi/fieldset.rb b/lib/fast_jsonapi/fieldset.rb deleted file mode 100644 index 0788a67..0000000 --- a/lib/fast_jsonapi/fieldset.rb +++ /dev/null @@ -1,7 +0,0 @@ -module FastJsonapi - class Fieldset - def initialize(fields) - @fields = fields - end - end -end diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index cbadf40..7d58e67 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -7,7 +7,6 @@ require 'fast_jsonapi/attribute' require 'fast_jsonapi/relationship' require 'fast_jsonapi/link' require 'fast_jsonapi/serialization_core' -require 'fast_jsonapi/fieldset' module FastJsonapi module ObjectSerializer @@ -120,7 +119,6 @@ module FastJsonapi subclass.race_condition_ttl = race_condition_ttl subclass.data_links = data_links subclass.cached = cached - subclass.fieldset = fieldset.dup if fieldset.present? end def reflected_record_type diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 03df974..8b3ea11 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -21,8 +21,7 @@ module FastJsonapi :cache_length, :race_condition_ttl, :cached, - :data_links, - :fieldset + :data_links end end From 77c7af2a5edd9ca0e1dee5207ae0470aaea8adb5 Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 17 Jul 2018 11:54:48 +0800 Subject: [PATCH 07/15] Set the record type for inherited serializers --- lib/fast_jsonapi/object_serializer.rb | 1 + spec/lib/object_serializer_inheritance_spec.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 7d58e67..2cc1b35 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -119,6 +119,7 @@ module FastJsonapi subclass.race_condition_ttl = race_condition_ttl subclass.data_links = data_links subclass.cached = cached + subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type end def reflected_record_type diff --git a/spec/lib/object_serializer_inheritance_spec.rb b/spec/lib/object_serializer_inheritance_spec.rb index 8cf5b53..beb7487 100644 --- a/spec/lib/object_serializer_inheritance_spec.rb +++ b/spec/lib/object_serializer_inheritance_spec.rb @@ -95,6 +95,11 @@ describe FastJsonapi::ObjectSerializer do has_one :account end + it 'sets the correct record type' do + expect(EmployeeSerializer.reflected_record_type).to eq :employee + expect(EmployeeSerializer.record_type).to eq :employee + end + context 'when testing inheritance of attributes' do it 'includes parent attributes' do From 590549731410e3244d930bf757a7acd681b660c7 Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 17 Jul 2018 11:56:36 +0800 Subject: [PATCH 08/15] Use record type instead of reflected record type --- lib/fast_jsonapi/object_serializer.rb | 4 ++-- lib/fast_jsonapi/serialization_core.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 2cc1b35..7f740c8 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -41,7 +41,7 @@ module FastJsonapi return serializable_hash unless @resource - serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.reflected_record_type.to_sym], @params) + serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params) serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? serializable_hash end @@ -51,7 +51,7 @@ module FastJsonapi data = [] included = [] - fieldset = @fieldsets[self.class.reflected_record_type.to_sym] + fieldset = @fieldsets[self.class.record_type.to_sym] @resource.each do |record| data << self.class.record_hash(record, fieldset, @params) included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present? diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 8b3ea11..6ec069a 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -132,7 +132,7 @@ module FastJsonapi known_included_objects[code] = inc_obj - included_records << serializer.record_hash(inc_obj, fieldsets[serializer.reflected_record_type], params) + included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], params) end end end From 3df48cd4cb7bdaf127b3eb8eee7a2b25d93d75ad Mon Sep 17 00:00:00 2001 From: Erol Date: Tue, 17 Jul 2018 12:02:58 +0800 Subject: [PATCH 09/15] Add README instructions for using sparse fieldsets --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index c812eac..a03e1a8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Fast JSON API serialized 250 records in 3.01 ms * [Params](#params) * [Conditional Attributes](#conditional-attributes) * [Conditional Relationships](#conditional-relationships) + * [Sparse Fieldsets](#sparse-fieldsets) * [Contributing](#contributing) @@ -388,6 +389,21 @@ serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? } serializer.serializable_hash ``` +### Sparse Fieldsets + +Attributes and relationships can be selectively returned per record type by using the `fields` option. + +```ruby +class MovieSerializer + include FastJsonapi::ObjectSerializer + + attributes :name, :year +end + +serializer = MovieSerializer.new(movie, { fields: { movie: [:name] } }) +serializer.serializable_hash +``` + ### Customizable Options Option | Purpose | Example From 9c659839e425f76da6364d41991b944a55bbd98a Mon Sep 17 00:00:00 2001 From: Manoj M J Date: Wed, 18 Jul 2018 11:11:47 +0530 Subject: [PATCH 10/15] =?UTF-8?q?Evaluate=20ids=20via=20the=20specified=20?= =?UTF-8?q?=E2=80=98id=5Fmethod=5Fname=E2=80=99=20when=20relationships=20a?= =?UTF-8?q?re=20evaluated=20via=20a=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/fast_jsonapi/object_serializer.rb | 7 +++++- lib/fast_jsonapi/relationship.rb | 14 +++++------ .../object_serializer_class_methods_spec.rb | 25 +++++++++++++++++++ spec/shared/contexts/movie_context.rb | 3 ++- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 7f740c8..2d83d3b 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -240,10 +240,15 @@ module FastJsonapi relationship_type: relationship_type, cached: options[:cached], polymorphic: fetch_polymorphic_option(options), - conditional_proc: options[:if] + conditional_proc: options[:if], + id_method_name_for_inferred_objects: compute_object_method_name_for_inferred_objects(options[:id_method_name], block) ) end + def compute_object_method_name_for_inferred_objects(id_method_name, block) + (id_method_name.present? && block.present?) ? id_method_name : :id + end + def compute_serializer_name(serializer_key) return serializer_key unless serializer_key.is_a? Symbol namespace = self.name.gsub(/()?\w+Serializer$/, '') diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index 0b3a101..797e489 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -1,6 +1,6 @@ module FastJsonapi class Relationship - attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc + attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :id_method_name_for_inferred_objects def initialize( key:, @@ -13,7 +13,8 @@ module FastJsonapi relationship_type:, cached: false, polymorphic:, - conditional_proc: + conditional_proc:, + id_method_name_for_inferred_objects: ) @key = key @name = name @@ -26,6 +27,7 @@ module FastJsonapi @cached = cached @polymorphic = polymorphic @conditional_proc = conditional_proc + @id_method_name_for_inferred_objects = id_method_name_for_inferred_objects end def serialize(record, serialization_params, output_hash) @@ -86,13 +88,11 @@ module FastJsonapi end def fetch_id(record, params) - unless object_block.nil? + if object_block.present? object = object_block.call(record, params) - - return object.map(&:id) if object.respond_to? :map - return object.try(:id) + return object.map { |item| item.public_send(id_method_name_for_inferred_objects) } if object.respond_to? :map + return object.try(id_method_name_for_inferred_objects) end - record.public_send(id_method_name) end end diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 3c477c3..33138d5 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -87,6 +87,31 @@ describe FastJsonapi::ObjectSerializer do end end + describe '#has_many with block and id_method_name' do + before do + MovieSerializer.has_many(:awards, id_method_name: :imdb_award_id) do |movie| + movie.actors.map(&:awards).flatten + end + end + + after do + MovieSerializer.relationships_to_serialize.delete(:awards) + end + + context 'awards is not included' do + subject(:hash) { MovieSerializer.new(movie).serializable_hash } + + it 'returns correct hash where id is obtained from the method specified via `id_method_name`' do + expected_award_data = movie.actors.map(&:awards).flatten.map do |actor| + { id: actor.imdb_award_id.to_s, type: actor.class.name.downcase.to_sym } + end + serialized_award_data = hash[:data][:relationships][:awards][:data] + + expect(serialized_award_data).to eq(expected_award_data) + end + end + end + describe '#belongs_to' do subject(:relationship) { MovieSerializer.relationships_to_serialize[:area] } diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index 9061226..5c5c102 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -80,6 +80,7 @@ RSpec.shared_context 'movie class' do a.id = i a.title = "Test Award #{i}" a.actor_id = id + a.imdb_award_id = i * 10 end end end @@ -110,7 +111,7 @@ RSpec.shared_context 'movie class' do end class Award - attr_accessor :id, :title, :actor_id + attr_accessor :id, :title, :actor_id, :imdb_award_id end class State From 07b6e614ac9963f19884ee9b7256831fc60baecb Mon Sep 17 00:00:00 2001 From: Trevor Hinesley Date: Thu, 19 Jul 2018 09:57:22 -0500 Subject: [PATCH 11/15] Params are now passed to nested includes --- lib/fast_jsonapi/serialization_core.rb | 2 +- spec/lib/object_serializer_spec.rb | 17 +++++++++++++++++ spec/shared/contexts/movie_context.rb | 8 +++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 11257aa..54b2d81 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -120,7 +120,7 @@ module FastJsonapi included_objects.each do |inc_obj| if remaining_items(items) - serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects) + serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, params) included_records.concat(serializer_records) unless serializer_records.empty? end diff --git a/spec/lib/object_serializer_spec.rb b/spec/lib/object_serializer_spec.rb index 07cbbef..06aac37 100644 --- a/spec/lib/object_serializer_spec.rb +++ b/spec/lib/object_serializer_spec.rb @@ -310,6 +310,23 @@ describe FastJsonapi::ObjectSerializer do end end + context 'when serializing included, params should be available in any serializer' do + subject(:serializable_hash) do + options = {} + options[:include] = [:"actors.awards"] + options[:params] = { include_award_year: true } + MovieSerializer.new(movie, options).serializable_hash + end + let(:actor) { movie.actors.first } + let(:award) { actor.awards.first } + let(:year) { award.year } + + it 'passes params to deeply nested includes' do + expect(year).to_not be_blank + expect(serializable_hash[:included][0][:attributes][:year]).to eq year + end + end + context 'when is_collection option present' do subject { MovieSerializer.new(resource, is_collection_options).serializable_hash } diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index bbd89a9..dcca065 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -76,6 +76,7 @@ RSpec.shared_context 'movie class' do a.id = i a.title = "Test Award #{i}" a.actor_id = id + a.year = 1990 + i end end end @@ -106,7 +107,7 @@ RSpec.shared_context 'movie class' do end class Award - attr_accessor :id, :title, :actor_id + attr_accessor :id, :title, :actor_id, :year end class State @@ -221,6 +222,11 @@ RSpec.shared_context 'movie class' do class AwardSerializer include FastJsonapi::ObjectSerializer attributes :id, :title + attribute :year, if: Proc.new { |record, params| + params[:include_award_year].present? ? + params[:include_award_year] : + false + } belongs_to :actor end From dd71bc15d6f486bb5cd7d14a46150e06e3740b72 Mon Sep 17 00:00:00 2001 From: Manoj M J Date: Fri, 20 Jul 2018 10:33:27 +0530 Subject: [PATCH 12/15] Introduce the ability to add `meta` tag for every resource in the collection --- README.md | 17 +++++++++++ lib/fast_jsonapi/object_serializer.rb | 5 ++++ lib/fast_jsonapi/serialization_core.rb | 9 +++++- .../object_serializer_class_methods_spec.rb | 28 +++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a03e1a8..b9fe2ba 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,23 @@ class MovieSerializer end ``` +### Meta Per Resource + +For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. + + +```ruby +class MovieSerializer + include FastJsonapi::ObjectSerializer + + meta do |movie| + { + years_since_release: Date.current.year - movie.year + } + end +end +``` + ### Compound Document Support for top-level and nested included associations through ` options[:include] `. diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 7f740c8..4441cc9 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -120,6 +120,7 @@ module FastJsonapi subclass.data_links = data_links subclass.cached = cached subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type + subclass.meta_to_serialize = meta_to_serialize end def reflected_record_type @@ -218,6 +219,10 @@ module FastJsonapi add_relationship(relationship) end + def meta(&block) + self.meta_to_serialize = block + end + def create_relationship(base_key, relationship_type, options, block) name = base_key.to_sym if relationship_type == :has_many diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 6ec069a..43ad4e3 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -21,7 +21,8 @@ module FastJsonapi :cache_length, :race_condition_ttl, :cached, - :data_links + :data_links, + :meta_to_serialize end end @@ -57,6 +58,10 @@ module FastJsonapi end end + def meta_hash(record, params = {}) + meta_to_serialize.call(record, params) + end + def record_hash(record, fieldset, params = {}) if cached record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do @@ -68,12 +73,14 @@ module FastJsonapi temp_hash end record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, 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[:links] = links_hash(record, params) if data_links.present? + record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present? record_hash end end diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 3c477c3..b8a1eea 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -249,6 +249,34 @@ describe FastJsonapi::ObjectSerializer do end end + describe '#meta' do + subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash } + + before do + movie.release_year = 2008 + MovieSerializer.meta do |movie| + { + years_since_release: year_since_release_calculator(movie.release_year) + } + end + end + + after do + movie.release_year = nil + MovieSerializer.meta_to_serialize = nil + end + + it 'returns correct hash when serializable_hash is called' do + expect(serializable_hash[:data][:meta]).to eq ({ years_since_release: year_since_release_calculator(movie.release_year) }) + end + + private + + def year_since_release_calculator(release_year) + Date.current.year - release_year + end + end + describe '#link' do subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash } From e1f782e79f38c6aa2a8efc53660fd8bab25d1f97 Mon Sep 17 00:00:00 2001 From: Erol Date: Fri, 20 Jul 2018 13:22:15 +0800 Subject: [PATCH 13/15] Add missing fieldset parameter --- lib/fast_jsonapi/serialization_core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 6ec069a..86a39f1 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -67,7 +67,7 @@ module FastJsonapi 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, params)) if uncachable_relationships_to_serialize.present? + record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, params)) if uncachable_relationships_to_serialize.present? record_hash else record_hash = id_hash(id_from_record(record), record_type, true) From 0c367d25749bb41eb71cb5c79d250e1b024467ca Mon Sep 17 00:00:00 2001 From: Manoj M J Date: Sat, 21 Jul 2018 10:05:01 +0530 Subject: [PATCH 14/15] Minor code refactor --- lib/fast_jsonapi/object_serializer.rb | 17 ++++++++++++----- lib/fast_jsonapi/relationship.rb | 10 ++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 2d83d3b..d6015c5 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -232,7 +232,11 @@ module FastJsonapi Relationship.new( key: options[:key] || run_key_transform(base_key), name: name, - id_method_name: options[:id_method_name] || "#{base_serialization_key}#{id_postfix}".to_sym, + id_method_name: compute_id_method_name( + options[:id_method_name], + "#{base_serialization_key}#{id_postfix}".to_sym, + block + ), record_type: options[:record_type] || run_key_transform(base_key_sym), object_method_name: options[:object_method_name] || name, object_block: block, @@ -240,13 +244,16 @@ module FastJsonapi relationship_type: relationship_type, cached: options[:cached], polymorphic: fetch_polymorphic_option(options), - conditional_proc: options[:if], - id_method_name_for_inferred_objects: compute_object_method_name_for_inferred_objects(options[:id_method_name], block) + conditional_proc: options[:if] ) end - def compute_object_method_name_for_inferred_objects(id_method_name, block) - (id_method_name.present? && block.present?) ? id_method_name : :id + def compute_id_method_name(custom_id_method_name, id_method_name_from_relationship, block) + if block.present? + custom_id_method_name || :id + else + custom_id_method_name || id_method_name_from_relationship + end end def compute_serializer_name(serializer_key) diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index 797e489..7924969 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -1,6 +1,6 @@ module FastJsonapi class Relationship - attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :id_method_name_for_inferred_objects + attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc def initialize( key:, @@ -13,8 +13,7 @@ module FastJsonapi relationship_type:, cached: false, polymorphic:, - conditional_proc:, - id_method_name_for_inferred_objects: + conditional_proc: ) @key = key @name = name @@ -27,7 +26,6 @@ module FastJsonapi @cached = cached @polymorphic = polymorphic @conditional_proc = conditional_proc - @id_method_name_for_inferred_objects = id_method_name_for_inferred_objects end def serialize(record, serialization_params, output_hash) @@ -90,8 +88,8 @@ module FastJsonapi def fetch_id(record, params) if object_block.present? object = object_block.call(record, params) - return object.map { |item| item.public_send(id_method_name_for_inferred_objects) } if object.respond_to? :map - return object.try(id_method_name_for_inferred_objects) + return object.map { |item| item.public_send(id_method_name) } if object.respond_to? :map + return object.try(id_method_name) end record.public_send(id_method_name) end From 5a5a5e5125debd93b1f385e7f8525caffc57f4fe Mon Sep 17 00:00:00 2001 From: Trevor Hinesley Date: Mon, 23 Jul 2018 14:31:17 -0500 Subject: [PATCH 15/15] Fixed conditional attributes documentation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a03e1a8..d7032c1 100644 --- a/README.md +++ b/README.md @@ -351,15 +351,15 @@ class MovieSerializer include FastJsonapi::ObjectSerializer attributes :name, :year - attribute :release_year, if: Proc.new do |record| + attribute :release_year, if: Proc.new { |record| # Release year will only be serialized if it's greater than 1990 record.release_year > 1990 - end + } - attribute :director, if: Proc.new do |record, params| + attribute :director, if: Proc.new { |record, params| # The director will be serialized only if the :admin key of params is true params && params[:admin] == true - end + } end # ...