jb 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +294 -2
- data/bin/benchmark.sh +5 -0
- data/lib/generators/rails/jb_generator.rb +34 -0
- data/lib/generators/rails/scaffold_controller_generator.rb +10 -0
- data/lib/generators/rails/templates/index.json.jb +8 -0
- data/lib/generators/rails/templates/show.json.jb +3 -0
- data/lib/jb/railtie.rb +6 -0
- data/lib/jb/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cc429668499e99d5acd8fc816c5edbecd363d57
|
4
|
+
data.tar.gz: debb9265b363fa660ef632fa1fde79c76f0a1065
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f24ea0311ab49e1bdc5559f04e9feabc829643c4a9f1bdf9714084bb903589d81ea266ef8c269adb06c10ba27b9b7b255eae9a1e3366116e475f8f9ac212d9f
|
7
|
+
data.tar.gz: 30a0eb11d629dae406df1407de317cad6aad8bd59119cbd926f1041342483f3dad4a5a3735c8cf8433c2693aa506c37955f874cbf486910fba51a587fce271c7
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,8 +16,7 @@ And bundle.
|
|
16
16
|
|
17
17
|
## Usage
|
18
18
|
|
19
|
-
|
20
|
-
Then the object will be `to_json`ed to a JSON String.
|
19
|
+
Put a template file named `*.jb` in your Rails app's `app/views/*` directory, and render it.
|
21
20
|
|
22
21
|
|
23
22
|
## Features
|
@@ -27,6 +26,299 @@ Then the object will be `to_json`ed to a JSON String.
|
|
27
26
|
* `render_partial` with :collection option actually renders the collection (unlike Jbuilder)
|
28
27
|
|
29
28
|
|
29
|
+
## Syntax
|
30
|
+
|
31
|
+
A `.jb` template should contain Ruby code that returns any Ruby Object that responds_to `to_json` (generally Hash or Array).
|
32
|
+
Then the return value will be `to_json`ed to a JSON String.
|
33
|
+
|
34
|
+
|
35
|
+
## Examples
|
36
|
+
|
37
|
+
``` ruby
|
38
|
+
# app/views/messages/show.json.jb
|
39
|
+
|
40
|
+
json = {
|
41
|
+
content: format_content(@message.content),
|
42
|
+
created_at: @message.created_at,
|
43
|
+
updated_at: @message.updated_at,
|
44
|
+
author: {
|
45
|
+
name: @message.creator.name.familiar,
|
46
|
+
email_address: @message.creator.email_address_with_name,
|
47
|
+
url: url_for(@message.creator, format: :json)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
if current_user.admin?
|
52
|
+
json[:visitors] = calculate_visitors(@message)
|
53
|
+
end
|
54
|
+
|
55
|
+
json[:comments] = {
|
56
|
+
content: @message.comments.content,
|
57
|
+
created_at: @message.comments.created_at
|
58
|
+
}
|
59
|
+
|
60
|
+
json[:attachments] = @message.attachments.map do |attachment|
|
61
|
+
filename: attachment.filename,
|
62
|
+
url: url_for(attachment)
|
63
|
+
end
|
64
|
+
|
65
|
+
json
|
66
|
+
```
|
67
|
+
|
68
|
+
This will build the following structure:
|
69
|
+
|
70
|
+
``` javascript
|
71
|
+
{
|
72
|
+
"content": "10x JSON",
|
73
|
+
"created_at": "2016-06-29T20:45:28-05:00",
|
74
|
+
"updated_at": "2016-06-29T20:45:28-05:00",
|
75
|
+
|
76
|
+
"author": {
|
77
|
+
"name": "Yukihiro Matz",
|
78
|
+
"email_address": "matz@example.com",
|
79
|
+
"url": "http://example.com/users/1-matz.json"
|
80
|
+
},
|
81
|
+
|
82
|
+
"visitors": 1326,
|
83
|
+
|
84
|
+
"comments": [
|
85
|
+
{ "content": "Hello, world!", "created_at": "2016-06-29T20:45:28-05:00" },
|
86
|
+
{ "content": "<script>alert('Hello, world!');</script>", "created_at": "2016-06-29T20:47:28-05:00" }
|
87
|
+
],
|
88
|
+
|
89
|
+
"attachments": [
|
90
|
+
{ "filename": "sushi.png", "url": "http://example.com/downloads/sushi.png" },
|
91
|
+
{ "filename": "sake.jpg", "url": "http://example.com/downloads/sake.jpg" }
|
92
|
+
]
|
93
|
+
}
|
94
|
+
```
|
95
|
+
|
96
|
+
To define attribute and structure names dynamically, just use Ruby Hash.
|
97
|
+
Note that modern Ruby Hash syntax pretty much looks alike JSON syntax.
|
98
|
+
It's super-straight forward. Who needs a DSL to do this?
|
99
|
+
|
100
|
+
``` ruby
|
101
|
+
{author: {name: 'Matz'}}
|
102
|
+
|
103
|
+
# => {"author": {"name": "Matz"}}
|
104
|
+
```
|
105
|
+
|
106
|
+
Top level arrays can be handled directly. Useful for index and other collection actions.
|
107
|
+
And you know, Ruby is such a powerful language for manipulating collections:
|
108
|
+
|
109
|
+
``` ruby
|
110
|
+
# @comments = @post.comments
|
111
|
+
|
112
|
+
@comments.reject {|c| c.marked_as_spam_by?(current_user) }.map do |comment|
|
113
|
+
body: comment.body,
|
114
|
+
author: {
|
115
|
+
first_name: comment.author.first_name,
|
116
|
+
last_name: comment.author.last_name
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
# => [{"body": "🍣 is omakase...", "author": {"first_name": "Yukihiro", "last_name": "Matz"}}]
|
121
|
+
```
|
122
|
+
|
123
|
+
Jb has no special DSL method for extracting attributes from array directly, but you can do that with Ruby.
|
124
|
+
|
125
|
+
``` ruby
|
126
|
+
# @people = People.all
|
127
|
+
|
128
|
+
@people.map {|p| {id: p.id, name: p.name}}
|
129
|
+
|
130
|
+
# => [{"id": 1, "name": "Matz"}, {"id": 2, "name": "Nobu"}]
|
131
|
+
```
|
132
|
+
|
133
|
+
You can use Jb directly as an Action View template language.
|
134
|
+
When required in Rails, you can create views ala show.json.jb.
|
135
|
+
You'll notice in the following example that the `.jb` template
|
136
|
+
doesn't have to be one big Ruby Hash literal as a whole
|
137
|
+
but it can be any Ruby code that finally returns a Hash instance.
|
138
|
+
|
139
|
+
``` ruby
|
140
|
+
# Any helpers available to views are available in the template
|
141
|
+
json = {
|
142
|
+
content: format_content(@message.content),
|
143
|
+
created_at: @message.created_at,
|
144
|
+
updated_at: @message.updated_at,
|
145
|
+
|
146
|
+
author: {
|
147
|
+
name: @message.creator.name.familiar,
|
148
|
+
email_address: @message.creator.email_address_with_name,
|
149
|
+
url: url_for(@message.creator, format: :json)
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
if current_user.admin?
|
154
|
+
json[:visitors] = calculate_visitors(@message)
|
155
|
+
end
|
156
|
+
|
157
|
+
json
|
158
|
+
```
|
159
|
+
|
160
|
+
You can use partials as well. The following will render the file
|
161
|
+
`views/comments/_comments.json.jb`, and set a local variable
|
162
|
+
`comments` with all this message's comments, which you can use inside
|
163
|
+
the partial.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
render 'comments/comments', comments: @message.comments
|
167
|
+
```
|
168
|
+
|
169
|
+
It's also possible to render collections of partials:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
render partial: 'posts/post', collection: @posts, as: :post
|
173
|
+
|
174
|
+
# or
|
175
|
+
|
176
|
+
render @post.comments
|
177
|
+
```
|
178
|
+
|
179
|
+
You can pass any objects into partial templates with or without `:locals` option.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
render 'sub_template', locals: {user: user}
|
183
|
+
|
184
|
+
# or
|
185
|
+
|
186
|
+
render 'sub_template', user: user
|
187
|
+
```
|
188
|
+
|
189
|
+
You can of course include Ruby `nil` as a Hash value if you want. That would become `null` in the JSON.
|
190
|
+
|
191
|
+
To prevent Jb from including null values in the output, Active Support provides `Hash#compact!` method for you:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
{foo: nil, bar: 'bar'}.compact
|
195
|
+
|
196
|
+
# => {"bar": "bar"}
|
197
|
+
```
|
198
|
+
|
199
|
+
If you want to cache a template fragment, just directly call `Rails.cache.fetch`:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
Rails.cache.fetch ['v1', @person], expires_in: 10.minutes do
|
203
|
+
{name: @person.name, age: @person.age}
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
|
208
|
+
## The Generator
|
209
|
+
Jb extends the default Rails scaffold generator and adds some .jb templates.
|
210
|
+
If you don't need them, please configure like so.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
Rails.application.config.generators.jb false
|
214
|
+
```
|
215
|
+
|
216
|
+
|
217
|
+
## Why is Jb fast?
|
218
|
+
|
219
|
+
Jbuilder's `partial` + `:collection` [internally calls `array!` method](https://github.com/rails/jbuilder/blob/83a682aeebde96c6ef02ce742c0b97dc393f5e22/lib/jbuilder/jbuilder_template.rb#L85-L95)
|
220
|
+
inside which [`_render_partial` is called per each element of the given collection](https://github.com/rails/jbuilder/blob/83a682aeebde96c6ef02ce742c0b97dc393f5e22/lib/jbuilder/jbuilder_template.rb#L93),
|
221
|
+
and then it [falls back to the `view_context`'s `render` method](https://github.com/rails/jbuilder/blob/83a682aeebde96c6ef02ce742c0b97dc393f5e22/lib/jbuilder/jbuilder_template.rb#L100-L103).
|
222
|
+
|
223
|
+
So, for example if the collection has 100 elements, Jbuilder's `render partial:` performs `render` method 100 times, and so it calls `find_template` method (which is known as one of the heaviest parts of Action View) 100 times.
|
224
|
+
|
225
|
+
OTOH, Jb simply calls [ActionView::PartialRenderer's `render`](https://github.com/rails/rails/blob/49a881e0db1ef64fcbae2b7ddccfd5ccea26ae01/actionview/lib/action_view/renderer/partial_renderer.rb#L423-L443) which is cleverly implmented to `find_template` only once beforehand, then pass each element to that template.
|
226
|
+
|
227
|
+
|
228
|
+
## Bencharks
|
229
|
+
Here're the results of a benchmark (which you can find [here](https://github.com/amatsuda/jb/blob/master/test/dummy_app/app/controllers/benchmarks_controller.rb) in this repo) rendering a collection to JSON.
|
230
|
+
|
231
|
+
### RAILS_ENV=development
|
232
|
+
```
|
233
|
+
% ./bin/benchmark.sh
|
234
|
+
* Rendering 10 partials via render_partial
|
235
|
+
Warming up --------------------------------------
|
236
|
+
jb 15.000 i/100ms
|
237
|
+
jbuilder 8.000 i/100ms
|
238
|
+
Calculating -------------------------------------
|
239
|
+
jb 156.375 (± 7.0%) i/s - 780.000 in 5.016581s
|
240
|
+
jbuilder 87.890 (± 6.8%) i/s - 440.000 in 5.037225s
|
241
|
+
|
242
|
+
Comparison:
|
243
|
+
jb: 156.4 i/s
|
244
|
+
jbuilder: 87.9 i/s - 1.78x slower
|
245
|
+
|
246
|
+
|
247
|
+
* Rendering 100 partials via render_partial
|
248
|
+
Warming up --------------------------------------
|
249
|
+
jb 13.000 i/100ms
|
250
|
+
jbuilder 1.000 i/100ms
|
251
|
+
Calculating -------------------------------------
|
252
|
+
jb 121.187 (±14.0%) i/s - 598.000 in 5.049667s
|
253
|
+
jbuilder 11.478 (±26.1%) i/s - 54.000 in 5.061996s
|
254
|
+
|
255
|
+
Comparison:
|
256
|
+
jb: 121.2 i/s
|
257
|
+
jbuilder: 11.5 i/s - 10.56x slower
|
258
|
+
|
259
|
+
|
260
|
+
* Rendering 1000 partials via render_partial
|
261
|
+
Warming up --------------------------------------
|
262
|
+
jb 4.000 i/100ms
|
263
|
+
jbuilder 1.000 i/100ms
|
264
|
+
Calculating -------------------------------------
|
265
|
+
jb 51.472 (± 7.8%) i/s - 256.000 in 5.006584s
|
266
|
+
jbuilder 1.510 (± 0.0%) i/s - 8.000 in 5.383548s
|
267
|
+
|
268
|
+
Comparison:
|
269
|
+
jb: 51.5 i/s
|
270
|
+
jbuilder: 1.5 i/s - 34.08x slower
|
271
|
+
```
|
272
|
+
|
273
|
+
|
274
|
+
### RAILS_ENV=production
|
275
|
+
```
|
276
|
+
% RAILS_ENV=production ./bin/benchmark.sh
|
277
|
+
* Rendering 10 partials via render_partial
|
278
|
+
Warming up --------------------------------------
|
279
|
+
jb 123.000 i/100ms
|
280
|
+
jbuilder 41.000 i/100ms
|
281
|
+
Calculating -------------------------------------
|
282
|
+
jb 1.406k (± 4.2%) i/s - 7.134k in 5.084030s
|
283
|
+
jbuilder 418.360 (± 9.8%) i/s - 2.091k in 5.043381s
|
284
|
+
|
285
|
+
Comparison:
|
286
|
+
jb: 1405.8 i/s
|
287
|
+
jbuilder: 418.4 i/s - 3.36x slower
|
288
|
+
|
289
|
+
|
290
|
+
* Rendering 100 partials via render_partial
|
291
|
+
Warming up --------------------------------------
|
292
|
+
jb 37.000 i/100ms
|
293
|
+
jbuilder 5.000 i/100ms
|
294
|
+
Calculating -------------------------------------
|
295
|
+
jb 383.082 (± 8.4%) i/s - 1.924k in 5.061973s
|
296
|
+
jbuilder 49.914 (± 8.0%) i/s - 250.000 in 5.040364s
|
297
|
+
|
298
|
+
Comparison:
|
299
|
+
jb: 383.1 i/s
|
300
|
+
jbuilder: 49.9 i/s - 7.67x slower
|
301
|
+
|
302
|
+
|
303
|
+
* Rendering 1000 partials via render_partial
|
304
|
+
Warming up --------------------------------------
|
305
|
+
jb 4.000 i/100ms
|
306
|
+
jbuilder 1.000 i/100ms
|
307
|
+
Calculating -------------------------------------
|
308
|
+
jb 43.017 (± 9.3%) i/s - 216.000 in 5.080482s
|
309
|
+
jbuilder 4.604 (±21.7%) i/s - 23.000 in 5.082100s
|
310
|
+
|
311
|
+
Comparison:
|
312
|
+
jb: 43.0 i/s
|
313
|
+
jbuilder: 4.6 i/s - 9.34x slower
|
314
|
+
```
|
315
|
+
|
316
|
+
|
317
|
+
### Summary
|
318
|
+
|
319
|
+
According to the benchmark results, you can expect 2-30x performance improvement in development env, and 3-10x performance improvement in production env.
|
320
|
+
|
321
|
+
|
30
322
|
## Contributing
|
31
323
|
|
32
324
|
Pull requests are welcome on GitHub at https://github.com/amatsuda/jb.
|
data/bin/benchmark.sh
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'rails/generators/resource_helpers'
|
3
|
+
|
4
|
+
module Rails
|
5
|
+
module Generators
|
6
|
+
class JbGenerator < NamedBase # :nodoc:
|
7
|
+
include Rails::Generators::ResourceHelpers
|
8
|
+
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
|
11
|
+
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
|
12
|
+
|
13
|
+
def create_root_folder
|
14
|
+
path = File.join('app/views', controller_file_path)
|
15
|
+
empty_directory path unless File.directory?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def copy_view_files
|
19
|
+
template 'index.json.jb', File.join('app/views', controller_file_path, 'index.json.jb')
|
20
|
+
template 'show.json.jb', File.join('app/views', controller_file_path, 'show.json.jb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
def attributes_names
|
26
|
+
[:id] + super
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes_names_with_timestamps
|
30
|
+
attributes_names + %w(created_at updated_at)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/jb/railtie.rb
CHANGED
@@ -7,5 +7,11 @@ module Jb
|
|
7
7
|
::ActionView::Template.register_template_handler :jb, Jb::Handler
|
8
8
|
end
|
9
9
|
end
|
10
|
+
|
11
|
+
generators do |app|
|
12
|
+
Rails::Generators.configure! app.config.generators
|
13
|
+
Rails::Generators.hidden_namespaces.uniq!
|
14
|
+
require_relative '../generators/rails/scaffold_controller_generator'
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
data/lib/jb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Akira Matsuda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -93,9 +93,14 @@ files:
|
|
93
93
|
- LICENSE.txt
|
94
94
|
- README.md
|
95
95
|
- Rakefile
|
96
|
+
- bin/benchmark.sh
|
96
97
|
- bin/console
|
97
98
|
- bin/setup
|
98
99
|
- jb.gemspec
|
100
|
+
- lib/generators/rails/jb_generator.rb
|
101
|
+
- lib/generators/rails/scaffold_controller_generator.rb
|
102
|
+
- lib/generators/rails/templates/index.json.jb
|
103
|
+
- lib/generators/rails/templates/show.json.jb
|
99
104
|
- lib/jb.rb
|
100
105
|
- lib/jb/action_view_monkeys.rb
|
101
106
|
- lib/jb/handler.rb
|
@@ -121,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
126
|
version: '0'
|
122
127
|
requirements: []
|
123
128
|
rubyforge_project:
|
124
|
-
rubygems_version: 2.
|
129
|
+
rubygems_version: 2.6.4
|
125
130
|
signing_key:
|
126
131
|
specification_version: 4
|
127
132
|
summary: Faster and simpler Jbuilder alternative
|