sinja 0.1.0.beta3 → 0.2.0.beta1
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 +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
|