skinny_controllers 0.7.4 → 0.8.0
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 +180 -8
- data/lib/generators/operation/USAGE +5 -0
- data/lib/generators/operation/operation_generator.rb +16 -0
- data/lib/generators/operation/templates/operation.rb.erb +64 -0
- data/lib/generators/policy/USAGE +5 -0
- data/lib/generators/policy/policy_generator.rb +20 -0
- data/lib/generators/policy/templates/policy.rb.erb +40 -0
- data/lib/generators/skinny_controller/USAGE +5 -0
- data/lib/generators/skinny_controller/skinny_controller_generator.rb +28 -0
- data/lib/generators/skinny_controller/templates/skinny_controller.rb.erb +24 -0
- data/lib/skinny_controllers/operation/model_helpers.rb +2 -2
- data/lib/skinny_controllers/version.rb +1 -1
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5387b4c2379bd53d4a19a66243c529d8e764dfab
|
4
|
+
data.tar.gz: 5c8df4c666ae85e6f762bb68391f06c03c5e21e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6a9c45f2ba2def7309e979a060a87f984640c03e33340778547f6fae68c56289c9d8f089e5092fd24fed89fef70ca7752095853a2ce219dd1021438cd5ddafe
|
7
|
+
data.tar.gz: 93434e6dd65a1c1a650810acb9b95fae6382570dbe908e538f84206c9f22790da2cfd4c4644eedc510309d9f2cf60c9b0e15700efa09f528b2c0735b0fc0f724
|
data/README.md
CHANGED
@@ -22,6 +22,20 @@ or
|
|
22
22
|
|
23
23
|
`gem install skinny_controllers`
|
24
24
|
|
25
|
+
|
26
|
+
## Generators
|
27
|
+
|
28
|
+
```
|
29
|
+
rails g operation event_summary
|
30
|
+
# => create app/operations/event_summary_operations.rb
|
31
|
+
|
32
|
+
rails g policy event_summary
|
33
|
+
# create app/policies/event_summary_policy.rb
|
34
|
+
|
35
|
+
rails g skinny_controller event_summaries
|
36
|
+
# create app/controllers/event_summaries_controller.rb
|
37
|
+
```
|
38
|
+
|
25
39
|
# Usage
|
26
40
|
|
27
41
|
## In a controller:
|
@@ -46,6 +60,8 @@ The above does a multitude of assumptions to make sure that you can type the lea
|
|
46
60
|
5. If relying on the default / implicit operations for create and update, the params key for your model's changes much be formatted as `{ Model.name.underscore => { attributes }}``
|
47
61
|
6. If using strong parameters, SkinnyControllers will look for `{action}_{model}_params` then `{model}_params` and then `params`. See the `strong_parameters_spec.rb` test to see an example.
|
48
62
|
|
63
|
+
|
64
|
+
|
49
65
|
### Your model name might be different from your resource name
|
50
66
|
Lets say you have a JSON API resource that you'd like to render that has some additional/subset of data.
|
51
67
|
Maybe the model is an `Event`, and the resource an `EventSummary` (which could do some aggregation of `Event` data).
|
@@ -244,6 +260,158 @@ end
|
|
244
260
|
```
|
245
261
|
|
246
262
|
|
263
|
+
## More Advanced Usage
|
264
|
+
|
265
|
+
These are snippets taking from other projects.
|
266
|
+
|
267
|
+
### Finding a record when the id parameter isn't passed
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
module HostOperations
|
273
|
+
class Read < SkinnyControllers::Operation::Base
|
274
|
+
def run
|
275
|
+
model # always allowed, never restricted
|
276
|
+
end
|
277
|
+
|
278
|
+
# Needs to be overridden, because a 'host' can be either
|
279
|
+
# an Event or an Organization.
|
280
|
+
#
|
281
|
+
# the params to this method should include the subdomain
|
282
|
+
# e.g.: { subdomain: 'swingin2015' }
|
283
|
+
def model_from_params
|
284
|
+
subdomain = params[:subdomain]
|
285
|
+
# first check the events, since those are more commonly used
|
286
|
+
host = Event.find_by_domain(subdomain)
|
287
|
+
# if the event doesn't exist, see if we have an organization
|
288
|
+
host ||= Organization.find_by_domain(subdomain)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
### The built in model-finding methods can be completely ignored
|
295
|
+
|
296
|
+
The `model` method does not need to be overridden. `run` is what is called on the operation.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
module MembershipRenewalOperations
|
300
|
+
# MembershipRenewalsController#index
|
301
|
+
class ReadAll < SkinnyControllers::Operation::Base
|
302
|
+
|
303
|
+
def run
|
304
|
+
# default 'model' functionality is avoided
|
305
|
+
latest_renewals
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def organization
|
311
|
+
id = params[:organization_id]
|
312
|
+
Organization.find(id)
|
313
|
+
end
|
314
|
+
|
315
|
+
def renewals
|
316
|
+
options = organization.membership_options.includes(renewals: [:user, :membership_option])
|
317
|
+
options.map(&:renewals).flatten
|
318
|
+
end
|
319
|
+
|
320
|
+
def latest_renewals
|
321
|
+
sorted_renewals = renewals.sort_by{|r| [r.user_id,r.updated_at]}.reverse
|
322
|
+
|
323
|
+
# unique picks the first option.
|
324
|
+
# so, because the list is sorted by user id, then updated at,
|
325
|
+
# for each user, the first renewal will be chosen...
|
326
|
+
# and because it is descending, that means the most recent renewal
|
327
|
+
sorted_renewals.uniq{|r| r.user_id}
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
## Testing
|
335
|
+
|
336
|
+
The whole goal of this project is to minimize the complexity or existence of controller tests, and provide
|
337
|
+
a way to unit test business logic.
|
338
|
+
|
339
|
+
In the following examples, I'll be using RSpec -- but there isn't anything that would prevent you from using a different testing framework, if you so choose.
|
340
|
+
|
341
|
+
### Operations
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
describe HostOperations do
|
345
|
+
describe HostOperations::Read do
|
346
|
+
context 'model_from_params' do
|
347
|
+
let(:subdomain){ 'subdomain' }
|
348
|
+
# an operation takes a user, and a list of params
|
349
|
+
# there are optional parameters as well, but generally may not be required.
|
350
|
+
# see: `SkinnyControllers:::Operation::Base`
|
351
|
+
let(:operation){ HostOperations::Read.new(nil, { subdomain: subdomain }) }
|
352
|
+
|
353
|
+
it 'finds an event' do
|
354
|
+
event = create(:event, domain: subdomain)
|
355
|
+
model = operation.run
|
356
|
+
expect(model).to eq event
|
357
|
+
end
|
358
|
+
#...
|
359
|
+
```
|
360
|
+
|
361
|
+
### Policies
|
362
|
+
|
363
|
+
With policies, I like to test using Procs, because the setup is the same for most actions, and it's easier to set up different scenarios.
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
describe PackagePolicy do
|
367
|
+
# will test if the owner of this object can access it
|
368
|
+
let(:by_owner){
|
369
|
+
->(method){
|
370
|
+
package = create(:package)
|
371
|
+
# a policy takes a user and an object
|
372
|
+
policy = PackagePolicy.new(package.event.hosted_by, package)
|
373
|
+
policy.send(method)
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
# will test if the person registering with this package has permission
|
378
|
+
let(:by_registrant){
|
379
|
+
->(method){
|
380
|
+
event = create(:event)
|
381
|
+
package = create(:package, event: event)
|
382
|
+
attendance = create(:attendance, host: event, package: package)
|
383
|
+
# a policy takes a user and an object
|
384
|
+
policy = PackagePolicy.new(attendance.attendee, package)
|
385
|
+
policy.send(method)
|
386
|
+
}
|
387
|
+
}
|
388
|
+
|
389
|
+
context 'can be read?' do
|
390
|
+
it 'by the event owner' do
|
391
|
+
result = by_owner.call(:read?)
|
392
|
+
expect(result).to eq true
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'by a registrant' do
|
396
|
+
result = by_registrant.call(:read?)
|
397
|
+
expect(result).to eq true
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context 'can be updated?' do
|
402
|
+
it 'by the event owner' do
|
403
|
+
result = by_owner.call(:update?)
|
404
|
+
expect(result).to eq true
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'by a registrant' do
|
408
|
+
result = by_registrant.call(:update?)
|
409
|
+
expect(result).to eq false
|
410
|
+
end
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
|
247
415
|
## Globally Configurable Options
|
248
416
|
|
249
417
|
All of these can be set on `SkinnyControllers`,
|
@@ -266,17 +434,21 @@ The following options are available:
|
|
266
434
|
|`action_map`| see [skinny_controllers.rb](./lib/skinny_controllers.rb#L61)| |
|
267
435
|
|
268
436
|
|
437
|
+
|
438
|
+
|
439
|
+
|
440
|
+
|
269
441
|
-------------------------------------------------------
|
270
442
|
|
271
443
|
## How is this different from trailblazer?
|
272
444
|
|
273
445
|
This may not be horribly apparent, but here is a table overviewing some highlevel differences
|
274
446
|
|
275
|
-
| Feature | skinny_controllers | [trailblazer](https://github.com/apotonick/trailblazer) |
|
276
|
-
|
277
|
-
| Purpose
|
278
|
-
| Added Layers
|
279
|
-
| Validation
|
280
|
-
| Additional objects
|
281
|
-
| Rendering
|
282
|
-
| App Structure
|
447
|
+
| Feature | - | skinny_controllers | [trailblazer](https://github.com/apotonick/trailblazer) |
|
448
|
+
|----|----|----|----|
|
449
|
+
| Purpose |-| API - works very well with [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers)| General - additional featers for server-side rendered views |
|
450
|
+
| Added Layers |-| Operations, Policies | Operations, Policies, Forms |
|
451
|
+
| Validation |-| stay in models | moved to operations via contract block |
|
452
|
+
| Additional objects|-| none | contacts, representers, callbacks, cells |
|
453
|
+
| Rendering |-| done in the controller, and up to the dev to decide how that is done. `ActiveModel::Serializers` with JSON-API is highly recommended |-| representers provide a way to define serializers for json, xml, json-api, etc |
|
454
|
+
| App Structure |-| same as rails. `app/operations` and `app/policies` are added | encourages a new structure 'concepts', where cells, view templates, assets, operations, etc are all under `concepts/{model-name}` |
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class OperationGenerator < Rails::Generators::NamedBase
|
2
|
+
# gives us file_name
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def generate_layout
|
6
|
+
template 'operation.rb.erb', File.join('app/operations', class_path, "#{file_name}_operations.rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
def operation_name
|
10
|
+
file_name.camelize
|
11
|
+
end
|
12
|
+
|
13
|
+
def controller_name
|
14
|
+
operation_name.pluralize + 'Controller'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module <%= operation_name %>Operations
|
2
|
+
|
3
|
+
# Below are all the available actions an Operation can have.
|
4
|
+
# These directly correlate to the controller actions. None of
|
5
|
+
# the operations below are required. Default functionality is basic CRUD,
|
6
|
+
# and to always allow the model object to return.
|
7
|
+
# See: SkinnyControllers::Operation::Default#run
|
8
|
+
#
|
9
|
+
# NOTE: If each operation gets big enough, it may be desirable to
|
10
|
+
# move each operation in to its own file.
|
11
|
+
#
|
12
|
+
# In every operation, the following variables are available to you:
|
13
|
+
# - current_user
|
14
|
+
# - params
|
15
|
+
# - params_for_action - params specific to the action, if configured
|
16
|
+
# - action - current controller action
|
17
|
+
# - model_key - the underscored model_name
|
18
|
+
# - model_params - params based on the model_key in params
|
19
|
+
#
|
20
|
+
# Methods:
|
21
|
+
# - allowed? - calls allowed_for?(model)
|
22
|
+
# - allowed_for? - allows you to pass an object to the policy that corresponds
|
23
|
+
# to this operation
|
24
|
+
|
25
|
+
# # <%= controller_name %>#create
|
26
|
+
# class Create < SkinnyControllers::Operation::Base
|
27
|
+
# def run
|
28
|
+
# # @model is used here, because the `model` method is memoized using
|
29
|
+
# # the @model instance variable
|
30
|
+
# @model = model_class.new(model_params)
|
31
|
+
# @model.save
|
32
|
+
# @model
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # <%= controller_name %>#index
|
37
|
+
# class ReadAll < SkinnyControllers::Operation::Base
|
38
|
+
# def run
|
39
|
+
# # model is a list of model_class instances
|
40
|
+
# model if allowed?
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # <%= controller_name %>#show
|
45
|
+
# class Read < SkinnyControllers::Operation::Base
|
46
|
+
# def run
|
47
|
+
# model if allowed?
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# # <%= controller_name %>#update
|
52
|
+
# class Update < SkinnyControllers::Operation::Base
|
53
|
+
# def run
|
54
|
+
# model.update(model_params) if allowed?
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# # <%= controller_name %>#destroy
|
59
|
+
# class Delete < SkinnyControllers::Operation::Base
|
60
|
+
# def run
|
61
|
+
# model.destroy if allowed?
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class PolicyGenerator < Rails::Generators::NamedBase
|
2
|
+
# gives us file_name
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def generate_layout
|
6
|
+
template 'policy.rb.erb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
def policy_name
|
10
|
+
operation_name + 'Policy'
|
11
|
+
end
|
12
|
+
|
13
|
+
def operation_name
|
14
|
+
file_name.camelize
|
15
|
+
end
|
16
|
+
|
17
|
+
def controller_name
|
18
|
+
operation_name.pluralize + 'Controller'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class <%= policy_name %> < SkinnyControllers::Policy::Base
|
2
|
+
|
3
|
+
# Below are all the available permissions. Each permission corresponds
|
4
|
+
# to an action in the controller.
|
5
|
+
# Default functionality is to return true (allow) -- so the methods
|
6
|
+
# below do not need to exist, unless you want to add custom logic
|
7
|
+
# to them.
|
8
|
+
#
|
9
|
+
# The following variables are available to you in each policy
|
10
|
+
# - object - the object the user is trying to access.
|
11
|
+
# - user - the current user
|
12
|
+
#
|
13
|
+
|
14
|
+
# <%= controller_name %>#index
|
15
|
+
def read_all?
|
16
|
+
default? # SkinnyControllers.allow_by_default # aka "true"
|
17
|
+
end
|
18
|
+
|
19
|
+
# <%= controller_name %>#show
|
20
|
+
def read?
|
21
|
+
default? # SkinnyControllers.allow_by_default # aka "true"
|
22
|
+
end
|
23
|
+
|
24
|
+
# <%= controller_name %>#create
|
25
|
+
def create?
|
26
|
+
default? # SkinnyControllers.allow_by_default # aka "true"
|
27
|
+
end
|
28
|
+
|
29
|
+
# <%= controller_name %>#update
|
30
|
+
def update?
|
31
|
+
default? # SkinnyControllers.allow_by_default # aka "true"
|
32
|
+
end
|
33
|
+
|
34
|
+
# <%= controller_name %>#destroy
|
35
|
+
def delete?
|
36
|
+
default? # SkinnyControllers.allow_by_default # aka "true"
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class SkinnyControllerGenerator < Rails::Generators::NamedBase
|
2
|
+
# gives us file_name
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def generate_layout
|
6
|
+
template 'skinny_controller.rb.erb', File.join('app/controllers', class_path, "#{file_name}_controller.rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
def operation_name
|
10
|
+
file_name.camelize
|
11
|
+
end
|
12
|
+
|
13
|
+
def controller_name
|
14
|
+
operation_name.pluralize + 'Controller'
|
15
|
+
end
|
16
|
+
|
17
|
+
def parent_class
|
18
|
+
if defined?(::ApiController)
|
19
|
+
'ApiController'
|
20
|
+
elsif defined?(::APIController)
|
21
|
+
'APIController'
|
22
|
+
elsif defined?(::ApplicationController)
|
23
|
+
'ApplicationController'
|
24
|
+
else
|
25
|
+
'ActionController::Base'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class <%= controller_name %> < <%= parent_class %>
|
2
|
+
include SkinnyControllers::Diet
|
3
|
+
|
4
|
+
def index
|
5
|
+
render json: model
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
render json: model
|
10
|
+
end
|
11
|
+
|
12
|
+
def show
|
13
|
+
render json: model
|
14
|
+
end
|
15
|
+
|
16
|
+
def update
|
17
|
+
render json: model
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
render json: model
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -50,7 +50,7 @@ module SkinnyControllers
|
|
50
50
|
|
51
51
|
def model_param_name
|
52
52
|
# model_key comes from Operation::Base
|
53
|
-
|
53
|
+
model_key || model_name.underscore
|
54
54
|
end
|
55
55
|
|
56
56
|
# @param [Hash] scoped_params
|
@@ -95,7 +95,7 @@ module SkinnyControllers
|
|
95
95
|
association = association_name_from_object
|
96
96
|
scoped.send(association)
|
97
97
|
else
|
98
|
-
|
98
|
+
raise "Parent object of type #{scope[:type]} not accessible"
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skinny_controllers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- L. Preston Sego III
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -186,6 +186,15 @@ extensions: []
|
|
186
186
|
extra_rdoc_files: []
|
187
187
|
files:
|
188
188
|
- README.md
|
189
|
+
- lib/generators/operation/USAGE
|
190
|
+
- lib/generators/operation/operation_generator.rb
|
191
|
+
- lib/generators/operation/templates/operation.rb.erb
|
192
|
+
- lib/generators/policy/USAGE
|
193
|
+
- lib/generators/policy/policy_generator.rb
|
194
|
+
- lib/generators/policy/templates/policy.rb.erb
|
195
|
+
- lib/generators/skinny_controller/USAGE
|
196
|
+
- lib/generators/skinny_controller/skinny_controller_generator.rb
|
197
|
+
- lib/generators/skinny_controller/templates/skinny_controller.rb.erb
|
189
198
|
- lib/skinny_controllers.rb
|
190
199
|
- lib/skinny_controllers/default_verbs.rb
|
191
200
|
- lib/skinny_controllers/diet.rb
|
@@ -225,5 +234,5 @@ rubyforge_project:
|
|
225
234
|
rubygems_version: 2.4.8
|
226
235
|
signing_key:
|
227
236
|
specification_version: 4
|
228
|
-
summary: SkinnyControllers-0.
|
237
|
+
summary: SkinnyControllers-0.8.0
|
229
238
|
test_files: []
|