sinja 1.2.1 → 1.2.2
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/README.md +145 -180
- data/lib/sinatra/jsonapi.rb +1 -1
- data/lib/sinja.rb +91 -100
- data/lib/sinja/errors.rb +24 -8
- data/lib/sinja/relationship_routes/has_many.rb +1 -1
- data/lib/sinja/resource.rb +8 -8
- data/lib/sinja/resource_routes.rb +3 -3
- data/lib/sinja/version.rb +1 -1
- data/sinja.gemspec +16 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: adc1346572a9c1a9ae1d2b85c70931cd329cdd1e
|
4
|
+
data.tar.gz: 73db963c1a496a98de718d1ea17ebda739d80ad9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55dc7ba3bc974625bb2cf65b943ccf8cf57541226f39eb0d66c7e56320c507cf3f8d76d0fddba5605698677f459958ede489477ebc9baed97d82a50c6a1e76f2
|
7
|
+
data.tar.gz: f75638978ade62140b1306644e4a81a62b18d1a8ce09b388ffa183c6c4a5859d531ce108b71299e5b82e39b3759e6d3c0b381799dc12be0f845a63784ec2280a
|
data/README.md
CHANGED
@@ -10,19 +10,23 @@
|
|
10
10
|
[](https://badge.fury.io/rb/sinja)
|
11
11
|
[](https://gemnasium.com/github.com/mwpastore/sinja)
|
12
12
|
[](https://travis-ci.org/mwpastore/sinja)
|
13
|
-
|
13
|
+
[][7]
|
14
14
|
[](https://gitter.im/sinja-rb/Lobby)
|
15
|
-
[](https://ember-community-slackin.herokuapp.com/?channel=-ember-data)
|
16
15
|
|
17
16
|
Sinja is a [Sinatra][1] [extension][10] for quickly building [RESTful][11],
|
18
|
-
[{json:api}][2]-
|
19
|
-
[JSONAPI::Serializers][3] gem
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
[{json:api}][2]-compliant web services, leveraging the excellent
|
18
|
+
[JSONAPI::Serializers][3] gem for payload serialization. It enhances Sinatra's
|
19
|
+
DSL to enable resource-, relationship-, and role-centric API development, and
|
20
|
+
it configures Sinatra with the proper settings, MIME-types, filters,
|
21
|
+
conditions, and error-handling.
|
22
|
+
|
23
|
+
There are [many][31] parsing (deserializing) and rendering (serializing) and
|
24
|
+
so-called "JSON API" libraries available for Ruby, but relatively few that
|
25
|
+
attempt to correctly implement the entire {json:api} specification, including
|
26
|
+
routing, request header and query parameter checking, and relationship
|
27
|
+
side-loading. Sinja lets you focus on the business logic of your applications
|
28
|
+
without worrying about the specification, and without pulling in a heavy
|
29
|
+
framework like [Rails][16]. It's lightweight and ORM-agnostic!
|
26
30
|
|
27
31
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
28
32
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
@@ -30,14 +34,7 @@ the {json:api} specification is).
|
|
30
34
|
|
31
35
|
- [Synopsis](#synopsis)
|
32
36
|
- [Installation](#installation)
|
33
|
-
- [
|
34
|
-
- [Ol' Blue Eyes is Back](#ol-blue-eyes-is-back)
|
35
|
-
- [Public APIs](#public-apis)
|
36
|
-
- [Commonly Used](#commonly-used)
|
37
|
-
- [Less-Commonly Used](#less-commonly-used)
|
38
|
-
- [Performance](#performance)
|
39
|
-
- [Extensions](#extensions)
|
40
|
-
- [Comparison with JSONAPI::Resources](#comparison-with-jsonapiresources)
|
37
|
+
- [Ol' Blue Eyes is Back](#ol-blue-eyes-is-back)
|
41
38
|
- [Basic Usage](#basic-usage)
|
42
39
|
- [Configuration](#configuration)
|
43
40
|
- [Sinatra](#sinatra)
|
@@ -45,24 +42,8 @@ the {json:api} specification is).
|
|
45
42
|
- [Resource Locators](#resource-locators)
|
46
43
|
- [Action Helpers](#action-helpers)
|
47
44
|
- [`resource`](#resource)
|
48
|
-
- [`index {..}` => Array](#index---array)
|
49
|
-
- [`show {|id| ..}` => Object](#show-id---object)
|
50
|
-
- [`show {..}` => Object](#show---object)
|
51
|
-
- [`show_many {|ids| ..}` => Array](#show_many-ids---array)
|
52
|
-
- [`create {|attr, id| ..}` => id, Object?](#create-attr-id---id-object)
|
53
|
-
- [`create {|attr| ..}` => id, Object](#create-attr---id-object)
|
54
|
-
- [`update {|attr| ..}` => Object?](#update-attr---object)
|
55
|
-
- [`destroy {..}`](#destroy-)
|
56
45
|
- [`has_one`](#has_one)
|
57
|
-
- [`pluck {..}` => Object](#pluck---object)
|
58
|
-
- [`prune {..}` => TrueClass?](#prune---trueclass)
|
59
|
-
- [`graft {|rio| ..}` => TrueClass?](#graft-rio---trueclass)
|
60
46
|
- [`has_many`](#has_many)
|
61
|
-
- [`fetch {..}` => Array](#fetch---array)
|
62
|
-
- [`clear {..}` => TrueClass?](#clear---trueclass)
|
63
|
-
- [`replace {|rios| ..}` => TrueClass?](#replace-rios---trueclass)
|
64
|
-
- [`merge {|rios| ..}` => TrueClass?](#merge-rios---trueclass)
|
65
|
-
- [`subtract {|rios| ..}` => TrueClass?](#subtract-rios---trueclass)
|
66
47
|
- [Advanced Usage](#advanced-usage)
|
67
48
|
- [Action Helper Hooks & Utilities](#action-helper-hooks--utilities)
|
68
49
|
- [Authorization](#authorization)
|
@@ -82,15 +63,19 @@ the {json:api} specification is).
|
|
82
63
|
- [Side-Unloading Related Resources](#side-unloading-related-resources)
|
83
64
|
- [Side-Loading Relationships](#side-loading-relationships)
|
84
65
|
- [Avoiding Null Foreign Keys](#avoiding-null-foreign-keys)
|
85
|
-
- [Many-to-One](#many-to-one)
|
86
|
-
- [One-to-Many](#one-to-many)
|
87
|
-
- [Many-to-Many](#many-to-many)
|
88
66
|
- [Coalesced Find Requests](#coalesced-find-requests)
|
89
67
|
- [Patchless Clients](#patchless-clients)
|
68
|
+
- [Extensions](#extensions)
|
69
|
+
- [Sequel](#sequel)
|
90
70
|
- [Application Concerns](#application-concerns)
|
71
|
+
- [Performance](#performance)
|
72
|
+
- [Public APIs](#public-apis)
|
73
|
+
- [Commonly Used](#commonly-used)
|
74
|
+
- [Less-Commonly Used](#less-commonly-used)
|
91
75
|
- [Sinja or Sinatra::JSONAPI](#sinja-or-sinatrajsonapi)
|
92
76
|
- [Code Organization](#code-organization)
|
93
77
|
- [Testing](#testing)
|
78
|
+
- [Comparison with JSONAPI::Resources](#comparison-with-jsonapiresources)
|
94
79
|
- [Development](#development)
|
95
80
|
- [Contributing](#contributing)
|
96
81
|
- [License](#license)
|
@@ -100,7 +85,6 @@ the {json:api} specification is).
|
|
100
85
|
## Synopsis
|
101
86
|
|
102
87
|
```ruby
|
103
|
-
require 'sinatra'
|
104
88
|
require 'sinatra/jsonapi'
|
105
89
|
|
106
90
|
resource :posts do
|
@@ -171,36 +155,12 @@ Or install it yourself as:
|
|
171
155
|
$ gem install sinja
|
172
156
|
```
|
173
157
|
|
174
|
-
##
|
175
|
-
|
176
|
-
* ORM-agnostic
|
177
|
-
* Simple role-based authorization
|
178
|
-
* To-one and to-many relationships and related resources
|
179
|
-
* Side-loaded relationships on resource creation and update
|
180
|
-
* Error-handling
|
181
|
-
* Conflicts (constraint violations)
|
182
|
-
* Missing records
|
183
|
-
* Validation failures
|
184
|
-
* Filtering, sorting, and paging collections
|
185
|
-
* Plus all the features of [JSONAPI::Serializers][3]!
|
186
|
-
|
187
|
-
Its main competitors in the Ruby space are [ActiveModelSerializers][12] (AMS)
|
188
|
-
with the JsonApi adapter, [JSONAPI::Resources][8] (JR), and
|
189
|
-
[jsonapi-utils][26], all of which are designed to work with [Rails][16] and
|
190
|
-
[ActiveRecord][17]/[ActiveModel][18] (although they may work with [Sequel][13]
|
191
|
-
via [sequel-rails][14] and Sequel's [`:active_model` plugin][15]). Otherwise,
|
192
|
-
you might use something like Sinatra, [Roda][20], or [Grape][19] with a
|
193
|
-
(de)serialization library, your own routes, and a ton of boilerplate. The goal
|
194
|
-
of this extension is to provide most or all of the boilerplate for a Sintara
|
195
|
-
application and automate the drawing of routes based on the resource
|
196
|
-
definitions.
|
197
|
-
|
198
|
-
### Ol' Blue Eyes is Back
|
158
|
+
## Ol' Blue Eyes is Back
|
199
159
|
|
200
160
|
The "power" so to speak of implementing this functionality as a Sinatra
|
201
161
|
extension is that all of Sinatra's usual features are available within your
|
202
|
-
resource definitions.
|
203
|
-
|
162
|
+
resource definitions. Action helper blocks get compiled into Sinatra helpers,
|
163
|
+
and the `resource`, `has_one`, and `has_many` keywords build
|
204
164
|
[Sinatra::Namespace][21] blocks. You can manage caching directives, set
|
205
165
|
headers, and even `halt` (or `not_found`, although such cases are usually
|
206
166
|
handled transparently by returning `nil` values or empty collections from
|
@@ -295,107 +255,11 @@ class App < Sinatra::Base
|
|
295
255
|
end
|
296
256
|
```
|
297
257
|
|
298
|
-
### Public APIs
|
299
|
-
|
300
|
-
Sinja makes a few APIs public to help you work around edge cases in your
|
301
|
-
application.
|
302
|
-
|
303
|
-
#### Commonly Used
|
304
|
-
|
305
|
-
**can?**
|
306
|
-
: Takes the symbol of an action helper and returns true if the current user has
|
307
|
-
access to call that action helper for the current resource using the `role`
|
308
|
-
helper and role definitions detailed under "Authorization" below.
|
309
|
-
|
310
|
-
**role?**
|
311
|
-
: Takes a list of role(s) and returns true if it has members in common with the
|
312
|
-
current user's role(s).
|
313
|
-
|
314
|
-
**sideloaded?**
|
315
|
-
: Returns true if the request was invoked from another action helper.
|
316
|
-
|
317
|
-
#### Less-Commonly Used
|
318
|
-
|
319
|
-
These are helpful if you want to add some custom routes to your Sinja
|
320
|
-
application.
|
321
|
-
|
322
|
-
**data**
|
323
|
-
: Returns the `data` key of the deserialized request payload (with symbolized
|
324
|
-
names).
|
325
|
-
|
326
|
-
**dedasherize**
|
327
|
-
: Takes a string or symbol and returns the string or symbol with any and all
|
328
|
-
dashes transliterated to underscores, and camelCase converted to snake_case.
|
329
|
-
|
330
|
-
**dedasherize_names**
|
331
|
-
: Takes a hash and returns the hash with its keys dedasherized (deeply).
|
332
|
-
|
333
|
-
**serialize_model**
|
334
|
-
: Takes a model (and optional hash of JSONAPI::Serializers options) and returns
|
335
|
-
a serialized model.
|
336
|
-
|
337
|
-
**serialize_model?**
|
338
|
-
: Takes a model (and optional hash of JSONAPI::Serializers options) and returns
|
339
|
-
a serialized model if non-`nil`, or the root metadata if present, or a HTTP
|
340
|
-
status 204.
|
341
|
-
|
342
|
-
**serialize_models**
|
343
|
-
: Takes an array of models (and optional hash of JSONAPI::Serializers options)
|
344
|
-
and returns a serialized collection.
|
345
|
-
|
346
|
-
**serialize_models?**
|
347
|
-
: Takes an array of models (and optional hash of JSONAPI::Serializers options)
|
348
|
-
and returns a serialized collection if non-empty, or the root metadata if
|
349
|
-
present, or a HTTP status 204.
|
350
|
-
|
351
|
-
### Performance
|
352
|
-
|
353
|
-
Although there is some heavy metaprogramming happening at boot time, the end
|
354
|
-
result is simply a collection of Sinatra namespaces, routes, filters,
|
355
|
-
conditions, helpers, etc., and Sinja applications should perform as if you had
|
356
|
-
written them verbosely. The main caveat is that there are quite a few block
|
357
|
-
closures, which don't perform as well as normal methods in Ruby. Feedback
|
358
|
-
welcome.
|
359
|
-
|
360
|
-
### Extensions
|
361
|
-
|
362
|
-
Sinja extensions provide additional helpers, DSL, and configuration, packaging
|
363
|
-
ORM-specific boilerplate as separate gems. At the moment, the only available
|
364
|
-
extension is for [Sequel][30], but community contributions are welcome!
|
365
|
-
|
366
|
-
### Comparison with JSONAPI::Resources
|
367
|
-
|
368
|
-
| Feature | JR | Sinja |
|
369
|
-
| :-------------- | :------------------------------- | :------------------------------------------------ |
|
370
|
-
| Serializer | Built-in | [JSONAPI::Serializers][3] |
|
371
|
-
| Framework | Rails | Sinatra, but easy to mount within others |
|
372
|
-
| Routing | ActionDispatch::Routing | Mustermann |
|
373
|
-
| Caching | ActiveSupport::Cache | BYO |
|
374
|
-
| ORM | ActiveRecord/ActiveModel | BYO |
|
375
|
-
| Authorization | [Pundit][9] | Role-based |
|
376
|
-
| Immutability | `immutable` method | Omit mutator action helpers (e.g. `update`) |
|
377
|
-
| Fetchability | `fetchable_fields` method | Omit attributes in Serializer |
|
378
|
-
| Creatability | `creatable_fields` method | Handle in `create` action helper or Model\* |
|
379
|
-
| Updatability | `updatable_fields` method | Handle in `update` action helper or Model\* |
|
380
|
-
| Sortability | `sortable_fields` method | `sort` helper and `:sort_by` option |
|
381
|
-
| Default sorting | `default_sort` method | Set default for `params[:sort]` |
|
382
|
-
| Context | `context` method | Rack middleware (e.g. `env['context']`) |
|
383
|
-
| Attributes | Define in Model and Resource | Define in Model\* and Serializer |
|
384
|
-
| Formatting | `:format` attribute keyword | Define attribute as a method in Serialier |
|
385
|
-
| Relationships | Define in Model and Resource | Define in Model, Resource, and Serializer |
|
386
|
-
| Filters | `filter(s)` keywords | `filter` helper and `:filter_by` option |
|
387
|
-
| Default filters | `:default` filter keyword | Set default for `params[:filter]` |
|
388
|
-
| Pagination | JSONAPI::Paginator | `page` helper and `page_using` configurable |
|
389
|
-
| Meta | `meta` method | Serializer `:meta` option |
|
390
|
-
| Primary keys | `resource_key_type` configurable | Serializer `id` method |
|
391
|
-
|
392
|
-
\* - Depending on your ORM.
|
393
|
-
|
394
258
|
## Basic Usage
|
395
259
|
|
396
260
|
You'll need a database schema and models (using the engine and ORM of your
|
397
261
|
choice) and [serializers][3] to get started. Create a new Sinatra application
|
398
|
-
(classic or modular) to hold all your {json:api}
|
262
|
+
(classic or modular) to hold all your {json:api} controllers and (if modular)
|
399
263
|
register this extension. Instead of defining routes with `get`, `post`, etc. as
|
400
264
|
you normally would, define `resource` blocks with action helpers and `has_one`
|
401
265
|
and `has_many` relationship blocks (with their own action helpers). Sinja will
|
@@ -417,7 +281,8 @@ these settings.
|
|
417
281
|
* Registers [Sinatra::Namespace][21] and [Mustermann][25]
|
418
282
|
* Disables [Rack::Protection][6] (can be reenabled with `enable :protection` or
|
419
283
|
by manually `use`-ing the Rack::Protection middleware)
|
420
|
-
* Disables static file routes (can be reenabled with `enable :static
|
284
|
+
* Disables static file routes (can be reenabled with `enable :static`; be sure
|
285
|
+
to reenable Rack::Protection::PathTraversal as well)
|
421
286
|
* Disables "classy" error pages (in favor of "classy" {json:api} error documents)
|
422
287
|
* Adds an `:api_json` MIME-type (`application/vnd.api+json`)
|
423
288
|
* Enforces strict checking of the `Accept` and `Content-Type` request headers
|
@@ -440,12 +305,12 @@ their defaults shown):
|
|
440
305
|
configure_jsonapi do |c|
|
441
306
|
#c.conflict_exceptions = [] # see "Conflicts" below
|
442
307
|
|
308
|
+
#c.not_found_exceptions = [] # see "Missing Records" below
|
309
|
+
|
443
310
|
# see "Validations" below
|
444
311
|
#c.validation_exceptions = []
|
445
312
|
#c.validation_formatter = ->{ [] }
|
446
313
|
|
447
|
-
#c.not_found_exceptions = [] # see "Missing Records" below
|
448
|
-
|
449
314
|
# see "Authorization" below
|
450
315
|
#c.default_roles = {}
|
451
316
|
#c.default_has_one_roles = {}
|
@@ -481,8 +346,9 @@ Much of Sinja's advanced functionality (e.g. updating and destroying resources,
|
|
481
346
|
relationship routes) is dependent upon its ability to locate the corresponding
|
482
347
|
resource for a request. To enable these features, define an ordinary helper
|
483
348
|
method named `find` in your resource definition that takes a single ID argument
|
484
|
-
and returns the corresponding object. Once defined, a `resource` object will
|
485
|
-
|
349
|
+
and returns the corresponding object. Once defined, a `resource` object will be
|
350
|
+
made available in any action helpers that operate on a single (parent)
|
351
|
+
resource.
|
486
352
|
|
487
353
|
```ruby
|
488
354
|
resource :posts do
|
@@ -574,13 +440,6 @@ any given resource block.)
|
|
574
440
|
Take an array of IDs and return an equally-lengthed array of objects to
|
575
441
|
serialize on the response. See "Coalesced Find Requests" below.
|
576
442
|
|
577
|
-
##### `create {|attr, id| ..}` => id, Object?
|
578
|
-
|
579
|
-
With client-generated IDs: Take a hash of (dedasherized) attributes and a
|
580
|
-
client-generated ID, create a new resource, and return the ID and optionally
|
581
|
-
the created resource. (Note that only one or the other `create` action helpers
|
582
|
-
is allowed in any given resource block.)
|
583
|
-
|
584
443
|
##### `create {|attr| ..}` => id, Object
|
585
444
|
|
586
445
|
Without client-generated IDs: Take a hash of (dedasherized) attributes, create
|
@@ -588,6 +447,13 @@ a new resource, and return the server-generated ID and the created resource.
|
|
588
447
|
(Note that only one or the other `create` action helpers is allowed in any
|
589
448
|
given resource block.)
|
590
449
|
|
450
|
+
##### `create {|attr, id| ..}` => id, Object?
|
451
|
+
|
452
|
+
With client-generated IDs: Take a hash of (dedasherized) attributes and a
|
453
|
+
client-generated ID, create a new resource, and return the ID and optionally
|
454
|
+
the created resource. (Note that only one or the other `create` action helpers
|
455
|
+
is allowed in any given resource block.)
|
456
|
+
|
591
457
|
##### `update {|attr| ..}` => Object?
|
592
458
|
|
593
459
|
Take a hash of (dedasherized) attributes, update `resource`, and optionally
|
@@ -885,8 +751,8 @@ The {json:api} specification states that any unhandled query parameters should
|
|
885
751
|
cause the request to abort with HTTP status 400. To enforce this requirement,
|
886
752
|
Sinja maintains a global "whitelist" of acceptable query parameters as well as
|
887
753
|
a per-route whitelist, and interrogates your application to see which features
|
888
|
-
it supports; for example, a route may allow a `filter` query
|
889
|
-
may not have defined a `filter` helper.
|
754
|
+
it supports; for example, a route may generally allow a `filter` query
|
755
|
+
parameter, but you may not have defined a `filter` helper.
|
890
756
|
|
891
757
|
To let a custom query parameter through to the standard action helpers, add it
|
892
758
|
to the `query_params` configurable with a `nil` value:
|
@@ -1235,7 +1101,6 @@ end
|
|
1235
1101
|
The following matrix outlines which combinations of action helpers and
|
1236
1102
|
`:sideload_on` options enable which behaviors:
|
1237
1103
|
|
1238
|
-
<small>
|
1239
1104
|
<table>
|
1240
1105
|
<thead>
|
1241
1106
|
<tr>
|
@@ -1274,7 +1139,6 @@ The following matrix outlines which combinations of action helpers and
|
|
1274
1139
|
</tr>
|
1275
1140
|
</tbody>
|
1276
1141
|
</table>
|
1277
|
-
</small>
|
1278
1142
|
|
1279
1143
|
#### Avoiding Null Foreign Keys
|
1280
1144
|
|
@@ -1428,14 +1292,86 @@ class MyApp < Sinatra::Base
|
|
1428
1292
|
end
|
1429
1293
|
```
|
1430
1294
|
|
1295
|
+
## Extensions
|
1296
|
+
|
1297
|
+
Sinja extensions provide additional helpers, DSL, and ORM-specific boilerplate
|
1298
|
+
as separate gems. Community contributions welcome!
|
1299
|
+
|
1300
|
+
### Sequel
|
1301
|
+
|
1302
|
+
Please see [Sinja::Sequel][30] for more information.
|
1303
|
+
|
1431
1304
|
## Application Concerns
|
1432
1305
|
|
1306
|
+
### Performance
|
1307
|
+
|
1308
|
+
Although there is some heavy metaprogramming happening at boot time, the end
|
1309
|
+
result is simply a collection of Sinatra namespaces, routes, filters,
|
1310
|
+
conditions, helpers, etc., and Sinja applications should perform as if you had
|
1311
|
+
written them verbosely. The main caveat is that there are quite a few block
|
1312
|
+
closures, which don't perform as well as normal methods in Ruby. Feedback
|
1313
|
+
welcome.
|
1314
|
+
|
1315
|
+
### Public APIs
|
1316
|
+
|
1317
|
+
Sinja makes a few APIs public to help you work around edge cases in your
|
1318
|
+
application.
|
1319
|
+
|
1320
|
+
#### Commonly Used
|
1321
|
+
|
1322
|
+
**can?**
|
1323
|
+
: Takes the symbol of an action helper and returns true if the current user has
|
1324
|
+
access to call that action helper for the current resource using the `role`
|
1325
|
+
helper and role definitions detailed under "Authorization" below.
|
1326
|
+
|
1327
|
+
**role?**
|
1328
|
+
: Takes a list of role(s) and returns true if it has members in common with the
|
1329
|
+
current user's role(s).
|
1330
|
+
|
1331
|
+
**sideloaded?**
|
1332
|
+
: Returns true if the request was invoked from another action helper.
|
1333
|
+
|
1334
|
+
#### Less-Commonly Used
|
1335
|
+
|
1336
|
+
These are helpful if you want to add some custom routes to your Sinja
|
1337
|
+
application.
|
1338
|
+
|
1339
|
+
**data**
|
1340
|
+
: Returns the `data` key of the deserialized request payload (with symbolized
|
1341
|
+
names).
|
1342
|
+
|
1343
|
+
**dedasherize**
|
1344
|
+
: Takes a string or symbol and returns the string or symbol with any and all
|
1345
|
+
dashes transliterated to underscores, and camelCase converted to snake_case.
|
1346
|
+
|
1347
|
+
**dedasherize_names**
|
1348
|
+
: Takes a hash and returns the hash with its keys dedasherized (deeply).
|
1349
|
+
|
1350
|
+
**serialize_model**
|
1351
|
+
: Takes a model (and optional hash of JSONAPI::Serializers options) and returns
|
1352
|
+
a serialized model.
|
1353
|
+
|
1354
|
+
**serialize_model?**
|
1355
|
+
: Takes a model (and optional hash of JSONAPI::Serializers options) and returns
|
1356
|
+
a serialized model if non-`nil`, or the root metadata if present, or a HTTP
|
1357
|
+
status 204.
|
1358
|
+
|
1359
|
+
**serialize_models**
|
1360
|
+
: Takes an array of models (and optional hash of JSONAPI::Serializers options)
|
1361
|
+
and returns a serialized collection.
|
1362
|
+
|
1363
|
+
**serialize_models?**
|
1364
|
+
: Takes an array of models (and optional hash of JSONAPI::Serializers options)
|
1365
|
+
and returns a serialized collection if non-empty, or the root metadata if
|
1366
|
+
present, or a HTTP status 204.
|
1367
|
+
|
1433
1368
|
### Sinja or Sinatra::JSONAPI
|
1434
1369
|
|
1435
1370
|
Everything is dual-namespaced under both Sinatra::JSONAPI and Sinja, and Sinja
|
1436
1371
|
requires Sinatra::Base, so this:
|
1437
1372
|
|
1438
1373
|
```ruby
|
1374
|
+
require 'sinatra/base'
|
1439
1375
|
require 'sinatra/jsonapi'
|
1440
1376
|
|
1441
1377
|
class App < Sinatra::Base
|
@@ -1527,6 +1463,34 @@ applications will behave according to the {json:api} spec (as long as you
|
|
1527
1463
|
follow the usage documented in this README) and focus on testing your business
|
1528
1464
|
logic.
|
1529
1465
|
|
1466
|
+
## Comparison with JSONAPI::Resources
|
1467
|
+
|
1468
|
+
| Feature | JR | Sinja |
|
1469
|
+
| :-------------- | :------------------------------- | :------------------------------------------------ |
|
1470
|
+
| Serializer | Built-in | [JSONAPI::Serializers][3] |
|
1471
|
+
| Framework | Rails | Sinatra, but easy to mount within others |
|
1472
|
+
| Routing | ActionDispatch::Routing | Mustermann |
|
1473
|
+
| Caching | ActiveSupport::Cache | BYO |
|
1474
|
+
| ORM | ActiveRecord/ActiveModel | BYO |
|
1475
|
+
| Authorization | [Pundit][9] | Role-based |
|
1476
|
+
| Immutability | `immutable` method | Omit mutator action helpers (e.g. `update`) |
|
1477
|
+
| Fetchability | `fetchable_fields` method | Omit attributes in Serializer |
|
1478
|
+
| Creatability | `creatable_fields` method | Handle in `create` action helper or Model\* |
|
1479
|
+
| Updatability | `updatable_fields` method | Handle in `update` action helper or Model\* |
|
1480
|
+
| Sortability | `sortable_fields` method | `sort` helper and `:sort_by` option |
|
1481
|
+
| Default sorting | `default_sort` method | Set default for `params[:sort]` |
|
1482
|
+
| Context | `context` method | Rack middleware (e.g. `env['context']`) |
|
1483
|
+
| Attributes | Define in Model and Resource | Define in Model\* and Serializer |
|
1484
|
+
| Formatting | `:format` attribute keyword | Define attribute as a method in Serialier |
|
1485
|
+
| Relationships | Define in Model and Resource | Define in Model, Resource, and Serializer |
|
1486
|
+
| Filters | `filter(s)` keywords | `filter` helper and `:filter_by` option |
|
1487
|
+
| Default filters | `:default` filter keyword | Set default for `params[:filter]` |
|
1488
|
+
| Pagination | JSONAPI::Paginator | `page` helper and `page_using` configurable |
|
1489
|
+
| Meta | `meta` method | Serializer `:meta` option |
|
1490
|
+
| Primary keys | `resource_key_type` configurable | Serializer `id` method |
|
1491
|
+
|
1492
|
+
\* – Depending on your ORM.
|
1493
|
+
|
1530
1494
|
## Development
|
1531
1495
|
|
1532
1496
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
@@ -1555,7 +1519,7 @@ License](http://opensource.org/licenses/MIT).
|
|
1555
1519
|
[4]: http://www.rubydoc.info/github/rack/rack/master/Rack/URLMap
|
1556
1520
|
[5]: http://rodauth.jeremyevans.net
|
1557
1521
|
[6]: https://github.com/sinatra/sinatra/tree/master/rack-protection
|
1558
|
-
[7]: http://jsonapi.org/format/
|
1522
|
+
[7]: http://jsonapi.org/format/1.0/
|
1559
1523
|
[8]: https://github.com/cerebris/jsonapi-resources
|
1560
1524
|
[9]: https://github.com/cerebris/jsonapi-resources#authorization
|
1561
1525
|
[10]: http://www.sinatrarb.com/extensions-wild.html
|
@@ -1574,8 +1538,9 @@ License](http://opensource.org/licenses/MIT).
|
|
1574
1538
|
[23]: http://jsonapi.org/recommendations/#patchless-clients
|
1575
1539
|
[24]: http://www.rubydoc.info/github/rack/rack/Rack/MethodOverride
|
1576
1540
|
[25]: http://www.sinatrarb.com/mustermann/
|
1577
|
-
[26]: https://github.
|
1541
|
+
[26]: https://jsonapi-suite.github.io/jsonapi_suite/
|
1578
1542
|
[27]: https://github.com/coryodaniel/munson
|
1579
1543
|
[28]: https://github.com/chingor13/json_api_client
|
1580
1544
|
[29]: https://github.com/brynary/rack-test
|
1581
1545
|
[30]: https://github.com/mwpastore/sinja-sequel
|
1546
|
+
[31]: http://jsonapi.org/implementations/#server-libraries-ruby
|
data/lib/sinatra/jsonapi.rb
CHANGED
data/lib/sinja.rb
CHANGED
@@ -14,71 +14,11 @@ require 'sinja/version'
|
|
14
14
|
|
15
15
|
module Sinja
|
16
16
|
MIME_TYPE = 'application/vnd.api+json'
|
17
|
-
ERROR_CODES =
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
NotAcceptableError,
|
23
|
-
ConflictError,
|
24
|
-
UnsupportedTypeError
|
25
|
-
].map! { |c| [c.new.http_status, c] }.to_h.tap do |h|
|
26
|
-
h[422] = UnprocessibleEntityError
|
27
|
-
end.freeze
|
28
|
-
|
29
|
-
def resource(resource_name, konst=nil, &block)
|
30
|
-
abort "Must supply proc constant or block for `resource'" \
|
31
|
-
unless block = (konst if konst.is_a?(Proc)) || block
|
32
|
-
|
33
|
-
resource_name = resource_name.to_s
|
34
|
-
.pluralize
|
35
|
-
.dasherize
|
36
|
-
.to_sym
|
37
|
-
|
38
|
-
# trigger default procs
|
39
|
-
config = _sinja.resource_config[resource_name]
|
40
|
-
|
41
|
-
namespace "/#{resource_name}" do
|
42
|
-
define_singleton_method(:_resource_config) { config }
|
43
|
-
define_singleton_method(:resource_config) { config[:resource] }
|
44
|
-
|
45
|
-
helpers do
|
46
|
-
define_method(:sanity_check!) do |*args|
|
47
|
-
super(resource_name, *args)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
before %r{/(?<id>[^/]+)(?:/.+)?} do |id|
|
52
|
-
self.resource =
|
53
|
-
if env.key?('sinja.resource')
|
54
|
-
env['sinja.resource']
|
55
|
-
elsif respond_to?(:find)
|
56
|
-
find(id)
|
57
|
-
end
|
58
|
-
|
59
|
-
raise NotFoundError, "Resource '#{id}' not found" unless resource
|
60
|
-
end
|
61
|
-
|
62
|
-
register Resource
|
63
|
-
|
64
|
-
instance_eval(&block)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
alias_method :resources, :resource
|
69
|
-
|
70
|
-
def sinja
|
71
|
-
if block_given?
|
72
|
-
yield _sinja
|
73
|
-
else
|
74
|
-
_sinja
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
alias_method :configure_jsonapi, :sinja
|
79
|
-
def freeze_jsonapi
|
80
|
-
_sinja.freeze
|
81
|
-
end
|
17
|
+
ERROR_CODES = ObjectSpace.each_object(Class).to_a
|
18
|
+
.keep_if { |klass| klass < HttpError }
|
19
|
+
.map! { |c| [(c.const_get(:HTTP_STATUS) rescue nil), c] }
|
20
|
+
.delete_if { |a| a.first.nil? }
|
21
|
+
.to_h.freeze
|
82
22
|
|
83
23
|
def self.registered(app)
|
84
24
|
app.register Mustermann if Sinatra::VERSION[/^\d+/].to_i < 2
|
@@ -101,20 +41,23 @@ module Sinja
|
|
101
41
|
end
|
102
42
|
end
|
103
43
|
|
104
|
-
app.set :
|
44
|
+
app.set :qcaptures do |*index|
|
105
45
|
condition do
|
106
46
|
@qcaptures ||= []
|
47
|
+
|
107
48
|
index.to_h.all? do |key, subkeys|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
49
|
+
key = key.to_s
|
50
|
+
|
51
|
+
Hash === params[key] && params[key].any? && [*subkeys].all? do |subkey|
|
52
|
+
subkey = subkey.to_s
|
53
|
+
|
54
|
+
# TODO: What if deleting one is successful, but not another?
|
55
|
+
# We'll need to restore the hash to its original state.
|
56
|
+
@qcaptures << params[key].delete(subkey) if params[key].key?(subkey)
|
57
|
+
end.tap do |ok|
|
58
|
+
# If us deleting key(s) causes the hash to become empty, delete it.
|
59
|
+
params.delete(key) if ok && params[key].empty?
|
60
|
+
end
|
118
61
|
end
|
119
62
|
end
|
120
63
|
end
|
@@ -132,7 +75,7 @@ module Sinja
|
|
132
75
|
# Ignore interal Sinatra query parameters (e.g. :captures) and any
|
133
76
|
# "known" query parameter set to `nil' in the configurable.
|
134
77
|
next if !env['rack.request.query_hash'].key?(key.to_s) ||
|
135
|
-
settings._sinja.query_params.fetch(key, :
|
78
|
+
settings._sinja.query_params.fetch(key, :__NOT_FOUND__).nil?
|
136
79
|
|
137
80
|
raise BadRequestError, "`#{key}' query parameter not allowed" \
|
138
81
|
unless allow_params.include?(key)
|
@@ -148,13 +91,13 @@ module Sinja
|
|
148
91
|
|
149
92
|
return true if env['sinja.normalized'] == params.object_id
|
150
93
|
|
151
|
-
settings._sinja.query_params.each do |key,
|
152
|
-
next if
|
94
|
+
settings._sinja.query_params.each do |key, default_value|
|
95
|
+
next if default_value.nil?
|
153
96
|
|
154
97
|
if respond_to?("normalize_#{key}_params")
|
155
|
-
params[key.to_s] = send("normalize_#{key}_params")
|
98
|
+
params[key.to_s] = send("normalize_#{key}_params", default_value)
|
156
99
|
else
|
157
|
-
params[key.to_s] ||=
|
100
|
+
params[key.to_s] ||= default_value
|
158
101
|
end
|
159
102
|
end
|
160
103
|
|
@@ -186,6 +129,8 @@ module Sinja
|
|
186
129
|
|
187
130
|
if method_defined?(:bad_request?)
|
188
131
|
# This screws up our error-handling logic in Sinatra 2.0, so monkeypatch it.
|
132
|
+
# https://github.com/sinatra/sinatra/issues/1211
|
133
|
+
# https://github.com/sinatra/sinatra/pull/1212
|
189
134
|
def bad_request?
|
190
135
|
false
|
191
136
|
end
|
@@ -209,8 +154,8 @@ module Sinja
|
|
209
154
|
end
|
210
155
|
end
|
211
156
|
|
212
|
-
def normalize_filter_params
|
213
|
-
return
|
157
|
+
def normalize_filter_params(default_value)
|
158
|
+
return default_value unless params[:filter]&.any?
|
214
159
|
|
215
160
|
raise BadRequestError, "Unsupported `filter' query parameter(s)" \
|
216
161
|
unless respond_to?(:filter)
|
@@ -230,7 +175,7 @@ module Sinja
|
|
230
175
|
raise BadRequestError, "Invalid `filter' query parameter(s)"
|
231
176
|
end
|
232
177
|
|
233
|
-
def normalize_sort_params
|
178
|
+
def normalize_sort_params(_default_value)
|
234
179
|
return {} unless params[:sort]&.any?
|
235
180
|
|
236
181
|
raise BadRequestError, "Unsupported `sort' query parameter(s)" \
|
@@ -252,8 +197,8 @@ module Sinja
|
|
252
197
|
raise BadRequestError, "Invalid `sort' query parameter(s)"
|
253
198
|
end
|
254
199
|
|
255
|
-
def normalize_page_params
|
256
|
-
return
|
200
|
+
def normalize_page_params(default_value)
|
201
|
+
return default_value unless params[:page]&.any?
|
257
202
|
|
258
203
|
raise BadRequestError, "Unsupported `page' query parameter(s)" \
|
259
204
|
unless respond_to?(:page)
|
@@ -267,25 +212,17 @@ module Sinja
|
|
267
212
|
return if params[:page].empty?
|
268
213
|
|
269
214
|
return params[:page] \
|
270
|
-
if params[:page].keys
|
215
|
+
if (params[:page].keys - settings._sinja.page_using.keys).empty?
|
271
216
|
|
272
217
|
raise BadRequestError, "Invalid `page' query parameter(s)"
|
273
218
|
end
|
274
219
|
|
275
220
|
def filter_sort_page?(action)
|
276
|
-
return enum_for(__callee__, action)
|
221
|
+
return enum_for(__callee__, action) unless block_given?
|
277
222
|
|
278
|
-
if filter = filter_by?(action)
|
279
|
-
|
280
|
-
end
|
281
|
-
|
282
|
-
if sort = sort_by?(action)
|
283
|
-
yield :sort, sort
|
284
|
-
end
|
285
|
-
|
286
|
-
if page = page_using?
|
287
|
-
yield :page, page
|
288
|
-
end
|
223
|
+
if filter = filter_by?(action) then yield :filter, filter end
|
224
|
+
if sort = sort_by?(action) then yield :sort, sort end
|
225
|
+
if page = page_using? then yield :page, page end
|
289
226
|
end
|
290
227
|
|
291
228
|
def filter_sort_page(collection, opts)
|
@@ -347,7 +284,7 @@ module Sinja
|
|
347
284
|
end
|
348
285
|
|
349
286
|
app.after do
|
350
|
-
body serialize_response_body if response.
|
287
|
+
body serialize_response_body if response.successful?
|
351
288
|
end
|
352
289
|
|
353
290
|
app.not_found do
|
@@ -376,6 +313,60 @@ module Sinja
|
|
376
313
|
end
|
377
314
|
end
|
378
315
|
|
316
|
+
def resource(resource_name, konst=nil, &block)
|
317
|
+
abort "Must supply proc constant or block for `resource'" \
|
318
|
+
unless block = (konst if konst.is_a?(Proc)) || block
|
319
|
+
|
320
|
+
resource_name = resource_name.to_s
|
321
|
+
.pluralize
|
322
|
+
.dasherize
|
323
|
+
.to_sym
|
324
|
+
|
325
|
+
# trigger default procs
|
326
|
+
config = _sinja.resource_config[resource_name]
|
327
|
+
|
328
|
+
namespace "/#{resource_name}" do
|
329
|
+
define_singleton_method(:_resource_config) { config }
|
330
|
+
define_singleton_method(:resource_config) { config[:resource] }
|
331
|
+
|
332
|
+
helpers do
|
333
|
+
define_method(:sanity_check!) do |*args|
|
334
|
+
super(resource_name, *args)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
before %r{/(?<id>[^/]+)(?:/.+)?} do |id|
|
339
|
+
self.resource =
|
340
|
+
if env.key?('sinja.resource')
|
341
|
+
env['sinja.resource']
|
342
|
+
elsif respond_to?(:find)
|
343
|
+
find(id)
|
344
|
+
end
|
345
|
+
|
346
|
+
raise NotFoundError, "Resource '#{id}' not found" unless resource
|
347
|
+
end
|
348
|
+
|
349
|
+
register Resource
|
350
|
+
|
351
|
+
instance_eval(&block)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
alias_method :resources, :resource
|
356
|
+
|
357
|
+
def sinja
|
358
|
+
if block_given?
|
359
|
+
yield _sinja
|
360
|
+
else
|
361
|
+
_sinja
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
alias_method :configure_jsonapi, :sinja
|
366
|
+
def freeze_jsonapi
|
367
|
+
_sinja.freeze
|
368
|
+
end
|
369
|
+
|
379
370
|
def self.extended(base)
|
380
371
|
def base.route(*, **opts)
|
381
372
|
opts[:qparams] ||= []
|
data/lib/sinja/errors.rb
CHANGED
@@ -27,34 +27,50 @@ module Sinja
|
|
27
27
|
end
|
28
28
|
|
29
29
|
class BadRequestError < HttpError
|
30
|
-
|
30
|
+
HTTP_STATUS = 400
|
31
|
+
|
32
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
31
33
|
end
|
32
34
|
|
33
35
|
class ForbiddenError < HttpError
|
34
|
-
|
36
|
+
HTTP_STATUS = 403
|
37
|
+
|
38
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
35
39
|
end
|
36
40
|
|
37
41
|
class NotFoundError < HttpError
|
38
|
-
|
42
|
+
HTTP_STATUS = 404
|
43
|
+
|
44
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
39
45
|
end
|
40
46
|
|
41
47
|
class MethodNotAllowedError < HttpError
|
42
|
-
|
48
|
+
HTTP_STATUS = 405
|
49
|
+
|
50
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
43
51
|
end
|
44
52
|
|
45
53
|
class NotAcceptableError < HttpError
|
46
|
-
|
54
|
+
HTTP_STATUS = 406
|
55
|
+
|
56
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
47
57
|
end
|
48
58
|
|
49
59
|
class ConflictError < HttpError
|
50
|
-
|
60
|
+
HTTP_STATUS = 409
|
61
|
+
|
62
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
51
63
|
end
|
52
64
|
|
53
65
|
class UnsupportedTypeError < HttpError
|
54
|
-
|
66
|
+
HTTP_STATUS = 415
|
67
|
+
|
68
|
+
def initialize(*args) super(HTTP_STATUS, *args) end
|
55
69
|
end
|
56
70
|
|
57
71
|
class UnprocessibleEntityError < HttpError
|
72
|
+
HTTP_STATUS = 422
|
73
|
+
|
58
74
|
attr_reader :tuples
|
59
75
|
|
60
76
|
def initialize(tuples=[])
|
@@ -63,7 +79,7 @@ module Sinja
|
|
63
79
|
fail 'Tuples not properly formatted' \
|
64
80
|
unless @tuples.any? && @tuples.all? { |t| Array === t && t.length == 2 }
|
65
81
|
|
66
|
-
super(
|
82
|
+
super(HTTP_STATUS)
|
67
83
|
end
|
68
84
|
end
|
69
85
|
end
|
@@ -26,7 +26,7 @@ module Sinja
|
|
26
26
|
app.get '', :qparams=>%i[include fields filter sort page], :actions=>:fetch do
|
27
27
|
fsp_opts = filter_sort_page?(:fetch)
|
28
28
|
collection, opts = fetch
|
29
|
-
collection, pagination = filter_sort_page(collection, fsp_opts)
|
29
|
+
collection, pagination = filter_sort_page(collection, fsp_opts.to_h)
|
30
30
|
serialize_models(collection, opts, pagination)
|
31
31
|
end
|
32
32
|
|
data/lib/sinja/resource.rb
CHANGED
@@ -13,6 +13,14 @@ require 'sinja/resource_routes'
|
|
13
13
|
|
14
14
|
module Sinja
|
15
15
|
module Resource
|
16
|
+
def self.registered(app)
|
17
|
+
app.helpers Helpers::Relationships do
|
18
|
+
attr_accessor :resource
|
19
|
+
end
|
20
|
+
|
21
|
+
app.register ResourceRoutes
|
22
|
+
end
|
23
|
+
|
16
24
|
def def_action_helper(context, action, allow_opts=[])
|
17
25
|
abort "Action helper names can't overlap with Sinatra DSL" \
|
18
26
|
if Sinatra::Base.respond_to?(action)
|
@@ -72,14 +80,6 @@ module Sinja
|
|
72
80
|
end
|
73
81
|
end
|
74
82
|
|
75
|
-
def self.registered(app)
|
76
|
-
app.helpers Helpers::Relationships do
|
77
|
-
attr_accessor :resource
|
78
|
-
end
|
79
|
-
|
80
|
-
app.register ResourceRoutes
|
81
|
-
end
|
82
|
-
|
83
83
|
%i[has_one has_many].each do |rel_type|
|
84
84
|
define_method(rel_type) do |rel, &block|
|
85
85
|
rel = rel.to_s
|
@@ -9,11 +9,11 @@ module Sinja
|
|
9
9
|
app.def_action_helper(app, :update, :roles)
|
10
10
|
app.def_action_helper(app, :destroy, :roles)
|
11
11
|
|
12
|
-
app.head '', :
|
12
|
+
app.head '', :qcaptures=>{ :filter=>:id } do
|
13
13
|
allow :get=>:show
|
14
14
|
end
|
15
15
|
|
16
|
-
app.get '', :
|
16
|
+
app.get '', :qcaptures=>{ :filter=>:id }, :qparams=>%i[include fields], :actions=>:show do
|
17
17
|
ids = @qcaptures.first # TODO: Get this as a block parameter?
|
18
18
|
ids = ids.split(',') if String === ids
|
19
19
|
ids = [*ids].tap(&:uniq!)
|
@@ -45,7 +45,7 @@ module Sinja
|
|
45
45
|
app.get '', :qparams=>%i[include fields filter sort page], :actions=>:index do
|
46
46
|
fsp_opts = filter_sort_page?(:index)
|
47
47
|
collection, opts = index
|
48
|
-
collection, pagination = filter_sort_page(collection, fsp_opts)
|
48
|
+
collection, pagination = filter_sort_page(collection, fsp_opts.to_h)
|
49
49
|
serialize_models(collection, opts, pagination)
|
50
50
|
end
|
51
51
|
|
data/lib/sinja/version.rb
CHANGED
data/sinja.gemspec
CHANGED
@@ -10,6 +10,22 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['mike@oobak.org']
|
11
11
|
|
12
12
|
spec.summary = 'RESTful, {json:api}-compliant web services in Sinatra'
|
13
|
+
spec.description = <<~EOF
|
14
|
+
Sinja is a Sinatra extension for quickly building RESTful,
|
15
|
+
{json:api}-compliant web services, leveraging the excellent
|
16
|
+
JSONAPI::Serializers gem for payload serialization. It enhances Sinatra's
|
17
|
+
DSL to enable resource-, relationship-, and role-centric API development,
|
18
|
+
and it configures Sinatra with the proper settings, MIME-types, filters,
|
19
|
+
conditions, and error-handling.
|
20
|
+
|
21
|
+
There are many parsing (deserializing) and rendering (serializing) and
|
22
|
+
so-called "JSON API" libraries available for Ruby, but relatively few that
|
23
|
+
attempt to correctly implement the entire {json:api} specification,
|
24
|
+
including routing, request header and query parameter checking, and
|
25
|
+
relationship side-loading. Sinja lets you focus on the business logic of
|
26
|
+
your applications without worrying about the specification, and without
|
27
|
+
pulling in a heavy framework like Rails. It's lightweight and ORM-agnostic!
|
28
|
+
EOF
|
13
29
|
spec.homepage = 'https://github.com/mwpastore/sinja'
|
14
30
|
spec.license = 'MIT'
|
15
31
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinja
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Pastore
|
@@ -208,7 +208,21 @@ dependencies:
|
|
208
208
|
- - "~>"
|
209
209
|
- !ruby/object:Gem::Version
|
210
210
|
version: '1.3'
|
211
|
-
description:
|
211
|
+
description: |
|
212
|
+
Sinja is a Sinatra extension for quickly building RESTful,
|
213
|
+
{json:api}-compliant web services, leveraging the excellent
|
214
|
+
JSONAPI::Serializers gem for payload serialization. It enhances Sinatra's
|
215
|
+
DSL to enable resource-, relationship-, and role-centric API development,
|
216
|
+
and it configures Sinatra with the proper settings, MIME-types, filters,
|
217
|
+
conditions, and error-handling.
|
218
|
+
|
219
|
+
There are many parsing (deserializing) and rendering (serializing) and
|
220
|
+
so-called "JSON API" libraries available for Ruby, but relatively few that
|
221
|
+
attempt to correctly implement the entire {json:api} specification,
|
222
|
+
including routing, request header and query parameter checking, and
|
223
|
+
relationship side-loading. Sinja lets you focus on the business logic of
|
224
|
+
your applications without worrying about the specification, and without
|
225
|
+
pulling in a heavy framework like Rails. It's lightweight and ORM-agnostic!
|
212
226
|
email:
|
213
227
|
- mike@oobak.org
|
214
228
|
executables: []
|