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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4491b24eba74ba902735fc26ff679b074bc5a76c
4
- data.tar.gz: 54f55fd1b953b7dd31b5bb911386bb6f1bf83419
3
+ metadata.gz: d785d25fe6c9bd3e61893ca5a1ec23b237d9e3cb
4
+ data.tar.gz: 0eb4c07d1691587fb117ebbfc6d2fb63a911ae81
5
5
  SHA512:
6
- metadata.gz: 362031551256e0e28912b13447825269388a54ff698624027ae44e557fc37489686c11427fe43c84b5983d23f8317772c58f5d0185c8373df1e4f43698abf90a
7
- data.tar.gz: f282eef01caa1c271f7555f1121f3d2d7fba5e65962a20c10e8e688e380ec67930be8b95002eaad734de1ed9b573421cadaa95b77af6bcea9ef64dd3975e8bf7
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. Please see the [demo-app](/demo-app) for more complete examples.
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
- JSONAPI::Serializers (or another (de)serialization library), your own routes,
177
- and a ton of boilerplate. The goal of this extension is to provide most or all
178
- of the boilerplate for a Sintara application and automate the drawing of routes
179
- based on the resource definitions.
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 examples.
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 allow a custom query parameter through, add it to the `query_params`
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 as well.) For example:
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 still experimental.
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
- %i[can? sanity_check! sideload?].each do |meth|
46
- define_method(meth) do |*args|
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) || sideload?(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?(resource_name, action, rel_type=nil, rel=nil)
182
- config = settings._sinja.resource_config[resource_name]
183
- config = rel_type && rel ? config[rel_type][rel] : config[:resource]
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
- halt 400, "Unsupported `filter' query parameter(s)" \
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
- halt 400, "Unsupported `sort' query parameter(s)" \
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
- halt 400, "Unsupported `page' query parameter(s)" \
237
+ raise BadRequestError, "Unsupported `page' query parameter(s)" \
231
238
  unless respond_to?(:page)
232
239
 
233
- h = params[:page].map do |k, v|
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 filter_sort_page(collection, action)
243
- unless params[:filter].empty?
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
- collection = filter(collection, params[:filter])
249
- end
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
- collection = sort(collection, params[:sort])
257
- end
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
- unless params[:page].empty?
260
- collection, pagination = page(collection, params[:page])
261
- end
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
@@ -27,15 +27,15 @@ module Sinja
27
27
  ::Sequel::DATABASES.first
28
28
  end
29
29
 
30
- def filter(collection, **fields)
30
+ def filter(collection, fields)
31
31
  collection.where(fields)
32
32
  end
33
33
 
34
- def sort(collection, **fields)
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, **opts)
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, :fetch)
28
+ collection, links = filter_sort_page(collection)
28
29
  (opts[:links] ||= {}).merge!(links)
29
30
  serialize_models(collection, opts)
30
31
  end
@@ -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 |*args|
96
- super(*args, rel_type, rel)
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
- opts = {}
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, :index)
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sinja
3
- VERSION = '1.1.0.pre1'
3
+ VERSION = '1.1.0.pre2'
4
4
  end
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.pre1
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-06 00:00:00.000000000 Z
11
+ date: 2016-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport