mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-23 06:16:50 +00:00
Merge pull request #1574 from remear/key-casing
Provide key transformation
This commit is contained in:
commit
9e992358d8
@ -3,6 +3,7 @@
|
||||
Breaking changes:
|
||||
|
||||
Features:
|
||||
- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Provide key translation. (@remear)
|
||||
- [#1494](https://github.com/rails-api/active_model_serializers/pull/1494) Make serializers serializalbe
|
||||
(using the Attributes adapter by default). (@bf4)
|
||||
- [#1550](https://github.com/rails-api/active_model_serializers/pull/1550) Add
|
||||
|
||||
@ -2,26 +2,95 @@
|
||||
|
||||
# Configuration Options
|
||||
|
||||
The following configuration options can be set on `ActiveModelSerializers.config`,
|
||||
preferably inside an initializer.
|
||||
The following configuration options can be set on
|
||||
`ActiveModelSerializers.config`, preferably inside an initializer.
|
||||
|
||||
## General
|
||||
|
||||
- `adapter`: The [adapter](adapters.md) to use. Possible values: `:attributes, :json, :json_api`. Default: `:attributes`.
|
||||
- `serializer_lookup_enabled`: When `false`, serializers must be explicitly specified. Default: `true`
|
||||
##### adapter
|
||||
|
||||
The [adapter](adapters.md) to use.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `:attributes` (default)
|
||||
- `:json`
|
||||
- `:json_api`
|
||||
|
||||
##### serializer_lookup_enabled
|
||||
|
||||
Enable automatic serializer lookup.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `true` (default)
|
||||
- `false`
|
||||
|
||||
When `false`, serializers must be explicitly specified.
|
||||
|
||||
##### key_transform
|
||||
|
||||
The [key transform](key_transform.md) to use.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `:camel` - ExampleKey
|
||||
- `:camel_lower` - exampleKey
|
||||
- `:dashed` - example-key
|
||||
- `:unaltered` - the original, unaltered key
|
||||
- `nil` - use the adapter default
|
||||
|
||||
Each adapter has a default key transform configured:
|
||||
|
||||
- `Json` - `:unaltered`
|
||||
- `JsonApi` - `:dashed`
|
||||
|
||||
`config.key_transform` is a global override of the adapter default. Adapters
|
||||
still prefer the render option `:key_transform` over this setting.
|
||||
|
||||
|
||||
## JSON API
|
||||
|
||||
- `jsonapi_resource_type`: Whether the `type` attributes of resources should be singular or plural. Possible values: `:singular, :plural`. Default: `:plural`.
|
||||
- `jsonapi_include_toplevel_object`: Whether to include a [top level JSON API member](http://jsonapi.org/format/#document-jsonapi-object)
|
||||
|
||||
##### jsonapi_resource_type
|
||||
|
||||
Sets whether the [type](http://jsonapi.org/format/#document-resource-identifier-objects)
|
||||
of the resource should be `singularized` or `pluralized` when it is not
|
||||
[explicitly specified by the serializer](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#type)
|
||||
|
||||
Possible values:
|
||||
|
||||
- `:singular`
|
||||
- `:plural` (default)
|
||||
|
||||
##### jsonapi_include_toplevel_object
|
||||
|
||||
Include a [top level jsonapi member](http://jsonapi.org/format/#document-jsonapi-object)
|
||||
in the response document.
|
||||
Default: `false`.
|
||||
- Used when `jsonapi_include_toplevel_object` is `true`:
|
||||
- `jsonapi_version`: The latest version of the spec the API conforms to.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `true`
|
||||
- `false` (default)
|
||||
|
||||
##### jsonapi_version
|
||||
|
||||
The latest version of the spec to which the API conforms.
|
||||
|
||||
Default: `'1.0'`.
|
||||
- `jsonapi_toplevel_meta`: Optional metadata. Not included if empty.
|
||||
|
||||
*Used when `jsonapi_include_toplevel_object` is `true`*
|
||||
|
||||
##### jsonapi_toplevel_meta
|
||||
|
||||
Optional top-level metadata. Not included if empty.
|
||||
|
||||
Default: `{}`.
|
||||
|
||||
*Used when `jsonapi_include_toplevel_object` is `true`*
|
||||
|
||||
|
||||
## Hooks
|
||||
|
||||
To run a hook when ActiveModelSerializers is loaded, use `ActiveSupport.on_load(:action_controller) do end`
|
||||
To run a hook when ActiveModelSerializers is loaded, use
|
||||
`ActiveSupport.on_load(:action_controller) do end`
|
||||
|
||||
34
docs/general/key_transform.md
Normal file
34
docs/general/key_transform.md
Normal file
@ -0,0 +1,34 @@
|
||||
[Back to Guides](../README.md)
|
||||
|
||||
# Key Transforms
|
||||
|
||||
Key transforms modify the keys in serialized responses.
|
||||
|
||||
Provided key transforms:
|
||||
|
||||
- `:camel` - ExampleKey
|
||||
- `:camel_lower` - exampleKey
|
||||
- `:dashed` - example-key
|
||||
- `:unaltered` - the original, unaltered key
|
||||
- `nil` - use the adapter default
|
||||
|
||||
Key translation precedence is as follows:
|
||||
|
||||
##### SerializableResource option
|
||||
|
||||
`key_transform` is provided as an option via render.
|
||||
|
||||
```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower```
|
||||
|
||||
##### Configuration option
|
||||
|
||||
`key_transform` is set in `ActiveModelSerializers.config.key_transform`.
|
||||
|
||||
```ActiveModelSerializers.config.key_transform = :camel_lower```
|
||||
|
||||
##### Adapter default
|
||||
|
||||
Each adapter has a default key transform configured:
|
||||
|
||||
- `Json` - `:unaltered`
|
||||
- `JsonApi` - `:dashed`
|
||||
@ -79,6 +79,12 @@ PR please :)
|
||||
|
||||
PR please :)
|
||||
|
||||
#### key_transform
|
||||
|
||||
```render json: posts, each_serializer: PostSerializer, key_transform: :camel_lower```
|
||||
|
||||
See [Key Transforms](key_transforms.md) for more informaiton.
|
||||
|
||||
#### meta
|
||||
|
||||
A `meta` member can be used to include non-standard meta-information. `meta` can
|
||||
|
||||
@ -56,7 +56,9 @@ module ActionController
|
||||
|
||||
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
|
||||
define_method renderer_method do |resource, options|
|
||||
options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) }
|
||||
options.fetch(:serialization_context) do
|
||||
options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options)
|
||||
end
|
||||
serializable_resource = get_serializer(resource, options)
|
||||
super(serializable_resource, options)
|
||||
end
|
||||
|
||||
@ -26,6 +26,7 @@ module ActiveModel
|
||||
# Make JSON API top-level jsonapi member opt-in
|
||||
# ref: http://jsonapi.org/format/#document-top-level
|
||||
config.jsonapi_include_toplevel_object = false
|
||||
config.key_transform = nil
|
||||
|
||||
config.schema_path = 'test/support/schemas'
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
require 'active_model_serializers/key_transform'
|
||||
|
||||
module ActiveModelSerializers
|
||||
module Adapter
|
||||
class Base
|
||||
@ -51,6 +53,27 @@ module ActiveModelSerializers
|
||||
json[meta_key] = meta unless meta.blank?
|
||||
json
|
||||
end
|
||||
|
||||
def default_key_transform
|
||||
:unaltered
|
||||
end
|
||||
|
||||
# Determines the transform to use in order of precedence:
|
||||
# serialization context, global config, adapter default.
|
||||
#
|
||||
# @param serialization_context [Object] the SerializationContext
|
||||
# @return [Symbol] the transform to use
|
||||
def key_transform(serialization_context)
|
||||
serialization_context.key_transform ||
|
||||
ActiveModelSerializers.config.key_transform ||
|
||||
default_key_transform
|
||||
end
|
||||
|
||||
def transform_key_casing!(value, serialization_context)
|
||||
return value unless serialization_context
|
||||
transform = key_transform(serialization_context)
|
||||
KeyTransform.send(transform, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -3,7 +3,8 @@ module ActiveModelSerializers
|
||||
class Json < Base
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
{ root => Attributes.new(serializer, instance_options).serializable_hash(options) }
|
||||
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
|
||||
transform_key_casing!(serialized_hash, options[:serialization_context])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -37,15 +37,20 @@ module ActiveModelSerializers
|
||||
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
|
||||
end
|
||||
|
||||
def default_key_transform
|
||||
:dashed
|
||||
end
|
||||
|
||||
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
|
||||
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
if serializer.success?
|
||||
document = if serializer.success?
|
||||
success_document(options)
|
||||
else
|
||||
failure_document
|
||||
end
|
||||
transform_key_casing!(document, options[:serialization_context])
|
||||
end
|
||||
|
||||
# {http://jsonapi.org/format/#document-top-level Primary data}
|
||||
|
||||
40
lib/active_model_serializers/key_transform.rb
Normal file
40
lib/active_model_serializers/key_transform.rb
Normal file
@ -0,0 +1,40 @@
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
|
||||
module ActiveModelSerializers
|
||||
module KeyTransform
|
||||
module_function
|
||||
|
||||
# Transforms keys to UpperCamelCase or PascalCase.
|
||||
#
|
||||
# @example:
|
||||
# "some_key" => "SomeKey",
|
||||
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
|
||||
def camel(hash)
|
||||
hash.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
|
||||
end
|
||||
|
||||
# Transforms keys to camelCase.
|
||||
#
|
||||
# @example:
|
||||
# "some_key" => "someKey",
|
||||
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize}
|
||||
def camel_lower(hash)
|
||||
hash.deep_transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
|
||||
end
|
||||
|
||||
# Transforms keys to dashed-case.
|
||||
# This is the default case for the JsonApi adapter.
|
||||
#
|
||||
# @example:
|
||||
# "some_key" => "some-key",
|
||||
# @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize}
|
||||
def dashed(hash)
|
||||
hash.deep_transform_keys! { |key| key.to_s.dasherize.to_sym }
|
||||
end
|
||||
|
||||
# Returns the hash unaltered
|
||||
def unaltered(hash)
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -4,13 +4,14 @@ module ActiveModelSerializers
|
||||
attr_writer :url_helpers, :default_url_options
|
||||
end
|
||||
|
||||
attr_reader :request_url, :query_parameters
|
||||
attr_reader :request_url, :query_parameters, :key_transform
|
||||
|
||||
def initialize(request, options = {})
|
||||
@request_url = request.original_url[/\A[^?]+/]
|
||||
@query_parameters = request.query_parameters
|
||||
@url_helpers = options.delete(:url_helpers) || self.class.url_helpers
|
||||
@default_url_options = options.delete(:default_url_options) || self.class.default_url_options
|
||||
@key_transform = options.delete(:key_transform)
|
||||
end
|
||||
|
||||
def self.url_helpers
|
||||
|
||||
180
test/action_controller/json_api/key_transform_test.rb
Normal file
180
test/action_controller/json_api/key_transform_test.rb
Normal file
@ -0,0 +1,180 @@
|
||||
require 'test_helper'
|
||||
|
||||
module ActionController
|
||||
module Serialization
|
||||
class JsonApi
|
||||
class KeyTransformTest < ActionController::TestCase
|
||||
class KeyTransformTestController < ActionController::Base
|
||||
Post = Class.new(::Model)
|
||||
class PostSerializer < ActiveModel::Serializer
|
||||
type 'posts'
|
||||
attributes :title, :body, :publish_at
|
||||
belongs_to :author
|
||||
has_many :comments
|
||||
|
||||
link(:post_authors) { 'https://example.com/post_authors' }
|
||||
|
||||
meta do
|
||||
{
|
||||
rating: 5,
|
||||
favorite_count: 10
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Author = Class.new(::Model)
|
||||
class AuthorSerializer < ActiveModel::Serializer
|
||||
type 'authors'
|
||||
attributes :first_name, :last_name
|
||||
end
|
||||
|
||||
Comment = Class.new(::Model)
|
||||
class CommentSerializer < ActiveModel::Serializer
|
||||
type 'comments'
|
||||
attributes :body
|
||||
belongs_to :author
|
||||
end
|
||||
|
||||
def setup_post
|
||||
ActionController::Base.cache_store.clear
|
||||
@author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones')
|
||||
@comment1 = Comment.new(id: 7, body: 'cool', author: @author)
|
||||
@comment2 = Comment.new(id: 12, body: 'awesome', author: @author)
|
||||
@post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1',
|
||||
author: @author, comments: [@comment1, @comment2],
|
||||
publish_at: '2020-03-16T03:55:25.291Z')
|
||||
@comment1.post = @post
|
||||
@comment2.post = @post
|
||||
end
|
||||
|
||||
def render_resource_with_key_transform
|
||||
setup_post
|
||||
render json: @post, serializer: PostSerializer, adapter: :json_api,
|
||||
key_transform: :camel
|
||||
end
|
||||
|
||||
def render_resource_with_key_transform_nil
|
||||
setup_post
|
||||
render json: @post, serializer: PostSerializer, adapter: :json_api,
|
||||
key_transform: nil
|
||||
end
|
||||
|
||||
def render_resource_with_key_transform_with_global_config
|
||||
setup_post
|
||||
old_transform = ActiveModelSerializers.config.key_transform
|
||||
ActiveModelSerializers.config.key_transform = :camel_lower
|
||||
render json: @post, serializer: PostSerializer, adapter: :json_api
|
||||
ActiveModelSerializers.config.key_transform = old_transform
|
||||
end
|
||||
end
|
||||
|
||||
tests KeyTransformTestController
|
||||
|
||||
def test_render_resource_with_key_transform
|
||||
get :render_resource_with_key_transform
|
||||
response = JSON.parse(@response.body)
|
||||
expected = {
|
||||
'Data' => {
|
||||
'Id' => '1337',
|
||||
'Type' => 'posts',
|
||||
'Attributes' => {
|
||||
'Title' => 'Title 1',
|
||||
'Body' => 'Body 1',
|
||||
'PublishAt' => '2020-03-16T03:55:25.291Z'
|
||||
},
|
||||
'Relationships' => {
|
||||
'Author' => {
|
||||
'Data' => {
|
||||
'Id' => '1',
|
||||
'Type' => 'authors'
|
||||
}
|
||||
},
|
||||
'Comments' => {
|
||||
'Data' => [
|
||||
{ 'Id' => '7', 'Type' => 'comments' },
|
||||
{ 'Id' => '12', 'Type' => 'comments' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'Links' => {
|
||||
'PostAuthors' => 'https://example.com/post_authors'
|
||||
},
|
||||
'Meta' => { 'Rating' => 5, 'FavoriteCount' => 10 }
|
||||
}
|
||||
}
|
||||
assert_equal expected, response
|
||||
end
|
||||
|
||||
def test_render_resource_with_key_transform_nil
|
||||
get :render_resource_with_key_transform_nil
|
||||
response = JSON.parse(@response.body)
|
||||
expected = {
|
||||
'data' => {
|
||||
'id' => '1337',
|
||||
'type' => 'posts',
|
||||
'attributes' => {
|
||||
'title' => 'Title 1',
|
||||
'body' => 'Body 1',
|
||||
'publish-at' => '2020-03-16T03:55:25.291Z'
|
||||
},
|
||||
'relationships' => {
|
||||
'author' => {
|
||||
'data' => {
|
||||
'id' => '1',
|
||||
'type' => 'authors'
|
||||
}
|
||||
},
|
||||
'comments' => {
|
||||
'data' => [
|
||||
{ 'id' => '7', 'type' => 'comments' },
|
||||
{ 'id' => '12', 'type' => 'comments' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'links' => {
|
||||
'post-authors' => 'https://example.com/post_authors'
|
||||
},
|
||||
'meta' => { 'rating' => 5, 'favorite-count' => 10 }
|
||||
}
|
||||
}
|
||||
assert_equal expected, response
|
||||
end
|
||||
|
||||
def test_render_resource_with_key_transform_with_global_config
|
||||
get :render_resource_with_key_transform_with_global_config
|
||||
response = JSON.parse(@response.body)
|
||||
expected = {
|
||||
'data' => {
|
||||
'id' => '1337',
|
||||
'type' => 'posts',
|
||||
'attributes' => {
|
||||
'title' => 'Title 1',
|
||||
'body' => 'Body 1',
|
||||
'publishAt' => '2020-03-16T03:55:25.291Z'
|
||||
},
|
||||
'relationships' => {
|
||||
'author' => {
|
||||
'data' => {
|
||||
'id' => '1',
|
||||
'type' => 'authors'
|
||||
}
|
||||
},
|
||||
'comments' => {
|
||||
'data' => [
|
||||
{ 'id' => '7', 'type' => 'comments' },
|
||||
{ 'id' => '12', 'type' => 'comments' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'links' => {
|
||||
'postAuthors' => 'https://example.com/post_authors'
|
||||
},
|
||||
'meta' => { 'rating' => 5, 'favoriteCount' => 10 }
|
||||
}
|
||||
}
|
||||
assert_equal expected, response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
test/adapter/json/key_case_test.rb
Normal file
93
test/adapter/json/key_case_test.rb
Normal file
@ -0,0 +1,93 @@
|
||||
require 'test_helper'
|
||||
|
||||
module ActiveModelSerializers
|
||||
module Adapter
|
||||
class Json
|
||||
class KeyCaseTest < ActiveSupport::TestCase
|
||||
def mock_request(key_transform = nil)
|
||||
context = Minitest::Mock.new
|
||||
context.expect(:request_url, URI)
|
||||
context.expect(:query_parameters, {})
|
||||
context.expect(:key_transform, key_transform)
|
||||
@options = {}
|
||||
@options[:serialization_context] = context
|
||||
end
|
||||
|
||||
Post = Class.new(::Model)
|
||||
class PostSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title, :body, :publish_at
|
||||
end
|
||||
|
||||
def setup
|
||||
ActionController::Base.cache_store.clear
|
||||
@blog = Blog.new(id: 1, name: 'My Blog!!', special_attribute: 'neat')
|
||||
serializer = CustomBlogSerializer.new(@blog)
|
||||
@adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
|
||||
end
|
||||
|
||||
def test_key_transform_default
|
||||
mock_request
|
||||
assert_equal({
|
||||
blog: { id: 1, special_attribute: 'neat', articles: nil }
|
||||
}, @adapter.serializable_hash(@options))
|
||||
end
|
||||
|
||||
def test_key_transform_global_config
|
||||
mock_request
|
||||
result = with_config(key_transform: :camel_lower) do
|
||||
@adapter.serializable_hash(@options)
|
||||
end
|
||||
assert_equal({
|
||||
blog: { id: 1, specialAttribute: 'neat', articles: nil }
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_key_transform_serialization_ctx_overrides_global_config
|
||||
mock_request(:camel)
|
||||
result = with_config(key_transform: :camel_lower) do
|
||||
@adapter.serializable_hash(@options)
|
||||
end
|
||||
assert_equal({
|
||||
Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_key_transform_undefined
|
||||
mock_request(:blam)
|
||||
result = nil
|
||||
assert_raises NoMethodError do
|
||||
result = @adapter.serializable_hash(@options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_key_transform_dashed
|
||||
mock_request(:dashed)
|
||||
assert_equal({
|
||||
blog: { id: 1, :"special-attribute" => 'neat', articles: nil }
|
||||
}, @adapter.serializable_hash(@options))
|
||||
end
|
||||
|
||||
def test_key_transform_unaltered
|
||||
mock_request(:unaltered)
|
||||
assert_equal({
|
||||
blog: { id: 1, special_attribute: 'neat', articles: nil }
|
||||
}, @adapter.serializable_hash(@options))
|
||||
end
|
||||
|
||||
def test_key_transform_camel
|
||||
mock_request(:camel)
|
||||
assert_equal({
|
||||
Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
|
||||
}, @adapter.serializable_hash(@options))
|
||||
end
|
||||
|
||||
def test_key_transform_camel_lower
|
||||
mock_request(:camel_lower)
|
||||
assert_equal({
|
||||
blog: { id: 1, specialAttribute: 'neat', articles: nil }
|
||||
}, @adapter.serializable_hash(@options))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
500
test/adapter/json_api/key_case_test.rb
Normal file
500
test/adapter/json_api/key_case_test.rb
Normal file
@ -0,0 +1,500 @@
|
||||
require 'test_helper'
|
||||
|
||||
module ActiveModelSerializers
|
||||
module Adapter
|
||||
class JsonApi
|
||||
class KeyCaseTest < ActiveSupport::TestCase
|
||||
Post = Class.new(::Model)
|
||||
class PostSerializer < ActiveModel::Serializer
|
||||
type 'posts'
|
||||
attributes :title, :body, :publish_at
|
||||
belongs_to :author
|
||||
has_many :comments
|
||||
|
||||
link(:self) { post_url(object.id) }
|
||||
link(:post_authors) { post_authors_url(object.id) }
|
||||
link(:subscriber_comments) { post_comments_url(object.id) }
|
||||
|
||||
meta do
|
||||
{
|
||||
rating: 5,
|
||||
favorite_count: 10
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Author = Class.new(::Model)
|
||||
class AuthorSerializer < ActiveModel::Serializer
|
||||
type 'authors'
|
||||
attributes :first_name, :last_name
|
||||
end
|
||||
|
||||
Comment = Class.new(::Model)
|
||||
class CommentSerializer < ActiveModel::Serializer
|
||||
type 'comments'
|
||||
attributes :body
|
||||
belongs_to :author
|
||||
end
|
||||
|
||||
def mock_request(key_transform = nil)
|
||||
context = Minitest::Mock.new
|
||||
context.expect(:request_url, URI)
|
||||
context.expect(:query_parameters, {})
|
||||
context.expect(:key_transform, key_transform)
|
||||
context.expect(:url_helpers, Rails.application.routes.url_helpers)
|
||||
@options = {}
|
||||
@options[:serialization_context] = context
|
||||
end
|
||||
|
||||
def setup
|
||||
Rails.application.routes.draw do
|
||||
resources :posts do
|
||||
resources :authors
|
||||
resources :comments
|
||||
end
|
||||
end
|
||||
@publish_at = 1.day.from_now
|
||||
@author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones')
|
||||
@comment1 = Comment.new(id: 7, body: 'cool', author: @author)
|
||||
@comment2 = Comment.new(id: 12, body: 'awesome', author: @author)
|
||||
@post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1',
|
||||
author: @author, comments: [@comment1, @comment2],
|
||||
publish_at: @publish_at)
|
||||
@comment1.post = @post
|
||||
@comment2.post = @post
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_default
|
||||
mock_request
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
assert_equal({
|
||||
data: {
|
||||
id: '1337',
|
||||
type: 'posts',
|
||||
attributes: {
|
||||
title: 'Title 1',
|
||||
body: 'Body 1',
|
||||
:"publish-at" => @publish_at
|
||||
},
|
||||
relationships: {
|
||||
author: {
|
||||
data: { id: '1', type: 'authors' }
|
||||
},
|
||||
comments: {
|
||||
data: [
|
||||
{ id: '7', type: 'comments' },
|
||||
{ id: '12', type: 'comments' }
|
||||
] }
|
||||
},
|
||||
links: {
|
||||
self: 'http://example.com/posts/1337',
|
||||
:"post-authors" => 'http://example.com/posts/1337/authors',
|
||||
:"subscriber-comments" => 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
meta: { rating: 5, :"favorite-count" => 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_global_config
|
||||
mock_request
|
||||
result = with_config(key_transform: :camel_lower) do
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
assert_equal({
|
||||
data: {
|
||||
id: '1337',
|
||||
type: 'posts',
|
||||
attributes: {
|
||||
title: 'Title 1',
|
||||
body: 'Body 1',
|
||||
publishAt: @publish_at
|
||||
},
|
||||
relationships: {
|
||||
author: {
|
||||
data: { id: '1', type: 'authors' }
|
||||
},
|
||||
comments: {
|
||||
data: [
|
||||
{ id: '7', type: 'comments' },
|
||||
{ id: '12', type: 'comments' }
|
||||
] }
|
||||
},
|
||||
links: {
|
||||
self: 'http://example.com/posts/1337',
|
||||
postAuthors: 'http://example.com/posts/1337/authors',
|
||||
subscriberComments: 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
meta: { rating: 5, favoriteCount: 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_doc_key_transform_serialization_ctx_overrides_global
|
||||
mock_request(:camel)
|
||||
result = with_config(key_transform: :camel_lower) do
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
assert_equal({
|
||||
Data: {
|
||||
Id: '1337',
|
||||
Type: 'posts',
|
||||
Attributes: {
|
||||
Title: 'Title 1',
|
||||
Body: 'Body 1',
|
||||
PublishAt: @publish_at
|
||||
},
|
||||
Relationships: {
|
||||
Author: {
|
||||
Data: { Id: '1', Type: 'authors' }
|
||||
},
|
||||
Comments: {
|
||||
Data: [
|
||||
{ Id: '7', Type: 'comments' },
|
||||
{ Id: '12', Type: 'comments' }
|
||||
] }
|
||||
},
|
||||
Links: {
|
||||
Self: 'http://example.com/posts/1337',
|
||||
PostAuthors: 'http://example.com/posts/1337/authors',
|
||||
SubscriberComments: 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
Meta: { Rating: 5, FavoriteCount: 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_dashed
|
||||
mock_request(:dashed)
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
assert_equal({
|
||||
data: {
|
||||
id: '1337',
|
||||
type: 'posts',
|
||||
attributes: {
|
||||
title: 'Title 1',
|
||||
body: 'Body 1',
|
||||
:"publish-at" => @publish_at
|
||||
},
|
||||
relationships: {
|
||||
author: {
|
||||
data: { id: '1', type: 'authors' }
|
||||
},
|
||||
comments: {
|
||||
data: [
|
||||
{ id: '7', type: 'comments' },
|
||||
{ id: '12', type: 'comments' }
|
||||
] }
|
||||
},
|
||||
links: {
|
||||
self: 'http://example.com/posts/1337',
|
||||
:"post-authors" => 'http://example.com/posts/1337/authors',
|
||||
:"subscriber-comments" => 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
meta: { rating: 5, :"favorite-count" => 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_unaltered
|
||||
mock_request(:unaltered)
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
assert_equal({
|
||||
data: {
|
||||
id: '1337',
|
||||
type: 'posts',
|
||||
attributes: {
|
||||
title: 'Title 1',
|
||||
body: 'Body 1',
|
||||
publish_at: @publish_at
|
||||
},
|
||||
relationships: {
|
||||
author: {
|
||||
data: { id: '1', type: 'authors' }
|
||||
},
|
||||
comments: {
|
||||
data: [
|
||||
{ id: '7', type: 'comments' },
|
||||
{ id: '12', type: 'comments' }
|
||||
] }
|
||||
},
|
||||
links: {
|
||||
self: 'http://example.com/posts/1337',
|
||||
post_authors: 'http://example.com/posts/1337/authors',
|
||||
subscriber_comments: 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
meta: { rating: 5, favorite_count: 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_undefined
|
||||
mock_request(:zoot)
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
assert_raises NoMethodError do
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_camel
|
||||
mock_request(:camel)
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
assert_equal({
|
||||
Data: {
|
||||
Id: '1337',
|
||||
Type: 'posts',
|
||||
Attributes: {
|
||||
Title: 'Title 1',
|
||||
Body: 'Body 1',
|
||||
PublishAt: @publish_at
|
||||
},
|
||||
Relationships: {
|
||||
Author: {
|
||||
Data: { Id: '1', Type: 'authors' }
|
||||
},
|
||||
Comments: {
|
||||
Data: [
|
||||
{ Id: '7', Type: 'comments' },
|
||||
{ Id: '12', Type: 'comments' }
|
||||
] }
|
||||
},
|
||||
Links: {
|
||||
Self: 'http://example.com/posts/1337',
|
||||
PostAuthors: 'http://example.com/posts/1337/authors',
|
||||
SubscriberComments: 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
Meta: { Rating: 5, FavoriteCount: 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_success_document_key_transform_camel_lower
|
||||
mock_request(:camel_lower)
|
||||
serializer = PostSerializer.new(@post)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
assert_equal({
|
||||
data: {
|
||||
id: '1337',
|
||||
type: 'posts',
|
||||
attributes: {
|
||||
title: 'Title 1',
|
||||
body: 'Body 1',
|
||||
publishAt: @publish_at
|
||||
},
|
||||
relationships: {
|
||||
author: {
|
||||
data: { id: '1', type: 'authors' }
|
||||
},
|
||||
comments: {
|
||||
data: [
|
||||
{ id: '7', type: 'comments' },
|
||||
{ id: '12', type: 'comments' }
|
||||
] }
|
||||
},
|
||||
links: {
|
||||
self: 'http://example.com/posts/1337',
|
||||
postAuthors: 'http://example.com/posts/1337/authors',
|
||||
subscriberComments: 'http://example.com/posts/1337/comments'
|
||||
},
|
||||
meta: { rating: 5, favoriteCount: 10 }
|
||||
}
|
||||
}, result)
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_default
|
||||
mock_request
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
expected_errors_object =
|
||||
{ :errors =>
|
||||
[
|
||||
{
|
||||
:source => { :pointer => '/data/attributes/published_at' },
|
||||
:detail => 'must be in the future' },
|
||||
{
|
||||
:source => { :pointer => '/data/attributes/title' },
|
||||
:detail => 'must be longer'
|
||||
}
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_global_config
|
||||
mock_request
|
||||
result = with_config(key_transform: :camel) do
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
expected_errors_object =
|
||||
{ :Errors =>
|
||||
[
|
||||
{
|
||||
:Source => { :Pointer => '/data/attributes/published_at' },
|
||||
:Detail => 'must be in the future'
|
||||
},
|
||||
{
|
||||
:Source => { :Pointer => '/data/attributes/title' },
|
||||
:Detail => 'must be longer'
|
||||
}
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_serialization_ctx_overrides_global
|
||||
mock_request(:camel)
|
||||
result = with_config(key_transform: :camel_lower) do
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
expected_errors_object =
|
||||
{ :Errors =>
|
||||
[
|
||||
{
|
||||
:Source => { :Pointer => '/data/attributes/published_at' },
|
||||
:Detail => 'must be in the future'
|
||||
},
|
||||
{
|
||||
:Source => { :Pointer => '/data/attributes/title' },
|
||||
:Detail => 'must be longer'
|
||||
}
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_dashed
|
||||
mock_request(:dashed)
|
||||
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
|
||||
expected_errors_object =
|
||||
{ :errors =>
|
||||
[
|
||||
{
|
||||
:source => { :pointer => '/data/attributes/published_at' },
|
||||
:detail => 'must be in the future'
|
||||
},
|
||||
{
|
||||
:source => { :pointer => '/data/attributes/title' },
|
||||
:detail => 'must be longer'
|
||||
}
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_unaltered
|
||||
mock_request(:unaltered)
|
||||
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
|
||||
expected_errors_object =
|
||||
{ :errors =>
|
||||
[
|
||||
{ :source => { :pointer => '/data/attributes/published_at' }, :detail => 'must be in the future' },
|
||||
{ :source => { :pointer => '/data/attributes/title' }, :detail => 'must be longer' }
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_undefined
|
||||
mock_request(:krazy)
|
||||
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
|
||||
assert_raises NoMethodError do
|
||||
adapter.serializable_hash(@options)
|
||||
end
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_camel
|
||||
mock_request(:camel)
|
||||
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
|
||||
expected_errors_object =
|
||||
{ :Errors =>
|
||||
[
|
||||
{ :Source => { :Pointer => '/data/attributes/published_at' }, :Detail => 'must be in the future' },
|
||||
{ :Source => { :Pointer => '/data/attributes/title' }, :Detail => 'must be longer' }
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
|
||||
def test_error_document_key_transform_camel_lower
|
||||
mock_request(:camel_lower)
|
||||
|
||||
resource = ModelWithErrors.new
|
||||
resource.errors.add(:published_at, 'must be in the future')
|
||||
resource.errors.add(:title, 'must be longer')
|
||||
|
||||
serializer = ActiveModel::Serializer::ErrorSerializer.new(resource)
|
||||
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
|
||||
result = adapter.serializable_hash(@options)
|
||||
|
||||
expected_errors_object =
|
||||
{ :errors =>
|
||||
[
|
||||
{ :source => { :pointer => '/data/attributes/published_at' }, :detail => 'must be in the future' },
|
||||
{ :source => { :pointer => '/data/attributes/title' }, :detail => 'must be longer' }
|
||||
]
|
||||
}
|
||||
assert_equal expected_errors_object, result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -25,6 +25,7 @@ module ActiveModelSerializers
|
||||
context = Minitest::Mock.new
|
||||
context.expect(:request_url, original_url)
|
||||
context.expect(:query_parameters, query_parameters)
|
||||
context.expect(:key_transform, nil)
|
||||
@options = {}
|
||||
@options[:serialization_context] = context
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user