Skip to content

Commit becf31a

Browse files
committed
Apply key transforms to keys referenced in values
1 parent 53c7e6e commit becf31a

31 files changed

+437
-225
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
Breaking changes:
44

5+
- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Changed :dashed key transform to :dash. (@remear)
6+
- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Default key case for the JsonApi adapter changed to dashed. (@remear)
7+
58
Features:
9+
- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Transform keys referenced in values. (@remear)
610
- [#1644](https://github.com/rails-api/active_model_serializers/pull/1644) Include adapter name in cache key so
711
that the same serializer can be cached per adapter. (@bf4 via #1346 by @kevintyll)
812
- [#1642](https://github.com/rails-api/active_model_serializers/pull/1642) Prefer object.cache_key over the generated

active_model_serializers.gemspec

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,10 @@ Gem::Specification.new do |spec|
5757
spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0']
5858
spec.add_development_dependency 'json_schema'
5959
spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0']
60+
61+
spec.post_install_message = <<-EOF
62+
NOTE: The default key case for the JsonApi adapter has changed to dashed.
63+
See https://github.com/rails-api/active_model_serializers/blob/master/docs/general/key_transform.md
64+
for more information on configuring this behavior.
65+
EOF
6066
end

docs/general/configuration_options.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,24 @@ When `false`, serializers must be explicitly specified.
3030

3131
##### key_transform
3232

33-
The [key transform](key_transform.md) to use.
33+
The [key transform](key_transforms.md) to use.
3434

35-
Possible values:
3635

37-
- `:camel` - ExampleKey
38-
- `:camel_lower` - exampleKey
39-
- `:dashed` - example-key
40-
- `:unaltered` - the original, unaltered key
41-
- `nil` - use the adapter default
36+
| Option | Result |
37+
|----|----|
38+
| `:camel` | ExampleKey |
39+
| `:camel_lower` | exampleKey |
40+
| `:dash` | example-key |
41+
| `:unaltered` | the original, unaltered key |
42+
| `:underscore` | example_key |
43+
| `nil` | use the adapter default |
4244

4345
Each adapter has a default key transform configured:
4446

45-
- `Json` - `:unaltered`
46-
- `JsonApi` - `:dashed`
47+
| Adapter | Default Key Transform |
48+
|----|----|
49+
| `Json` | `:unaltered` |
50+
| `JsonApi` | `:dash` |
4751

4852
`config.key_transform` is a global override of the adapter default. Adapters
4953
still prefer the render option `:key_transform` over this setting.

docs/general/key_transform.md

Lines changed: 0 additions & 34 deletions
This file was deleted.

docs/general/key_transforms.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[Back to Guides](../README.md)
2+
3+
# Key Transforms
4+
5+
Key Transforms modify the casing of keys and keys referenced in values in
6+
serialized responses.
7+
8+
Provided key transforms:
9+
10+
| Option | Result |
11+
|----|----|
12+
| `:camel` | ExampleKey |
13+
| `:camel_lower` | exampleKey |
14+
| `:dash` | example-key |
15+
| `:unaltered` | the original, unaltered key |
16+
| `:underscore` | example_key |
17+
| `nil` | use the adapter default |
18+
19+
Key translation precedence is as follows:
20+
21+
##### Adapter option
22+
23+
`key_transform` is provided as an option via render.
24+
25+
```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower```
26+
27+
##### Configuration option
28+
29+
`key_transform` is set in `ActiveModelSerializers.config.key_transform`.
30+
31+
```ActiveModelSerializers.config.key_transform = :camel_lower```
32+
33+
##### Adapter default
34+
35+
Each adapter has a default transform configured:
36+
37+
| Adapter | Default Key Transform |
38+
|----|----|
39+
| `Json` | `:unaltered` |
40+
| `JsonApi` | `:dash` |

docs/general/rendering.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ PR please :)
8383

8484
```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower```
8585

86-
See [Key Transforms](key_transforms.md) for more informaiton.
86+
See [Key Tansforms](key_transforms.md) for more informaiton.
8787

8888
#### meta
8989

lib/active_model_serializers/adapter/base.rb

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,32 @@ def include_meta(json)
5858
json
5959
end
6060

61-
def default_key_transform
62-
:unaltered
63-
end
61+
class << self
62+
# Sets the default transform for the adapter.
63+
#
64+
# @return [Symbol] the default transform for the adapter
65+
def default_key_transform
66+
:unaltered
67+
end
6468

65-
# Determines the transform to use in order of precedence:
66-
# serialization context, global config, adapter default.
67-
#
68-
# @param serialization_context [Object] the SerializationContext
69-
# @return [Symbol] the transform to use
70-
def key_transform(serialization_context)
71-
serialization_context.key_transform ||
72-
ActiveModelSerializers.config.key_transform ||
73-
default_key_transform
74-
end
69+
# Determines the transform to use in order of precedence:
70+
# adapter option, global config, adapter default.
71+
#
72+
# @param options [Object]
73+
# @return [Symbol] the transform to use
74+
def transform(options)
75+
return options[:key_transform] if options && options[:key_transform]
76+
ActiveModelSerializers.config.key_transform || default_key_transform
77+
end
7578

76-
def transform_key_casing!(value, serialization_context)
77-
return value unless serialization_context
78-
transform = key_transform(serialization_context)
79-
KeyTransform.send(transform, value)
79+
# Transforms the casing of the supplied value.
80+
#
81+
# @param value [Object] the value to be transformed
82+
# @param options [Object] serializable resource options
83+
# @return [Symbol] the default transform for the adapter
84+
def transform_key_casing!(value, options)
85+
KeyTransform.send(transform(options), value)
86+
end
8087
end
8188
end
8289
end

lib/active_model_serializers/adapter/json.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class Json < Base
44
def serializable_hash(options = nil)
55
options ||= {}
66
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
7-
transform_key_casing!(serialized_hash, options[:serialization_context])
7+
self.class.transform_key_casing!(serialized_hash, options)
88
end
99
end
1010
end

lib/active_model_serializers/adapter/json_api.rb

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def initialize(serializer, options = {})
3737
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
3838
end
3939

40-
def default_key_transform
41-
:dashed
40+
def self.default_key_transform
41+
:dash
4242
end
4343

4444
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
@@ -48,9 +48,9 @@ def serializable_hash(options = nil)
4848
document = if serializer.success?
4949
success_document(options)
5050
else
51-
failure_document
51+
failure_document(options)
5252
end
53-
transform_key_casing!(document, options[:serialization_context])
53+
self.class.transform_key_casing!(document, options)
5454
end
5555

5656
# {http://jsonapi.org/format/#document-top-level Primary data}
@@ -71,7 +71,7 @@ def serializable_hash(options = nil)
7171
def success_document(options)
7272
is_collection = serializer.respond_to?(:each)
7373
serializers = is_collection ? serializer : [serializer]
74-
primary_data, included = resource_objects_for(serializers)
74+
primary_data, included = resource_objects_for(serializers, options)
7575

7676
hash = {}
7777
# toplevel_data
@@ -148,7 +148,7 @@ def success_document(options)
148148
# }.reject! {|_,v| v.nil? }
149149
# prs:
150150
# https://github.com/rails-api/active_model_serializers/pull/1004
151-
def failure_document
151+
def failure_document(options)
152152
hash = {}
153153
# PR Please :)
154154
# Jsonapi.add!(hash)
@@ -163,10 +163,10 @@ def failure_document
163163
# ]
164164
if serializer.respond_to?(:each)
165165
hash[:errors] = serializer.flat_map do |error_serializer|
166-
Error.resource_errors(error_serializer)
166+
Error.resource_errors(error_serializer, options)
167167
end
168168
else
169-
hash[:errors] = Error.resource_errors(serializer)
169+
hash[:errors] = Error.resource_errors(serializer, options)
170170
end
171171
hash
172172
end
@@ -224,21 +224,21 @@ def fragment_cache(cached_hash, non_cached_hash)
224224
# [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
225225
# meta
226226
# [x] https://github.com/rails-api/active_model_serializers/pull/1340
227-
def resource_objects_for(serializers)
227+
def resource_objects_for(serializers, options)
228228
@primary = []
229229
@included = []
230230
@resource_identifiers = Set.new
231-
serializers.each { |serializer| process_resource(serializer, true) }
232-
serializers.each { |serializer| process_relationships(serializer, @include_tree) }
231+
serializers.each { |serializer| process_resource(serializer, true, options) }
232+
serializers.each { |serializer| process_relationships(serializer, @include_tree, options) }
233233

234234
[@primary, @included]
235235
end
236236

237-
def process_resource(serializer, primary)
238-
resource_identifier = ResourceIdentifier.new(serializer).as_json
237+
def process_resource(serializer, primary, options)
238+
resource_identifier = ResourceIdentifier.new(serializer, options).as_json
239239
return false unless @resource_identifiers.add?(resource_identifier)
240240

241-
resource_object = resource_object_for(serializer)
241+
resource_object = resource_object_for(serializer, options)
242242
if primary
243243
@primary << resource_object
244244
else
@@ -248,21 +248,21 @@ def process_resource(serializer, primary)
248248
true
249249
end
250250

251-
def process_relationships(serializer, include_tree)
251+
def process_relationships(serializer, include_tree, options)
252252
serializer.associations(include_tree).each do |association|
253-
process_relationship(association.serializer, include_tree[association.key])
253+
process_relationship(association.serializer, include_tree[association.key], options)
254254
end
255255
end
256256

257-
def process_relationship(serializer, include_tree)
257+
def process_relationship(serializer, include_tree, options)
258258
if serializer.respond_to?(:each)
259-
serializer.each { |s| process_relationship(s, include_tree) }
259+
serializer.each { |s| process_relationship(s, include_tree, options) }
260260
return
261261
end
262262
return unless serializer && serializer.object
263-
return unless process_resource(serializer, false)
263+
return unless process_resource(serializer, false, options)
264264

265-
process_relationships(serializer, include_tree)
265+
process_relationships(serializer, include_tree, options)
266266
end
267267

268268
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
@@ -286,9 +286,9 @@ def attributes_for(serializer, fields)
286286
end
287287

288288
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
289-
def resource_object_for(serializer)
289+
def resource_object_for(serializer, options)
290290
resource_object = cache_check(serializer) do
291-
resource_object = ResourceIdentifier.new(serializer).as_json
291+
resource_object = ResourceIdentifier.new(serializer, options).as_json
292292

293293
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
294294
attributes = attributes_for(serializer, requested_fields)
@@ -297,7 +297,7 @@ def resource_object_for(serializer)
297297
end
298298

299299
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
300-
relationships = relationships_for(serializer, requested_associations)
300+
relationships = relationships_for(serializer, requested_associations, options)
301301
resource_object[:relationships] = relationships if relationships.any?
302302

303303
links = links_for(serializer)
@@ -425,15 +425,16 @@ def resource_object_for(serializer)
425425
# id: 'required-id',
426426
# meta: meta
427427
# }.reject! {|_,v| v.nil? }
428-
def relationships_for(serializer, requested_associations)
428+
def relationships_for(serializer, requested_associations, options)
429429
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
430430
serializer.associations(include_tree).each_with_object({}) do |association, hash|
431431
hash[association.key] = Relationship.new(
432432
serializer,
433433
association.serializer,
434-
association.options,
435-
association.links,
436-
association.meta
434+
options,
435+
options: association.options,
436+
links: association.links,
437+
meta: association.meta
437438
).as_json
438439
end
439440
end

lib/active_model_serializers/adapter/json_api/deserialization.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,21 @@ def parse_relationship(assoc_name, assoc_data, options)
182182
prefix_key = field_key(assoc_name, options).to_s.singularize
183183
hash =
184184
if assoc_data.is_a?(Array)
185-
{ "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri[:id] } }
185+
{ "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
186186
else
187-
{ "#{prefix_key}_id".to_sym => assoc_data && assoc_data.is_a?(Hash) ? assoc_data[:id] : nil }
187+
{ "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
188188
end
189189

190190
polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
191-
hash["#{prefix_key}_type".to_sym] = assoc_data[:type] if polymorphic
191+
hash["#{prefix_key}_type".to_sym] = assoc_data['type'] if polymorphic
192192

193193
hash
194194
end
195195

196196
# @api private
197197
def parse_relationships(relationships, options)
198198
transform_keys(relationships, options)
199-
.map { |(k, v)| parse_relationship(k, v[:data], options) }
199+
.map { |(k, v)| parse_relationship(k, v['data'], options) }
200200
.reduce({}, :merge)
201201
end
202202

lib/active_model_serializers/adapter/json_api/error.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ module Error
1010
#
1111
# @param [ActiveModel::Serializer::ErrorSerializer] error_serializer
1212
# @return [Array<Symbol, Array<String>>] i.e. attribute_name, [attribute_errors]
13-
def self.resource_errors(error_serializer)
13+
def self.resource_errors(error_serializer, options)
1414
error_serializer.as_json.flat_map do |attribute_name, attribute_errors|
15+
attribute_name = JsonApi.send(:transform_key_casing!, attribute_name,
16+
options)
1517
attribute_error_objects(attribute_name, attribute_errors)
1618
end
1719
end

0 commit comments

Comments
 (0)