granola 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf1c567fa58a115fc7f8d55fa10ca0e0d482baa9
4
- data.tar.gz: a6c5f64bf66e7445fc3f11c48ff046d66fd701c6
3
+ metadata.gz: 6b87c4322ddf8aa97dd49c26607172fe2119d681
4
+ data.tar.gz: 959742990805ff8890d821177d1c70c5804e5c18
5
5
  SHA512:
6
- metadata.gz: a6918a2d025d20e08ea0f2756adafaa5d0dbf5134a2ad05059edfff3eba26e31d36ffbd7d0ae72a394243edad1d125317b09b40e4eb02aeba0fd31a2eb3e3e55
7
- data.tar.gz: 2117365b5ee8f946ab307a6d2020d8beb0173d06adb26fd3e974272cfa95a8911f348ca5e78023f9c0b1755c6e21fceb332f45d76bb87a947050eae907e63284
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://travis-ci.org/foca/granola.svg?branch=master)](https://travis-ci.org/foca/granola)
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 serialized
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 serialized
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(serialized)
141
+ MsgPack.pack(data)
145
142
  end
146
143
  end
147
144
  ```
148
145
 
149
- Now all serializers that inherit from `BaseSerializer` can be serialized into
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 for
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 "json"
3
-
4
- module Granola
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"
@@ -1,4 +1,4 @@
1
- require "granola"
1
+ require "granola/serializer"
2
2
 
3
3
  module Granola
4
4
  # Mixin to add caching-awareness to Serializers.
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/helper"
3
+ require "granola/serializer"
4
+ require "granola/util"
5
5
  require "granola/caching"
6
+ require "granola/json"
6
7
 
7
- # Mixin to render JSON in the context of a Rack application. See the #json
8
- # method for the specifics.
9
- module Granola::Rack
10
- def self.included(base)
11
- base.send(:include, Granola::Helper)
12
- end
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
- # Public: Renders a JSON representation of an object using a
15
- # Granola::Serializer. This takes care of setting the `Last-Modified` and
16
- # `ETag` headers if appropriate.
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
- if serializer.last_modified
45
- headers["Last-Modified".freeze] = serializer.last_modified.httpdate
46
- end
47
+ if serializer.cache_key
48
+ headers["ETag".freeze] = Digest::MD5.hexdigest(serializer.cache_key)
49
+ end
47
50
 
48
- if serializer.cache_key
49
- headers["ETag".freeze] = Digest::MD5.hexdigest(serializer.cache_key)
50
- end
51
+ headers["Content-Type".freeze] = serializer.mime_type(as)
51
52
 
52
- headers["Content-Type".freeze] = serializer.mime_type(as)
53
+ body = Enumerator.new do |yielder|
54
+ yielder << serializer.public_send(:"to_#{as}", opts)
55
+ end
53
56
 
54
- body = Enumerator.new { |y| y << serializer.public_send(:"to_#{as}", opts) }
55
-
56
- [status, headers, body]
57
+ [status, headers, body]
58
+ end
57
59
  end
58
60
  end
@@ -1,3 +1,3 @@
1
1
  module Granola
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
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.9.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-15 00:00:00.000000000 Z
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
@@ -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