Skip to content

Commit a56b942

Browse files
committed
Provide key case translation
1 parent daabb89 commit a56b942

File tree

14 files changed

+797
-22
lines changed

14 files changed

+797
-22
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Breaking changes:
44

55
Features:
6+
- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Provide key translation. (@remear)
67
- [#1494](https://github.com/rails-api/active_model_serializers/pull/1494) Make serializers serializalbe
78
(using the Attributes adapter by default). (@bf4)
89
- [#1550](https://github.com/rails-api/active_model_serializers/pull/1550) Add

docs/general/configuration_options.md

+81-14
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,93 @@
22

33
# Configuration Options
44

5-
The following configuration options can be set on `ActiveModelSerializers.config`,
6-
preferably inside an initializer.
5+
The following configuration options can be set on
6+
`ActiveModelSerializers.config`, preferably inside an initializer.
77

88
## General
99

10-
- `adapter`: The [adapter](adapters.md) to use. Possible values: `:attributes, :json, :json_api`. Default: `:attributes`.
11-
- `serializer_lookup_enabled`: When `false`, serializers must be explicitly specified. Default: `true`
10+
##### adapter
11+
12+
The [adapter](adapters.md) to use.
13+
14+
Possible values:
15+
16+
- `:attributes` (default)
17+
- `:json`
18+
- `:json_api`
19+
20+
##### serializer_lookup_enabled
21+
22+
Enable automatic serializer lookup.
23+
24+
Possible values:
25+
26+
- `true` (default)
27+
- `false`
28+
29+
When `false`, serializers must be explicitly specified.
30+
31+
##### key_transform
32+
33+
The [key transform](key_transform.md) to use.
34+
35+
Possible values:
36+
37+
- `:camel` - ExampleKey
38+
- `:camel_lower` - exampleKey
39+
- `:dashed` - example-key
40+
- `:unaltered` - the original, unaltered key
41+
- `nil` - use the adapter default
42+
43+
Each adapter has a default key transform configured:
44+
45+
- `Json` - `:unaltered`
46+
- `JsonApi` - `:dashed`
47+
48+
`config.key_transform` is a global override of the adapter default. Adapters
49+
still prefer the render option `:key_transform` over this setting.
50+
1251

1352
## JSON API
1453

15-
- `jsonapi_resource_type`: Whether the `type` attributes of resources should be singular or plural. Possible values: `:singular, :plural`. Default: `:plural`.
16-
- `jsonapi_include_toplevel_object`: Whether to include a [top level JSON API member](http://jsonapi.org/format/#document-jsonapi-object)
17-
in the response document.
18-
Default: `false`.
19-
- Used when `jsonapi_include_toplevel_object` is `true`:
20-
- `jsonapi_version`: The latest version of the spec the API conforms to.
21-
Default: `'1.0'`.
22-
- `jsonapi_toplevel_meta`: Optional metadata. Not included if empty.
23-
Default: `{}`.
54+
55+
##### jsonapi_resource_type
56+
57+
Set the `type` attributes of resources to singular or plural.
58+
59+
Possible values:
60+
61+
- `:singular`
62+
- `:plural` (default)
63+
64+
##### jsonapi_include_toplevel_object
65+
66+
Include a [top level jsonapi member](http://jsonapi.org/format/#document-jsonapi-object)
67+
in the response document.
68+
69+
Possible values:
70+
71+
- `true`
72+
- `false` (default)
73+
74+
##### jsonapi_version
75+
76+
The latest version of the spec to which the API conforms.
77+
78+
Default: `'1.0'`.
79+
80+
*Used when `jsonapi_include_toplevel_object` is `true`*
81+
82+
##### jsonapi_toplevel_meta
83+
84+
Optional top-level metadata. Not included if empty.
85+
86+
Default: `{}`.
87+
88+
*Used when `jsonapi_include_toplevel_object` is `true`*
89+
2490

2591
## Hooks
2692

27-
To run a hook when ActiveModelSerializers is loaded, use `ActiveSupport.on_load(:action_controller) do end`
93+
To run a hook when ActiveModelSerializers is loaded, use
94+
`ActiveSupport.on_load(:action_controller) do end`

docs/general/key_transform.md

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

docs/general/rendering.md

+6
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ PR please :)
7979

8080
PR please :)
8181

82+
#### key_transform
83+
84+
```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower```
85+
86+
See [Key Transforms](key_transforms.md) for more informaiton.
87+
8288
#### meta
8389

8490
A `meta` member can be used to include non-standard meta-information. `meta` can

lib/action_controller/serialization.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def use_adapter?
5656

5757
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
5858
define_method renderer_method do |resource, options|
59-
options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) }
59+
options.fetch(:serialization_context) do
60+
options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options)
61+
end
6062
serializable_resource = get_serializer(resource, options)
6163
super(serializable_resource, options)
6264
end

lib/active_model/serializer/configuration.rb

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def config.array_serializer
2626
# Make JSON API top-level jsonapi member opt-in
2727
# ref: http://jsonapi.org/format/#document-top-level
2828
config.jsonapi_include_toplevel_object = false
29+
config.key_transform = nil
2930

3031
config.schema_path = 'test/support/schemas'
3132
end

lib/active_model_serializers/adapter/base.rb

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'active_model_serializers/key_transform'
2+
13
module ActiveModelSerializers
24
module Adapter
35
class Base
@@ -51,6 +53,27 @@ def include_meta(json)
5153
json[meta_key] = meta unless meta.blank?
5254
json
5355
end
56+
57+
def default_key_transform
58+
:unaltered
59+
end
60+
61+
# Determines the transform to use in order of precedence:
62+
# serialization context, global config, adapter default.
63+
#
64+
# @param serialization_context [Object] the SerializationContext
65+
# @return [Symbol] the transform to use
66+
def key_transform(serialization_context)
67+
serialization_context.key_transform ||
68+
ActiveModelSerializers.config.key_transform ||
69+
default_key_transform
70+
end
71+
72+
def transform_key_casing!(value, serialization_context)
73+
return value unless serialization_context
74+
transform = key_transform(serialization_context)
75+
KeyTransform.send(transform, value)
76+
end
5477
end
5578
end
5679
end

lib/active_model_serializers/adapter/json.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module Adapter
33
class Json < Base
44
def serializable_hash(options = nil)
55
options ||= {}
6-
{ root => Attributes.new(serializer, instance_options).serializable_hash(options) }
6+
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
7+
transform_key_casing!(serialized_hash, options[:serialization_context])
78
end
89
end
910
end

lib/active_model_serializers/adapter/json_api.rb

+10-5
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,20 @@ 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
42+
end
43+
4044
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
4145
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
4246
def serializable_hash(options = nil)
4347
options ||= {}
44-
if serializer.success?
45-
success_document(options)
46-
else
47-
failure_document
48-
end
48+
document = if serializer.success?
49+
success_document(options)
50+
else
51+
failure_document
52+
end
53+
transform_key_casing!(document, options[:serialization_context])
4954
end
5055

5156
# {http://jsonapi.org/format/#document-top-level Primary data}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require 'active_support/core_ext/hash/keys'
2+
3+
module ActiveModelSerializers
4+
module KeyTransform
5+
module_function
6+
7+
# Transforms keys to UpperCamelCase or PascalCase.
8+
#
9+
# @example:
10+
# "some_key" => "SomeKey",
11+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
12+
def camel(hash)
13+
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
14+
end
15+
16+
# Transforms keys to camelCase.
17+
#
18+
# @example:
19+
# "some_key" => "someKey",
20+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
21+
def camel_lower(hash)
22+
hash.deep_transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
23+
end
24+
25+
# Transforms keys to dashed-case.
26+
# This is the default case for the JsonApi adapter.
27+
#
28+
# @example:
29+
# "some_key" => "some-key",
30+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize}
31+
def dashed(hash)
32+
hash.deep_transform_keys! { |key| key.to_s.dasherize.to_sym }
33+
end
34+
35+
# Returns the hash unaltered
36+
def unaltered(hash)
37+
hash
38+
end
39+
end
40+
end

lib/active_model_serializers/serialization_context.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ class << self
44
attr_writer :url_helpers, :default_url_options
55
end
66

7-
attr_reader :request_url, :query_parameters
7+
attr_reader :request_url, :query_parameters, :key_transform
88

99
def initialize(request, options = {})
1010
@request_url = request.original_url[/\A[^?]+/]
1111
@query_parameters = request.query_parameters
1212
@url_helpers = options.delete(:url_helpers) || self.class.url_helpers
1313
@default_url_options = options.delete(:default_url_options) || self.class.default_url_options
14+
@key_transform = options.delete(:key_transform)
1415
end
1516

1617
def self.url_helpers

0 commit comments

Comments
 (0)