Skip to content

Commit 84b3796

Browse files
committed
Make serializing to different formats trivial
By adding formats to `Serializer::MIME_TYPES` and a `to_#{format}`.
1 parent 7e0286e commit 84b3796

File tree

4 files changed

+92
-13
lines changed

4 files changed

+92
-13
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,36 @@ appropriate `If-Modified-Since` or `If-None-Match` headers.
127127

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

130+
## Different Formats
131+
132+
Although Granola out of the box only ships with JSON serialization support, it's
133+
easy to extend and add support for different types of serialization in case your
134+
API needs to provide multiple formats. For example, in order to add MsgPack
135+
support (via the [msgpack-ruby][] library), you'd do this:
136+
137+
``` ruby
138+
require "msgpack"
139+
140+
class BaseSerializer < Granola::Serializer
141+
MIME_TYPES[:msgpack] = "application/x-msgpack".freeze
142+
143+
def to_msgpack(*)
144+
MsgPack.pack(serialized)
145+
end
146+
end
147+
```
148+
149+
Now all serializers that inherit from `BaseSerializer` can be serialized into
150+
MsgPack. In order to use this from our Rack helpers, you'd do:
151+
152+
``` ruby
153+
granola(object, as: :msgpack)
154+
```
155+
156+
This will set the correct MIME type.
157+
158+
[msgpack-ruby]: https://github.com/msgpack/msgpack-ruby
159+
130160
## License
131161

132162
This project is shared under the MIT license. See the attached LICENSE file for

lib/granola.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class << self
2727
class Serializer
2828
attr_reader :object
2929

30+
# Public: Map of the default MIME type for each given type of serialization
31+
# for this object.
32+
MIME_TYPES = { json: "application/json".freeze }
33+
3034
# Public: Instantiates a list serializer that wraps around an iterable of
3135
# objects of the type expected by this serializer class.
3236
#
@@ -70,9 +74,11 @@ def to_json(**options)
7074
# this will be `application/json`, but you can override in your serializers
7175
# if your API uses a different MIME type (e.g. `application/my-app+json`).
7276
#
77+
# type - A Symbol describing the expected mime type.
78+
#
7379
# Returns a String.
74-
def mime_type
75-
"application/json".freeze
80+
def mime_type(type = :json)
81+
MIME_TYPES.fetch(type)
7682
end
7783
end
7884

lib/granola/rack.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,22 @@ def self.included(base)
2323
# object - An object to serialize into JSON.
2424
#
2525
# Keywords:
26-
# with: A specific serializer class to use. If this is `nil`,
27-
# `Helper.serializer_class_for` will be used to infer the
28-
# serializer class.
29-
# status: The HTTP status to return on stale responses. Defaults to
30-
# `200`.
31-
# headers: A Hash of default HTTP headers. Defaults to an empty Hash.
32-
# **json_options: Any other keywords passed will be forwarded to the
33-
# serializer's `#to_json` call.
26+
# with: A specific serializer class to use. If this is `nil`,
27+
# `Helper.serializer_class_for` will be used to infer the
28+
# serializer class.
29+
# as: A Symbol with the type of serialization desired. Defaults to
30+
# `:json` (and it's the only one available with Granola by default)
31+
# but could be expanded with plugins to provide serialization to,
32+
# for example, MsgPack.
33+
# status: The HTTP status to return on stale responses. Defaults to `200`.
34+
# headers: A Hash of default HTTP headers. Defaults to an empty Hash.
35+
# **opts: Any other keywords passed will be forwarded to the serializer's
36+
# serialization backend call.
3437
#
3538
# Raises NameError if no specific serializer is provided and we fail to infer
3639
# one for this object.
3740
# Returns a Rack response tuple.
38-
def granola(object, with: nil, status: 200, headers: {}, **json_options)
41+
def granola(object, with: nil, status: 200, headers: {}, as: :json, **opts)
3942
serializer = serializer_for(object, with: with)
4043

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

49-
headers["Content-Type".freeze] = serializer.mime_type
52+
headers["Content-Type".freeze] = serializer.mime_type(as)
5053

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

5356
[status, headers, body]
5457
end

test/different_format_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require "yaml"
2+
require "granola/rack"
3+
4+
class BaseSerializer < Granola::Serializer
5+
MIME_TYPES[:yaml] = "application/x-yaml".freeze
6+
7+
def to_yaml(**opts)
8+
YAML.dump(serialized)
9+
end
10+
end
11+
12+
User = Struct.new(:name, :age)
13+
14+
class UserSerializer < BaseSerializer
15+
def serialized
16+
{ "name" => object.name, "age" => object.age }
17+
end
18+
end
19+
20+
class Context
21+
include Granola::Rack
22+
end
23+
24+
prepare do
25+
@user = User.new("John Doe", 25)
26+
end
27+
28+
setup { Context.new }
29+
30+
test "allows rendering as a different format" do |context|
31+
status, headers, body = context.granola(@user, as: :yaml)
32+
33+
assert_equal 200, status
34+
assert_equal "application/x-yaml", headers["Content-Type"]
35+
36+
assert_equal(
37+
{ "name" => "John Doe", "age" => 25 },
38+
YAML.load(body.to_a.first)
39+
)
40+
end

0 commit comments

Comments
 (0)