rails-rapido 0.7.1 → 0.9.3
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/.rubocop.yml +371 -64
- data/.ruby-version +1 -1
- data/README.md +75 -12
- data/lib/rapido.rb +0 -8
- data/lib/rapido/api_controller.rb +63 -36
- data/lib/rapido/controller.rb +103 -101
- data/lib/rapido/version.rb +1 -1
- metadata +3 -11
- data/app/views/rapido/app/edit.slim +0 -4
- data/app/views/rapido/app/index.slim +0 -13
- data/app/views/rapido/app/new.slim +0 -5
- data/app/views/rapido/app/show.slim +0 -13
- data/lib/rapido/app_controller.rb +0 -124
- data/lib/rapido/app_record_not_found.rb +0 -12
- data/lib/rapido/auth.rb +0 -4
- data/lib/rapido/auth/api_key.rb +0 -42
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.2
|
data/README.md
CHANGED
@@ -4,9 +4,53 @@
|
|
4
4
|
|
5
5
|
Rapido is a simple, highly opinionated library that can be included into your Rails controllers to enforce standardized behavior and security.
|
6
6
|
|
7
|
+
## Example
|
8
|
+
|
9
|
+
Below is a typical example of a class using Rapido. More examples are available in the [dummy application](https://github.com/starfighterheavy/rapido/tree/master/test/dummy).
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# Create, Index: https://www.example.com/api/documents/
|
13
|
+
# Show, update, delete: https://www.example.com/api/documents/123
|
14
|
+
|
15
|
+
class DocumentsController < ApplicationController
|
16
|
+
include Rapido::ApiController
|
17
|
+
|
18
|
+
attr_permitted :file, file_name, :category
|
19
|
+
|
20
|
+
belongs_to :user, getter: :current_user
|
21
|
+
|
22
|
+
present_with DocumentPresenter
|
23
|
+
|
24
|
+
present_collection_with DocumentCollectionPresenter, :query # Query could be a string supplied as a URL parameter to search documents by name.
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Another example where records are automatically retrieved from route parameters.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# Create, Index: https://www.example.com/api/documents/123/pages
|
32
|
+
# Show, update, delete: https://www.example.com/api/documents/123/pages/345
|
33
|
+
|
34
|
+
class PagesController < ApplicationController
|
35
|
+
include Rapido::ApiController
|
36
|
+
|
37
|
+
attr_permitted :contents, :page_number
|
38
|
+
|
39
|
+
belongs_to :document, owner: :current_user
|
40
|
+
|
41
|
+
present_with PagePresenter
|
42
|
+
|
43
|
+
present_collection_with PageCollectionPresenter
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
7
47
|
## API
|
8
48
|
|
9
|
-
|
49
|
+
### `attr_permitted`
|
50
|
+
|
51
|
+
Accepts a list of symbols. These symbols specify the attributes that are supplied to StrongParameters as permitted parameters in `create` and `update` actions.
|
52
|
+
|
53
|
+
### `belongs_to`
|
10
54
|
|
11
55
|
Specifies the owner of the resource. For example, if many `Post` belonged to `User`, then in the `PostsController`, the following would be included: `belongs_to :user` and `Post` would be retrieved like so:
|
12
56
|
|
@@ -32,6 +76,10 @@ Specifies the method to call to retrieve the owner, rather than retrieving by wi
|
|
32
76
|
|
33
77
|
This can be useful when ownership exists but is not reflected in the route structure explicitly.
|
34
78
|
|
79
|
+
**build**
|
80
|
+
|
81
|
+
You can create override the class Rapido::Controller build method directly in situations where the owner class does not have a build_[resource] method. Note that `has_one` will execute first if set to true, ignoring any `build` method you create directly, so if you do override it do not set `has_one: true`.
|
82
|
+
|
35
83
|
**foreign_key**
|
36
84
|
|
37
85
|
Default `id`. Specifies the name of the lookup column for the owner. For example, if `Post` belongs to `User`, then in the `PostsController` if `belongs_to :user, foreign_key: :token` is supplied, then the post would be retrieved like so:
|
@@ -44,41 +92,56 @@ Default `[singular owner name]_id`. Specifies the param used as the owner's fore
|
|
44
92
|
|
45
93
|
`User.find(params[:author_id]).posts.find(params[:id])`.
|
46
94
|
|
47
|
-
|
95
|
+
### `lookup_param`
|
48
96
|
|
49
97
|
Specifies the param used to retrieve the resource. For example, if `Post` belongs to `User`, then in the `PostsController` if `lookup_param :token` is supplied, then the post would be retrieved like so:
|
50
98
|
|
51
99
|
`User.find_by(params[:id]).posts.find_by(token: params[:token])`.
|
52
100
|
|
53
|
-
|
101
|
+
### `present_with`
|
54
102
|
|
55
103
|
Specifies the class that will present the resource. This class must accept the resource as the only parameter at initialization, and respond to the `as_json` for output when used with the `Rapido::ApiController`.
|
56
104
|
|
57
105
|
For example, if `params` contained a `:filter` parameter which should be used by the presenter , then the following would work:
|
58
106
|
|
59
|
-
`
|
107
|
+
`present_with WidgetsPresenter, :filter`
|
60
108
|
|
61
109
|
The `initialize` method of the presenter should be structured as such:
|
62
110
|
|
63
111
|
`def initialize(widgets, filter = nil)`
|
64
112
|
|
65
|
-
|
113
|
+
### `present_collection_with`
|
66
114
|
|
67
115
|
Specifies the class that will present a collection of resources, as in the index method. Similar to `presented_by`, the class must accept the resource collection as the only argument at initialization, and respond to `as_json` for output when used with the `Rapido::ApiController`. The `collection_presented_by` can also accept a list of arguments, as symbols, that should be pulled from the `params` hash and passed to presenter class at initialization as optional argments.
|
68
116
|
|
69
|
-
Collection presenters can also be provided args, similar to `
|
117
|
+
Collection presenters can also be provided args, similar to `present_with`
|
70
118
|
|
71
|
-
|
119
|
+
### `permit_no_params!`
|
72
120
|
|
73
|
-
|
121
|
+
This will disallow any parameters in `create` and `update` actions.
|
74
122
|
|
75
|
-
|
123
|
+
## Filters
|
76
124
|
|
77
|
-
|
125
|
+
The following filters are available to override, similar to the standard Rails action filters.
|
126
|
+
|
127
|
+
#### Create
|
128
|
+
|
129
|
+
* before_build
|
130
|
+
* before_create
|
131
|
+
* after_create_success
|
132
|
+
* after_create_failure
|
133
|
+
|
134
|
+
#### Destroy
|
135
|
+
|
136
|
+
* before_destroy
|
137
|
+
* after_destroy_success
|
78
138
|
|
79
|
-
|
139
|
+
#### Update
|
80
140
|
|
81
|
-
|
141
|
+
* before_assign_attributes
|
142
|
+
* before_updat
|
143
|
+
* after_update_success
|
144
|
+
* after_update_failure
|
82
145
|
|
83
146
|
## Development
|
84
147
|
|
data/lib/rapido.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
require 'rapido/version'
|
2
2
|
require 'rapido/controller'
|
3
3
|
require 'rapido/api_controller'
|
4
|
-
require 'rapido/app_controller'
|
5
|
-
require 'rapido/app_record_not_found'
|
6
|
-
require 'rapido/auth/api_key'
|
7
4
|
|
8
5
|
module Rapido
|
9
6
|
class << self
|
@@ -11,12 +8,7 @@ module Rapido
|
|
11
8
|
end
|
12
9
|
|
13
10
|
class Configuration
|
14
|
-
attr_accessor :authority_class, :authority_lookup_param, :authority_lookup_field
|
15
|
-
|
16
11
|
def initialize
|
17
|
-
@authority_class = :account
|
18
|
-
@authority_lookup_param = :api_key
|
19
|
-
@authority_lookup_field = :api_key
|
20
12
|
end
|
21
13
|
end
|
22
14
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext'
|
3
3
|
require 'active_support/rescuable'
|
4
|
+
require 'rapido/controller'
|
4
5
|
require 'rapido/errors'
|
5
6
|
|
6
7
|
module Rapido
|
@@ -10,6 +11,8 @@ module Rapido
|
|
10
11
|
include Rapido::Errors
|
11
12
|
|
12
13
|
included do
|
14
|
+
include Rapido::Controller
|
15
|
+
|
13
16
|
rescue_from RecordNotFound do |e|
|
14
17
|
render json: { errors: [ e.to_s ] }, status: 404
|
15
18
|
end
|
@@ -18,21 +21,32 @@ module Rapido
|
|
18
21
|
end
|
19
22
|
|
20
23
|
def index
|
21
|
-
|
24
|
+
return if performed?
|
25
|
+
render json: resource_collection_presenter
|
22
26
|
end
|
23
27
|
|
24
28
|
def show
|
25
|
-
|
29
|
+
return if performed?
|
30
|
+
if request.format.to_sym == :json
|
31
|
+
render json: resource_presenter
|
32
|
+
elsif request.format.to_sym == :xml
|
33
|
+
render xml: resource_presenter
|
34
|
+
elsif request.format.to_sym == :csv
|
35
|
+
render plain: resource_presenter.send("to_csv")
|
36
|
+
else
|
37
|
+
render json: resource_presenter
|
38
|
+
end
|
26
39
|
end
|
27
40
|
|
28
41
|
def create
|
42
|
+
return if performed?
|
29
43
|
before_build
|
30
44
|
new_resource = build_resource(resource_params)
|
31
45
|
before_create(new_resource)
|
32
46
|
if new_resource.save
|
33
47
|
after_create_success(new_resource)
|
34
48
|
return if performed?
|
35
|
-
render json:
|
49
|
+
render json: resource_presenter(new_resource), status: :created
|
36
50
|
else
|
37
51
|
after_create_failure(new_resource)
|
38
52
|
return if performed?
|
@@ -41,7 +55,8 @@ module Rapido
|
|
41
55
|
end
|
42
56
|
|
43
57
|
def destroy
|
44
|
-
|
58
|
+
return if performed?
|
59
|
+
resource_before_destruction = resource_presenter
|
45
60
|
before_destroy
|
46
61
|
resource.destroy
|
47
62
|
after_destroy_success
|
@@ -50,13 +65,14 @@ module Rapido
|
|
50
65
|
end
|
51
66
|
|
52
67
|
def update
|
68
|
+
return if performed?
|
53
69
|
before_assign_attributes
|
54
70
|
resource.assign_attributes(resource_params)
|
55
71
|
before_update
|
56
72
|
if resource.save
|
57
73
|
after_update_success
|
58
74
|
return if performed?
|
59
|
-
render json:
|
75
|
+
render json: resource_presenter
|
60
76
|
else
|
61
77
|
after_update_failure
|
62
78
|
return if performed?
|
@@ -66,52 +82,63 @@ module Rapido
|
|
66
82
|
|
67
83
|
private
|
68
84
|
|
69
|
-
|
70
|
-
|
85
|
+
def after_create_failure(new_resource)
|
86
|
+
end
|
71
87
|
|
72
|
-
|
73
|
-
|
88
|
+
def after_create_success(new_resource)
|
89
|
+
end
|
74
90
|
|
75
|
-
|
76
|
-
|
91
|
+
def after_destroy_success
|
92
|
+
end
|
77
93
|
|
78
|
-
|
79
|
-
|
94
|
+
def after_update_failure
|
95
|
+
end
|
80
96
|
|
81
|
-
|
82
|
-
|
97
|
+
def after_update_success
|
98
|
+
end
|
83
99
|
|
84
|
-
|
85
|
-
|
100
|
+
def before_assign_attributes
|
101
|
+
end
|
86
102
|
|
87
|
-
|
88
|
-
|
103
|
+
def before_build
|
104
|
+
end
|
89
105
|
|
90
|
-
|
91
|
-
|
106
|
+
def before_create(new_resource)
|
107
|
+
end
|
92
108
|
|
93
|
-
|
94
|
-
|
109
|
+
def before_destroy
|
110
|
+
end
|
95
111
|
|
96
|
-
|
97
|
-
|
112
|
+
def before_update
|
113
|
+
end
|
98
114
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
115
|
+
def permit_only_allowed_actions
|
116
|
+
return unless allowed_actions
|
117
|
+
head :unauthorized unless allowed_actions.include?(params[:action].to_sym)
|
118
|
+
end
|
103
119
|
|
104
|
-
|
120
|
+
def resource_presenter(new_resource = nil)
|
121
|
+
@resource_presenter ||= begin
|
105
122
|
args = presenter_args.nil? ? nil : presenter_args.map { |arg| params[arg] }
|
106
|
-
|
107
|
-
|
123
|
+
if presenter
|
124
|
+
presenter.new(new_resource || resource, *args)
|
125
|
+
else
|
126
|
+
(new_resource || resource).to_h
|
127
|
+
end
|
108
128
|
end
|
129
|
+
end
|
109
130
|
|
110
|
-
|
131
|
+
def resource_collection_presenter
|
132
|
+
@resource_collection_presenter ||= begin
|
111
133
|
args = collection_presenter_args.nil? ? nil : collection_presenter_args.map { |arg| params[arg] }
|
112
|
-
|
113
|
-
|
114
|
-
|
134
|
+
if collection_presenter
|
135
|
+
collection_presenter.new(resource_collection, *args)
|
136
|
+
elsif presenter
|
137
|
+
resource_collection.map { |r| presenter.new(r) }
|
138
|
+
else
|
139
|
+
resource_collection.map(&:to_h)
|
140
|
+
end
|
115
141
|
end
|
142
|
+
end
|
116
143
|
end
|
117
144
|
end
|
data/lib/rapido/controller.rb
CHANGED
@@ -56,6 +56,7 @@ module Rapido
|
|
56
56
|
@collection_presenter ||= presenter_class
|
57
57
|
@collection_presenter_args = args if args.count > 0
|
58
58
|
end
|
59
|
+
alias present_collection_with collection_presented_by
|
59
60
|
|
60
61
|
def owner_lookup_defaults
|
61
62
|
owner_lookup_param(@owner_class, :id)
|
@@ -91,140 +92,141 @@ module Rapido
|
|
91
92
|
@presenter ||= presenter_class
|
92
93
|
@presenter_args = args if args.count > 0
|
93
94
|
end
|
95
|
+
alias present_with presented_by
|
94
96
|
end
|
95
97
|
|
96
98
|
private
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
|
100
|
+
def allowed_actions
|
101
|
+
setting(:allowed_actions)
|
102
|
+
end
|
101
103
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
104
|
+
def build_resource(params = {})
|
105
|
+
return owner.send('build_' + resource_class_name, params) if setting(:has_one)
|
106
|
+
return owner.send(resource_class_name.pluralize).build(params) if owner && owner.respond_to?(resource_class_name.pluralize)
|
107
|
+
begin
|
108
|
+
send(:build)
|
109
|
+
rescue NoMethodError => _e
|
110
|
+
raise 'Rapido::Controller must belong to something that responds to build or define a build method'
|
110
111
|
end
|
112
|
+
end
|
111
113
|
|
112
|
-
|
113
|
-
|
114
|
-
|
114
|
+
def collection_presenter
|
115
|
+
setting(:collection_presenter)
|
116
|
+
end
|
115
117
|
|
116
|
-
|
117
|
-
|
118
|
-
|
118
|
+
def collection_presenter_args
|
119
|
+
setting(:collection_presenter_args)
|
120
|
+
end
|
119
121
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
122
|
+
def owner_class
|
123
|
+
return nil unless setting(:owner_class)
|
124
|
+
@owner_class ||= begin
|
125
|
+
name = setting(:owner_class)
|
126
|
+
name.to_s.camelize.constantize
|
127
|
+
rescue NameError
|
128
|
+
raise BadOwnerClassName, name
|
128
129
|
end
|
130
|
+
end
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
-
|
132
|
+
def owner_class_name
|
133
|
+
@owner_class_name ||= owner_class.to_s.underscore
|
134
|
+
end
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
136
|
+
def owner_lookup_param
|
137
|
+
@owner_lookup_param ||=
|
138
|
+
setting(:owner_lookup_param).to_s
|
139
|
+
end
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def owner_lookup_field
|
142
|
+
@owner_lookup_field ||=
|
143
|
+
(setting(:owner_lookup_field) || owner_lookup_param).to_s
|
144
|
+
end
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
else
|
152
|
-
base = owner_class
|
153
|
-
end
|
154
|
-
base.find_by!(owner_lookup_field => params[owner_lookup_param])
|
146
|
+
def owner
|
147
|
+
@owner ||= begin
|
148
|
+
if setting(:owner_getter)
|
149
|
+
send(setting(:owner_getter))
|
150
|
+
elsif setting(:owner_class)
|
151
|
+
if setting(:owners_owner)
|
152
|
+
base = send(setting(:owners_owner)).send(owner_class_name.pluralize)
|
155
153
|
else
|
156
|
-
|
154
|
+
base = owner_class
|
157
155
|
end
|
158
|
-
|
159
|
-
|
156
|
+
base.find_by!(owner_lookup_field => params[owner_lookup_param])
|
157
|
+
else
|
158
|
+
nil
|
160
159
|
end
|
160
|
+
rescue ActiveRecord::RecordNotFound
|
161
|
+
raise RecordNotFound
|
161
162
|
end
|
163
|
+
end
|
162
164
|
|
163
|
-
|
164
|
-
|
165
|
-
|
165
|
+
def presenter
|
166
|
+
setting(:presenter)
|
167
|
+
end
|
166
168
|
|
167
|
-
|
168
|
-
|
169
|
-
|
169
|
+
def presenter_args
|
170
|
+
setting(:presenter_args)
|
171
|
+
end
|
170
172
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
end
|
173
|
+
def resource
|
174
|
+
@resource ||= begin
|
175
|
+
if setting(:has_one)
|
176
|
+
owner.send(resource_class_name)
|
177
|
+
elsif owner && owner.respond_to?(resource_class_name.pluralize)
|
178
|
+
owner
|
179
|
+
.send(resource_class_name.pluralize)
|
180
|
+
.find_by!(resource_lookup_param => params[resource_lookup_param])
|
181
|
+
else
|
182
|
+
begin
|
183
|
+
send(:find)
|
184
|
+
rescue NoMethodError => _e
|
185
|
+
raise 'Rapido::Controller must belong to something that has many or has one of resource, or define a find method'
|
185
186
|
end
|
186
|
-
rescue ActiveRecord::RecordNotFound
|
187
|
-
raise RecordNotFound
|
188
187
|
end
|
188
|
+
rescue ActiveRecord::RecordNotFound
|
189
|
+
raise RecordNotFound
|
189
190
|
end
|
191
|
+
end
|
190
192
|
|
191
|
-
|
192
|
-
|
193
|
-
|
193
|
+
def resource_class
|
194
|
+
@resource_class ||= resource_class_name.to_s.camelize.constantize
|
195
|
+
end
|
194
196
|
|
195
|
-
|
196
|
-
|
197
|
-
|
197
|
+
def resource_class_name
|
198
|
+
self.class.resource_class_name
|
199
|
+
end
|
198
200
|
|
199
201
|
# Todo: FIXME
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
end
|
202
|
+
def resource_collection
|
203
|
+
@resource_collection ||= begin
|
204
|
+
if setting(:has_one)
|
205
|
+
owner.send(resource_class_name)
|
206
|
+
else
|
207
|
+
owner.send(resource_class_name.pluralize)
|
207
208
|
end
|
208
209
|
end
|
210
|
+
end
|
209
211
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
212
|
+
def resource_lookup_param
|
213
|
+
@resource_lookup_param ||=
|
214
|
+
setting(:resource_lookup_param) || :id
|
215
|
+
end
|
214
216
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
217
|
+
def resource_permitted_params
|
218
|
+
@resource_permitted_params ||=
|
219
|
+
setting(:resource_permitted_params)
|
220
|
+
end
|
219
221
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
222
|
+
def resource_params
|
223
|
+
return {} if setting(:permit_no_params)
|
224
|
+
base = params.require(resource_class_name)
|
225
|
+
base.permit(resource_permitted_params)
|
226
|
+
end
|
225
227
|
|
226
|
-
|
227
|
-
|
228
|
-
|
228
|
+
def setting(var)
|
229
|
+
self.class.instance_variable_get("@#{var}")
|
230
|
+
end
|
229
231
|
end
|
230
232
|
end
|