rails-rapido 0.7.1 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.3
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
- #### `belongs_to`
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
- #### `lookup_param`
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
- #### `presented_by`
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
- `presented_by WidgetsPresenter, :filter`
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
- #### `collection_presented_by`
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 `presented_by`
117
+ Collection presenters can also be provided args, similar to `present_with`
70
118
 
71
- #### `attr_permitted`
119
+ ### `permit_no_params!`
72
120
 
73
- Accepts a list of symbols. These symbols specify the attributes that are supplied to StrongParameters as permitted parameters in `create` and `update` actions.
121
+ This will disallow any parameters in `create` and `update` actions.
74
122
 
75
- #### `permit_no_params!`
123
+ ## Filters
76
124
 
77
- This will disallow any parameters in `create` and `update` actions.
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
- ## Notes
139
+ #### Update
80
140
 
81
- Authentication & AppController functionality will be deprecated in v0.6 and removed in v1.0. With the 1.0 release, Rapido will remove all functionality that is not strictly oriented to streamlining API Controller development and security.
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
- render json: present_resource_collection(resource_collection)
24
+ return if performed?
25
+ render json: resource_collection_presenter
22
26
  end
23
27
 
24
28
  def show
25
- render json: present_resource(resource)
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: present_resource(new_resource), status: :created
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
- resource_before_destruction = present_resource(resource)
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: present_resource(resource)
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
- def after_create_failure(new_resource)
70
- end
85
+ def after_create_failure(new_resource)
86
+ end
71
87
 
72
- def after_create_success(new_resource)
73
- end
88
+ def after_create_success(new_resource)
89
+ end
74
90
 
75
- def after_destroy_success
76
- end
91
+ def after_destroy_success
92
+ end
77
93
 
78
- def after_update_failure
79
- end
94
+ def after_update_failure
95
+ end
80
96
 
81
- def after_update_success
82
- end
97
+ def after_update_success
98
+ end
83
99
 
84
- def before_assign_attributes
85
- end
100
+ def before_assign_attributes
101
+ end
86
102
 
87
- def before_build
88
- end
103
+ def before_build
104
+ end
89
105
 
90
- def before_create(new_resource)
91
- end
106
+ def before_create(new_resource)
107
+ end
92
108
 
93
- def before_destroy
94
- end
109
+ def before_destroy
110
+ end
95
111
 
96
- def before_update
97
- end
112
+ def before_update
113
+ end
98
114
 
99
- def permit_only_allowed_actions
100
- return unless allowed_actions
101
- head :unauthorized unless allowed_actions.include?(params[:action].to_sym)
102
- end
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
- def present_resource(resource)
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
- return presenter.new(*[resource, *args]).as_json if presenter
107
- resource.to_h
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
- def present_resource_collection(resource_collection)
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
- return collection_presenter.new(*[resource_collection, *args]).as_json if collection_presenter
113
- return resource_collection.map { |r| presenter.new(r).as_json } if presenter
114
- resource_collection.map(&:to_h)
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
@@ -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
- def allowed_actions
99
- setting(:allowed_actions)
100
- end
100
+ def allowed_actions
101
+ setting(:allowed_actions)
102
+ end
101
103
 
102
- def build_resource(params = {})
103
- return owner.send('build_' + resource_class_name, params) if setting(:has_one)
104
- return owner.send(resource_class_name.pluralize).build(params) if owner && owner.respond_to?(resource_class_name.pluralize)
105
- begin
106
- send(:build)
107
- rescue NoMethodError
108
- raise 'Rapido::Controller must belong to something that responds to build or define a build method'
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
- def collection_presenter
113
- setting(:collection_presenter)
114
- end
114
+ def collection_presenter
115
+ setting(:collection_presenter)
116
+ end
115
117
 
116
- def collection_presenter_args
117
- setting(:collection_presenter_args)
118
- end
118
+ def collection_presenter_args
119
+ setting(:collection_presenter_args)
120
+ end
119
121
 
120
- def owner_class
121
- return nil unless setting(:owner_class)
122
- @owner_class ||= begin
123
- name = setting(:owner_class)
124
- name.to_s.camelize.constantize
125
- rescue NameError
126
- raise BadOwnerClassName, name
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
- def owner_class_name
131
- @owner_class_name ||= owner_class.to_s.underscore
132
- end
132
+ def owner_class_name
133
+ @owner_class_name ||= owner_class.to_s.underscore
134
+ end
133
135
 
134
- def owner_lookup_param
135
- @owner_lookup_param ||=
136
- setting(:owner_lookup_param).to_s
137
- end
136
+ def owner_lookup_param
137
+ @owner_lookup_param ||=
138
+ setting(:owner_lookup_param).to_s
139
+ end
138
140
 
139
- def owner_lookup_field
140
- @owner_lookup_field ||=
141
- (setting(:owner_lookup_field) || owner_lookup_param).to_s
142
- end
141
+ def owner_lookup_field
142
+ @owner_lookup_field ||=
143
+ (setting(:owner_lookup_field) || owner_lookup_param).to_s
144
+ end
143
145
 
144
- def owner
145
- @owner ||= begin
146
- if setting(:owner_getter)
147
- send(setting(:owner_getter))
148
- elsif setting(:owner_class)
149
- if setting(:owners_owner)
150
- base = send(setting(:owners_owner)).send(owner_class_name.pluralize)
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
- nil
154
+ base = owner_class
157
155
  end
158
- rescue ActiveRecord::RecordNotFound
159
- raise RecordNotFound
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
- def presenter
164
- setting(:presenter)
165
- end
165
+ def presenter
166
+ setting(:presenter)
167
+ end
166
168
 
167
- def presenter_args
168
- setting(:presenter_args)
169
- end
169
+ def presenter_args
170
+ setting(:presenter_args)
171
+ end
170
172
 
171
- def resource
172
- @resource ||= begin
173
- if setting(:has_one)
174
- owner.send(resource_class_name)
175
- elsif owner && owner.respond_to?(resource_class_name.pluralize)
176
- owner
177
- .send(resource_class_name.pluralize)
178
- .find_by!(resource_lookup_param => params[resource_lookup_param])
179
- else
180
- begin NoMethodError
181
- send(:find)
182
- rescue
183
- raise 'Rapido::Controller must belong to something that has many or has one of resource, or define a find method'
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
- def resource_class
192
- @resource_class ||= resource_class_name.to_s.camelize.constantize
193
- end
193
+ def resource_class
194
+ @resource_class ||= resource_class_name.to_s.camelize.constantize
195
+ end
194
196
 
195
- def resource_class_name
196
- self.class.resource_class_name
197
- end
197
+ def resource_class_name
198
+ self.class.resource_class_name
199
+ end
198
200
 
199
201
  # Todo: FIXME
200
- def resource_collection
201
- @resource_collection ||= begin
202
- if setting(:has_one)
203
- owner.send(resource_class_name)
204
- else
205
- owner.send(resource_class_name.pluralize)
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
- def resource_lookup_param
211
- @resource_lookup_param ||=
212
- setting(:resource_lookup_param) || :id
213
- end
212
+ def resource_lookup_param
213
+ @resource_lookup_param ||=
214
+ setting(:resource_lookup_param) || :id
215
+ end
214
216
 
215
- def resource_permitted_params
216
- @resource_permitted_params ||=
217
- setting(:resource_permitted_params)
218
- end
217
+ def resource_permitted_params
218
+ @resource_permitted_params ||=
219
+ setting(:resource_permitted_params)
220
+ end
219
221
 
220
- def resource_params
221
- return {} if setting(:permit_no_params)
222
- base = params.require(resource_class_name)
223
- base.permit(resource_permitted_params)
224
- end
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
- def setting(var)
227
- self.class.instance_variable_get("@#{var}")
228
- end
228
+ def setting(var)
229
+ self.class.instance_variable_get("@#{var}")
230
+ end
229
231
  end
230
232
  end