jsend_wrapper-rails 0.1.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.
data/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # Rails JSend Wrapper
2
+
3
+ ## Usage
4
+
5
+ Add the following lines to your Gemfile:
6
+
7
+ ```ruby
8
+ gem 'jsend_wrapper-rails'
9
+ gem 'jbuilder' # optional
10
+ ```
11
+
12
+ ### render jsend: ...
13
+
14
+ ```ruby
15
+ # Success
16
+ render jsend: @object
17
+ render jsend: { success: @object }
18
+
19
+ # Fail
20
+ render jsend: { fail: 'too bad' }
21
+
22
+ # Error
23
+ render jsend: { error: 'too bad' }
24
+ render jsend: { error: 'too bad', code: 123 }
25
+ render jsend: { error: 'too bad', data: @object }
26
+ render jsend: { error: 'too bad', code: 123, data: @object }
27
+ ```
28
+
29
+ The elements `code` and `data` are optional for JSend Error containers. If you
30
+ leave them out, they will be absent from the rendered JSON. Note the differences
31
+ in these two examples:
32
+
33
+ `render jsend: {error: 'too bad'}`
34
+
35
+ **Result**:
36
+ ```json
37
+ {
38
+ "status": "error",
39
+ "message": "too bad"
40
+ }
41
+ ```
42
+
43
+ `render jsend: {error: 'too bad', data: nil}`
44
+
45
+ **Result**:
46
+ ```json
47
+ {
48
+ "status": "error",
49
+ "message": "too bad",
50
+ "data": null
51
+ }
52
+ ```
53
+
54
+
55
+ ### Templates (optional)
56
+
57
+ If [JBuilder](https://rubygems.org/gems/jbuilder) is available, you can create
58
+ view files with a `.jsend` (example: `app/views/posts/show.json.jsend`)
59
+ extension. These views are rendered by `Jbuilder`, exactly as if they were
60
+ named with a `.jbuilder` extension instead. These views will be wrapped in a
61
+ JSend Success container.
62
+
63
+ **Example**: `app/views/posts/show.json.jsend`
64
+ ```ruby
65
+ json.title 'The Pragmatic Programmer'
66
+ json.year 1999
67
+ ```
68
+
69
+ **Result**:
70
+ ```json
71
+ {
72
+ "status": "success",
73
+ "data": {
74
+ "title": "The Pragmatic Programmer",
75
+ "year": 1999
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Tips
81
+
82
+ #### Handling Errors
83
+
84
+ You can use `rescue_from` to automatically handle errors:
85
+
86
+ ```ruby
87
+ rescue_from ActiveRecord::RecordNotFound do
88
+ render status: :not_found, jsend: {
89
+ error: 'record not found', code: 404, data: { id: params[:id] } }
90
+ end
91
+ ```
92
+
93
+ **Example**: `GET /posts/1234.json`:
94
+ ```json
95
+ {
96
+ "status": "error",
97
+ "message": "record not found",
98
+ "code": 404,
99
+ "data": {
100
+ "id": "1234"
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## JSend Spec
106
+
107
+ * **What?** - Put simply, JSend is a specification that lays down some rules
108
+ for how [JSON](http://json.org) responses from web servers should be
109
+ formatted. JSend focuses on application-level (as opposed to protocol- or
110
+ transport-level) messaging which makes it ideal for use in
111
+ [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer)-style
112
+ applications and APIs.
113
+ * **Why?** - There are lots of web services out there providing JSON data,
114
+ and each has its own way of formatting responses. Also, developers writing
115
+ for JavaScript front-ends continually re-invent the wheel on communicating
116
+ data from their servers. While there are many common patterns for
117
+ structuring this data, there is no consistency in things like naming or types
118
+ of responses. Also, this helps promote happiness and unity between backend
119
+ developers and frontend designers, as everyone can come to expect a common
120
+ approach to interacting with one another.
121
+ * **Hold on now, aren't there already specs for this kind of thing?** -
122
+ Well... no. While there are a few handy specifications for dealing with JSON
123
+ data, most notably [Douglas Crockford](http://www.crockford.com/)'s
124
+ [JSONRequest](http://www.json.org/JSONRequest.html) proposal, there's nothing
125
+ to address the problems of general application-level messaging. More on this
126
+ later.
127
+ * **(Why) Should I care?** - If you're a library or framework developer, this
128
+ gives you a consistent format which your users are more likely to already be
129
+ familiar with, which means they'll already know how to consume and interact
130
+ with your code. If you're a web app developer, you won't have to think about
131
+ how to structure the JSON data in your application, and you'll have existing
132
+ reference implementations to get you up and running quickly.
133
+ * **Discuss** - [Mailing list](http://lists.omniti.com/mailman/listinfo/jsend-users)
134
+
135
+ ### So how's it work?
136
+
137
+ A basic JSend-compliant response is as simple as this:
138
+ ```json
139
+ {
140
+ "status": "success",
141
+ "data": {
142
+ "post": {
143
+ "id": 1,
144
+ "title": "A blog post",
145
+ "body": "Some useful content"
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ When setting up a JSON API, you'll have all kinds of different types of calls
152
+ and responses. JSend separates responses into some basic types, and defines
153
+ required and optional keys for each type:
154
+
155
+ <table>
156
+ <thead>
157
+ <tr>
158
+ <th>Type</th>
159
+ <th>Description</th>
160
+ <th>Required Keys</th>
161
+ <th>Optional Keys</th>
162
+ </tr>
163
+ </thead>
164
+ <tbody>
165
+ <tr>
166
+ <td>success</td>
167
+ <td>All went well, and (usually) some data was returned.</td>
168
+ <td>status, data</td>
169
+ <td></td>
170
+ </tr>
171
+ <tr>
172
+ <td>fail</td>
173
+ <td>
174
+ There was a problem with the data submitted, or some pre-condition of
175
+ the API call wasn't satisfied.
176
+ </td>
177
+ <td>status, message</td>
178
+ <td></td>
179
+ </tr>
180
+ <tr>
181
+ <td>error</td>
182
+ <td>
183
+ An error occurred in processing the request, i.e. an exception was
184
+ thrown.
185
+ </td>
186
+ <td>status, message</td>
187
+ <td>code, data</td>
188
+ </tr>
189
+ </tbody>
190
+ </table>
191
+
192
+ ### Example response types
193
+
194
+ **Success**: When an API call is successful, the JSend object is used as a
195
+ simple envelope for the results, using the {{{data}}} key, as in the following:
196
+
197
+ **`GET /posts.json`:**
198
+ ```json
199
+ {
200
+ "status": "success",
201
+ "data": [
202
+ {
203
+ "id": 1,
204
+ "title": "A blog post",
205
+ "body": "Some useful content"
206
+ },
207
+ {
208
+ "id": 2,
209
+ "title": "Another blog post",
210
+ "body": "More content"
211
+ },
212
+ ]
213
+ }
214
+ ```
215
+
216
+ **`GET /posts/2.json`:**
217
+ ```json
218
+ {
219
+ "status": "success",
220
+ "data": {
221
+ "id": 2,
222
+ "title": "Another blog post",
223
+ "body": "More content"
224
+ }
225
+ }
226
+ ```
227
+
228
+ **`DELETE /posts/2.json`:**
229
+ ```json
230
+ {
231
+ "status": "success",
232
+ "data": null
233
+ }
234
+ ```
235
+
236
+ Required keys:
237
+ * status: Should always be set to "success".
238
+ * data: Acts as the wrapper for any data returned by the API call. If the call
239
+ returns no data (as in the last example), data should be set to null.
240
+
241
+ **Fail**: When an API call is rejected due to invalid data or call conditions,
242
+ the JSend object's data key contains an object explaining what went wrong,
243
+ typically a hash of validation errors. For example:
244
+
245
+ **`POST /posts.json`** (with data `body: "Trying to creating a blog post"`):
246
+ ```json
247
+ {
248
+ "status": "fail",
249
+ "data": {
250
+ "title": "A title is required"
251
+ }
252
+ }
253
+ ```
254
+
255
+ Required keys:
256
+ * status: Should always be set to "fail".
257
+ * data: Again, provides the wrapper for the details of why the request failed.
258
+ If the reasons for failure correspond to `POST` values, the response
259
+ object's keys *should* correspond to those `POST` values.
260
+
261
+ **Error**: When an API call fails due to an error on the server. For example:
262
+
263
+ **`GET /posts.json`**:
264
+ ```json
265
+ {
266
+ "status" : "error",
267
+ "message" : "A title is required"
268
+ }
269
+ ```
270
+
271
+ Required keys:
272
+ * status: Should always be set to "error".
273
+ * message: A meaningful, end-user-readable (or at the least log-worthy)
274
+ message, explaining what went wrong.
275
+
276
+ Optional keys:
277
+ * code: A numeric code corresponding to the error, if applicable
278
+ * data: A generic container for any other information about the error, i.e. the
279
+ conditions that caused the error, stack traces, etc.
280
+
281
+ ### Whither HTTP?
282
+
283
+ But wait, you ask, doesn't HTTP already provide a way to communicate response
284
+ statuses? Why yes, astute reader, it does. So how does the notion of
285
+ indicating response status in the message body fit within the context of HTTP?
286
+ Two things:
287
+ * The official HTTP spec has 41 status codes, and there are many
288
+ interpretations on how to use each one. JSend, on the other hand, defines a
289
+ more constrained set of status codes, specifically related to handling JSON
290
+ traffic in the context of a dynamic web UI.
291
+ * The spec is meant to be as small, constrained, and generally-applicable as
292
+ possible. As such, it has to be somewhat self-contained. A common pattern
293
+ for implementing JSON services is to load a JavaScript file which passes a
294
+ JSON block into a user-specified callback. JSON-over-XHR handling in many
295
+ JavaScript frameworks follows similar patterns. As such, the end-user
296
+ (developer) never has a chance to access the HTTP response itself.
297
+
298
+ So where does that leave us? Accounting for deficiencies in the status quo does
299
+ not negate the usefulness of HTTP compliance. Therefore it is advised that
300
+ server-side developers use both: provide a JSend response body, and whatever
301
+ HTTP header(s) are most appropriate to the corresponding body.
302
+
303
+ ### License
304
+
305
+ The JSend specification (this page) is covered under a
306
+ [License modified BSD License](http://labs.omniti.com/labs/jsend/wiki/License)
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require 'rubygems'
17
+ require 'bundler'
18
+ require 'rake'
19
+ require 'jeweler'
20
+
21
+ require_relative 'lib/jsend_wrapper/version'
22
+
23
+ begin
24
+ Bundler.setup :default, :development
25
+ rescue Bundler::BundlerError => e
26
+ $stderr.puts e.message
27
+ $stderr.puts 'Run `bundle install` to install missing gems'
28
+ exit e.status_code
29
+ end
30
+
31
+ Jeweler::Tasks.new do |gem|
32
+ gem.name = 'jsend_wrapper-rails'
33
+ gem.version = JsendWrapper::Version.to_s
34
+ gem.homepage = 'http://github.com/sangster/jsend_wrapper-rails'
35
+ gem.license = 'GPL 3'
36
+ gem.summary = 'Wraps JSON in a JSend envelope'
37
+ gem.description = <<-EOF.strip.gsub /\s+/, ' '
38
+ Some description here.
39
+ EOF
40
+ gem.email = 'jon@ertt.ca'
41
+ gem.authors = ['Jon Sangster']
42
+
43
+ gem.files = Dir['{lib}/**/*', 'Rakefile', 'README.md', 'LICENSE']
44
+ gem.files.exclude 'lib/jsend_wrapper/version.rb'
45
+ end
46
+
47
+ Jeweler::RubygemsDotOrgTasks.new
48
+
49
+ task :pry do
50
+ exec 'pry --gem'
51
+ end
52
+
53
+ task bump: ['bump:patch']
54
+ namespace :bump do
55
+ [:major, :minor, :patch].each do |part|
56
+ bumper = JsendWrapper::Version::Bumper.new part
57
+ desc "Bump the version to #{bumper}"
58
+ task(part) { bumper.bump }
59
+ end
60
+ end
61
+
62
+ # RSpec
63
+ require 'rspec/core/rake_task'
64
+ RSpec::Core::RakeTask.new :spec
65
+ task default: :spec
@@ -0,0 +1,58 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require 'rails/railtie'
17
+
18
+ module JsendWrapper
19
+ module Rails
20
+ class Railtie < ::Rails::Railtie
21
+ initializer 'jsend_wrapper-rails.initialization' do
22
+ if JsendWrapper::Rails.jbuilder_available?
23
+ JsendWrapper::Rails.install_template
24
+ end
25
+ JsendWrapper::Rails.install_render_option
26
+ end
27
+ end
28
+
29
+
30
+ #@return [Boolean] true if the {Jbuilder} gem is available
31
+ def self.jbuilder_available?
32
+ require 'jbuilder'
33
+ true
34
+ rescue LoadError
35
+ STDERR.puts 'WARN: Please include the "jbuilder" gem for .jsend templates'
36
+ false
37
+ end
38
+
39
+ # Install a "template handler" for .jsend view files. These files will be
40
+ # processed with {Jbuilder}, just like .jbuilder view files, but the result
41
+ # will be wrapped in a "success" JSend wrapper.
42
+ def self.install_template
43
+ ActionView::Template.register_template_handler \
44
+ :jsend, JsendWrapper::Rails::TemplateHandler
45
+ end
46
+
47
+
48
+ # Adds the "jsend:" option to {ActiveController::Base#render}
49
+ def self.install_render_option
50
+ require_relative 'render_option'
51
+
52
+ ActionController::Renderers.add :jsend do |value, _|
53
+ self.content_type ||= Mime::JSON
54
+ RenderOption.new(value).render
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,76 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative '../renderers'
17
+
18
+ module JsendWrapper
19
+ # Parses the "render jsend: {...}" command. Valid forms:
20
+ class RenderOption
21
+ VALID_TYPES = [:success, :fail, :error]
22
+ VALID_OPTIONS = [:data, :code, :message]
23
+ VALID_KEYS = VALID_TYPES + VALID_OPTIONS
24
+
25
+ #@param obj [Hash,Object] Examples:
26
+ # render jsend: @object
27
+ # render jsend: {success: @object}
28
+ # render jsend: {fail: @object}
29
+ # render jsend: {error: @object}
30
+ # render jsend: {error: @object, code: 123}
31
+ # render jsend: {error: @object, data: @another_object}
32
+ # render jsend: {error: @object, code: 123, data: @another_object}
33
+ def initialize(obj)
34
+ @hash = normalize obj
35
+ end
36
+
37
+
38
+ #@return [String] A string containing the rendered JSON
39
+ def render
40
+ renderer.call
41
+ end
42
+
43
+
44
+ #@return [Renderer] One of {SuccessRenderer}, {FailRenderer}, or
45
+ # {ErrorRenderer}
46
+ def renderer
47
+ @renderer ||=
48
+ if @hash.key? :success
49
+ SuccessRenderer.new @hash[:success]
50
+ elsif @hash.key? :fail
51
+ FailRenderer.new @hash[:fail]
52
+ elsif @hash.key? :error
53
+ ErrorRenderer.new @hash[:error], @hash.slice(:code, :data)
54
+ else
55
+ raise 'render jsend:{...} must include "success:", "fail:", or "error:"'
56
+ end
57
+ end
58
+
59
+
60
+ private
61
+
62
+
63
+ def normalize(obj)
64
+ if Hash === obj && jsend_hash?(obj)
65
+ obj
66
+ else
67
+ {success: obj}
68
+ end
69
+ end
70
+
71
+
72
+ def jsend_hash?(hash)
73
+ hash.keys.all? { |key| VALID_KEYS.include? key }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,43 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ module JsendWrapper
17
+ module Rails
18
+ class TemplateHandler
19
+ def self.call(template)
20
+ json_handler = ActionView::Template.registered_template_handler :jbuilder
21
+ json = json_handler.call template
22
+
23
+ <<-RUBY
24
+ content = instance_eval #{json.inspect}
25
+ JsendWrapper::Handlers::Success.new(self).render content
26
+ RUBY
27
+ end
28
+
29
+
30
+ #@param view [ActiveView::Base]
31
+ def initialize(view)
32
+ @view = view
33
+ end
34
+
35
+
36
+ #@param json [String]
37
+ def render(json, *)
38
+ json = 'null' if !json || json.empty?
39
+ %[{"status":"success","data":#{json}}]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative 'renderer'
17
+
18
+ module JsendWrapper
19
+ # Wraps the given message in a JSend Error. JSend Errors have two required
20
+ # elements (status, message) and two optional elements (code, data).
21
+ class ErrorRenderer < Renderer
22
+ attr_accessor :message, :has_code, :code, :has_data, :data
23
+
24
+ alias_method :code?, :has_code
25
+ alias_method :data?, :has_data
26
+
27
+ #@param message [#to_s]
28
+ #@param optional [Hash] the optional elements
29
+ #@option optional [#to_i] :code a numeric code representing the error
30
+ #@option optional [Object] :data a generic container for any other
31
+ # information about the error
32
+ def initialize(message, optional)
33
+ self.message = message.to_s
34
+ self.has_code = optional.key? :code
35
+ self.has_data = optional.key? :data
36
+
37
+ self.code = parse_code optional[:code] if code?
38
+ self.data = optional[:data] if data?
39
+ end
40
+
41
+
42
+ #@return [String] the rendered JSON
43
+ def call
44
+ %[{"status":"error","message":#{message.to_json}#{optional}}]
45
+ end
46
+
47
+
48
+ private
49
+
50
+
51
+ def parse_code(code)
52
+ if code.respond_to? :to_i
53
+ code.to_i
54
+ else
55
+ raise '"code" must respond to #to_i'
56
+ end
57
+ end
58
+
59
+
60
+ def optional
61
+ ''.tap do |json|
62
+ json << ',"code":' << code.to_s if code?
63
+ json << ',"data":' << json_string(data) if data?
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ require_relative 'renderer'
17
+
18
+ module JsendWrapper
19
+ # Wraps the given message in a JSend Failure. JSend Failures have two required
20
+ # elements (status, data).
21
+ class FailRenderer < Renderer
22
+ attr_accessor :data
23
+
24
+ def initialize(obj)
25
+ self.data = obj
26
+ end
27
+
28
+ def call
29
+ %[{"status":"fail","data":#{json_string data}}]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # jsend_wrapper-rails: Wrap JSON views in JSend containers
2
+ # Copyright (C) 2014 Jon Sangster
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ module JsendWrapper
17
+ class Renderer
18
+ protected
19
+
20
+ def json_string(obj)
21
+ if obj.respond_to? :to_json
22
+ obj.to_json
23
+ elsif obj.nil?
24
+ 'null'
25
+ else
26
+ obj.to_s.to_json
27
+ end
28
+ end
29
+ end
30
+ end