jb 0.1.1 → 0.2.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 +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
|