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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f9c02facf2a390b5d23ca3f70c5740bf649f3bc
4
- data.tar.gz: ee725724fdf275ea1740c6641d1207b5ed9949f6
3
+ metadata.gz: 5387b4c2379bd53d4a19a66243c529d8e764dfab
4
+ data.tar.gz: 5c8df4c666ae85e6f762bb68391f06c03c5e21e6
5
5
  SHA512:
6
- metadata.gz: 5011b18d544388bfb015a4223a0777a926d95540341d0f7107461d658406cbd4cceb535dabe8bb77de5e235ddd1ed28920bf8b74fcd2e252ff7448fa283ddce3
7
- data.tar.gz: 9d70c6dd3cdecc598afc7dc49c5f522c09e1fcf1f801ab533488801bf49a747b79815c00af86890f009d8dc67dc50ce6959498b2679f9eaa63271c71ed5ecbd5
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 | API - works very well with [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers)| General - additional featers for server-side rendered views |
278
- | Added Layers | Operations, Policies | Operations, Policies, Forms |
279
- | Validation | stay in models | moved to operations via contract block |
280
- | Additional objects| none | contacts, representers, callbacks, cells |
281
- | 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 |
282
- | 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}` |
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,5 @@
1
+ Description:
2
+ Generates an operation for the given resource.
3
+
4
+ Example:
5
+ `rails generate operation event_summary`
@@ -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,5 @@
1
+ Description:
2
+ Generates a policy for the given resource.
3
+
4
+ Example:
5
+ `rails generate policy event_summary`
@@ -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,5 @@
1
+ Description:
2
+ Generates a controller with the given name, including SkinnyControllers::Diet
3
+
4
+ Example:
5
+ `rails generate skinny_controller event_summaries`
@@ -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
- self.model_key || model_name.underscore
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
- fail "Parent object of type #{scope[:type]} not accessible"
98
+ raise "Parent object of type #{scope[:type]} not accessible"
99
99
  end
100
100
  end
101
101
 
@@ -1,3 +1,3 @@
1
1
  module SkinnyControllers
2
- VERSION = '0.7.4'
2
+ VERSION = '0.8.0'.freeze
3
3
  end
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.7.4
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-02 00:00:00.000000000 Z
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.7.4
237
+ summary: SkinnyControllers-0.8.0
229
238
  test_files: []