Skip to content

Commit 0db0ebf

Browse files
authored
Merge pull request #1 from surkus/pluralize-type
Pluralize type
2 parents d345b55 + 6cde627 commit 0db0ebf

9 files changed

+287
-28
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Fast JSON API serialized 250 records in 3.01 ms
2727
* [Object Serialization](#object-serialization)
2828
* [Compound Document](#compound-document)
2929
* [Key Transforms](#key-transforms)
30+
* [Pluralize Type](#pluralize-type)
3031
* [Collection Serialization](#collection-serialization)
3132
* [Caching](#caching)
3233
* [Params](#params)
@@ -170,6 +171,28 @@ set_key_transform :dash # "some_key" => "some-key"
170171
set_key_transform :underscore # "some_key" => "some_key"
171172
```
172173

174+
### Pluralize Type
175+
By default fast_jsonapi does not pluralize type names. You can turn pluralization on using this syntax:
176+
177+
```ruby
178+
class AwardSerializer
179+
include FastJsonapi::ObjectSerializer
180+
belongs_to :actor
181+
pluralize_type true # "award" => "awards"
182+
end
183+
```
184+
185+
Relationship types are not automatically pluralized, even when their base types have `pluralize_type` set. Pluralization can be enabled in the relationship definition.
186+
187+
```ruby
188+
class ActorSerializer
189+
include FastJsonapi::ObjectSerializer
190+
has_many :awards, pluralize_type: true # "award" => "awards"
191+
end
192+
```
193+
194+
The most common use case for this feature is to easily migrate from serialization engines that pluralize by default, such as AMS.
195+
173196
### Attributes
174197
Attributes are defined in FastJsonapi using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute.
175198

lib/fast_jsonapi/object_serializer.rb

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def inherited(subclass)
118118
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
119119
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
120120
subclass.transform_method = transform_method
121+
subclass.pluralized_type = pluralized_type
121122
subclass.cache_length = cache_length
122123
subclass.race_condition_ttl = race_condition_ttl
123124
subclass.data_links = data_links.dup if data_links.present?
@@ -126,6 +127,10 @@ def inherited(subclass)
126127
subclass.meta_to_serialize = meta_to_serialize
127128
end
128129

130+
def transformed_record_type
131+
run_key_transform(run_key_pluralization(record_type))
132+
end
133+
129134
def reflected_record_type
130135
return @reflected_record_type if defined?(@reflected_record_type)
131136

@@ -153,6 +158,17 @@ def set_key_transform(transform_name)
153158
end
154159
end
155160

161+
def pluralize_type(pluralize)
162+
self.pluralized_type = pluralize
163+
164+
# ensure that the record type is correctly transformed
165+
if record_type
166+
set_type(record_type)
167+
elsif reflected_record_type
168+
set_type(reflected_record_type)
169+
end
170+
end
171+
156172
def run_key_transform(input)
157173
if self.transform_method.present?
158174
input.to_s.send(*@transform_method).to_sym
@@ -161,13 +177,21 @@ def run_key_transform(input)
161177
end
162178
end
163179

180+
def run_key_pluralization(input)
181+
if self.pluralized_type
182+
input.to_s.pluralize.to_sym
183+
else
184+
input.to_sym
185+
end
186+
end
187+
164188
def use_hyphen
165189
warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead')
166190
set_key_transform :dash
167191
end
168192

169193
def set_type(type_name)
170-
self.record_type = run_key_transform(type_name)
194+
self.record_type = run_key_transform(run_key_pluralization(type_name))
171195
end
172196

173197
def set_id(id_name = nil, &block)
@@ -243,16 +267,18 @@ def create_relationship(base_key, relationship_type, options, block)
243267
end
244268
Relationship.new(
245269
key: options[:key] || run_key_transform(base_key),
270+
base_key: run_key_transform(base_key_sym),
246271
name: name,
247272
id_method_name: compute_id_method_name(
248273
options[:id_method_name],
249274
"#{base_serialization_key}#{id_postfix}".to_sym,
250275
block
251276
),
252-
record_type: options[:record_type] || run_key_transform(base_key_sym),
277+
record_type: options[:record_type],
278+
pluralize_type: options[:pluralize_type],
253279
object_method_name: options[:object_method_name] || name,
254280
object_block: block,
255-
serializer: compute_serializer_name(options[:serializer] || base_key_sym),
281+
serializer_name: compute_serializer_name(options[:serializer] || base_key_sym),
256282
relationship_type: relationship_type,
257283
cached: options[:cached],
258284
polymorphic: fetch_polymorphic_option(options),
@@ -305,7 +331,7 @@ def validate_includes!(includes)
305331
relationships_to_serialize = klass.relationships_to_serialize || {}
306332
relationship_to_include = relationships_to_serialize[parsed_include]
307333
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
308-
klass = relationship_to_include.serializer.to_s.constantize unless relationship_to_include.polymorphic.is_a?(Hash)
334+
klass = relationship_to_include.serializer unless relationship_to_include.polymorphic.is_a?(Hash)
309335
end
310336
end
311337
end

lib/fast_jsonapi/relationship.rb

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
module FastJsonapi
22
class Relationship
3-
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data
3+
attr_reader :key, :base_key, :name, :id_method_name, :pluralized_type, :object_method_name, :object_block, :serializer_name, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data
44

55
def initialize(
66
key:,
7+
base_key:,
78
name:,
89
id_method_name:,
910
record_type:,
11+
pluralize_type:,
1012
object_method_name:,
1113
object_block:,
12-
serializer:,
14+
serializer_name:,
1315
relationship_type:,
1416
cached: false,
1517
polymorphic:,
@@ -19,12 +21,14 @@ def initialize(
1921
lazy_load_data: false
2022
)
2123
@key = key
24+
@base_key = base_key
2225
@name = name
2326
@id_method_name = id_method_name
24-
@record_type = record_type
27+
@pluralized_type = pluralize_type
28+
@record_type = run_key_pluralization(record_type)
2529
@object_method_name = object_method_name
2630
@object_block = object_block
27-
@serializer = serializer
31+
@serializer_name = serializer_name
2832
@relationship_type = relationship_type
2933
@cached = cached
3034
@polymorphic = polymorphic
@@ -59,6 +63,16 @@ def include_relationship?(record, serialization_params)
5963
end
6064
end
6165

66+
def serializer
67+
@serializer ||= @serializer_name.to_s.constantize
68+
end
69+
70+
def record_type
71+
@record_type || serializer.transformed_record_type
72+
rescue NameError
73+
run_key_transform(run_key_pluralization(base_key))
74+
end
75+
6276
private
6377

6478
def ids_hash_from_record_and_relationship(record, params = {})
@@ -78,8 +92,8 @@ def ids_hash_from_record_and_relationship(record, params = {})
7892
def id_hash_from_record(record, record_types)
7993
klass_name = record.class.respond_to?(:model_name) ? record.class.model_name.to_s : record.class.name
8094
# memoize the record type within the record_types dictionary, then assigning to record_type:
81-
associated_record_type = record_types[record.class] ||= run_key_transform(klass_name.demodulize.underscore)
8295

96+
associated_record_type = record_types[record.class] ||= run_key_transform(run_key_pluralization(klass_name.demodulize.underscore))
8397
id_hash(record.id, associated_record_type)
8498
end
8599

@@ -118,5 +132,14 @@ def run_key_transform(input)
118132
input.to_sym
119133
end
120134
end
135+
136+
def run_key_pluralization(input)
137+
return unless input
138+
if self.pluralized_type
139+
input.to_s.pluralize.to_sym
140+
else
141+
input.to_sym
142+
end
143+
end
121144
end
122145
end

lib/fast_jsonapi/serialization_core.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class << self
1616
:cachable_relationships_to_serialize,
1717
:uncachable_relationships_to_serialize,
1818
:transform_method,
19+
:pluralized_type,
1920
:record_type,
2021
:record_id,
2122
:cache_length,
@@ -122,7 +123,7 @@ def get_included_records(record, includes_list, known_included_objects, fieldset
122123
next unless relationship_item.include_relationship?(record, params)
123124
unless relationship_item.polymorphic.is_a?(Hash)
124125
record_type = relationship_item.record_type
125-
serializer = relationship_item.serializer.to_s.constantize
126+
serializer = relationship_item.serializer
126127
end
127128
relationship_type = relationship_item.relationship_type
128129

spec/lib/object_serializer_class_methods_spec.rb

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
end
1313

1414
after do
15-
serializer.relationships_to_serialize = {}
15+
MovieSerializer.relationships_to_serialize.delete(children.first)
1616
end
1717

1818
context 'with namespace' do
@@ -120,7 +120,7 @@
120120
end
121121

122122
after do
123-
MovieSerializer.relationships_to_serialize = {}
123+
MovieSerializer.relationships_to_serialize.delete(parent.first)
124124
end
125125

126126
context 'with overrides' do
@@ -176,7 +176,7 @@
176176
end
177177

178178
after do
179-
MovieSerializer.relationships_to_serialize = {}
179+
MovieSerializer.relationships_to_serialize.delete(partner.first)
180180
end
181181

182182
context 'with overrides' do
@@ -486,4 +486,75 @@ def year_since_release_calculator(release_year)
486486
end
487487
end
488488
end
489+
490+
describe '#pluralize_type' do
491+
subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash }
492+
493+
before do
494+
MovieSerializer.pluralize_type pluralize
495+
end
496+
497+
after do
498+
MovieSerializer.pluralize_type nil
499+
MovieSerializer.set_type :movie
500+
end
501+
502+
context 'when pluralize is true' do
503+
let(:pluralize) { true }
504+
505+
it 'returns correct hash which type equals pluralized value' do
506+
expect(serializable_hash[:data][:type]).to eq :movies
507+
end
508+
end
509+
510+
context 'when pluralize is false' do
511+
let(:pluralize) { false }
512+
513+
it 'returns correct hash which type equals non-pluralized value' do
514+
expect(serializable_hash[:data][:type]).to eq :movie
515+
end
516+
end
517+
end
518+
519+
describe '#pluralize_type after #set_type' do
520+
subject(:serializable_hash) { MovieSerializer.new(movie, include: [:actors]).serializable_hash }
521+
let(:type_name) { :film }
522+
523+
before do
524+
MovieSerializer.set_type type_name
525+
MovieSerializer.pluralize_type true
526+
end
527+
528+
after do
529+
MovieSerializer.pluralize_type nil
530+
MovieSerializer.set_type :movie
531+
end
532+
533+
context 'when sets singular type name' do
534+
it 'returns correct hash which type equals transformed set_type value' do
535+
expect(serializable_hash[:data][:type]).to eq :films
536+
end
537+
end
538+
539+
context 'when sets plural type name' do
540+
it 'returns correct hash which type equals transformed set_type value' do
541+
expect(serializable_hash[:data][:type]).to eq :films
542+
end
543+
end
544+
545+
context 'when pluralizing a relationship type after #set_type' do
546+
before do
547+
ActorSerializer.pluralize_type true
548+
end
549+
550+
after do
551+
ActorSerializer.pluralize_type nil
552+
end
553+
554+
it 'returns correct hash which relationship type equals transformed set_type value' do
555+
expect(serializable_hash[:data][:relationships][:actors][:data][0][:type]).to eq(:actors)
556+
expect(serializable_hash[:included][0][:type]).to eq(:actors)
557+
end
558+
end
559+
end
489560
end

spec/lib/object_serializer_inheritance_spec.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,23 @@ class EmployeeSerializer < UserSerializer
9595
has_one :account
9696
end
9797

98+
class LegacyUserSerializer < UserSerializer
99+
pluralize_type true
100+
end
101+
102+
class LegacyEmployeeSerializer < LegacyUserSerializer
103+
attributes :location
104+
attributes :compensation
105+
106+
has_one :account
107+
end
108+
98109
it 'sets the correct record type' do
99110
expect(EmployeeSerializer.reflected_record_type).to eq :employee
100111
expect(EmployeeSerializer.record_type).to eq :employee
101112
end
102113

103114
context 'when testing inheritance of attributes' do
104-
105115
it 'includes parent attributes' do
106116
subclass_attributes = EmployeeSerializer.attributes_to_serialize
107117
superclass_attributes = UserSerializer.attributes_to_serialize
@@ -157,12 +167,15 @@ class EmployeeSerializer < UserSerializer
157167
end
158168
end
159169

160-
context 'when test inheritence of other attributes' do
161-
162-
it 'inherits the tranform method' do
170+
context 'when testing inheritence of other attributes' do
171+
it 'inherits the transform method' do
163172
EmployeeSerializer
164173
expect(UserSerializer.transform_method).to eq EmployeeSerializer.transform_method
165174
end
166175

176+
it 'inherits pluralized_type' do
177+
LegacyEmployeeSerializer
178+
expect(LegacyUserSerializer.pluralized_type).to eq LegacyEmployeeSerializer.pluralized_type
179+
end
167180
end
168181
end

0 commit comments

Comments
 (0)