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