granola 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -13
- data/lib/granola.rb +3 -116
- data/lib/granola/caching.rb +1 -1
- data/lib/granola/rack.rb +49 -47
- data/lib/granola/version.rb +1 -1
- metadata +2 -3
- data/lib/granola/helper.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b87c4322ddf8aa97dd49c26607172fe2119d681
|
4
|
+
data.tar.gz: 959742990805ff8890d821177d1c70c5804e5c18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cbdca00522d796177eaa836f822355fbda7959033b71a1eefaea8ceac9dcf48517fd8395c628cf3ab137fa30433d8e66e7a063eb74a42c88be8560caac63251
|
7
|
+
data.tar.gz: 05a3665f2b5e13385c014de61e6a213520a339ae162159ca4e86f5e753dc506c2ad2d02016119aa016198ae25b5fa56ffac28ec9e6f5ee5f0f08f1f208ba4240
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
# Granola, a JSON serializer [![Build Status](https://
|
1
|
+
# Granola, a JSON serializer [![Build Status](https://img.shields.io/travis/foca/granola.svg)](https://travis-ci.org/foca/granola) [![RubyGem](https://img.shields.io/gem/v/granola.svg)](https://rubygems.org/gems/granola)
|
2
2
|
|
3
3
|
![A tasty bowl of Granola](https://cloud.githubusercontent.com/assets/437/4827156/9e8d33da-5f76-11e4-8574-7803e84845f2.JPG)
|
4
4
|
|
5
|
-
|
6
5
|
Granola aims to provide a simple interface to generate JSON responses based on
|
7
6
|
your application's domain models. It doesn't make assumptions about anything and
|
8
7
|
gets out of your way. You just write plain ruby.
|
@@ -11,7 +10,7 @@ gets out of your way. You just write plain ruby.
|
|
11
10
|
|
12
11
|
``` ruby
|
13
12
|
class PersonSerializer < Granola::Serializer
|
14
|
-
def
|
13
|
+
def data
|
15
14
|
{
|
16
15
|
"name" => object.name,
|
17
16
|
"email" => object.email,
|
@@ -38,7 +37,9 @@ Granola.json = Yajl::Encoder.method(:encode)
|
|
38
37
|
```
|
39
38
|
|
40
39
|
If your project already uses [MultiJson][] then we will default to whatever it's
|
41
|
-
using, so you shouldn't worry.
|
40
|
+
using, so you shouldn't worry. Be warned that using MultiJson instead of
|
41
|
+
using a library (such as Yajl) straight away incurs a small performance penalty
|
42
|
+
(see, and run, [the benchmark](./benchmarks/multi_json.rb)).
|
42
43
|
|
43
44
|
[Yajl]: https://github.com/brianmario/yajl-ruby
|
44
45
|
[MultiJson]: https://github.com/intridea/multi_json
|
@@ -63,10 +64,6 @@ granola(person) #=> This will infer PersonSerializer from a Person instance
|
|
63
64
|
granola(person, with: AnotherSerializer)
|
64
65
|
```
|
65
66
|
|
66
|
-
*NOTE* The method relies on being an `env` method that returns the Rack
|
67
|
-
environment Hash in the same context where you call the method. See [the
|
68
|
-
documentation](./lib/granola/rack.rb) for further details.
|
69
|
-
|
70
67
|
This method returns a Rack response tuple that you can use like so (this example
|
71
68
|
uses [Cuba][], but similar code will work for other frameworks):
|
72
69
|
|
@@ -99,7 +96,7 @@ generating the JSON response altogether. For example, using Cuba:
|
|
99
96
|
|
100
97
|
``` ruby
|
101
98
|
class UserSerializer < Granola::Serializer
|
102
|
-
def
|
99
|
+
def data
|
103
100
|
{ "id" => object.id, "name" => object.name, "email" => object.email }
|
104
101
|
end
|
105
102
|
|
@@ -141,12 +138,12 @@ class BaseSerializer < Granola::Serializer
|
|
141
138
|
MIME_TYPES[:msgpack] = "application/x-msgpack".freeze
|
142
139
|
|
143
140
|
def to_msgpack(*)
|
144
|
-
MsgPack.pack(
|
141
|
+
MsgPack.pack(data)
|
145
142
|
end
|
146
143
|
end
|
147
144
|
```
|
148
145
|
|
149
|
-
Now all serializers that inherit from `BaseSerializer` can be
|
146
|
+
Now all serializers that inherit from `BaseSerializer` can be data into
|
150
147
|
MsgPack. In order to use this from our Rack helpers, you'd do:
|
151
148
|
|
152
149
|
``` ruby
|
@@ -159,5 +156,7 @@ This will set the correct MIME type.
|
|
159
156
|
|
160
157
|
## License
|
161
158
|
|
162
|
-
This project is shared under the MIT license. See the attached LICENSE file
|
163
|
-
details.
|
159
|
+
This project is shared under the MIT license. See the attached [LICENSE][] file
|
160
|
+
for details.
|
161
|
+
|
162
|
+
[LICENSE]: ./LICENSE
|
data/lib/granola.rb
CHANGED
@@ -1,117 +1,4 @@
|
|
1
1
|
require "granola/version"
|
2
|
-
require "
|
3
|
-
|
4
|
-
|
5
|
-
class << self
|
6
|
-
# Public: Get/Set a Proc that takes an Object and a Hash of options and
|
7
|
-
# returns a JSON String.
|
8
|
-
#
|
9
|
-
# The default implementation uses the standard library's JSON module, but
|
10
|
-
# you're welcome to swap it out.
|
11
|
-
#
|
12
|
-
# Example:
|
13
|
-
#
|
14
|
-
# require "yajl"
|
15
|
-
# Granola.json = ->(obj, **opts) { Yajl::Encoder.encode(obj, opts) }
|
16
|
-
attr_accessor :json
|
17
|
-
end
|
18
|
-
|
19
|
-
if defined?(MultiJson)
|
20
|
-
self.json = MultiJson.method(:dump)
|
21
|
-
else
|
22
|
-
self.json = JSON.method(:generate)
|
23
|
-
end
|
24
|
-
|
25
|
-
# A Serializer describes how to serialize a certain type of object, by
|
26
|
-
# declaring the structure of JSON objects.
|
27
|
-
class Serializer
|
28
|
-
attr_reader :object
|
29
|
-
|
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
|
-
|
34
|
-
# Public: Instantiates a list serializer that wraps around an iterable of
|
35
|
-
# objects of the type expected by this serializer class.
|
36
|
-
#
|
37
|
-
# Example:
|
38
|
-
#
|
39
|
-
# serializer = PersonSerializer.list(people)
|
40
|
-
# serializer.to_json
|
41
|
-
#
|
42
|
-
# Returns a Granola::List.
|
43
|
-
def self.list(ary, *args)
|
44
|
-
List.new(ary, *args, with: self)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Public: Initialize the serializer with a given object.
|
48
|
-
#
|
49
|
-
# object - The domain model that we want to serialize into JSON.
|
50
|
-
def initialize(object)
|
51
|
-
@object = object
|
52
|
-
end
|
53
|
-
|
54
|
-
# Public: Returns a primitive Object that can be serialized into JSON,
|
55
|
-
# meaning one of `nil`, `true`, `false`, a String, a Numeric, an Array of
|
56
|
-
# primitive objects, or a Hash with String keys and primitive objects as
|
57
|
-
# values.
|
58
|
-
#
|
59
|
-
# Raises NotImplementedError unless you override in subclasses.
|
60
|
-
def serialized
|
61
|
-
fail NotImplementedError
|
62
|
-
end
|
63
|
-
|
64
|
-
# Public: Generate the JSON String.
|
65
|
-
#
|
66
|
-
# **options - Any options to be passed to the `Granola.json` Proc.
|
67
|
-
#
|
68
|
-
# Returns a String.
|
69
|
-
def to_json(**options)
|
70
|
-
Granola.json.(serialized, options)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Public: Returns the MIME type generated by this serializer. By default
|
74
|
-
# this will be `application/json`, but you can override in your serializers
|
75
|
-
# if your API uses a different MIME type (e.g. `application/my-app+json`).
|
76
|
-
#
|
77
|
-
# type - A Symbol describing the expected mime type.
|
78
|
-
#
|
79
|
-
# Returns a String.
|
80
|
-
def mime_type(type = :json)
|
81
|
-
MIME_TYPES.fetch(type)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Internal: The List serializer provides an interface for serializing lists of
|
86
|
-
# objects, wrapping around a specific serializer. The preferred API for this
|
87
|
-
# is to use `Granola::Serializer.list`.
|
88
|
-
#
|
89
|
-
# Example:
|
90
|
-
#
|
91
|
-
# serializer = Granola::List.new(people, with: PersonSerializer)
|
92
|
-
# serializer.to_json
|
93
|
-
#
|
94
|
-
# You should use Serializer.list instead of this class.
|
95
|
-
class List < Serializer
|
96
|
-
# Internal: Get the serializer class to use for each item of the list.
|
97
|
-
attr_reader :item_serializer
|
98
|
-
|
99
|
-
# Public: Instantiate a new list serializer.
|
100
|
-
#
|
101
|
-
# list - An Array-like structure.
|
102
|
-
# *args - Any other arguments that the item serializer takes.
|
103
|
-
#
|
104
|
-
# Keywords:
|
105
|
-
# with: The subclass of Granola::Serializer to use when serializing
|
106
|
-
# specific elements in the list.
|
107
|
-
def initialize(list, *args, with: serializer)
|
108
|
-
@item_serializer = with
|
109
|
-
@list = list.map { |obj| @item_serializer.new(obj, *args) }
|
110
|
-
end
|
111
|
-
|
112
|
-
# Public: Returns an Array of Hashes that can be serialized into JSON.
|
113
|
-
def serialized
|
114
|
-
@list.map(&:serialized)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
2
|
+
require "granola/serializer"
|
3
|
+
require "granola/json"
|
4
|
+
require "granola/rack"
|
data/lib/granola/caching.rb
CHANGED
data/lib/granola/rack.rb
CHANGED
@@ -1,58 +1,60 @@
|
|
1
1
|
require "digest/md5"
|
2
2
|
require "time"
|
3
|
-
require "granola"
|
4
|
-
require "granola/
|
3
|
+
require "granola/serializer"
|
4
|
+
require "granola/util"
|
5
5
|
require "granola/caching"
|
6
|
+
require "granola/json"
|
6
7
|
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
module Granola
|
9
|
+
# Mixin to render JSON in the context of a Rack application. See the #json
|
10
|
+
# method for the specifics.
|
11
|
+
module Rack
|
12
|
+
# Public: Renders a JSON representation of an object using a
|
13
|
+
# Granola::Serializer. This takes care of setting the `Last-Modified` and
|
14
|
+
# `ETag` headers if appropriate.
|
15
|
+
#
|
16
|
+
# You can customize the response tuple by passing the status and the default
|
17
|
+
# headers, as in the following example:
|
18
|
+
#
|
19
|
+
# granola(user, status: 400, headers: { "X-Error" => "Boom!" })
|
20
|
+
#
|
21
|
+
# object - An object to serialize into JSON.
|
22
|
+
#
|
23
|
+
# Keywords:
|
24
|
+
# with: A specific serializer class to use. If this is `nil`,
|
25
|
+
# `Util.serializer_class_for` will be used to infer the
|
26
|
+
# serializer class.
|
27
|
+
# as: A Symbol with the type of serialization desired. Defaults to
|
28
|
+
# `:json` (and it's the only one available by default), but could
|
29
|
+
# be expanded with plugins to provide serialization to, for
|
30
|
+
# example, MsgPack.
|
31
|
+
# status: The HTTP status to return on stale responses. Defaults to `200`
|
32
|
+
# headers: A Hash of default HTTP headers. Defaults to an empty Hash.
|
33
|
+
# **opts: Any other keywords passed will be forwarded to the serializer's
|
34
|
+
# serialization backend call.
|
35
|
+
#
|
36
|
+
# Raises NameError if no specific serializer is provided and we fail to
|
37
|
+
# infer one for this object.
|
38
|
+
#
|
39
|
+
# Returns a Rack response tuple.
|
40
|
+
def granola(object, with: nil, status: 200, headers: {}, as: :json, **opts)
|
41
|
+
serializer = Granola::Util.serializer_for(object, with: with)
|
13
42
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
# You can customize the response tuple by passing the status and the default
|
19
|
-
# headers, as in the following example:
|
20
|
-
#
|
21
|
-
# granola(user, status: 400, headers: { "X-Error" => "Boom!" })
|
22
|
-
#
|
23
|
-
# object - An object to serialize into JSON.
|
24
|
-
#
|
25
|
-
# 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
|
-
# 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.
|
37
|
-
#
|
38
|
-
# Raises NameError if no specific serializer is provided and we fail to infer
|
39
|
-
# one for this object.
|
40
|
-
# Returns a Rack response tuple.
|
41
|
-
def granola(object, with: nil, status: 200, headers: {}, as: :json, **opts)
|
42
|
-
serializer = serializer_for(object, with: with)
|
43
|
+
if serializer.last_modified
|
44
|
+
headers["Last-Modified".freeze] = serializer.last_modified.httpdate
|
45
|
+
end
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
if serializer.cache_key
|
48
|
+
headers["ETag".freeze] = Digest::MD5.hexdigest(serializer.cache_key)
|
49
|
+
end
|
47
50
|
|
48
|
-
|
49
|
-
headers["ETag".freeze] = Digest::MD5.hexdigest(serializer.cache_key)
|
50
|
-
end
|
51
|
+
headers["Content-Type".freeze] = serializer.mime_type(as)
|
51
52
|
|
52
|
-
|
53
|
+
body = Enumerator.new do |yielder|
|
54
|
+
yielder << serializer.public_send(:"to_#{as}", opts)
|
55
|
+
end
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
[status, headers, body]
|
57
|
+
[status, headers, body]
|
58
|
+
end
|
57
59
|
end
|
58
60
|
end
|
data/lib/granola/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: granola
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Sanguinetti
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cutest
|
@@ -49,7 +49,6 @@ files:
|
|
49
49
|
- README.md
|
50
50
|
- lib/granola.rb
|
51
51
|
- lib/granola/caching.rb
|
52
|
-
- lib/granola/helper.rb
|
53
52
|
- lib/granola/rack.rb
|
54
53
|
- lib/granola/version.rb
|
55
54
|
homepage: http://github.com/foca/granola
|
data/lib/granola/helper.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require "granola"
|
2
|
-
|
3
|
-
module Granola::Helper
|
4
|
-
# Public: Returns the serializer object for rendering a specific object. The
|
5
|
-
# class will attempt to be inferred based on the class of the passed object,
|
6
|
-
# but a specific serializer can be passed via a keyword argument `with`.
|
7
|
-
#
|
8
|
-
# object - The Object to serialize.
|
9
|
-
#
|
10
|
-
# Keywords
|
11
|
-
# with: A specific serializer class to use. If this is `nil`,
|
12
|
-
# `Helper.serializer_class_for` will be used to infer the serializer
|
13
|
-
# class. This is ignored if `object` is already a Granola::Serializer.
|
14
|
-
#
|
15
|
-
# Raises NameError if no specific serializer is provided and we fail to infer
|
16
|
-
# one for this object.
|
17
|
-
# Returns an instance of a Granola::Serializer subclass.
|
18
|
-
def serializer_for(object, with: nil)
|
19
|
-
return object if Granola::Serializer === object
|
20
|
-
serializer_class = with || Granola::Helper.serializer_class_for(object)
|
21
|
-
method = object.respond_to?(:to_ary) ? :list : :new
|
22
|
-
serializer_class.send(method, object)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Internal: Infers the name of a serialized based on the class of the passed
|
26
|
-
# object. The pattern is the Object's class + "Serializer". So
|
27
|
-
# `PersonSerializer` for `Person`.
|
28
|
-
#
|
29
|
-
# object - An object of a class with a matching serializer.
|
30
|
-
#
|
31
|
-
# Raises NameError if no matching class exists.
|
32
|
-
# Returns a Class.
|
33
|
-
def self.serializer_class_for(object)
|
34
|
-
object = object.respond_to?(:to_ary) ? object.to_ary.fetch(0, nil) : object
|
35
|
-
name = object.class.name
|
36
|
-
Object.const_get("%sSerializer" % name)
|
37
|
-
rescue NameError
|
38
|
-
case object
|
39
|
-
when NilClass, TrueClass, FalseClass, Numeric, String
|
40
|
-
PrimitiveTypesSerializer
|
41
|
-
else
|
42
|
-
raise
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Internal: Null serializer that transparently handles rendering `nil` in case
|
47
|
-
# it's passed.
|
48
|
-
class PrimitiveTypesSerializer < Granola::Serializer
|
49
|
-
def serialized
|
50
|
-
object
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|