skinny_controllers 0.10.6 → 0.10.7

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: 4b9b095f6a365b6f06431c544fd1a7146e545083
4
- data.tar.gz: d196ad00f8899a0acea246b05d328ef5820468db
3
+ metadata.gz: ec8abdfb85c06f6eb9502eed56d1c9aaefe44dae
4
+ data.tar.gz: 1cb87906f028c8e73e8c6cffb0d1c4e4ede94b4f
5
5
  SHA512:
6
- metadata.gz: a78d527c37406567ddabd23f646069dde3823ce96d9b1158645f890a9526a332b41f9d922f7d84b7b6ec1dceb0ff28fae0aafc76e472df3d32834ee075afa869
7
- data.tar.gz: 77927377b4f721cb662790d308df61df1c070022b174728db9515869991872edda42da55093201ef0bca8a6f24a83cdc689dde7c0f889fa4850898a286216434
6
+ metadata.gz: 93867fac825426ab77574c718b2a8b4027dc3c32a2c0e72dd7764757c49e2a612e87aa1445f217e1a160534cb7f72859e72c4e771e3a5e56a3859f60f3300eab
7
+ data.tar.gz: 89bc46531b3fa9fe7a0c1dac5cc7f877181814a0a457460d88fe8477ce145d782ecd466d7009c8ec546889a76a3fde7706f6fd6d01ab723de6462ad59639ef7a
data/README.md CHANGED
@@ -38,20 +38,6 @@ or
38
38
  gem install skinny_controllers
39
39
  ```
40
40
 
41
-
42
- ## Generators
43
-
44
- ```bash
45
- rails g operation event_summary
46
- # => create app/operations/event_summary_operations.rb
47
-
48
- rails g policy event_summary
49
- # create app/policies/event_summary_policy.rb
50
-
51
- rails g skinny_controller event_summaries
52
- # create app/controllers/event_summaries_controller.rb
53
- ```
54
-
55
41
  # Usage
56
42
 
57
43
  ## In a controller:
@@ -65,117 +51,6 @@ render json: model
65
51
 
66
52
  and that's it!
67
53
 
68
- The above does a multitude of assumptions to make sure that you can type the least amount code possible.
69
-
70
- 1. Your controller name is the name of your _resource_.
71
- 2. Any defined policies or operations follow the formats (though they don't have to exist):
72
- - `class #{resource_name}Policy`
73
- - `module #{resource_name}Operations`
74
- 3. Your model responds to `find`, and `where`
75
- 4. 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 }}`
76
- 5. 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.
77
-
78
- ### Per Controller Configuration
79
-
80
- ```ruby
81
- skinny_controllers_config model_class: AClass,
82
- parent_class: ParentClass,
83
- asociation_name: :association_aclasses,
84
- model_params_key: :aclass
85
- ```
86
-
87
- #### model_class
88
- Lets say you have a JSON API resource that you'd like to render that has some additional/subset of data.
89
- Maybe the model is an `Event`, and the resource an `EventSummary` (which could do some aggregation of `Event` data).
90
-
91
- The naming of all the objects should be as follows:
92
- - `EventSummariesController`
93
- - `EventSummaryOperations::*`
94
- - `EventSummaryPolicy`
95
- - and the model is still `Event`
96
-
97
- In `EventSummariesController`, you would make the following additions:
98
- ```ruby
99
- class EventSummariesController < ApiController # or whatever your superclass is
100
- include SkinnyControllers::Diet
101
-
102
- skinny_controllers_config model_class: Event
103
-
104
- def index
105
- render json: model, each_serializer: EventSummariesSerializer
106
- end
107
-
108
- def show
109
- render json: model, serializer: EventSummariesSerializer
110
- end
111
- end
112
- ```
113
-
114
- Note that `each_serializer` and `serializer` is not part of `SkinnyControllers`, and is part of [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers).
115
-
116
- Also note that setting `model_class` may be required if your model is namespaced.
117
-
118
- #### parent_class and association_name
119
-
120
- If you want to scope the finding of a resource to a parent object, `parent_class` must be set
121
-
122
- ```ruby
123
- skinny_controllers_config parent_class: ParentClass,
124
- assaciation_name: :children,
125
- model_class: Child
126
- ```
127
-
128
- Given the above configuration in a controller, and a request with the params:
129
-
130
- ```
131
- {
132
- id: 2,
133
- parent_id: 78
134
- }
135
- ```
136
-
137
- The following query will be made:
138
-
139
- ```ruby
140
- Parent.find(78).children.find(2)
141
- ```
142
-
143
- #### model_params_key
144
-
145
- Date stored another a different key in `params`?
146
-
147
- ```ruby
148
- skinny_controllers_config model_class: Child,
149
- model_params_key: :progeny
150
- ```
151
-
152
- Given the above configuration in a controller, and a request with the params:
153
-
154
- ```
155
- {
156
- progeny: {
157
- attribute1: 'value'
158
- }
159
- }
160
- ```
161
-
162
- The attributes inside the `progeny` sub hash will be used instead of the default, `child`.
163
-
164
- ### What if your model is namespaced?
165
-
166
- All you have to do is set the `model_class`, and `model_key`.
167
-
168
- ```ruby
169
- class ItemsController < ApiController # or whatever your superclass is
170
- include SkinnyControllers::Diet
171
-
172
- skinny_controllers_config model_class: NameSpace::Item
173
- model_params_key: :item
174
- end
175
- ```
176
- `model_key` specifies the key to look for params when creating / updating the model.
177
-
178
- Note that while `model_key` doesn't *have* to be specified, it would default to name_space/item. So, just keep that in mind.
179
54
 
180
55
  ### What if you want to call your own operations?
181
56
 
@@ -227,314 +102,6 @@ end
227
102
 
228
103
  Note that we don't need the id under the data hash, because in a RESTful api, the id will be available to us through the top level params hash.
229
104
 
230
-
231
- ## Defining Operations
232
-
233
- Operations should be placed in `app/operations` of your rails app.
234
-
235
- For operations concerning an `Event`, they should be under `app/operations/event_operations/`.
236
-
237
- Using the example from the specs:
238
- ```ruby
239
- module EventOperations
240
- class Read < SkinnyControllers::Operation::Base
241
- def run
242
- model if allowed?
243
- end
244
- end
245
- end
246
- ```
247
-
248
- alternatively, all operation verbs can be stored in the same file under (for example) `app/operations/user_operations.rb`
249
-
250
- ```ruby
251
- module UserOperations
252
- class Read < SkinnyControllers::Operation::Base
253
- def run
254
- model if allowed?
255
- end
256
- end
257
-
258
- class ReadAll < SkinnyControllers::Operation::Base
259
- def run
260
- model if allowed?
261
- end
262
- end
263
- end
264
- ```
265
-
266
- ### Creating
267
-
268
- To achieve default functionality, this operation *may* be defined -- though, it is implicitly assumed to function this way if not defined.
269
- ```ruby
270
- module UserOperations
271
- class Create < SkinnyControllers::Operation::Base
272
- def run
273
- @model = User.new(model_params)
274
-
275
- # raising an exception here allows the corresponding resource controller to
276
- # `rescue_from SkinnyControllers::DeniedByPolicy` and have a uniform error
277
- # returned to the frontend
278
- raise SkinnyControllers::DeniedByPolicy.new('Something Horrible') unless allowed?
279
-
280
- @model.save
281
- return @model # or just `model`
282
- end
283
- end
284
- end
285
- ```
286
-
287
- ### Updating
288
- ```ruby
289
- module UserOperations
290
- class Update < SkinnyControllers::Operation::Base
291
- def run
292
- # this throws a DeniedByPolicy exception if `allowed?` returns false
293
- check_allowed!
294
-
295
- model.update(model_params)
296
- model
297
- end
298
- end
299
- end
300
- ```
301
-
302
- ### Deleting
303
-
304
- Goal: Users should only be able to delete themselves
305
-
306
- To achieve default functionality, this operation *may* be defined -- though, it is implicitly assumed to function this way if not defined.
307
- ```ruby
308
- module UserOperations
309
- class Delete < SkinnyControllers::Operation::Base
310
- def run
311
- model.destroy if allowed?
312
- end
313
- end
314
- end
315
- ```
316
-
317
- NOTE: `allowed?` is `true` by default via the `SkinnyControllers.allow_by_default` option.
318
-
319
- ## Defining Policies
320
-
321
- Policies should be placed in `app/policies` of your rails app.
322
- These are where you define your access logic, and how to decide if a user has access to the `object`
323
-
324
- ```ruby
325
- class EventPolicy < SkinnyControllers::Policy::Base
326
- def read?(event = object)
327
- event.user == user
328
- end
329
- end
330
- ```
331
-
332
-
333
- ## More Advanced Usage
334
-
335
- These are snippets taking from other projects.
336
-
337
- ### Using ransack
338
-
339
- ```ruby
340
- # config/initializers/skinny_controllers.rb
341
- SkinnyControllers.search_proc = lambda do |relation|
342
- relation.ransack(params[:q]).result
343
- end
344
- ```
345
-
346
- ### Finding a record when the id parameter isn't passed
347
-
348
- ```ruby
349
- module HostOperations
350
- class Read < SkinnyControllers::Operation::Base
351
- def run
352
- # always allowed, never restricted
353
- # (because there is now call to allowed?)
354
- model
355
- end
356
-
357
- # the params to this method should include the subdomain
358
- # e.g.: { subdomain: 'swingin2015' }
359
- def model_from_params
360
- subdomain = params[:subdomain]
361
- host = Host.find_by_subdomain(subdomain)
362
- end
363
- end
364
- end
365
- ```
366
-
367
- ### The built in model-finding methods can be completely ignored
368
-
369
- The `model` method does not need to be overridden. `run` is what is called on the operation.
370
-
371
- ```ruby
372
- module MembershipRenewalOperations
373
- # MembershipRenewalsController#index
374
- class ReadAll < SkinnyControllers::Operation::Base
375
-
376
- def run
377
- # default 'model' functionality is avoided
378
- latest_renewals
379
- end
380
-
381
- private
382
-
383
- def organization
384
- id = params[:organization_id]
385
- Organization.find(id)
386
- end
387
-
388
- def renewals
389
- options = organization.membership_options.includes(renewals: [:user, :membership_option])
390
- options.map(&:renewals).flatten
391
- end
392
-
393
- def latest_renewals
394
- sorted_renewals = renewals.sort_by{|r| [r.user_id,r.updated_at]}.reverse
395
-
396
- # unique picks the first option.
397
- # so, because the list is sorted by user id, then updated at,
398
- # for each user, the first renewal will be chosen...
399
- # and because it is descending, that means the most recent renewal
400
- sorted_renewals.uniq { |r| r.user_id }
401
- end
402
- end
403
-
404
- end
405
- ```
406
-
407
- ### Updating / Deleting the current_user
408
-
409
- This is something you could do if you always know your model ahead of time.
410
-
411
- ```ruby
412
- module UserOperations
413
- class Update < SkinnyControllers::Operation::Base
414
- def run
415
- return unless allowed_for?(current_user)
416
- # update with password provided by Devise
417
- current_user.update_with_password(model_params)
418
- current_user
419
- end
420
- end
421
-
422
- class Delete < SkinnyControllers::Operation::Base
423
- def run
424
- if allowed_for?(current_user)
425
- if current_user.upcoming_events.count > 0
426
- current_user.errors.add(
427
- :base,
428
- "You cannot delete your account when you are about to attend an event."
429
- )
430
- else
431
- current_user.destroy
432
- end
433
-
434
- current_user
435
- end
436
- end
437
- end
438
-
439
- end
440
- ```
441
-
442
- ## Testing
443
-
444
- The whole goal of this project is to minimize the complexity or existence of controller tests, and provide
445
- a way to unit test business logic.
446
-
447
- 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.
448
-
449
- ### Operations
450
-
451
- ```ruby
452
- describe HostOperations do
453
- describe HostOperations::Read do
454
- context 'model_from_params' do
455
- let(:subdomain){ 'subdomain' }
456
- # an operation takes a user, and a list of params
457
- # there are optional parameters as well, but generally may not be required.
458
- # see: `SkinnyControllers:::Operation::Base`
459
- let(:operation){ HostOperations::Read.new(nil, { subdomain: subdomain }) }
460
-
461
- it 'finds an event' do
462
- host = create(:host, domain: subdomain)
463
- model = operation.run
464
- expect(model).to eq host
465
- end
466
- #...
467
- ```
468
-
469
- ### Policies
470
-
471
- 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.
472
-
473
- ```ruby
474
- describe PackagePolicy do
475
- # will test if the owner of this object can access it
476
- let(:by_owner){
477
- ->(method){
478
- package = create(:package)
479
- # a policy takes a user and an object
480
- policy = PackagePolicy.new(package.event.hosted_by, package)
481
- policy.send(method)
482
- }
483
- }
484
-
485
- # will test if the person registering with this package has permission
486
- let(:by_registrant){
487
- ->(method){
488
- event = create(:event)
489
- package = create(:package, event: event)
490
- attendance = create(:attendance, host: event, package: package)
491
- # a policy takes a user and an object
492
- policy = PackagePolicy.new(attendance.attendee, package)
493
- policy.send(method)
494
- }
495
- }
496
-
497
- context 'can be read?' do
498
- it 'by the event owner' do
499
- result = by_owner.call(:read?)
500
- expect(result).to eq true
501
- end
502
-
503
- it 'by a registrant' do
504
- result = by_registrant.call(:read?)
505
- expect(result).to eq true
506
- end
507
- end
508
-
509
- context 'can be updated?' do
510
- it 'by the event owner' do
511
- result = by_owner.call(:update?)
512
- expect(result).to eq true
513
- end
514
-
515
- it 'by a registrant' do
516
- result = by_registrant.call(:update?)
517
- expect(result).to eq false
518
- end
519
- end
520
- ```
521
-
522
-
523
- ## Globally Configurable Options
524
-
525
- The following options are available:
526
-
527
- |Option|Default|Note|
528
- |------|-------|----|
529
- |`operations_namespace` | '' | Optional namespace to put all the operations in. |
530
- |`operations_suffix`|`'Operations'` | Default suffix for the operations namespaces. |
531
- |`policy_suffix`|`'Policy'` | Default suffix for policies classes. |
532
- |`controller_namespace`|`''`| Global Namespace for all controllers (e.g.: `'API'`) |
533
- |`allow_by_default`| `true` | Default permission |
534
- |`action_map`| see [skinny_controllers.rb](./lib/skinny_controllers.rb#L61)| |
535
- | `search_proc`| passthrough | can be used to filter results, such as with using ransack |
536
-
537
-
538
105
  -------------------------------------------------------
539
106
 
540
107
  ## How is this different from trailblazer?
@@ -549,3 +116,14 @@ This may not be horribly apparent, but here is a table overviewing some highleve
549
116
  | Additional objects|-| none | contacts, representers, callbacks, cells |
550
117
  | 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 |
551
118
  | 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}` |
119
+
120
+
121
+ # Contributing
122
+
123
+ Please refer to each project's style guidelines and guidelines for submitting patches and additions. In general, we follow the "fork-and-pull" Git workflow.
124
+
125
+ 1. **Fork** the repo on GitHub
126
+ 2. **Clone** the project to your own machine
127
+ 3. **Commit** changes to your own branch
128
+ 4. **Push** your work back up to your fork
129
+ 5. Submit a **Pull request** so that we can review your changes
@@ -77,7 +77,9 @@ module SkinnyControllers
77
77
 
78
78
  # In order of most specific, to least specific:
79
79
  # - {action}_{model_name}_params
80
- # - {model_name}_params
80
+ # - {action}_params
81
+ # - {model_key}_params
82
+ # - resource_params
81
83
  # - params
82
84
  #
83
85
  # It's recommended to use whitelisted strong parameters on
@@ -100,16 +102,26 @@ module SkinnyControllers
100
102
  _lookup.model_name.underscore
101
103
  end
102
104
 
103
- action_params_method = "#{action_name}_#{model_key}_params"
104
- model_params_method = "#{model_key}_params"
105
+ params_lookups = [
106
+ # e.g.: create_post_params
107
+ "#{action_name}_#{model_key}_params",
108
+ # generic for action
109
+ "#{action_name}_params",
110
+ # e.g.: post_params
111
+ "#{model_key}_params",
112
+ # most generic
113
+ 'resource_params'
114
+ ]
115
+
116
+ lookup_params_for_action(params_lookups)
117
+ end
105
118
 
106
- if respond_to?(action_params_method, true)
107
- send(action_params_method)
108
- elsif respond_to?(model_params_method, true)
109
- send(model_params_method)
110
- else
111
- params
119
+ def lookup_params_for_action(lookups)
120
+ lookups.each do |method_name|
121
+ return send(method_name) if respond_to?(method_name, true)
112
122
  end
123
+
124
+ params
113
125
  end
114
126
 
115
127
  # action name is inherited from ActionController::Base
@@ -64,10 +64,6 @@ module SkinnyControllers
64
64
  # TODO: think of a way to override the authorized_via_parent functionality
65
65
  def read_all?
66
66
  return true if authorized_via_parent
67
-
68
- # Might be deceptive...
69
- return true if object.nil? || object.empty?
70
-
71
67
  # This is expensive, so try to avoid it
72
68
  # TODO: look in to creating a cache for
73
69
  # these look ups that's invalidated upon
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module SkinnyControllers
3
- VERSION = '0.10.6'
3
+ VERSION = '0.10.7'
4
4
  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.10.6
4
+ version: 0.10.7
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: 2017-07-15 00:00:00.000000000 Z
11
+ date: 2017-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -235,8 +235,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
235
  version: '0'
236
236
  requirements: []
237
237
  rubyforge_project:
238
- rubygems_version: 2.6.11
238
+ rubygems_version: 2.6.8
239
239
  signing_key:
240
240
  specification_version: 4
241
- summary: SkinnyControllers-0.10.6
241
+ summary: SkinnyControllers-0.10.7
242
242
  test_files: []