sinja 1.1.0.pre1 → 1.1.0.pre2
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 +85 -11
- data/lib/sinja.rb +37 -44
- data/lib/sinja/helpers/sequel.rb +3 -3
- data/lib/sinja/relationship_routes/has_many.rb +2 -1
- data/lib/sinja/resource.rb +6 -2
- data/lib/sinja/resource_routes.rb +7 -7
- data/lib/sinja/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d785d25fe6c9bd3e61893ca5a1ec23b237d9e3cb
|
4
|
+
data.tar.gz: 0eb4c07d1691587fb117ebbfc6d2fb63a911ae81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ee40f0a379063b13846967163f181c50ec7c86303155864ecf234369e41faf1d84006c3ac4d07aeff6df13cfcd7ae1010b8055e59b9d0e06d98e666700159aa
|
7
|
+
data.tar.gz: 6c82ef3fc4fba1670526d5f2aab3fa09ea76921cfe2c8de997f8b13fd52b110fcd45ea34c03bbfaeddc7c43dd76ded5936f71bd5acc1e12f73c47058331e280c
|
data/README.md
CHANGED
@@ -35,6 +35,7 @@ the {json:api} specification is).
|
|
35
35
|
- [`resource`](#resource)
|
36
36
|
- [`index {..}` => Array](#index---array)
|
37
37
|
- [`show {|id| ..}` => Object](#show-id---object)
|
38
|
+
- [`show_many {|ids| ..}` => Array](#show_many-ids---array)
|
38
39
|
- [`create {|attr, id| ..}` => id, Object?](#create-attr-id---id-object)
|
39
40
|
- [`create {|attr| ..}` => id, Object](#create-attr---id-object)
|
40
41
|
- [`update {|attr| ..}` => Object?](#update-attr---object)
|
@@ -114,7 +115,7 @@ all other {json:api} endpoints returning 404 or 405):
|
|
114
115
|
* `POST /posts`
|
115
116
|
|
116
117
|
The resource locator and other action helpers, documented below, enable other
|
117
|
-
endpoints.
|
118
|
+
endpoints.
|
118
119
|
|
119
120
|
Of course, "modular"-style Sinatra aplications require you to register the
|
120
121
|
extension:
|
@@ -134,6 +135,8 @@ class App < Sinatra::Base
|
|
134
135
|
end
|
135
136
|
```
|
136
137
|
|
138
|
+
Please see the [demo-app](/demo-app) for a more complete example.
|
139
|
+
|
137
140
|
## Installation
|
138
141
|
|
139
142
|
Add this line to your application's Gemfile:
|
@@ -172,11 +175,11 @@ with the JsonApi adapter, [JSONAPI::Resources][8] (JR), and
|
|
172
175
|
[jsonapi-utils][26], all of which are designed to work with [Rails][16] and
|
173
176
|
[ActiveRecord][17]/[ActiveModel][18] (although they may work with [Sequel][13]
|
174
177
|
via [sequel-rails][14] and Sequel's [`:active_model` plugin][15]). Otherwise,
|
175
|
-
you might use something like Sinatra, [Roda][20], or [Grape][19] with
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
178
|
+
you might use something like Sinatra, [Roda][20], or [Grape][19] with a
|
179
|
+
(de)serialization library, your own routes, and a ton of boilerplate. The goal
|
180
|
+
of this extension is to provide most or all of the boilerplate for a Sintara
|
181
|
+
application and automate the drawing of routes based on the resource
|
182
|
+
definitions.
|
180
183
|
|
181
184
|
### Ol' Blue Eyes is Back
|
182
185
|
|
@@ -836,7 +839,7 @@ resource :foos do
|
|
836
839
|
end
|
837
840
|
```
|
838
841
|
|
839
|
-
Please see the [demo-app](/demo-app) for more complete
|
842
|
+
Please see the [demo-app](/demo-app) for a more complete example.
|
840
843
|
|
841
844
|
### Query Parameters
|
842
845
|
|
@@ -847,8 +850,8 @@ a per-route whitelist, and interrogates your application to see which features
|
|
847
850
|
it supports; for example, a route may allow a `filter` query parameter, but you
|
848
851
|
may not have defined a `filter` helper.
|
849
852
|
|
850
|
-
To
|
851
|
-
configurable with a `nil` value:
|
853
|
+
To let a custom query parameter through to the standard action helpers, add it
|
854
|
+
to the `query_params` configurable with a `nil` value:
|
852
855
|
|
853
856
|
```ruby
|
854
857
|
configure_jsonapi do |c|
|
@@ -898,6 +901,23 @@ resource :posts do
|
|
898
901
|
end
|
899
902
|
```
|
900
903
|
|
904
|
+
The easiest way to set a default filter is to tweak the post-processed query
|
905
|
+
parameter(s) in a `before_<action>` hook:
|
906
|
+
|
907
|
+
```ruby
|
908
|
+
resource :posts do
|
909
|
+
helpers do
|
910
|
+
def before_index
|
911
|
+
params['filter']['type'] ||= 'article'
|
912
|
+
end
|
913
|
+
end
|
914
|
+
|
915
|
+
index do
|
916
|
+
# ..
|
917
|
+
end
|
918
|
+
end
|
919
|
+
```
|
920
|
+
|
901
921
|
#### Sorting
|
902
922
|
|
903
923
|
Allow clients to sort the collections returned by the `index` and `fetch`
|
@@ -924,6 +944,23 @@ resource :posts do
|
|
924
944
|
end
|
925
945
|
```
|
926
946
|
|
947
|
+
The easiest way to set a default sort order is to tweak the post-processed
|
948
|
+
query parameter(s) in a `before_<action>` hook:
|
949
|
+
|
950
|
+
```ruby
|
951
|
+
resource :posts do
|
952
|
+
helpers do
|
953
|
+
def before_index
|
954
|
+
params['sort'] << [:title, :asc] if params['sort'].empty?
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
958
|
+
index do
|
959
|
+
# ..
|
960
|
+
end
|
961
|
+
end
|
962
|
+
```
|
963
|
+
|
927
964
|
#### Paging
|
928
965
|
|
929
966
|
Allow clients to page the collections returned by the `index` and `fetch`
|
@@ -965,6 +1002,23 @@ and `:size` for the above example) along with their default values (or `nil`).
|
|
965
1002
|
Please see the [Sequel helpers](/lib/sinja/helpers/sequel.rb) in this
|
966
1003
|
repository for a detailed, working example.
|
967
1004
|
|
1005
|
+
The easiest way to page a collection by default is to tweak the post-processed
|
1006
|
+
query parameter(s) in a `before_<action>` hook:
|
1007
|
+
|
1008
|
+
```ruby
|
1009
|
+
resource :posts do
|
1010
|
+
helpers do
|
1011
|
+
def before_index
|
1012
|
+
params['page']['number'] ||= 1
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
index do
|
1017
|
+
# ..
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
```
|
1021
|
+
|
968
1022
|
#### Finalizing
|
969
1023
|
|
970
1024
|
If you need to perform any additional actions on a collection after it is
|
@@ -1123,7 +1177,8 @@ either `graft` or `create`.
|
|
1123
1177
|
`graft`, `merge`, and `clear` are the only action helpers invoked by
|
1124
1178
|
sideloading. You must indicate which combinations are valid using the
|
1125
1179
|
`:sideload_on` action helper option. (Note that if you want to sideload `merge`
|
1126
|
-
on `update`, you must define a `clear` action helper
|
1180
|
+
on `update`, you must define a `clear` action helper and allow it to sideload
|
1181
|
+
on `update` as well.) For example:
|
1127
1182
|
|
1128
1183
|
```ruby
|
1129
1184
|
resource :photos do
|
@@ -1259,7 +1314,26 @@ status 404.
|
|
1259
1314
|
Optionally, to reduce round trips to the database, you may define a "special"
|
1260
1315
|
`show_many` action helper that takes an array of IDs to show. It does not take
|
1261
1316
|
`:roles` or any other options and will only be invoked if the current user has
|
1262
|
-
access to `show`. This feature is
|
1317
|
+
access to `show`. This feature is experimental.
|
1318
|
+
|
1319
|
+
Collections assembled during coalesced find requests will not be filtered,
|
1320
|
+
sorted, or paged. The easiest way to limit the number of records that can be
|
1321
|
+
queried is to define a `show_many` action helper and validate the length of the
|
1322
|
+
passed array in the `before_show_many` hook:
|
1323
|
+
|
1324
|
+
```ruby
|
1325
|
+
resource :foos do
|
1326
|
+
helpers do
|
1327
|
+
def before_show_many(ids)
|
1328
|
+
halt 413, 'You want the impossible.' if ids.length > 50
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
show_many do |ids|
|
1333
|
+
# ..
|
1334
|
+
end
|
1335
|
+
end
|
1336
|
+
```
|
1263
1337
|
|
1264
1338
|
### Patchless Clients
|
1265
1339
|
|
data/lib/sinja.rb
CHANGED
@@ -42,10 +42,8 @@ module Sinja
|
|
42
42
|
define_singleton_method(:resource_config) { config[:resource] }
|
43
43
|
|
44
44
|
helpers do
|
45
|
-
|
46
|
-
|
47
|
-
super(resource_name, *args)
|
48
|
-
end
|
45
|
+
define_method(:sanity_check!) do |*args|
|
46
|
+
super(resource_name, *args)
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
@@ -93,7 +91,7 @@ module Sinja
|
|
93
91
|
condition do
|
94
92
|
actions.each do |action|
|
95
93
|
raise ForbiddenError, 'You are not authorized to perform this action' \
|
96
|
-
unless can?(action)
|
94
|
+
unless can?(action)
|
97
95
|
raise MethodNotAllowedError, 'Action or method not implemented or supported' \
|
98
96
|
unless respond_to?(action)
|
99
97
|
end
|
@@ -178,14 +176,9 @@ module Sinja
|
|
178
176
|
end
|
179
177
|
end
|
180
178
|
|
181
|
-
def can?(
|
182
|
-
|
183
|
-
|
184
|
-
# JRuby issues with nil default_proc (fixed in 9.1.7.0?)
|
185
|
-
# https://github.com/jruby/jruby/issues/4302
|
186
|
-
#roles = config&.dig(action, :roles)
|
187
|
-
roles = config&.key?(action) && config[action][:roles]
|
188
|
-
!roles || roles.empty? || roles === memoized_role
|
179
|
+
def can?(action)
|
180
|
+
roles = settings._resource_config[:resource].fetch(action, {})[:roles]
|
181
|
+
roles.nil? || roles.empty? || roles === memoized_role
|
189
182
|
end
|
190
183
|
|
191
184
|
def content?
|
@@ -204,7 +197,7 @@ module Sinja
|
|
204
197
|
def normalize_filter_params
|
205
198
|
return {} unless params[:filter]&.any?
|
206
199
|
|
207
|
-
|
200
|
+
raise BadRequestError, "Unsupported `filter' query parameter(s)" \
|
208
201
|
unless respond_to?(:filter)
|
209
202
|
|
210
203
|
params[:filter].map do |k, v|
|
@@ -212,10 +205,17 @@ module Sinja
|
|
212
205
|
end.to_h
|
213
206
|
end
|
214
207
|
|
208
|
+
def filter_by?(action)
|
209
|
+
return true if settings.resource_config[action][:filter_by].empty? ||
|
210
|
+
params[:filter].keys.to_set.subset?(settings.resource_config[action][:filter_by])
|
211
|
+
|
212
|
+
raise BadRequestError, "Invalid `filter' query parameter(s)"
|
213
|
+
end
|
214
|
+
|
215
215
|
def normalize_sort_params
|
216
216
|
return [] unless params[:sort]&.any?
|
217
217
|
|
218
|
-
|
218
|
+
raise BadRequestError, "Unsupported `sort' query parameter(s)" \
|
219
219
|
unless respond_to?(:sort)
|
220
220
|
|
221
221
|
params[:sort].map do |k|
|
@@ -224,41 +224,40 @@ module Sinja
|
|
224
224
|
end.to_h
|
225
225
|
end
|
226
226
|
|
227
|
+
def sort_by?(action)
|
228
|
+
return true if settings.resource_config[action][:sort_by].empty? ||
|
229
|
+
params[:sort].keys.to_set.subset?(settings.resource_config[action][:sort_by])
|
230
|
+
|
231
|
+
raise BadRequestError, "Invalid `sort' query parameter(s)"
|
232
|
+
end
|
233
|
+
|
227
234
|
def normalize_page_params
|
228
235
|
return {} unless params[:page]&.any?
|
229
236
|
|
230
|
-
|
237
|
+
raise BadRequestError, "Unsupported `page' query parameter(s)" \
|
231
238
|
unless respond_to?(:page)
|
232
239
|
|
233
|
-
|
240
|
+
params[:page].map do |k, v|
|
234
241
|
[dedasherize(k).to_sym, v]
|
235
242
|
end.to_h
|
236
|
-
|
237
|
-
return h if h.keys.to_set.subset?(settings._sinja.page_using.keys.to_set)
|
238
|
-
|
239
|
-
halt 400, "Invalid `page' query parameter(s)"
|
240
243
|
end
|
241
244
|
|
242
|
-
def
|
243
|
-
|
244
|
-
halt 400, "Invalid `filter' query parameter(s)" \
|
245
|
-
unless settings.resource_config[action][:filter_by].empty? ||
|
246
|
-
params[:filter].keys.to_set.subset?(settings.resource_config[action][:filter_by])
|
245
|
+
def page_using?
|
246
|
+
return true if params[:page].keys.to_set.subset?(settings._sinja.page_using.keys.to_set)
|
247
247
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
unless params[:sort].empty?
|
252
|
-
halt 400, "Invalid `sort' query parameter(s)" \
|
253
|
-
unless settings.resource_config[action][:sort_by].empty? ||
|
254
|
-
params[:sort].keys.to_set.subset?(settings.resource_config[action][:sort_by])
|
248
|
+
raise BadRequestError, "Invalid `page' query parameter(s)"
|
249
|
+
end
|
255
250
|
|
256
|
-
|
257
|
-
|
251
|
+
def filter_sort_page?(action)
|
252
|
+
filter_by?(action) unless params[:filter].empty?
|
253
|
+
sort_by?(action) unless params[:sort].empty?
|
254
|
+
page_using? unless params[:page].empty?
|
255
|
+
end
|
258
256
|
|
259
|
-
|
260
|
-
|
261
|
-
|
257
|
+
def filter_sort_page(collection)
|
258
|
+
collection = filter(collection, params[:filter]) unless params[:filter].empty?
|
259
|
+
collection = sort(collection, params[:sort]) unless params[:sort].empty?
|
260
|
+
collection, pagination = page(collection, params[:page]) unless params[:page].empty?
|
262
261
|
|
263
262
|
collection = finalize(collection) if respond_to?(:finalize)
|
264
263
|
|
@@ -279,12 +278,6 @@ module Sinja
|
|
279
278
|
@role ||= role
|
280
279
|
end
|
281
280
|
|
282
|
-
def sideload?(resource_name, child)
|
283
|
-
return unless sideloaded?
|
284
|
-
parent = env['sinja.passthru'].to_sym
|
285
|
-
settings.resource_config[child][:sideload_on]&.include?(parent) && can?(parent)
|
286
|
-
end
|
287
|
-
|
288
281
|
def sideloaded?
|
289
282
|
env.key?('sinja.passthru')
|
290
283
|
end
|
data/lib/sinja/helpers/sequel.rb
CHANGED
@@ -27,15 +27,15 @@ module Sinja
|
|
27
27
|
::Sequel::DATABASES.first
|
28
28
|
end
|
29
29
|
|
30
|
-
def filter(collection,
|
30
|
+
def filter(collection, fields)
|
31
31
|
collection.where(fields)
|
32
32
|
end
|
33
33
|
|
34
|
-
def sort(collection,
|
34
|
+
def sort(collection, fields)
|
35
35
|
collection.order(*fields.map { |k, v| ::Sequel.send(v, k) })
|
36
36
|
end
|
37
37
|
|
38
|
-
def page(collection,
|
38
|
+
def page(collection, opts)
|
39
39
|
opts = settings._sinja.page_using.merge(opts)
|
40
40
|
collection = collection.dataset \
|
41
41
|
unless collection.respond_to?(:paginate)
|
@@ -23,8 +23,9 @@ module Sinja
|
|
23
23
|
end
|
24
24
|
|
25
25
|
app.get '', :qparams=>%i[include fields filter sort page], :actions=>:fetch do
|
26
|
+
filter_sort_page?(:fetch)
|
26
27
|
collection, opts = fetch
|
27
|
-
collection, links = filter_sort_page(collection
|
28
|
+
collection, links = filter_sort_page(collection)
|
28
29
|
(opts[:links] ||= {}).merge!(links)
|
29
30
|
serialize_models(collection, opts)
|
30
31
|
end
|
data/lib/sinja/resource.rb
CHANGED
@@ -92,8 +92,12 @@ module Sinja
|
|
92
92
|
define_singleton_method(:resource_config) { config }
|
93
93
|
|
94
94
|
helpers Helpers::Nested do
|
95
|
-
define_method(:can?) do
|
96
|
-
|
95
|
+
define_method(:can?) do |action, *args|
|
96
|
+
parent = sideloaded? && env['sinja.passthru'].to_sym
|
97
|
+
|
98
|
+
roles, sideload_on = config.fetch(action, {}).values_at(:roles, :sideload_on)
|
99
|
+
roles.nil? || roles.empty? || roles === memoized_role ||
|
100
|
+
parent && sideload_on.include?(parent) && super(parent, *args)
|
97
101
|
end
|
98
102
|
|
99
103
|
define_method(:serialize_linkage) do |*args|
|
@@ -18,21 +18,20 @@ module Sinja
|
|
18
18
|
ids = ids.split(',') if String === ids
|
19
19
|
ids = [*ids].tap(&:uniq!)
|
20
20
|
|
21
|
+
resources, opts = [], {}
|
21
22
|
if respond_to?(:show_many)
|
22
23
|
resources, opts = show_many(ids)
|
23
24
|
raise NotFoundError, "Resource(s) not found" \
|
24
25
|
unless ids.length == resources.length
|
25
|
-
serialize_models(resources, opts)
|
26
26
|
else
|
27
|
-
|
28
|
-
resources = ids.map! do |id|
|
27
|
+
ids.each do |id|
|
29
28
|
tmp, opts = show(id)
|
30
29
|
raise NotFoundError, "Resource '#{id}' not found" unless tmp
|
31
|
-
tmp
|
30
|
+
resources << tmp
|
32
31
|
end
|
33
|
-
|
34
|
-
serialize_models(resources, opts)
|
35
32
|
end
|
33
|
+
|
34
|
+
serialize_models(resources, opts)
|
36
35
|
end
|
37
36
|
|
38
37
|
app.head '' do
|
@@ -40,8 +39,9 @@ module Sinja
|
|
40
39
|
end
|
41
40
|
|
42
41
|
app.get '', :qparams=>%i[include fields filter sort page], :actions=>:index do
|
42
|
+
filter_sort_page?(:index)
|
43
43
|
collection, opts = index
|
44
|
-
collection, links = filter_sort_page(collection
|
44
|
+
collection, links = filter_sort_page(collection)
|
45
45
|
(opts[:links] ||= {}).merge!(links)
|
46
46
|
serialize_models(collection, opts)
|
47
47
|
end
|
data/lib/sinja/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinja
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.0.
|
4
|
+
version: 1.1.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Pastore
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|