pg_jbuilder 0.0.1

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +10 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +468 -0
  8. data/Rakefile +7 -0
  9. data/config/database.yml +3 -0
  10. data/lib/pg_jbuilder.rb +108 -0
  11. data/lib/pg_jbuilder/railtie.rb +44 -0
  12. data/lib/pg_jbuilder/version.rb +3 -0
  13. data/pg_jbuilder.gemspec +30 -0
  14. data/queries/test1.sql +1 -0
  15. data/spec/dummy/README.rdoc +28 -0
  16. data/spec/dummy/Rakefile +6 -0
  17. data/spec/dummy/app/assets/images/.keep +0 -0
  18. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  19. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  20. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  21. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  22. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  23. data/spec/dummy/app/mailers/.keep +0 -0
  24. data/spec/dummy/app/models/.keep +0 -0
  25. data/spec/dummy/app/models/concerns/.keep +0 -0
  26. data/spec/dummy/app/models/test_model.rb +2 -0
  27. data/spec/dummy/app/queries/test.sql +1 -0
  28. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/spec/dummy/bin/bundle +3 -0
  30. data/spec/dummy/bin/rails +4 -0
  31. data/spec/dummy/bin/rake +4 -0
  32. data/spec/dummy/bin/setup +29 -0
  33. data/spec/dummy/config.ru +4 -0
  34. data/spec/dummy/config/application.rb +26 -0
  35. data/spec/dummy/config/boot.rb +5 -0
  36. data/spec/dummy/config/database.yml +26 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +41 -0
  39. data/spec/dummy/config/environments/production.rb +79 -0
  40. data/spec/dummy/config/environments/test.rb +42 -0
  41. data/spec/dummy/config/initializers/assets.rb +11 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  44. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  45. data/spec/dummy/config/initializers/inflections.rb +16 -0
  46. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  47. data/spec/dummy/config/initializers/session_store.rb +3 -0
  48. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  49. data/spec/dummy/config/locales/en.yml +23 -0
  50. data/spec/dummy/config/routes.rb +56 -0
  51. data/spec/dummy/config/secrets.yml +22 -0
  52. data/spec/dummy/db/development.sqlite3 +0 -0
  53. data/spec/dummy/db/migrate/20150210004140_add_test_model.rb +11 -0
  54. data/spec/dummy/db/schema.rb +25 -0
  55. data/spec/dummy/db/test.sqlite3 +0 -0
  56. data/spec/dummy/lib/assets/.keep +0 -0
  57. data/spec/dummy/log/.keep +0 -0
  58. data/spec/dummy/log/development.log +303 -0
  59. data/spec/dummy/log/test.log +354 -0
  60. data/spec/dummy/public/404.html +67 -0
  61. data/spec/dummy/public/422.html +67 -0
  62. data/spec/dummy/public/500.html +66 -0
  63. data/spec/dummy/public/favicon.ico +0 -0
  64. data/spec/pg_jbuilder/pg_jbuilder_spec.rb +182 -0
  65. data/spec/pg_jbuilder/railtie_spec.rb +88 -0
  66. data/spec/queries/test2.sql +1 -0
  67. data/spec/spec_helper.rb +29 -0
  68. metadata +298 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ae34d2f0e3340562b45bf77c7015b26ad1c37f37
4
+ data.tar.gz: 9e6a3affabb5efb332cd355333ade4c371979be3
5
+ SHA512:
6
+ metadata.gz: cb19034a07ef837b4967edfa1da9a4a2426a8c7ee27d56c975be529608988c3e93c11e8897bf41da075559294f3863533be7e2592ef7358dbf6f698a108628b5
7
+ data.tar.gz: b02fd0d3f694f40f7a2bdad9656016942f6d70beb4940d57f3e14d8dcb974075ba2d49a6fe80fe09a14d1df8a9fdb028249c0feb43914916d8e0e68db3fc8426
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pg-json.gemspec
4
+ gemspec
@@ -0,0 +1,10 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ # require "guard/rspec/dsl"
3
+ # dsl = Guard::RSpec::Dsl.new(self)
4
+ # rspec = dsl.rspec
5
+ # watch(rspec.spec_helper) { rspec.spec_dir }
6
+ # watch(rspec.spec_support) { rspec.spec_dir }
7
+ # watch(rspec.spec_files)
8
+ # ruby = dsl.ruby
9
+ # dsl.watch_spec_files_for(ruby.lib_files)
10
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Tye Shavik <tye@tye.ca>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,468 @@
1
+ # PgJbuilder
2
+
3
+ PgJbuilder provides a wrapper around PostgreSQL's JSON functions ([array_to_json and row_to_json](http://www.postgresql.org/docs/9.3/static/functions-json.html)) allowing you to write queries that serialize their results directly to a JSON string. This completely bypasses creating ActiveRecord objects and using Arel giving a large speed boost. It is especially useful for creating JSON APIs with low response times.
4
+
5
+ ## Benefits
6
+
7
+ Using PostgreSQL to serialize your query results to JSON is much
8
+ faster than serializing the records inside of Ruby.
9
+
10
+ ## Installation
11
+
12
+ Add to your Gemfile:
13
+
14
+ gem 'pg_jbuilder'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ ## Requirements
21
+
22
+ PgJbuilder requires:
23
+
24
+ - PostgreSQL 9.2+
25
+ - ActiveRecord 3.0+
26
+
27
+ Compatible with Rails 3.0+
28
+
29
+ ## Initializing the gem in a non-Rails environment
30
+
31
+ If you're using Rails you don't need to do any additional setup. To use
32
+ the gem outside of Rails there are a few things you need to do:
33
+
34
+ 1. Set the database connection. This needs to be an ActiveRecord connection.
35
+
36
+ PgJbuilder.connection = ActiveRecord::Base.connection
37
+ This can also be a lambda{} that when called returns a connection.
38
+
39
+ 2. Set the path where your queries will be. For example if your queries are in the app/queries directory:
40
+
41
+ PgJbuilder.paths.unshift File.join(File.dirname(__FILE__),'app','queries')
42
+
43
+ 3. The examples below are for Rails. For non-Rails applications where
44
+ the examples below use `select_object` and `select_array` you can use
45
+ `PgJbuilder.render_object`, `PbJbuilder.render_array`,
46
+ `PgJbuilder.render` to render your
47
+ queries. Once rendered they can be sent to your database and will return
48
+ a single string of JSON. For example:
49
+
50
+ ```ruby
51
+ def user_json id
52
+ sql = PgJbuilder.render_object 'users/show', id: id
53
+ ActiveRecord::Base.connection.select_value(sql)
54
+ end
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ For Rails applications queries are expected to be in `app/queries`. You can change this
60
+ by creating an initializer and adding a different path to `PgJbuilder.paths` (see the example in Initializing the gem in a non-Rails environment).
61
+
62
+ ### Returning a simple object:
63
+
64
+ 1. Create a query that will select the columns you want to return in your JSON. For example to return a User as json you might create a query called `app/queries/users/show.sql`:
65
+
66
+ ```sql
67
+ SELECT
68
+ users.id,
69
+ users.email,
70
+ users.first_name,
71
+ users.last_name
72
+ FROM users
73
+ WHERE id = {{id}}
74
+ ORDER BY id ASC
75
+ ```
76
+
77
+ 2. Add a method to your model that will render the JSON. For the user
78
+ example you would add this to app/models/user.rb
79
+
80
+ ```ruby
81
+ class User < ActiveRecord::Base
82
+ def show_json
83
+ select_object 'users/show', id: id
84
+ end
85
+ end
86
+ ```
87
+
88
+ Note that queries use Handlebars for templating. We pass in the id to
89
+ `select_object` then the `{{id}}` in the template will be replaced
90
+ with this value. Read more on Handlebars syntax [on their
91
+ website](http://handlebarsjs.com/expressions.html).
92
+
93
+ This query would return a JSON object like:
94
+ ```json
95
+ {
96
+ "id": 1,
97
+ "email": "mbolton@initech.com",
98
+ "first_name": "Michael",
99
+ "last_name": "Bolton"
100
+ }
101
+ ```
102
+
103
+ Since this is a JSON object and not an array the query must return
104
+ only a single row. If more than one row is returned by the query
105
+ PostgreSQL will raise an error and the query will fail.
106
+
107
+ 3. Call the `show_json` method added to `User` to return the user as
108
+ JSON. For example if you were using this in a JSON API then in your controller you might use:
109
+
110
+ ```ruby
111
+ class UsersController < ApplicationController
112
+ before_filter :load_user
113
+
114
+ def show
115
+ render json: @user.show_json
116
+ end
117
+
118
+ private
119
+
120
+ def load_user
121
+ @user = User.find(params[:id])
122
+ end
123
+ end
124
+ ```
125
+
126
+ ### Returning a simple array
127
+
128
+ 1. Create a query that will return all the rows and columns you want
129
+ in your JSON. For example if you want to return a list of users we
130
+ would create a query in `app/queries/users/index.sql` like this:
131
+
132
+ ```sql
133
+ SELECT
134
+ users.id,
135
+ users.email,
136
+ users.first_name,
137
+ users.last_name
138
+ FROM users
139
+ ORDER BY id
140
+ ```
141
+
142
+ 2. Add a method to your `User` model that renders the array:
143
+
144
+ ```ruby
145
+ class User < ActiveRecord::Base
146
+ def self.index_json
147
+ select_array 'users/index'
148
+ end
149
+ end
150
+ ```
151
+
152
+ This would return a JSON array like this:
153
+
154
+ ```json
155
+ [
156
+ {
157
+ "id": 1,
158
+ "email": "mbolton@initech.com",
159
+ "first_name": "Michael",
160
+ "last_name": "Bolton"
161
+ },
162
+ {
163
+ "id": 2,
164
+ "email": "pgibbons@initech.com",
165
+ "first_name": "Peter",
166
+ "last_name": "Gibbons"
167
+ },
168
+ {
169
+ "id": 3,
170
+ "email": "snagheenanajar@initech.com",
171
+ "first_name": "Samir",
172
+ "last_name": "Nagheenanajar"
173
+ }
174
+ ]
175
+ ```
176
+
177
+ 3. Call the method added to the `User` model to return the JSON. For
178
+ example in your controller you might add:
179
+
180
+ ```ruby
181
+ class UsersController < ApplicationController
182
+ def index
183
+ render json: User.index_json
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Quoting/Escaping values
189
+
190
+ You can use the `{{quote}}` helper to escape user inputted values to
191
+ make them safe to include in the query. For example if your query is
192
+ `app/queries/users/search.sql`:
193
+
194
+ ```sql
195
+ SELECT users.id
196
+ FROM users
197
+ WHERE
198
+ users.first_name = {{quote first_name}}
199
+ ```
200
+
201
+ and you call the query:
202
+ ```ruby
203
+ select_array 'users/search', first_name: 'John'
204
+ ```
205
+
206
+ it will render the query as:
207
+ ```sql
208
+ SELECT users.id
209
+ FROM users
210
+ WHERE
211
+ users.first_name = 'John'
212
+ ```
213
+
214
+ Without the quote helper it would render as:
215
+
216
+ ```sql
217
+ SELECT users.id
218
+ FROM users
219
+ WHERE
220
+ users.first_name = John
221
+ ```
222
+
223
+ without the quotes which would allow SQL injection attacks. `{{quote}}`
224
+ will also escape quotes for example:
225
+
226
+ ```ruby
227
+ select_array 'users/search', first_name: "Jo'hn"
228
+ ```
229
+
230
+ will render as:
231
+ ```sql
232
+ SELECT users.id
233
+ FROM users
234
+ WHERE
235
+ users.first_name = 'Jo''hn'
236
+ ```
237
+
238
+ ### Partials
239
+
240
+ You can include partials in your template using the `{{include}}`
241
+ helper. For example you might refactor the SELECT portion of your query
242
+ into its own partial `app/queries/users/select.sql`
243
+
244
+ ```sql
245
+ SELECT
246
+ users.id,
247
+ users.first_name,
248
+ users.last_name,
249
+ users.email
250
+ ```
251
+
252
+ Then in `app/queries/users/show.sql` you would have:
253
+
254
+ ```sql
255
+ {{include 'users/select'}}
256
+ FROM users
257
+ WHERE id = {{id}}
258
+ ```
259
+
260
+ Variables passed into a query will automatically be passed into the
261
+ partial. In the above example there is a `{{id}}` variable. You would
262
+ also be able to use this variable in the partial.
263
+
264
+ You can pass additional variables into the partial using this syntax:
265
+
266
+ `{{include 'template_name' variable1='value' variable2='value' ...}}`
267
+
268
+ ### Embedding objects and arrays
269
+
270
+ #### Objects
271
+
272
+ You can embed objects using the `{{object}}` helper. For example if you
273
+ want to have a user object inside a your comment index in
274
+ `app/queries/comments/index.sql`:
275
+
276
+ ```sql
277
+ SELECT
278
+ comments.id,
279
+ comments.body,
280
+ {{#object}}
281
+ SELECT
282
+ users.id,
283
+ users.first_name,
284
+ users.last_name,
285
+ users.email
286
+ FROM users
287
+ WHERE
288
+ users.id = comments.user_id
289
+ {{/object}} AS user
290
+ FROM comments
291
+ ORDER BY id
292
+ ```
293
+
294
+ This would create a JSON object like:
295
+ ```json
296
+ {
297
+ "id": 1,
298
+ "body": "This is my comment",
299
+ "user": {
300
+ "id": 100,
301
+ "username": "witty_commenter"
302
+ }
303
+ }
304
+ ```
305
+
306
+ You can also refactor the object into a partial. So you could create a
307
+ query in `app/queries/users/object.sql`:
308
+
309
+ ```sql
310
+ SELECT
311
+ users.id,
312
+ users.first_name,
313
+ users.last_name,
314
+ users.email
315
+ FROM users
316
+ WHERE
317
+ users.id = {{id}}
318
+ ```
319
+
320
+ Then include it using this syntax in `app/queries/comments/index.sql`:
321
+
322
+ ```sql
323
+ SELECT
324
+ comments.id,
325
+ comments.body,
326
+ {{object 'users/object' id='comments.user_id'}} AS user
327
+ FROM comments
328
+ ORDER BY id
329
+ ```
330
+
331
+ This would produce the same JSON as above.
332
+
333
+ #### Arrays
334
+
335
+ Embedding arrays works just like embedding objects but uses the
336
+ `{{array}}` helper. For example if you have a user object in
337
+ `app/queries/users/show.sql` and want to return a list of the user's
338
+ comments inside the user object:
339
+
340
+ ```sql
341
+ SELECT
342
+ users.id,
343
+ users.first_name,
344
+ users.last_name,
345
+ users.email,
346
+ {{#array}}
347
+ SELECT
348
+ comments.id,
349
+ comments.body
350
+ FROM comments
351
+ WHERE comments.user_id = users.id
352
+ {{/array}} AS comments
353
+ FROM users
354
+ WHERE id = {{id}}
355
+ ```
356
+
357
+ This would return a JSON object like:
358
+
359
+ ```json
360
+ {
361
+ "id": 1,
362
+ "username": "witty_commenter",
363
+ "comments": [
364
+ {
365
+ "id": 100,
366
+ "body": "Witty Comment #1"
367
+ },
368
+ {
369
+ "id": 200,
370
+ "body": "Witty Comment #2"
371
+ }
372
+ ]
373
+ }
374
+ ```
375
+
376
+ Just like with `{{object}}` you can refactor your arrays into a partial.
377
+ So if you have `app/queries/users/comments.sql`
378
+
379
+ ```sql
380
+ SELECT
381
+ comments.id,
382
+ comments.body
383
+ FROM comments
384
+ WHERE comments.user_id = {{user_id}}
385
+ ```
386
+
387
+ then in `app/queries/users/show.sql` you can have:
388
+
389
+ ```sql
390
+ SELECT
391
+ users.id,
392
+ users.username,
393
+ {{array 'users/comments' user_id='users.id'}} AS comments
394
+ FROM users
395
+ WHERE id = {{id}}
396
+ ```
397
+
398
+ ### Pagination
399
+
400
+ To do pagination you need to execute two queries. One to count the rows,
401
+ then another to return the results with a LIMIT and OFFSET. To
402
+ accomplish this with pg_jbuilder your query would have to look like
403
+ this:
404
+
405
+ ```sql
406
+ SELECT
407
+ {{#if count}}
408
+ COUNT(*) AS total_rows
409
+ {{else}}
410
+ comments.id,
411
+ comments.body
412
+ {{/if}}
413
+ FROM comments
414
+ {{#unless count}}
415
+ ORDER BY id
416
+ LIMIT {{per_page}}
417
+ OFFSET ({{quote page}} - 1) * {{per_page}}
418
+ {{/unless}}
419
+ ```
420
+
421
+ Then in your model:
422
+ ```ruby
423
+ class Comment < ActiveRecord::Base
424
+ PER_PAGE = 20
425
+ def self.count_index_json attrs={}
426
+ attrs[:count] = true
427
+ attrs[:per_page] = PER_PAGE
428
+ select_value('comments/index').to_i
429
+ end
430
+
431
+ def self.index_json attrs={}
432
+ attrs[:per_page] = PER_PAGE
433
+ select_array 'comments/index', attrs
434
+ end
435
+ end
436
+ ```
437
+
438
+ `select_value` will return render your query and return a single value
439
+ from it.
440
+
441
+ And in your controller:
442
+ ```ruby
443
+ class CommentsController < ApplicationController
444
+ def index
445
+ count = Comment.count_index_json(index_params)
446
+ headers['X-Pagination-Total-Entries'] = count.to_s
447
+ render json: Comment.index_json(index_params)
448
+ end
449
+
450
+ private
451
+
452
+ def index_params
453
+ params.permit :page
454
+ end
455
+ end
456
+ ```
457
+
458
+ The API consumer can then read the `X-Pagination-Total-Entries` to see the
459
+ total number of entries and can pass a `page` parameter to specify which
460
+ page to fetch.
461
+
462
+ ## Contributing
463
+
464
+ 1. Fork it ( https://github.com/[my-github-username]/pg-json/fork )
465
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
466
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
467
+ 4. Push to the branch (`git push origin my-new-feature`)
468
+ 5. Create a new Pull Request