toast 0.9.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +171 -86
- data/config/routes.rb +1 -27
- data/lib/generators/toast/USAGE +1 -0
- data/lib/generators/toast/templates/toast-api.rb.erb +10 -0
- data/lib/generators/toast/toast_generator.rb +9 -0
- data/lib/toast/canonical_request.rb +179 -0
- data/lib/toast/collection_request.rb +161 -0
- data/lib/toast/config_dsl/association.rb +72 -0
- data/lib/toast/config_dsl/base.rb +50 -0
- data/lib/toast/config_dsl/collection.rb +45 -0
- data/lib/toast/config_dsl/common.rb +38 -0
- data/lib/toast/config_dsl/default_handlers.rb +83 -0
- data/lib/toast/config_dsl/expose.rb +176 -0
- data/lib/toast/config_dsl/settings.rb +35 -0
- data/lib/toast/config_dsl/single.rb +15 -0
- data/lib/toast/config_dsl/via_verb.rb +49 -0
- data/lib/toast/config_dsl.rb +60 -225
- data/lib/toast/engine.rb +19 -30
- data/lib/toast/errors.rb +41 -0
- data/lib/toast/http_range.rb +17 -0
- data/lib/toast/plural_assoc_request.rb +285 -0
- data/lib/toast/rack_app.rb +133 -0
- data/lib/toast/request_helpers.rb +134 -0
- data/lib/toast/single_request.rb +66 -0
- data/lib/toast/singular_assoc_request.rb +207 -0
- data/lib/toast/version.rb +2 -2
- data/lib/toast.rb +100 -1
- metadata +83 -116
- data/app/controller/toast_controller.rb +0 -103
- data/lib/toast/active_record_extensions.rb +0 -85
- data/lib/toast/association.rb +0 -219
- data/lib/toast/collection.rb +0 -139
- data/lib/toast/record.rb +0 -123
- data/lib/toast/resource.rb +0 -175
- data/lib/toast/single.rb +0 -89
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a702e1c3fefb8b0f86ae4169cf2bf0d5c06c59e
|
4
|
+
data.tar.gz: 7909991e0e45f40ab0f9889656a5fa0f64244bb2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae832ad5e6df4d2b1fe56e52125fc2721029bd084c8c8ef7f1c1271d64bffbdea500f3fe9b3fbc3cdac7c47f9762cc3010cee3b8335c406aed1f084a8a2b20e7
|
7
|
+
data.tar.gz: b7c9bd99a118e16f6d722e5bc4323335e85f151be89e59457684426cee8b4c3bf8bd3afaea460620f3013da83d4d27f9e18fc9281505f3d55bef98e0bdd090d6
|
data/README.md
CHANGED
@@ -3,46 +3,58 @@
|
|
3
3
|
Summary
|
4
4
|
=======
|
5
5
|
|
6
|
-
Toast is
|
7
|
-
programming effort in a coherent way. Toast extends ActiveRecord such
|
8
|
-
that each model can be declared to be a web resource, exposing defined
|
9
|
-
attributes for reading and writing using HTTP.
|
6
|
+
Toast is a Rack application that hooks into Ruby on Rails. It exposes ActiveRecord models as a web service (REST API). The main difference from doing that with Ruby on Rails itself is it's DSL that covers all aspects of an API in one single configuration. For each model and API endpoint you define:
|
10
7
|
|
11
|
-
|
8
|
+
* what models and attributes are to be exposed
|
9
|
+
* what methods are supported (GET, PATCH, DELETE, POST,...)
|
10
|
+
* hooks to handle authorization
|
11
|
+
* customized handlers
|
12
12
|
|
13
|
-
|
14
|
-
* generic controller handles all actions
|
15
|
-
* automated routing
|
16
|
-
* exposing data values with JSON maps
|
17
|
-
* exposing associations by links (URLs)
|
13
|
+
When using Toast there's no Rails controller involved. Model classes and the API configuration is sufficient.
|
18
14
|
|
19
|
-
Toast
|
15
|
+
Toast uses a REST/hypermedia style API, which is an own interpretation of the REST idea, not compatible with others like JSON API, Siren etc. It's design is much simpler and based on the idea of traversing opaque URIs.
|
20
16
|
|
21
|
-
|
22
|
-
* Ruby 1.8.7, 1.9.3, 2.0.0
|
17
|
+
Other features are:
|
23
18
|
|
24
|
-
|
19
|
+
* windowing of collections via _Range/Content-Range_ headers (paging)
|
20
|
+
* attribute selection per request
|
21
|
+
* processing of URI parameters
|
22
|
+
|
23
|
+
Toast v1 is build for Rails 5. The predecesssor v0.9 supports 3 and 4, but has a much different and smaller DSL.
|
24
|
+
|
25
|
+
See the User Manual (to be published soon) for a detailed description.
|
25
26
|
|
26
27
|
Status
|
27
|
-
|
28
|
+
======
|
28
29
|
|
29
|
-
Toast
|
30
|
-
|
31
|
-
effects.
|
30
|
+
Toast v1 for Rails 5 is a complete rewrite of v0.9, which was first published and used in production since 2012.
|
31
|
+
It comes now with secure defaults: Nothing is exposed unless declared, all endpoints have a default authorization hook responding with 401.
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
From my point of view it is production ready. I am in the process of porting a large API from v0.9 to v1 that uses all features and it looks very good so far. Of course minor issues will appear, please help to report and fix them.
|
34
|
+
|
35
|
+
Installation
|
36
|
+
============
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
with Bundler (Gemfile) from Rubygems:
|
39
|
+
|
40
|
+
source 'http://rubygems.org'
|
41
|
+
gem "toast"
|
42
|
+
|
43
|
+
from Github:
|
44
|
+
|
45
|
+
gem "toast", :git => "https://github.com/robokopp/toast.git"
|
39
46
|
|
40
|
-
|
47
|
+
then run
|
48
|
+
|
49
|
+
bundle
|
50
|
+
rails generate toast init
|
51
|
+
create config/toast-api.rb
|
52
|
+
create config/toast-api
|
41
53
|
|
42
54
|
Example
|
43
55
|
=======
|
44
56
|
|
45
|
-
Let the table
|
57
|
+
Let the table _bananas_ have the following schema:
|
46
58
|
|
47
59
|
create_table "bananas", :force => true do |t|
|
48
60
|
t.string "name"
|
@@ -51,104 +63,177 @@ Let the table `bananas` have the following schema:
|
|
51
63
|
t.integer "apple_id"
|
52
64
|
end
|
53
65
|
|
54
|
-
and let a corresponding model class have
|
66
|
+
and let a corresponding model class have this code:
|
55
67
|
|
56
68
|
class Banana < ActiveRecord::Base
|
57
69
|
belongs_to :apple
|
58
70
|
has_many :coconuts
|
59
71
|
|
60
|
-
scope :less_than_100, where("number < 100")
|
72
|
+
scope :less_than_100, where("number < 100")
|
73
|
+
end
|
74
|
+
|
75
|
+
Then we can define the API like this (in `config/toast-api/banana.rb`):
|
61
76
|
|
62
|
-
|
63
|
-
|
64
|
-
readables :
|
77
|
+
expose(Banana) {
|
78
|
+
|
79
|
+
readables :color
|
65
80
|
writables :name, :number
|
66
81
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
82
|
+
via_get {
|
83
|
+
allow do |user, model, uri_params|
|
84
|
+
true
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
via_patch {
|
89
|
+
allow do |user, model, uri_params|
|
90
|
+
true
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
via_delete {
|
95
|
+
allow do |user, model, uri_params|
|
96
|
+
true
|
97
|
+
end
|
98
|
+
}
|
99
|
+
|
100
|
+
collection(:less_than_100) {
|
101
|
+
via_get {
|
102
|
+
allow do |user, model, uri_params|
|
103
|
+
true
|
104
|
+
end
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
collection(:all) {
|
109
|
+
max_window 16
|
110
|
+
|
111
|
+
via_get {
|
112
|
+
allow do |user, model, uri_params|
|
113
|
+
true
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
via_post {
|
118
|
+
allow do |user, model, uri_params|
|
119
|
+
true
|
120
|
+
end
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
association(:coconuts) {
|
125
|
+
via_get {
|
126
|
+
allow do |user, model, uri_params|
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
handler do |banana, uri_params|
|
131
|
+
if uri_params[:max_weight] =~ /\A\d+\z/
|
132
|
+
banana.coconuts.where("weight <= #{uri_params[:max_weight]}")
|
133
|
+
else
|
134
|
+
banana.coconuts
|
135
|
+
end.order(:weight)
|
136
|
+
end
|
137
|
+
}
|
138
|
+
|
139
|
+
via_post {
|
140
|
+
allow do |user, model, uri_params|
|
141
|
+
true
|
142
|
+
end
|
143
|
+
}
|
144
|
+
|
145
|
+
via_link {
|
146
|
+
allow do |user, model, uri_params|
|
147
|
+
true
|
148
|
+
end
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
association(:apple) {
|
153
|
+
via_get {
|
154
|
+
allow do |user, model, uri_params|
|
155
|
+
true
|
156
|
+
end
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
Note, that all `allow`-blocks returning _true_. In practice authorization logic should be applied. An `allow`-block must be defined for each endpoint because it defaults to return `false`, which causes a 401 response.
|
162
|
+
|
163
|
+
The above definition exposes the model Banana as such:
|
164
|
+
|
165
|
+
### Get a single resource representation:
|
166
|
+
GET http://www.example.com/bananas/23
|
167
|
+
--> 200, '{"self": "http://www.example.com/bananas/23"
|
168
|
+
"name": "Fred",
|
169
|
+
"number": 33,
|
170
|
+
"color": "yellow",
|
171
|
+
"coconuts": "http://www.example.com/bananas/23/coconuts",
|
172
|
+
"apple": "http://www.example.com/bananas/23/apple" }'
|
71
173
|
|
72
|
-
The
|
73
|
-
records of the model Banana automatically via a generic controller to
|
74
|
-
the outside world, accepting and delivering JSON representations of
|
75
|
-
the records. Let the associated models Apple and Coconut be
|
76
|
-
exposed as a resource, too:
|
174
|
+
The representation of a record is a flat JSON map: _name_ → _value_, in case of associations _name_ → _URI_. The special key _self_ contains the URI from which this record can be fetch alone. _self_ can be treated as a unique ID of the record (globally unique, if under a FQDN).
|
77
175
|
|
78
|
-
### Get a collection
|
79
|
-
GET /bananas
|
176
|
+
### Get a collection (the :all collection)
|
177
|
+
GET http://www.example.com/bananas
|
80
178
|
--> 200, '[{"self": "http://www.example.com/bananas/23",
|
81
179
|
"name": "Fred",
|
82
180
|
"number": 33,
|
181
|
+
"color": "yellow",
|
83
182
|
"coconuts": "http://www.example.com/bananas/23/coconuts",
|
84
183
|
"apple": "http://www.example.com/bananas/23/apple,
|
85
184
|
{"self": "http://www.example.com/bananas/24",
|
86
|
-
... }, ... ]
|
87
|
-
|
88
|
-
|
185
|
+
... }, ... ]'
|
186
|
+
|
187
|
+
The default length of collections is limited to 42, this can be adjusted globally or for each endpoint separately. In this case no more than 16 will be delivered due to the `max_window 16` directive.
|
188
|
+
|
189
|
+
### Get a customized collection
|
190
|
+
GET http://www.example.com/bananas/less_than_100
|
89
191
|
--> 200, '[{BANANA}, {BANANA}, ...]'
|
90
192
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
"coconuts": "http://www.example.com/bananas/23/coconuts",
|
97
|
-
"apple": "http://www.example.com/bananas/23/apple" }'
|
193
|
+
Any scope can be published this way as well as any model class method returning a relation.
|
194
|
+
|
195
|
+
### Get an associated collection + filter
|
196
|
+
GET http://www.example.com/bananas/23/coconuts?max_weight=3
|
197
|
+
--> 200, '[{COCONUT},{COCONUT},...]',
|
98
198
|
|
99
|
-
|
100
|
-
"GET" /bananas/23/coconuts
|
101
|
-
--> 200, '[{COCNUT},{COCONUT},...]',
|
199
|
+
The COCONUT model must be exposed too. URI parameters can be processed in custom handlers for sorting and filtering.
|
102
200
|
|
103
201
|
### Update a single resource:
|
104
|
-
|
105
|
-
"name": "Barney",
|
106
|
-
"number": 44}'
|
202
|
+
PATCH http://www.example.com/bananas/23, '{"name": "Barney", "number": 44, "foo" => "bar"}'
|
107
203
|
--> 200, '{"self": "http://www.example.com/bananas/23"
|
108
204
|
"name": "Barney",
|
109
205
|
"number": 44,
|
206
|
+
"color": "yellow",
|
110
207
|
"coconuts": "http://www.example.com/bananas/23/coconuts",
|
111
208
|
"apple": "http://www.example.com/bananas/23/apple"}'
|
112
209
|
|
210
|
+
Toast ingores unknown attributes, but prints warnings in it's log file. Only attributes from the 'writables' list will be updated.
|
211
|
+
|
113
212
|
### Create a new record
|
114
|
-
|
115
|
-
|
116
|
-
--> 201, {"self": "http://www.example.com/bananas/102"
|
213
|
+
POST http://www.example.com/bananas, '{"name": "Johnny", "number": 888}'
|
214
|
+
--> 201, '{"self": "http://www.example.com/bananas/102"
|
117
215
|
"name": "Johnny",
|
118
|
-
"number":
|
119
|
-
"
|
120
|
-
"
|
216
|
+
"number": 888,
|
217
|
+
"color": null,
|
218
|
+
"coconuts": "http://www.example.com/bananas/102/coconuts",
|
219
|
+
"apple": "http://www.example.com/bananas/102/apple }'
|
121
220
|
|
122
221
|
### Create an associated record
|
123
|
-
|
124
|
-
--> 201, {"self":"http://www.example.com/coconuts/432,
|
125
|
-
...}
|
222
|
+
POST http://www.example.com/bananas/23/coconuts, '{COCONUT}'
|
223
|
+
--> 201, {"self":"http://www.example.com/coconuts/432, ...}
|
126
224
|
|
225
|
+
|
127
226
|
### Delete records
|
128
|
-
DELETE /bananas/23
|
227
|
+
DELETE http://www.example.com/bananas/23
|
129
228
|
--> 200
|
130
229
|
|
131
|
-
|
132
|
-
|
133
|
-
Installation
|
134
|
-
============
|
135
|
-
|
136
|
-
With bundler from (rubygems.org)
|
230
|
+
### Linking records
|
137
231
|
|
138
|
-
|
232
|
+
LINK "http://www.example.com/bananas/23/coconuts",
|
233
|
+
Link: "http://www.example.com/coconuts/31"
|
234
|
+
--> 200
|
139
235
|
|
140
|
-
the
|
236
|
+
Toast uses the (unusual) methods LINK and UNLINK in order to express the action of linking or unlinking existing resources. The above request will add _Coconut#31_ to the association _Banana#coconuts_.
|
141
237
|
|
142
|
-
gem "toast", :git => "https://github.com/robokopp/toast.git"
|
143
|
-
|
144
|
-
Remarks
|
145
|
-
=======
|
146
238
|
|
147
|
-
REST is more than some pretty URIs, the use of the HTTP verbs and
|
148
|
-
response codes. It's on the Toast user to invent meaningful media
|
149
|
-
types that control the application's state and introduce
|
150
|
-
semantics. With toast you can build REST services or tightly coupled
|
151
|
-
server-client applications, which ever suits the task best. That's why
|
152
|
-
TOAST stands for:
|
153
239
|
|
154
|
-
> **TOast Ain't reST**
|
data/config/routes.rb
CHANGED
@@ -1,29 +1,3 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
|
3
|
-
ActiveRecord::Base.descendants.each do |model|
|
4
|
-
next unless model.is_resourceful_model?
|
5
|
-
|
6
|
-
resource_name = model.to_s.pluralize.underscore
|
7
|
-
|
8
|
-
namespaces = []
|
9
|
-
|
10
|
-
# routes must be defined for all defined namespaces of a model
|
11
|
-
model.toast_configs.each do |tc|
|
12
|
-
# once per namespace
|
13
|
-
next if namespaces.include? tc.namespace
|
14
|
-
|
15
|
-
namespaces << tc.namespace
|
16
|
-
|
17
|
-
match("#{tc.namespace}/#{resource_name}(/:id(/:subresource))" => 'toast#catch_all',
|
18
|
-
:constraints => { :id => /\d+/ },
|
19
|
-
:resource => resource_name,
|
20
|
-
:as => resource_name,
|
21
|
-
:defaults => { :format => 'json' })
|
22
|
-
|
23
|
-
match("#{tc.namespace}/#{resource_name}/:subresource" => 'toast#catch_all',
|
24
|
-
:resource => resource_name,
|
25
|
-
:defaults => { :format => 'json' })
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
2
|
+
mount Toast::RackApp.new, at: '/*toast_path'
|
29
3
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Use this generator to initialize Toast's main configuration file and the API config directory.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
toast_settings {
|
2
|
+
max_window 42
|
3
|
+
link_unlink_via_post false
|
4
|
+
|
5
|
+
authenticate do |request|
|
6
|
+
# authenticate the request here (ActionDispatch::Request)
|
7
|
+
# returned object (except false) is passed as first argument to every allow-block for authorization
|
8
|
+
false
|
9
|
+
end
|
10
|
+
}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'toast/request_helpers'
|
2
|
+
|
3
|
+
class Toast::CanonicalRequest
|
4
|
+
include Toast::RequestHelpers
|
5
|
+
include Toast::Errors
|
6
|
+
|
7
|
+
def initialize id, base_config, auth, request
|
8
|
+
@id = id
|
9
|
+
@base_config = base_config
|
10
|
+
@selected_attributes = request.query_parameters.delete(:toast_select).try(:split,',')
|
11
|
+
@uri_params = request.query_parameters
|
12
|
+
@base_uri = base_uri(request)
|
13
|
+
@verb = request.request_method.downcase
|
14
|
+
@auth = auth
|
15
|
+
@request = request
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond
|
19
|
+
if @verb.in? %w(get patch put delete)
|
20
|
+
self.send(@verb)
|
21
|
+
else
|
22
|
+
response :method_not_allowed,
|
23
|
+
headers: {'Allow' => allowed_methods(@base_config)},
|
24
|
+
msg: "method #{@verb.upcase} not supported for collection URIs"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
def get
|
31
|
+
if @base_config.via_get.nil?
|
32
|
+
# not declared under expose {}
|
33
|
+
response :method_not_allowed,
|
34
|
+
headers: {'Allow' => allowed_methods(@base_config)},
|
35
|
+
msg: "GET not configured"
|
36
|
+
else
|
37
|
+
begin
|
38
|
+
model = @base_config.via_get.handler.call(
|
39
|
+
@base_config.model_class.find(@id), @uri_params
|
40
|
+
)
|
41
|
+
|
42
|
+
# call allow blocks to authorize
|
43
|
+
if @base_config.via_get.permissions.all?{|p| p.call(@auth, model, @uri_params)}
|
44
|
+
response :ok,
|
45
|
+
headers: {"Content-Type" => @base_config.media_type},
|
46
|
+
msg: "sent #{model.class}##{model.id}",
|
47
|
+
body: represent(model, @base_config)
|
48
|
+
else
|
49
|
+
response :unauthorized, msg: "authorization failed"
|
50
|
+
end
|
51
|
+
|
52
|
+
rescue ActiveRecord::RecordNotFound
|
53
|
+
response :not_found, msg: "#{@base_config.model_class}##{@id} not found"
|
54
|
+
|
55
|
+
rescue BadRequest => error
|
56
|
+
response :bad_request, msg: "`#{error.message}' in: #{error.source_location}"
|
57
|
+
|
58
|
+
rescue => error
|
59
|
+
response :internal_server_error, msg: "exception from via_get handler: " + error.message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def put
|
65
|
+
patch
|
66
|
+
end
|
67
|
+
|
68
|
+
def patch
|
69
|
+
if @base_config.via_patch.nil?
|
70
|
+
# not declared under expose {}
|
71
|
+
response :method_not_allowed,
|
72
|
+
headers: {'Allow' => allowed_methods(@base_config)},
|
73
|
+
msg: "PATCH not configured"
|
74
|
+
else
|
75
|
+
begin
|
76
|
+
# decode payload
|
77
|
+
payload = JSON.parse(@request.body.read)
|
78
|
+
|
79
|
+
# remove all attributes not in writables from payload
|
80
|
+
payload.delete_if do |attr,val|
|
81
|
+
unless attr.to_sym.in?(@base_config.writables)
|
82
|
+
Toast.logger.warn "<PATCH #{@request.fullpath}> received attribute `#{attr}' is not writable or unknown"
|
83
|
+
true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
model_instance = @base_config.model_class.find(@id)
|
88
|
+
call_allow(@base_config.via_patch.permissions,
|
89
|
+
@auth, model_instance, @uri_params)
|
90
|
+
|
91
|
+
if call_handler(@base_config.via_patch.handler,
|
92
|
+
model_instance, payload, @uri_params)
|
93
|
+
|
94
|
+
response :ok, headers: {"Content-Type" => @base_config.media_type},
|
95
|
+
msg: "updated #{@base_config.model_class}##{@id}",
|
96
|
+
body: represent(@base_config.model_class.find(@id), @base_config)
|
97
|
+
|
98
|
+
else
|
99
|
+
message = model_instance.errors.count > 0 ?
|
100
|
+
": " + model_instance.errors.full_messages.join(',') : ''
|
101
|
+
|
102
|
+
response :conflict,
|
103
|
+
msg: "patch of #{model_instance.class}##{model_instance.id} aborted#{message}"
|
104
|
+
end
|
105
|
+
|
106
|
+
rescue JSON::ParserError => error
|
107
|
+
response :internal_server_error, msg: "expect JSON body"
|
108
|
+
|
109
|
+
rescue ActiveRecord::RecordNotFound => error
|
110
|
+
response :not_found, msg: error.message
|
111
|
+
|
112
|
+
rescue BadRequest => error
|
113
|
+
response :bad_request, msg: "`#{error.message}' in: #{error.source_location}"
|
114
|
+
|
115
|
+
rescue AllowError => error
|
116
|
+
response :internal_server_error,
|
117
|
+
msg: "exception raised in allow block: `#{error.orig_error.message}' in #{error.source_location}"
|
118
|
+
rescue HandlerError => error
|
119
|
+
response :internal_server_error,
|
120
|
+
msg: "exception raised in via_patch handler: `#{error.orig_error.message}' in #{error.source_location}"
|
121
|
+
rescue NotAllowed => error
|
122
|
+
response :unauthorized, msg: "not authorized by allow block in: #{error.source_location}"
|
123
|
+
|
124
|
+
rescue => error
|
125
|
+
response :internal_server_error, msg: "exception from via_patch handler: "+ error.message
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete
|
131
|
+
if @base_config.via_delete.nil?
|
132
|
+
# not declared
|
133
|
+
response :method_not_allowed,
|
134
|
+
headers: {'Allow' => allowed_methods(@base_config)},
|
135
|
+
msg: "DELETE not configured"
|
136
|
+
else
|
137
|
+
begin
|
138
|
+
|
139
|
+
model_instance = @base_config.model_class.find(@id)
|
140
|
+
|
141
|
+
call_allow(@base_config.via_delete.permissions,
|
142
|
+
@auth, model_instance, @uri_params)
|
143
|
+
|
144
|
+
if call_handler(@base_config.via_delete.handler,
|
145
|
+
model_instance, @uri_params)
|
146
|
+
response :no_content, msg: "deleted #{@base_config.model_class}##{@id}"
|
147
|
+
else
|
148
|
+
|
149
|
+
message = model_instance.errors.count > 0 ?
|
150
|
+
": " + model_instance.errors.full_messages.join(',') : ''
|
151
|
+
|
152
|
+
response :conflict,
|
153
|
+
msg: "deletion of #{model_instance.class}##{model_instance.id} aborted#{message}"
|
154
|
+
end
|
155
|
+
|
156
|
+
rescue AllowError => error
|
157
|
+
return response :internal_server_error,
|
158
|
+
msg: "exception raised in allow block: `#{error.orig_error.message}' in #{error.source_location}"
|
159
|
+
|
160
|
+
rescue BadRequest => error
|
161
|
+
response :bad_request, msg: "`#{error.message}' in: #{error.source_location}"
|
162
|
+
|
163
|
+
rescue HandlerError => error
|
164
|
+
return response :internal_server_error,
|
165
|
+
msg: "exception raised in handler: `#{error.orig_error.message}' in #{error.source_location}"
|
166
|
+
rescue NotAllowed => error
|
167
|
+
return response :unauthorized, msg: "not authorized by allow block in: #{error.source_location}"
|
168
|
+
|
169
|
+
rescue ActiveRecord::RecordNotFound => error
|
170
|
+
response :not_found,
|
171
|
+
msg: error.message
|
172
|
+
|
173
|
+
rescue => error
|
174
|
+
response :internal_server_error,
|
175
|
+
msg: "exception from via_delete handler: #{error.message}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|