Skip to content

Commit

Permalink
Make serializing to different formats trivial
Browse files Browse the repository at this point in the history
By adding formats to `Serializer::MIME_TYPES` and a `to_#{format}`.
  • Loading branch information
foca committed Mar 15, 2015
1 parent 7e0286e commit 84b3796
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 13 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,36 @@ appropriate `If-Modified-Since` or `If-None-Match` headers.

[cg]: http://www.rubydoc.info/github/rack/rack/Rack/ConditionalGet

## Different Formats

Although Granola out of the box only ships with JSON serialization support, it's
easy to extend and add support for different types of serialization in case your
API needs to provide multiple formats. For example, in order to add MsgPack
support (via the [msgpack-ruby][] library), you'd do this:

``` ruby
require "msgpack"

class BaseSerializer < Granola::Serializer
MIME_TYPES[:msgpack] = "application/x-msgpack".freeze

def to_msgpack(*)
MsgPack.pack(serialized)
end
end
```

Now all serializers that inherit from `BaseSerializer` can be serialized into
MsgPack. In order to use this from our Rack helpers, you'd do:

``` ruby
granola(object, as: :msgpack)
```

This will set the correct MIME type.

[msgpack-ruby]: https://github.com/msgpack/msgpack-ruby

## License

This project is shared under the MIT license. See the attached LICENSE file for
Expand Down
10 changes: 8 additions & 2 deletions lib/granola.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class << self
class Serializer
attr_reader :object

# Public: Map of the default MIME type for each given type of serialization
# for this object.
MIME_TYPES = { json: "application/json".freeze }

# Public: Instantiates a list serializer that wraps around an iterable of
# objects of the type expected by this serializer class.
#
Expand Down Expand Up @@ -70,9 +74,11 @@ def to_json(**options)
# this will be `application/json`, but you can override in your serializers
# if your API uses a different MIME type (e.g. `application/my-app+json`).
#
# type - A Symbol describing the expected mime type.
#
# Returns a String.
def mime_type
"application/json".freeze
def mime_type(type = :json)
MIME_TYPES.fetch(type)
end
end

Expand Down
25 changes: 14 additions & 11 deletions lib/granola/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,22 @@ def self.included(base)
# object - An object to serialize into JSON.
#
# Keywords:
# with: A specific serializer class to use. If this is `nil`,
# `Helper.serializer_class_for` will be used to infer the
# serializer class.
# status: The HTTP status to return on stale responses. Defaults to
# `200`.
# headers: A Hash of default HTTP headers. Defaults to an empty Hash.
# **json_options: Any other keywords passed will be forwarded to the
# serializer's `#to_json` call.
# with: A specific serializer class to use. If this is `nil`,
# `Helper.serializer_class_for` will be used to infer the
# serializer class.
# as: A Symbol with the type of serialization desired. Defaults to
# `:json` (and it's the only one available with Granola by default)
# but could be expanded with plugins to provide serialization to,
# for example, MsgPack.
# status: The HTTP status to return on stale responses. Defaults to `200`.
# headers: A Hash of default HTTP headers. Defaults to an empty Hash.
# **opts: Any other keywords passed will be forwarded to the serializer's
# serialization backend call.
#
# Raises NameError if no specific serializer is provided and we fail to infer
# one for this object.
# Returns a Rack response tuple.
def granola(object, with: nil, status: 200, headers: {}, **json_options)
def granola(object, with: nil, status: 200, headers: {}, as: :json, **opts)
serializer = serializer_for(object, with: with)

if serializer.last_modified
Expand All @@ -46,9 +49,9 @@ def granola(object, with: nil, status: 200, headers: {}, **json_options)
headers["ETag".freeze] = Digest::MD5.hexdigest(serializer.cache_key)
end

headers["Content-Type".freeze] = serializer.mime_type
headers["Content-Type".freeze] = serializer.mime_type(as)

body = Enumerator.new { |y| y << serializer.to_json(json_options) }
body = Enumerator.new { |y| y << serializer.public_send(:"to_#{as}", opts) }

[status, headers, body]
end
Expand Down
40 changes: 40 additions & 0 deletions test/different_format_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "yaml"
require "granola/rack"

class BaseSerializer < Granola::Serializer
MIME_TYPES[:yaml] = "application/x-yaml".freeze

def to_yaml(**opts)
YAML.dump(serialized)
end
end

User = Struct.new(:name, :age)

class UserSerializer < BaseSerializer
def serialized
{ "name" => object.name, "age" => object.age }
end
end

class Context
include Granola::Rack
end

prepare do
@user = User.new("John Doe", 25)
end

setup { Context.new }

test "allows rendering as a different format" do |context|
status, headers, body = context.granola(@user, as: :yaml)

assert_equal 200, status
assert_equal "application/x-yaml", headers["Content-Type"]

assert_equal(
{ "name" => "John Doe", "age" => 25 },
YAML.load(body.to_a.first)
)
end

0 comments on commit 84b3796

Please sign in to comment.