skinny_controllers 0.10.6 → 0.10.7

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: 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: []