Skip to content

Commit 8e699e1

Browse files
committed
Provide key case translation
1 parent 6014b52 commit 8e699e1

File tree

9 files changed

+510
-8
lines changed

9 files changed

+510
-8
lines changed

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_serializers/adapter/base.rb

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
require 'active_model_serializers/key_transform'
2+
13
module ActiveModelSerializers
24
module Adapter
35
class Base
6+
include ActiveModelSerializers::KeyTransform
47
# Automatically register adapters when subclassing
58
def self.inherited(subclass)
69
ActiveModelSerializers::Adapter.register(subclass)
@@ -51,6 +54,16 @@ def include_meta(json)
5154
json[meta_key] = meta if meta
5255
json
5356
end
57+
58+
def default_key_transform
59+
:unaltered
60+
end
61+
62+
def transform_key_casing!(value, serialization_context)
63+
return value unless serialization_context
64+
transform = serialization_context.key_transform || default_key_transform
65+
KeyTransform.send(transform, value)
66+
end
5467
end
5568
end
5669
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,38 @@
1+
module ActiveModelSerializers
2+
module KeyTransform
3+
# Transforms keys to UpperCamelCase or PascalCase.
4+
#
5+
# @example:
6+
# "some_key" => "SomeKey",
7+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
8+
def camel(hash)
9+
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
10+
end
11+
12+
# Transforms keys to camelCase.
13+
#
14+
# @example:
15+
# "some_key" => "someKey",
16+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
17+
def camel_lower(hash)
18+
hash.deep_transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
19+
end
20+
21+
# Transforms keys to dashed-case.
22+
# This is the default case for the JsonApi adapter.
23+
#
24+
# @example:
25+
# "some_key" => "some-key",
26+
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize}
27+
def dashed(hash)
28+
hash.deep_transform_keys! { |key| key.to_s.dasherize.to_sym }
29+
end
30+
31+
# Returns the hash unaltered
32+
def unaltered(hash)
33+
hash
34+
end
35+
36+
module_function :camel, :camel_lower, :dashed, :unaltered
37+
end
38+
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

test/adapter/json/key_case_test.rb

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require 'test_helper'
2+
3+
module ActiveModelSerializers
4+
module Adapter
5+
class Json
6+
class KeyCaseTest < ActiveSupport::TestCase
7+
def mock_request(key_transform = nil)
8+
context = Minitest::Mock.new
9+
context.expect(:request_url, URI)
10+
context.expect(:query_parameters, {})
11+
context.expect(:key_transform, key_transform)
12+
@options = {}
13+
@options[:serialization_context] = context
14+
end
15+
16+
Post = Class.new(::Model)
17+
class PostSerializer < ActiveModel::Serializer
18+
attributes :id, :title, :body, :publish_at
19+
end
20+
21+
def setup
22+
ActionController::Base.cache_store.clear
23+
@blog = Blog.new(id: 1, name: 'My Blog!!', special_attribute: 'neat')
24+
serializer = CustomBlogSerializer.new(@blog)
25+
@adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
26+
end
27+
28+
def test_key_transform_default
29+
mock_request
30+
assert_equal({
31+
blog: { id: 1, special_attribute: 'neat', articles: nil }
32+
}, @adapter.serializable_hash(@options))
33+
end
34+
35+
def test_key_transform_undefined
36+
mock_request(:blam)
37+
result = nil
38+
assert_raises NoMethodError do
39+
result = @adapter.serializable_hash(@options)
40+
end
41+
end
42+
43+
def test_key_transform_dashed
44+
mock_request(:dashed)
45+
assert_equal({
46+
blog: { id: 1, :"special-attribute" => 'neat', articles: nil }
47+
}, @adapter.serializable_hash(@options))
48+
end
49+
50+
def test_key_transform_unaltered
51+
mock_request(:unaltered)
52+
assert_equal({
53+
blog: { id: 1, special_attribute: 'neat', articles: nil }
54+
}, @adapter.serializable_hash(@options))
55+
end
56+
57+
def test_key_transform_camel
58+
mock_request(:camel)
59+
assert_equal({
60+
Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
61+
}, @adapter.serializable_hash(@options))
62+
end
63+
64+
def test_key_transform_camel_lower
65+
mock_request(:camel_lower)
66+
assert_equal({
67+
blog: { id: 1, specialAttribute: 'neat', articles: nil }
68+
}, @adapter.serializable_hash(@options))
69+
end
70+
end
71+
end
72+
end
73+
end

0 commit comments

Comments
 (0)