sinja 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/sinja.svg)](https://badge.fury.io/rb/sinja)
|
11
11
|
[![Dependency Status](https://gemnasium.com/badges/github.com/mwpastore/sinja.svg)](https://gemnasium.com/github.com/mwpastore/sinja)
|
12
12
|
[![Build Status](https://travis-ci.org/mwpastore/sinja.svg?branch=master)](https://travis-ci.org/mwpastore/sinja)
|
13
|
-
|
13
|
+
[![{json:api} version](https://img.shields.io/badge/%7Bjson%3Aapi%7D%20version-1.0-lightgrey.svg)][7]
|
14
14
|
[![Chat in #sinja-rb on Gitter](https://badges.gitter.im/sinja-rb/Lobby.svg)](https://gitter.im/sinja-rb/Lobby)
|
15
|
-
[![Chat in #-ember-data on Slack](https://ember-community-slackin.herokuapp.com/badge.svg)](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: []
|