skinny_controllers 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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: []