Skip to content

Commit dfcbe26

Browse files
Merge branch 'master' into params-in-nested-includes
2 parents 07b6e61 + 5ff3fa9 commit dfcbe26

10 files changed

Lines changed: 141 additions & 19 deletions

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Fast JSON API serialized 250 records in 3.01 ms
3232
* [Params](#params)
3333
* [Conditional Attributes](#conditional-attributes)
3434
* [Conditional Relationships](#conditional-relationships)
35+
* [Sparse Fieldsets](#sparse-fieldsets)
3536
* [Contributing](#contributing)
3637

3738

@@ -207,6 +208,18 @@ class MovieSerializer
207208
end
208209
```
209210

211+
Attributes can also use a different name by passing the original method or accessor with a proc shortcut:
212+
213+
```ruby
214+
class MovieSerializer
215+
include FastJsonapi::ObjectSerializer
216+
217+
attributes :name
218+
219+
attribute :released_in_year, &:year
220+
end
221+
```
222+
210223
### Links Per Object
211224
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.
212225

@@ -376,6 +389,21 @@ serializer = MovieSerializer.new(movie, { params: { admin: current_user.admin? }
376389
serializer.serializable_hash
377390
```
378391

392+
### Sparse Fieldsets
393+
394+
Attributes and relationships can be selectively returned per record type by using the `fields` option.
395+
396+
```ruby
397+
class MovieSerializer
398+
include FastJsonapi::ObjectSerializer
399+
400+
attributes :name, :year
401+
end
402+
403+
serializer = MovieSerializer.new(movie, { fields: { movie: [:name] } })
404+
serializer.serializable_hash
405+
```
406+
379407
### Customizable Options
380408

381409
Option | Purpose | Example

lib/fast_jsonapi/attribute.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def initialize(key:, method:, options: {})
1111
def serialize(record, serialization_params, output_hash)
1212
if include_attribute?(record, serialization_params)
1313
output_hash[key] = if method.is_a?(Proc)
14-
method.arity == 1 ? method.call(record) : method.call(record, serialization_params)
14+
method.arity.abs == 1 ? method.call(record) : method.call(record, serialization_params)
1515
else
1616
record.public_send(method)
1717
end
@@ -26,4 +26,4 @@ def include_attribute?(record, serialization_params)
2626
end
2727
end
2828
end
29-
end
29+
end

lib/fast_jsonapi/object_serializer.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def hash_for_one_record
4141

4242
return serializable_hash unless @resource
4343

44-
serializable_hash[:data] = self.class.record_hash(@resource, @params)
45-
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @params) if @includes.present?
44+
serializable_hash[:data] = self.class.record_hash(@resource, @fieldsets[self.class.record_type.to_sym], @params)
45+
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
4646
serializable_hash
4747
end
4848

@@ -51,9 +51,10 @@ def hash_for_collection
5151

5252
data = []
5353
included = []
54+
fieldset = @fieldsets[self.class.record_type.to_sym]
5455
@resource.each do |record|
55-
data << self.class.record_hash(record, @params)
56-
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @params) if @includes.present?
56+
data << self.class.record_hash(record, fieldset, @params)
57+
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @fieldsets, @params) if @includes.present?
5758
end
5859

5960
serializable_hash[:data] = data
@@ -70,6 +71,8 @@ def serialized_json
7071
private
7172

7273
def process_options(options)
74+
@fieldsets = deep_symbolize(options[:fields].presence || {})
75+
7376
return if options.blank?
7477

7578
@known_included_objects = {}
@@ -85,6 +88,18 @@ def process_options(options)
8588
end
8689
end
8790

91+
def deep_symbolize(collection)
92+
if collection.is_a? Hash
93+
Hash[collection.map do |k, v|
94+
[k.to_sym, deep_symbolize(v)]
95+
end]
96+
elsif collection.is_a? Array
97+
collection.map { |i| deep_symbolize(i) }
98+
else
99+
collection.to_sym
100+
end
101+
end
102+
88103
def is_collection?(resource, force_is_collection = nil)
89104
return force_is_collection unless force_is_collection.nil?
90105

@@ -104,6 +119,7 @@ def inherited(subclass)
104119
subclass.race_condition_ttl = race_condition_ttl
105120
subclass.data_links = data_links
106121
subclass.cached = cached
122+
subclass.set_type(subclass.reflected_record_type) if subclass.reflected_record_type
107123
end
108124

109125
def reflected_record_type

lib/fast_jsonapi/serialization_core.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,39 @@ def links_hash(record, params = {})
4040
end
4141
end
4242

43-
def attributes_hash(record, params = {})
44-
attributes_to_serialize.each_with_object({}) do |(_k, attribute), hash|
43+
def attributes_hash(record, fieldset = nil, params = {})
44+
attributes = attributes_to_serialize
45+
attributes = attributes.slice(*fieldset) if fieldset.present?
46+
attributes.each_with_object({}) do |(_k, attribute), hash|
4547
attribute.serialize(record, params, hash)
4648
end
4749
end
4850

49-
def relationships_hash(record, relationships = nil, params = {})
51+
def relationships_hash(record, relationships = nil, fieldset = nil, params = {})
5052
relationships = relationships_to_serialize if relationships.nil?
53+
relationships = relationships.slice(*fieldset) if fieldset.present?
5154

5255
relationships.each_with_object({}) do |(_k, relationship), hash|
5356
relationship.serialize(record, params, hash)
5457
end
5558
end
5659

57-
def record_hash(record, params = {})
60+
def record_hash(record, fieldset, params = {})
5861
if cached
5962
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
6063
temp_hash = id_hash(id_from_record(record), record_type, true)
61-
temp_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
64+
temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
6265
temp_hash[:relationships] = {}
63-
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, params) if cachable_relationships_to_serialize.present?
66+
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, params) if cachable_relationships_to_serialize.present?
6467
temp_hash[:links] = links_hash(record, params) if data_links.present?
6568
temp_hash
6669
end
6770
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, params)) if uncachable_relationships_to_serialize.present?
6871
record_hash
6972
else
7073
record_hash = id_hash(id_from_record(record), record_type, true)
71-
record_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
72-
record_hash[:relationships] = relationships_hash(record, nil, params) if relationships_to_serialize.present?
74+
record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
75+
record_hash[:relationships] = relationships_hash(record, nil, fieldset, params) if relationships_to_serialize.present?
7376
record_hash[:links] = links_hash(record, params) if data_links.present?
7477
record_hash
7578
end
@@ -100,7 +103,7 @@ def remaining_items(items)
100103
end
101104

102105
# includes handler
103-
def get_included_records(record, includes_list, known_included_objects, params = {})
106+
def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {})
104107
return unless includes_list.present?
105108

106109
includes_list.sort.each_with_object([]) do |include_item, included_records|
@@ -120,15 +123,16 @@ def get_included_records(record, includes_list, known_included_objects, params =
120123

121124
included_objects.each do |inc_obj|
122125
if remaining_items(items)
123-
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, params)
126+
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects, fieldsets, params)
124127
included_records.concat(serializer_records) unless serializer_records.empty?
125128
end
126129

127130
code = "#{record_type}_#{inc_obj.id}"
128131
next if known_included_objects.key?(code)
129132

130133
known_included_objects[code] = inc_obj
131-
included_records << serializer.record_hash(inc_obj, params)
134+
135+
included_records << serializer.record_hash(inc_obj, fieldsets[serializer.record_type], params)
132136
end
133137
end
134138
end

lib/fast_jsonapi/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module FastJsonapi
2-
VERSION = "1.2"
2+
VERSION = "1.3"
33
end

spec/lib/object_serializer_class_methods_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,23 @@
230230
expect(serializable_hash[:data][:attributes][:title_with_year]).to eq "#{movie.name} (#{movie.release_year})"
231231
end
232232
end
233+
234+
context 'with &:proc' do
235+
before do
236+
movie.release_year = 2008
237+
MovieSerializer.attribute :released_in_year, &:release_year
238+
MovieSerializer.attribute :name, &:local_name
239+
end
240+
241+
after do
242+
MovieSerializer.attributes_to_serialize.delete(:released_in_year)
243+
end
244+
245+
it 'returns correct hash when serializable_hash is called' do
246+
expect(serializable_hash[:data][:attributes][:name]).to eq "english #{movie.name}"
247+
expect(serializable_hash[:data][:attributes][:released_in_year]).to eq movie.release_year
248+
end
249+
end
233250
end
234251

235252
describe '#link' do
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require 'spec_helper'
2+
3+
describe FastJsonapi::ObjectSerializer do
4+
include_context 'movie class'
5+
6+
let(:fields) do
7+
{
8+
movie: %i[name actors advertising_campaign],
9+
actor: %i[name agency]
10+
}
11+
end
12+
13+
it 'only returns specified fields' do
14+
hash = MovieSerializer.new(movie, fields: fields).serializable_hash
15+
16+
expect(hash[:data][:attributes].keys.sort).to eq %i[name]
17+
end
18+
19+
it 'only returns specified relationships' do
20+
hash = MovieSerializer.new(movie, fields: fields).serializable_hash
21+
22+
expect(hash[:data][:relationships].keys.sort).to eq %i[actors advertising_campaign]
23+
end
24+
25+
it 'only returns specified fields for included relationships' do
26+
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors]).serializable_hash
27+
28+
expect(hash[:included].first[:attributes].keys.sort).to eq %i[name]
29+
end
30+
31+
it 'only returns specified relationships for included relationships' do
32+
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
33+
34+
expect(hash[:included].first[:relationships].keys.sort).to eq %i[agency]
35+
end
36+
37+
it 'returns all fields for included relationships when no explicit fields have been specified' do
38+
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
39+
40+
expect(hash[:included][3][:attributes].keys.sort).to eq %i[id name]
41+
end
42+
43+
it 'returns all fields for included relationships when no explicit fields have been specified' do
44+
hash = MovieSerializer.new(movie, fields: fields, include: %i[actors advertising_campaign]).serializable_hash
45+
46+
expect(hash[:included][3][:relationships].keys.sort).to eq %i[movie]
47+
end
48+
end

spec/lib/object_serializer_inheritance_spec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ class EmployeeSerializer < UserSerializer
9595
has_one :account
9696
end
9797

98+
it 'sets the correct record type' do
99+
expect(EmployeeSerializer.reflected_record_type).to eq :employee
100+
expect(EmployeeSerializer.record_type).to eq :employee
101+
end
102+
98103
context 'when testing inheritance of attributes' do
99104

100105
it 'includes parent attributes' do

spec/lib/serialization_core_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
known_included_objects = {}
6565
included_records = []
6666
[movie, movie].each do |record|
67-
included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, nil)
67+
included_records.concat MovieSerializer.send(:get_included_records, record, includes_list, known_included_objects, {}, nil)
6868
end
6969
expect(included_records.size).to eq 3
7070
end

spec/shared/contexts/movie_context.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def cache_key
5454
"#{id}"
5555
end
5656

57+
def local_name(locale = :english)
58+
"#{locale} #{name}"
59+
end
60+
5761
def url
5862
"http://movies.com/#{id}"
5963
end

0 commit comments

Comments
 (0)