sinja 0.1.0.beta3 → 0.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -18
- data/bin/console +1 -1
- data/lib/role_list.rb +8 -0
- data/lib/sinatra/jsonapi.rb +1 -129
- data/lib/sinja.rb +135 -8
- data/lib/{sinatra/jsonapi → sinja}/config.rb +7 -6
- data/lib/{sinatra/jsonapi → sinja}/helpers/relationships.rb +1 -1
- data/lib/{sinatra/jsonapi → sinja}/helpers/sequel.rb +9 -12
- data/lib/{sinatra/jsonapi → sinja}/helpers/serializers.rb +7 -7
- data/lib/{sinatra/jsonapi → sinja}/relationship_routes/has_many.rb +1 -1
- data/lib/{sinatra/jsonapi → sinja}/relationship_routes/has_one.rb +1 -1
- data/lib/sinja/relationship_routes/sequel.rb +53 -0
- data/lib/{sinatra/jsonapi → sinja}/resource.rb +19 -23
- data/lib/{sinatra/jsonapi → sinja}/resource_routes.rb +18 -3
- data/lib/sinja/version.rb +2 -4
- data/sinja.gemspec +1 -1
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a2a92548635cd6db938186715c337d3d19bec29
|
4
|
+
data.tar.gz: c31184c7f6c8c33698096846a986dce1e4ec53cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e0ec4ee3a7b6bf7fa0ffb66fc0cde0cb736be5a16ba3ef1dcba40bd0d6b1bd8b378aa640c3dc21df7d439f5b0d78a9e0ea8ba05c6b0ecd4621ff9b0ac2f1c82
|
7
|
+
data.tar.gz: 1db8dcd6991dc3f89540a8e5e3a3230d42a210d1951a7fadbdb13b561a38727231575f36ad4b7351f78b4b78644bbdff66f74ba58d6ce2f8c163ed7fc3798c8a
|
data/README.md
CHANGED
@@ -55,6 +55,7 @@ has not yet been thoroughly tested or vetted in a production environment.**
|
|
55
55
|
- [`role` helper](#role-helper)
|
56
56
|
- [Conflicts](#conflicts)
|
57
57
|
- [Transactions](#transactions)
|
58
|
+
- [Coalesced Find Requests](#coalesced-find-requests)
|
58
59
|
- [Module Namespaces](#module-namespaces)
|
59
60
|
- [Code Organization](#code-organization)
|
60
61
|
- [Development](#development)
|
@@ -280,8 +281,6 @@ end
|
|
280
281
|
|
281
282
|
**dedasherize_names**
|
282
283
|
: Takes a hash and returns the hash with its keys dedasherized (deeply).
|
283
|
-
Useful for fixing up the hashes of attributes passed to the `create` and
|
284
|
-
`update` action helpers before they are passed on to ORM methods.
|
285
284
|
|
286
285
|
### Performance
|
287
286
|
|
@@ -356,7 +355,7 @@ these settings.
|
|
356
355
|
by manually `use`-ing the Rack::Protection middleware)
|
357
356
|
* Disables static file routes (can be reenabled with `enable :static`)
|
358
357
|
* Sets `:show_exceptions` to `:after_handler`
|
359
|
-
* Adds an `:api_json` MIME-type (`
|
358
|
+
* Adds an `:api_json` MIME-type (`Sinja::MIME_TYPE`)
|
360
359
|
* Enforces strict checking of the `Accept` and `Content-Type` request headers
|
361
360
|
* Sets the `Content-Type` response header to `:api_json` (can be overriden with
|
362
361
|
the `content_type` helper)
|
@@ -408,7 +407,9 @@ to JSONAPI::Serializers. You may also use the special `:exclude` option to
|
|
408
407
|
prevent specific relationships from being included in the response. This
|
409
408
|
accepts the same formats as JSONAPI::Serializers does for `:include`. If you
|
410
409
|
exclude a relationship, any sub-relationships will also be excluded. The
|
411
|
-
`:sort`, `:page`, and `:filter` query parameters must be handled manually
|
410
|
+
`:sort`, `:page`, and `:filter` query parameters must be handled manually (with
|
411
|
+
the exception of the `:id` filter, discussed under "Coalesced Find Requests"
|
412
|
+
below).
|
412
413
|
|
413
414
|
All arguments to action helpers are "tainted" and should be treated as
|
414
415
|
potentially dangerous: IDs, attribute hashes, and [resource identifier
|
@@ -436,22 +437,22 @@ serialize on the response.
|
|
436
437
|
|
437
438
|
##### `create {|attr, id| ..}` => id, Object?
|
438
439
|
|
439
|
-
With client-generated IDs: Take a hash of attributes and a
|
440
|
-
create a new resource, and return the ID and optionally
|
441
|
-
(Note that only one or the other `create` action helpers
|
442
|
-
given resource block.)
|
440
|
+
With client-generated IDs: Take a hash of (dedasherized) attributes and a
|
441
|
+
client-generated ID, create a new resource, and return the ID and optionally
|
442
|
+
the created resource. (Note that only one or the other `create` action helpers
|
443
|
+
is allowed in any given resource block.)
|
443
444
|
|
444
445
|
##### `create {|attr| ..}` => id, Object
|
445
446
|
|
446
|
-
Without client-generated IDs: Take a hash of attributes, create
|
447
|
-
and return the server-generated ID and the created resource.
|
448
|
-
one or the other `create` action helpers is allowed in any
|
449
|
-
block.)
|
447
|
+
Without client-generated IDs: Take a hash of (dedasherized) attributes, create
|
448
|
+
a new resource, and return the server-generated ID and the created resource.
|
449
|
+
(Note that only one or the other `create` action helpers is allowed in any
|
450
|
+
given resource block.)
|
450
451
|
|
451
452
|
##### `update {|attr| ..}` => Object?
|
452
453
|
|
453
|
-
Take a hash of attributes, update `resource`, and optionally
|
454
|
-
resource.
|
454
|
+
Take a hash of (dedasherized) attributes, update `resource`, and optionally
|
455
|
+
return the updated resource.
|
455
456
|
|
456
457
|
##### `destroy {..}`
|
457
458
|
|
@@ -462,8 +463,7 @@ Delete or destroy `resource`.
|
|
462
463
|
##### `pluck {..}` => Object
|
463
464
|
|
464
465
|
Return the related object vis-à-vis `resource` to serialize on the
|
465
|
-
response.
|
466
|
-
overridden or disabled entirely with `pluck(&nil)`.
|
466
|
+
response.
|
467
467
|
|
468
468
|
##### `prune {..}` => TrueClass?
|
469
469
|
|
@@ -493,8 +493,7 @@ Take a [resource identifier object][22] and update the relationship on
|
|
493
493
|
##### `fetch {..}` => Array
|
494
494
|
|
495
495
|
Return an array of related objects vis-à-vis `resource` to serialize on
|
496
|
-
the response.
|
497
|
-
overridden or disabled entirely with `fetch(&nil)`.
|
496
|
+
the response.
|
498
497
|
|
499
498
|
##### `clear {..}` => TrueClass?
|
500
499
|
|
@@ -614,6 +613,23 @@ helpers do
|
|
614
613
|
end
|
615
614
|
```
|
616
615
|
|
616
|
+
If you need more fine-grained control, for example if your action helper logic
|
617
|
+
varies by the user's role, you can use a simple switch statement along with the
|
618
|
+
`RoleList` utility class:
|
619
|
+
|
620
|
+
```ruby
|
621
|
+
index(roles: []) do
|
622
|
+
case role
|
623
|
+
when RoleList[:user]
|
624
|
+
# logic specific to user role
|
625
|
+
when RoleList[:admin, :super]
|
626
|
+
# logic specific to administrative roles
|
627
|
+
else
|
628
|
+
halt 403, 'Access denied!'
|
629
|
+
end
|
630
|
+
end
|
631
|
+
```
|
632
|
+
|
617
633
|
### Conflicts
|
618
634
|
|
619
635
|
If your database driver raises exceptions on constraint violations, you should
|
@@ -649,6 +665,15 @@ helpers do
|
|
649
665
|
end
|
650
666
|
```
|
651
667
|
|
668
|
+
### Coalesced Find Requests
|
669
|
+
|
670
|
+
If your JSON:API client coalesces find requests, the `show` action helper will
|
671
|
+
be invoked once for each ID in the `:id` filter, and the resulting collection
|
672
|
+
will be serialized on the response. Both query parameter syntaxes for arrays
|
673
|
+
are supported: `?filter[id]=1,2` and `?filter[id][]=1&filter[id][]=2`. If any
|
674
|
+
ID is not found (i.e. `show` returns `nil`), the route will halt with HTTP
|
675
|
+
status code 404.
|
676
|
+
|
652
677
|
### Module Namespaces
|
653
678
|
|
654
679
|
Everything is dual-namespaced under both Sinatra::JSONAPI and Sinja, and Sinja
|
data/bin/console
CHANGED
data/lib/role_list.rb
ADDED
data/lib/sinatra/jsonapi.rb
CHANGED
@@ -1,134 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'set'
|
3
|
-
require 'sinatra/base'
|
4
|
-
require 'sinatra/namespace'
|
5
|
-
|
6
2
|
require 'sinja'
|
7
|
-
require 'sinja/version'
|
8
|
-
|
9
|
-
require 'sinatra/jsonapi/config'
|
10
|
-
require 'sinatra/jsonapi/helpers/serializers'
|
11
|
-
require 'sinatra/jsonapi/resource'
|
12
|
-
|
13
|
-
module Sinatra::JSONAPI
|
14
|
-
def resource(resource_name, konst=nil, &block)
|
15
|
-
abort "Must supply proc constant or block for `resource'" \
|
16
|
-
unless block = konst and konst.is_a?(Proc) or block
|
17
|
-
|
18
|
-
sinja_config.resource_roles[resource_name.to_sym] # trigger default proc
|
19
|
-
|
20
|
-
namespace "/#{resource_name.to_s.tr('_', '-')}" do
|
21
|
-
define_singleton_method(:can) do |action, roles|
|
22
|
-
sinja_config.resource_roles[resource_name.to_sym].merge!(action=>roles)
|
23
|
-
end
|
24
|
-
|
25
|
-
helpers do
|
26
|
-
define_method(:can?) do |*args|
|
27
|
-
super(resource_name.to_sym, *args)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
register Resource
|
32
|
-
|
33
|
-
instance_eval(&block)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def sinja
|
38
|
-
if block_given?
|
39
|
-
yield sinja_config
|
40
|
-
else
|
41
|
-
sinja_config
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
alias_method :configure_jsonapi, :sinja
|
46
|
-
def freeze_jsonapi
|
47
|
-
sinja_config.freeze
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.registered(app)
|
51
|
-
app.register Sinatra::Namespace
|
52
|
-
|
53
|
-
app.disable :protection, :static
|
54
|
-
app.set :sinja_config, Sinatra::JSONAPI::Config.new
|
55
|
-
app.configure(:development) do |c|
|
56
|
-
c.set :show_exceptions, :after_handler
|
57
|
-
end
|
58
|
-
|
59
|
-
app.set :actions do |*actions|
|
60
|
-
condition do
|
61
|
-
actions.each do |action|
|
62
|
-
halt 403, 'You are not authorized to perform this action' unless can?(action)
|
63
|
-
halt 405, 'Action or method not implemented or supported' unless respond_to?(action)
|
64
|
-
end
|
65
|
-
true
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
app.set :nullif do |nullish|
|
70
|
-
condition { nullish.(data) }
|
71
|
-
end
|
72
|
-
|
73
|
-
app.mime_type :api_json, MIME_TYPE
|
74
|
-
|
75
|
-
app.helpers Helpers::Serializers do
|
76
|
-
def can?(resource_name, action)
|
77
|
-
roles = settings.sinja_config.resource_roles[resource_name][action]
|
78
|
-
roles.nil? || roles.empty? || Set[*role].intersect?(roles)
|
79
|
-
end
|
80
|
-
|
81
|
-
def content?
|
82
|
-
request.body.respond_to?(:size) && request.body.size > 0
|
83
|
-
end
|
84
|
-
|
85
|
-
def data
|
86
|
-
@data ||= deserialized_request_body[:data]
|
87
|
-
end
|
88
|
-
|
89
|
-
def normalize_params!
|
90
|
-
# TODO: halt 400 if other params, or params not implemented?
|
91
|
-
{
|
92
|
-
:fields=>{}, # passthru
|
93
|
-
:include=>[], # passthru
|
94
|
-
:filter=>{},
|
95
|
-
:page=>{},
|
96
|
-
:sort=>''
|
97
|
-
}.each { |k, v| params[k] ||= v }
|
98
|
-
end
|
99
|
-
|
100
|
-
def role
|
101
|
-
nil
|
102
|
-
end
|
103
|
-
|
104
|
-
def transaction
|
105
|
-
yield
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
app.before do
|
110
|
-
halt 406 unless request.preferred_type.entry == MIME_TYPE
|
111
|
-
|
112
|
-
if content?
|
113
|
-
halt 415 unless request.media_type == MIME_TYPE
|
114
|
-
halt 415 if request.media_type_params.keys.any? { |k| k != 'charset' }
|
115
|
-
end
|
116
|
-
|
117
|
-
content_type :api_json
|
118
|
-
|
119
|
-
normalize_params!
|
120
|
-
end
|
121
|
-
|
122
|
-
app.after do
|
123
|
-
body serialized_response_body if response.ok?
|
124
|
-
end
|
125
|
-
|
126
|
-
app.error 400...600, nil do
|
127
|
-
serialized_error
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
3
|
|
132
4
|
module Sinatra
|
133
|
-
register JSONAPI
|
5
|
+
register JSONAPI = Sinja
|
134
6
|
end
|
data/lib/sinja.rb
CHANGED
@@ -1,13 +1,140 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
MIME_TYPE = 'application/vnd.api+json'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'sinatra/namespace'
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
require 'role_list'
|
6
|
+
require 'sinja/config'
|
7
|
+
require 'sinja/helpers/serializers'
|
8
|
+
require 'sinja/resource'
|
9
|
+
|
10
|
+
module Sinja
|
11
|
+
MIME_TYPE = 'application/vnd.api+json'
|
12
|
+
|
13
|
+
SinjaError = Class.new(StandardError)
|
14
|
+
ActionHelperError = Class.new(SinjaError)
|
15
|
+
|
16
|
+
def resource(resource_name, konst=nil, &block)
|
17
|
+
abort "Must supply proc constant or block for `resource'" \
|
18
|
+
unless block = (konst and konst.is_a?(Proc) or block)
|
19
|
+
|
20
|
+
_sinja.resource_roles[resource_name.to_sym] # trigger default proc
|
21
|
+
|
22
|
+
namespace "/#{resource_name.to_s.tr('_', '-')}" do
|
23
|
+
define_singleton_method(:can) do |action, roles|
|
24
|
+
_sinja.resource_roles[resource_name.to_sym].merge!(action=>roles)
|
25
|
+
end
|
26
|
+
|
27
|
+
helpers do
|
28
|
+
define_method(:can?) do |*args|
|
29
|
+
super(resource_name.to_sym, *args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
register Resource
|
34
|
+
|
35
|
+
instance_eval(&block)
|
36
|
+
end
|
8
37
|
end
|
9
|
-
end
|
10
38
|
|
11
|
-
|
39
|
+
def sinja
|
40
|
+
if block_given?
|
41
|
+
yield _sinja
|
42
|
+
else
|
43
|
+
_sinja
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :configure_jsonapi, :sinja
|
48
|
+
def freeze_jsonapi
|
49
|
+
_sinja.freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.registered(app)
|
53
|
+
app.register Sinatra::Namespace
|
54
|
+
|
55
|
+
app.disable :protection, :static
|
56
|
+
app.set :_sinja, Sinja::Config.new
|
57
|
+
app.configure(:development) do |c|
|
58
|
+
c.set :show_exceptions, :after_handler
|
59
|
+
end
|
60
|
+
|
61
|
+
app.set :actions do |*actions|
|
62
|
+
condition do
|
63
|
+
actions.each do |action|
|
64
|
+
halt 403, 'You are not authorized to perform this action' unless can?(action)
|
65
|
+
halt 405, 'Action or method not implemented or supported' unless respond_to?(action)
|
66
|
+
end
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
app.set :pfilters do |*pfilters|
|
72
|
+
condition do
|
73
|
+
pfilters.all? do |pfilter|
|
74
|
+
params.key?('filter') && params['filter'].key?(pfilter.to_s)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
12
78
|
|
13
|
-
|
79
|
+
app.set :nullif do |nullish|
|
80
|
+
condition { nullish.(data) }
|
81
|
+
end
|
82
|
+
|
83
|
+
app.mime_type :api_json, MIME_TYPE
|
84
|
+
|
85
|
+
app.helpers Helpers::Serializers do
|
86
|
+
def can?(resource_name, action)
|
87
|
+
roles = settings._sinja.resource_roles[resource_name][action]
|
88
|
+
roles.nil? || roles.empty? || roles === role
|
89
|
+
end
|
90
|
+
|
91
|
+
def content?
|
92
|
+
request.body.respond_to?(:size) && request.body.size > 0
|
93
|
+
end
|
94
|
+
|
95
|
+
def data
|
96
|
+
@data ||= deserialized_request_body[:data]
|
97
|
+
end
|
98
|
+
|
99
|
+
def normalize_params!
|
100
|
+
# TODO: halt 400 if other params, or params not implemented?
|
101
|
+
{
|
102
|
+
:fields=>{}, # passthru
|
103
|
+
:include=>[], # passthru
|
104
|
+
:filter=>{},
|
105
|
+
:page=>{},
|
106
|
+
:sort=>''
|
107
|
+
}.each { |k, v| params[k] ||= v }
|
108
|
+
end
|
109
|
+
|
110
|
+
def role
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def transaction
|
115
|
+
yield
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
app.before do
|
120
|
+
halt 406 unless request.preferred_type.entry == MIME_TYPE
|
121
|
+
|
122
|
+
if content?
|
123
|
+
halt 415 unless request.media_type == MIME_TYPE
|
124
|
+
halt 415 if request.media_type_params.keys.any? { |k| k != 'charset' }
|
125
|
+
end
|
126
|
+
|
127
|
+
content_type :api_json
|
128
|
+
|
129
|
+
normalize_params!
|
130
|
+
end
|
131
|
+
|
132
|
+
app.after do
|
133
|
+
body serialized_response_body if response.ok?
|
134
|
+
end
|
135
|
+
|
136
|
+
app.error 400...600, nil do
|
137
|
+
serialized_error
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -2,11 +2,12 @@
|
|
2
2
|
require 'forwardable'
|
3
3
|
require 'set'
|
4
4
|
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require '
|
5
|
+
require 'role_list'
|
6
|
+
require 'sinja/relationship_routes/has_many'
|
7
|
+
require 'sinja/relationship_routes/has_one'
|
8
|
+
require 'sinja/resource_routes'
|
8
9
|
|
9
|
-
module
|
10
|
+
module Sinja
|
10
11
|
module ConfigUtils
|
11
12
|
def deep_copy(c)
|
12
13
|
Marshal.load(Marshal.dump(c))
|
@@ -98,7 +99,7 @@ module Sinatra::JSONAPI
|
|
98
99
|
ResourceRoutes::ACTIONS,
|
99
100
|
RelationshipRoutes::HasMany::ACTIONS,
|
100
101
|
RelationshipRoutes::HasOne::ACTIONS
|
101
|
-
].reduce([], :concat).map { |action| [action,
|
102
|
+
].reduce([], :concat).map { |action| [action, RoleList.new] }.to_h
|
102
103
|
end
|
103
104
|
|
104
105
|
def_delegator :@data, :[]
|
@@ -107,7 +108,7 @@ module Sinatra::JSONAPI
|
|
107
108
|
h.each do |action, roles|
|
108
109
|
abort "Unknown or invalid action helper `#{action}' in configuration" \
|
109
110
|
unless @data.key?(action)
|
110
|
-
@data[action].replace(
|
111
|
+
@data[action].replace(RoleList[*roles])
|
111
112
|
end
|
112
113
|
@data
|
113
114
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'sequel/model/inflections'
|
3
3
|
|
4
|
-
module
|
4
|
+
module Sinja
|
5
5
|
module Helpers
|
6
6
|
module Sequel
|
7
7
|
include ::Sequel::Inflections
|
@@ -26,12 +26,12 @@ module Sinatra::JSONAPI
|
|
26
26
|
[resource.pk, resource, opts]
|
27
27
|
end
|
28
28
|
|
29
|
-
# <= association, rios,
|
29
|
+
# <= association, rios, block
|
30
30
|
def add_missing(*args)
|
31
31
|
add_remove(:add, :-, *args)
|
32
32
|
end
|
33
33
|
|
34
|
-
# <= association, rios,
|
34
|
+
# <= association, rios, block
|
35
35
|
def remove_present(*args)
|
36
36
|
add_remove(:remove, :&, *args)
|
37
37
|
end
|
@@ -42,10 +42,8 @@ module Sinatra::JSONAPI
|
|
42
42
|
meth = "#{meth_prefix}_#{singularize(association)}".to_sym
|
43
43
|
transaction do
|
44
44
|
resource.lock!
|
45
|
-
venn(operator, association, rios) do |subresource
|
46
|
-
|
47
|
-
args.push(yield(rio)) if block_given?
|
48
|
-
resource.send(meth, *args)
|
45
|
+
venn(operator, association, rios) do |subresource|
|
46
|
+
resource.send(meth, subresource)
|
49
47
|
end
|
50
48
|
resource.reload
|
51
49
|
end
|
@@ -53,11 +51,10 @@ module Sinatra::JSONAPI
|
|
53
51
|
|
54
52
|
def venn(operator, association, rios)
|
55
53
|
dataset = resource.send("#{association}_dataset")
|
56
|
-
|
57
|
-
rios
|
58
|
-
|
59
|
-
yield
|
60
|
-
end
|
54
|
+
# does not / will not work with composite primary keys
|
55
|
+
rios.map { |rio| rio[:id].to_i }
|
56
|
+
.send(operator, dataset.select_map(:id))
|
57
|
+
.each { |id| yield dataset.with_pk!(id) }
|
61
58
|
end
|
62
59
|
end
|
63
60
|
end
|
@@ -3,7 +3,7 @@ require 'json'
|
|
3
3
|
require 'jsonapi-serializers'
|
4
4
|
require 'set'
|
5
5
|
|
6
|
-
module
|
6
|
+
module Sinja
|
7
7
|
module Helpers
|
8
8
|
module Serializers
|
9
9
|
def dedasherize(s=nil)
|
@@ -32,7 +32,7 @@ module Sinatra::JSONAPI
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def serialized_response_body
|
35
|
-
JSON.send settings.
|
35
|
+
JSON.send settings._sinja.json_generator, response.body
|
36
36
|
rescue JSON::GeneratorError
|
37
37
|
halt 400, 'Unserializable entities in the response body'
|
38
38
|
end
|
@@ -64,7 +64,7 @@ module Sinatra::JSONAPI
|
|
64
64
|
exclude!(options) if options[:include] && options[:exclude]
|
65
65
|
|
66
66
|
::JSONAPI::Serializer.serialize model,
|
67
|
-
settings.
|
67
|
+
settings._sinja.serializer_opts.merge(options)
|
68
68
|
end
|
69
69
|
|
70
70
|
def serialize_model?(model=nil, options={})
|
@@ -87,7 +87,7 @@ module Sinatra::JSONAPI
|
|
87
87
|
exclude!(options) if options[:include] && options[:exclude]
|
88
88
|
|
89
89
|
::JSONAPI::Serializer.serialize [*models],
|
90
|
-
settings.
|
90
|
+
settings._sinja.serializer_opts.merge(options)
|
91
91
|
end
|
92
92
|
|
93
93
|
def serialize_models?(models=[], options={})
|
@@ -101,7 +101,7 @@ module Sinatra::JSONAPI
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def serialize_linkage(options={})
|
104
|
-
options = settings.
|
104
|
+
options = settings._sinja.serializer_opts.merge(options)
|
105
105
|
linkage.tap do |c|
|
106
106
|
c[:meta] = options[:meta] if options.key?(:meta)
|
107
107
|
c[:jsonapi] = options[:jsonapi] if options.key?(:jsonapi)
|
@@ -142,8 +142,8 @@ module Sinatra::JSONAPI
|
|
142
142
|
|
143
143
|
def serialized_error
|
144
144
|
hash = error_hash(normalized_error)
|
145
|
-
logger.error(settings.
|
146
|
-
JSON.send settings.
|
145
|
+
logger.error(settings._sinja.logger_progname) { hash }
|
146
|
+
JSON.send settings._sinja.json_error_generator,
|
147
147
|
::JSONAPI::Serializer.serialize_errors([hash])
|
148
148
|
end
|
149
149
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sinja/helpers/sequel'
|
3
|
+
|
4
|
+
module Sinja
|
5
|
+
module Resource
|
6
|
+
alias_method :core_has_one, :has_one
|
7
|
+
|
8
|
+
def has_one(rel, &block)
|
9
|
+
core_has_one(rel) do
|
10
|
+
pluck do
|
11
|
+
resource.send(rel)
|
12
|
+
end
|
13
|
+
|
14
|
+
prune do
|
15
|
+
resource.send("#{rel}=", nil)
|
16
|
+
resource.save_changes
|
17
|
+
end
|
18
|
+
|
19
|
+
graft do |rio|
|
20
|
+
klass = resource.class.association_reflection(rel).associated_class
|
21
|
+
resource.send("#{rel}=", klass.with_pk!(rio[:id]))
|
22
|
+
resource.save_changes
|
23
|
+
end
|
24
|
+
|
25
|
+
instance_eval(&block) if block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :core_has_many, :has_many
|
30
|
+
|
31
|
+
def has_many(rel, &block)
|
32
|
+
core_has_many(rel) do
|
33
|
+
fetch do
|
34
|
+
resource.send(rel)
|
35
|
+
end
|
36
|
+
|
37
|
+
clear do
|
38
|
+
resource.send("remove_all_#{rel}")
|
39
|
+
end
|
40
|
+
|
41
|
+
merge do |rios|
|
42
|
+
add_missing(rel, rios)
|
43
|
+
end
|
44
|
+
|
45
|
+
subtract do |rios|
|
46
|
+
remove_present(rel, rios)
|
47
|
+
end
|
48
|
+
|
49
|
+
instance_eval(&block) if block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -2,12 +2,12 @@
|
|
2
2
|
require 'sinatra/base'
|
3
3
|
require 'sinatra/namespace'
|
4
4
|
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require '
|
8
|
-
require '
|
5
|
+
require 'sinja/helpers/relationships'
|
6
|
+
require 'sinja/relationship_routes/has_many'
|
7
|
+
require 'sinja/relationship_routes/has_one'
|
8
|
+
require 'sinja/resource_routes'
|
9
9
|
|
10
|
-
module
|
10
|
+
module Sinja
|
11
11
|
module Resource
|
12
12
|
def def_action_helper(action, context=nil)
|
13
13
|
abort "JSONAPI resource actions can't be HTTP verbs!" \
|
@@ -16,24 +16,18 @@ module Sinatra::JSONAPI
|
|
16
16
|
context.define_singleton_method(action) do |**opts, &block|
|
17
17
|
can(action, opts[:roles]) if opts.key?(:roles)
|
18
18
|
|
19
|
-
if block.nil?
|
20
|
-
if respond_to?(action)
|
21
|
-
# TODO: Is this safe?
|
22
|
-
remove_method(action)
|
23
|
-
return
|
24
|
-
else
|
25
|
-
# TODO: Throw an error?
|
26
|
-
end
|
27
|
-
end
|
19
|
+
return if block.nil?
|
28
20
|
|
29
21
|
define_method(action) do |*args|
|
22
|
+
send("before_#{action}") if respond_to?("before_#{action}")
|
23
|
+
|
30
24
|
result =
|
31
25
|
begin
|
32
26
|
instance_exec(*args.take(block.arity.abs), &block)
|
33
27
|
rescue Exception=>e
|
34
|
-
halt 409, e.message if settings.
|
35
|
-
#halt 422, resource.errors if settings.
|
36
|
-
#not_found if settings.
|
28
|
+
halt 409, e.message if settings._sinja.conflict?(action, e.class)
|
29
|
+
#halt 422, resource.errors if settings._sinja.invalid?(action, e.class) # TODO
|
30
|
+
#not_found if settings._sinja.not_found?(action, e.class) # TODO
|
37
31
|
raise
|
38
32
|
end
|
39
33
|
|
@@ -63,6 +57,10 @@ module Sinatra::JSONAPI
|
|
63
57
|
[result, nil].take(required_arity.abs).push({})
|
64
58
|
end
|
65
59
|
end
|
60
|
+
|
61
|
+
define_singleton_method("remove_#{action}") do
|
62
|
+
remove_method(action)
|
63
|
+
end
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
@@ -74,6 +72,10 @@ module Sinatra::JSONAPI
|
|
74
72
|
app.helpers Helpers::Relationships do
|
75
73
|
attr_accessor :resource
|
76
74
|
|
75
|
+
def attributes
|
76
|
+
dedasherize_names(data.fetch(:attributes, {}))
|
77
|
+
end
|
78
|
+
|
77
79
|
def sanity_check!(id=nil)
|
78
80
|
halt 409, 'Resource type in payload does not match endpoint' \
|
79
81
|
if data[:type] != request.path.split('/').last # TODO
|
@@ -123,12 +125,6 @@ module Sinatra::JSONAPI
|
|
123
125
|
register RelationshipRoutes.const_get \
|
124
126
|
rel_type.to_s.split('_').map(&:capitalize).join.to_sym
|
125
127
|
|
126
|
-
if rel_type == :has_one
|
127
|
-
pluck { resource.send(rel) }
|
128
|
-
elsif rel_type == :has_many
|
129
|
-
fetch { resource.send(rel) }
|
130
|
-
end
|
131
|
-
|
132
128
|
instance_eval(&block) if block
|
133
129
|
end
|
134
130
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
module
|
2
|
+
module Sinja
|
3
3
|
module ResourceRoutes
|
4
4
|
ACTIONS = %i[index show create update destroy].freeze
|
5
5
|
CONFLICT_ACTIONS = %i[create update].freeze
|
@@ -7,6 +7,21 @@ module Sinatra::JSONAPI
|
|
7
7
|
def self.registered(app)
|
8
8
|
app.def_action_helpers(ACTIONS, app)
|
9
9
|
|
10
|
+
app.get '', :pfilters=>:id, :actions=>:show do
|
11
|
+
ids = params['filter'].delete('id')
|
12
|
+
ids = ids.split(',') if ids.respond_to?(:split)
|
13
|
+
|
14
|
+
opts = {}
|
15
|
+
resources = [*ids].tap(&:uniq!).map! do |id|
|
16
|
+
self.resource, opts = show(id)
|
17
|
+
not_found "Resource '#{id}' not found" unless resource
|
18
|
+
resource
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO: Serialize collection with opts from last model found?
|
22
|
+
serialize_models(resources, opts)
|
23
|
+
end
|
24
|
+
|
10
25
|
app.get '', :actions=>:index do
|
11
26
|
serialize_models(*index)
|
12
27
|
end
|
@@ -23,7 +38,7 @@ module Sinatra::JSONAPI
|
|
23
38
|
if data[:id] && method(:create).arity != 2
|
24
39
|
|
25
40
|
_, self.resource, opts = transaction do
|
26
|
-
create(
|
41
|
+
create(attributes, data[:id]).tap do |id, *|
|
27
42
|
dispatch_relationship_requests!(id, :method=>:patch)
|
28
43
|
end
|
29
44
|
end
|
@@ -46,7 +61,7 @@ module Sinatra::JSONAPI
|
|
46
61
|
self.resource, = show(id)
|
47
62
|
not_found "Resource '#{id}' not found" unless resource
|
48
63
|
serialize_model?(transaction do
|
49
|
-
update(
|
64
|
+
update(attributes).tap do
|
50
65
|
dispatch_relationship_requests!(id, :method=>:patch)
|
51
66
|
end
|
52
67
|
end)
|
data/lib/sinja/version.rb
CHANGED
data/sinja.gemspec
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: 0.
|
4
|
+
version: 0.2.0.beta1
|
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-11-
|
11
|
+
date: 2016-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -124,16 +124,18 @@ files:
|
|
124
124
|
- Rakefile
|
125
125
|
- bin/console
|
126
126
|
- bin/setup
|
127
|
+
- lib/role_list.rb
|
127
128
|
- lib/sinatra/jsonapi.rb
|
128
|
-
- lib/sinatra/jsonapi/config.rb
|
129
|
-
- lib/sinatra/jsonapi/helpers/relationships.rb
|
130
|
-
- lib/sinatra/jsonapi/helpers/sequel.rb
|
131
|
-
- lib/sinatra/jsonapi/helpers/serializers.rb
|
132
|
-
- lib/sinatra/jsonapi/relationship_routes/has_many.rb
|
133
|
-
- lib/sinatra/jsonapi/relationship_routes/has_one.rb
|
134
|
-
- lib/sinatra/jsonapi/resource.rb
|
135
|
-
- lib/sinatra/jsonapi/resource_routes.rb
|
136
129
|
- lib/sinja.rb
|
130
|
+
- lib/sinja/config.rb
|
131
|
+
- lib/sinja/helpers/relationships.rb
|
132
|
+
- lib/sinja/helpers/sequel.rb
|
133
|
+
- lib/sinja/helpers/serializers.rb
|
134
|
+
- lib/sinja/relationship_routes/has_many.rb
|
135
|
+
- lib/sinja/relationship_routes/has_one.rb
|
136
|
+
- lib/sinja/relationship_routes/sequel.rb
|
137
|
+
- lib/sinja/resource.rb
|
138
|
+
- lib/sinja/resource_routes.rb
|
137
139
|
- lib/sinja/version.rb
|
138
140
|
- sinja.gemspec
|
139
141
|
homepage: https://github.com/mwpastore/sinja
|