Skip to content

Commit

Permalink
Provide key case translation
Browse files Browse the repository at this point in the history
  • Loading branch information
remear committed Mar 10, 2016
1 parent 31a30c8 commit daadf98
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 8 deletions.
4 changes: 3 additions & 1 deletion lib/action_controller/serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def use_adapter?

[:_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) {
options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options)
}
serializable_resource = get_serializer(resource, options)
super(serializable_resource, options)
end
Expand Down
12 changes: 12 additions & 0 deletions lib/active_model_serializers/adapter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ def include_meta(json)
json[meta_key] = meta if meta
json
end

def translate_key_casing!(value, serialization_context)
return value unless serialization_context
case serialization_context.key_case
when :camel
value.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
when :camel_lower
value.deep_transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
else
value
end
end
end
end
end
3 changes: 2 additions & 1 deletion lib/active_model_serializers/adapter/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ 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) }
translate_key_casing!(serialized_hash, options[:serialization_context])
end

private
Expand Down
3 changes: 2 additions & 1 deletion lib/active_model_serializers/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ def initialize(serializer, options = {})
# {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
translate_key_casing!(document, options[:serialization_context])
end

# {http://jsonapi.org/format/#document-top-level Primary data}
Expand Down
3 changes: 2 additions & 1 deletion lib/active_model_serializers/serialization_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ class << self
attr_writer :url_helpers, :default_url_options
end

attr_reader :request_url, :query_parameters
attr_reader :request_url, :query_parameters, :key_case

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_case = options.delete(:key_case) || :default
end

def self.url_helpers
Expand Down
51 changes: 51 additions & 0 deletions test/adapter/json/key_case_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require 'test_helper'

module ActiveModelSerializers
module Adapter
class Json
class KeyCaseTest < ActiveSupport::TestCase
def mock_request(key_case)
context = Minitest::Mock.new
context.expect(:request_url, URI)
context.expect(:query_parameters, {})
context.expect(:key_case, key_case)
@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_case_default
mock_request(:default)
assert_equal({
blog: { id: 1, special_attribute: "neat", articles: nil }
}, @adapter.serializable_hash(@options))
end

def test_key_case_camel
mock_request(:camel)
assert_equal({
Blog: { Id: 1, SpecialAttribute: "neat", Articles: nil }
}, @adapter.serializable_hash(@options))
end

def test_key_case_camel_lower
mock_request(:camel_lower)
assert_equal({
blog: { id: 1, specialAttribute: "neat", articles: nil }
}, @adapter.serializable_hash(@options))
end
end
end
end
end
234 changes: 234 additions & 0 deletions test/adapter/json_api/key_case_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
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_case = :default)
context = Minitest::Mock.new
context.expect(:request_url, URI)
context.expect(:query_parameters, {})
context.expect(:key_case, key_case)
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_key_case_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_key_case_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_key_case_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_case_default
mock_request(:default)

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_case_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_case_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
8 changes: 4 additions & 4 deletions test/adapter/json_api/links_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_toplevel_links
stuff: 'value'
}
}
}).serializable_hash
}).serializable_hash(@options)
expected = {
self: {
href: 'http://example.com/posts',
Expand All @@ -57,7 +57,7 @@ def test_nil_toplevel_links
@post,
adapter: :json_api,
links: nil
).serializable_hash
).serializable_hash(@options)
refute hash.key?(:links), 'No links key to be output'
end

Expand All @@ -66,12 +66,12 @@ def test_nil_toplevel_links_json_adapter
@post,
adapter: :json,
links: nil
).serializable_hash
).serializable_hash(@options)
refute hash.key?(:links), 'No links key to be output'
end

def test_resource_links
hash = serializable(@author, adapter: :json_api).serializable_hash
hash = serializable(@author, adapter: :json_api).serializable_hash(@options)
expected = {
self: {
href: 'http://example.com/link_author/1337',
Expand Down
1 change: 1 addition & 0 deletions test/adapter/json_api/pagination_links_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def mock_request(query_parameters = {}, original_url = URI)
context = Minitest::Mock.new
context.expect(:request_url, original_url)
context.expect(:query_parameters, query_parameters)
context.expect(:key_case, :default)
@options = {}
@options[:serialization_context] = context
end
Expand Down

0 comments on commit daadf98

Please sign in to comment.