sinja 1.1.0.pre1 → 1.1.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|