rails_ops 1.0.0.beta1

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rubocop.yml +84 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +1216 -0
  8. data/RUBY_VERSION +1 -0
  9. data/Rakefile +39 -0
  10. data/VERSION +1 -0
  11. data/lib/rails_ops.rb +96 -0
  12. data/lib/rails_ops/authorization_backend/abstract.rb +7 -0
  13. data/lib/rails_ops/authorization_backend/can_can_can.rb +14 -0
  14. data/lib/rails_ops/configuration.rb +4 -0
  15. data/lib/rails_ops/context.rb +35 -0
  16. data/lib/rails_ops/controller_mixin.rb +105 -0
  17. data/lib/rails_ops/exceptions.rb +19 -0
  18. data/lib/rails_ops/hooked_job.rb +25 -0
  19. data/lib/rails_ops/hookup.rb +80 -0
  20. data/lib/rails_ops/hookup/dsl.rb +29 -0
  21. data/lib/rails_ops/hookup/dsl_validator.rb +45 -0
  22. data/lib/rails_ops/hookup/hook.rb +11 -0
  23. data/lib/rails_ops/log_subscriber.rb +24 -0
  24. data/lib/rails_ops/mixins.rb +2 -0
  25. data/lib/rails_ops/mixins/authorization.rb +83 -0
  26. data/lib/rails_ops/mixins/log_settings.rb +20 -0
  27. data/lib/rails_ops/mixins/model.rb +4 -0
  28. data/lib/rails_ops/mixins/model/authorization.rb +64 -0
  29. data/lib/rails_ops/mixins/model/nesting.rb +180 -0
  30. data/lib/rails_ops/mixins/policies.rb +42 -0
  31. data/lib/rails_ops/mixins/require_context.rb +33 -0
  32. data/lib/rails_ops/mixins/routes.rb +35 -0
  33. data/lib/rails_ops/mixins/schema_validation.rb +25 -0
  34. data/lib/rails_ops/mixins/sub_ops.rb +35 -0
  35. data/lib/rails_ops/model_casting.rb +17 -0
  36. data/lib/rails_ops/model_mixins.rb +12 -0
  37. data/lib/rails_ops/model_mixins/ar_extension.rb +20 -0
  38. data/lib/rails_ops/model_mixins/parent_op.rb +10 -0
  39. data/lib/rails_ops/model_mixins/protected_attributes.rb +78 -0
  40. data/lib/rails_ops/model_mixins/virtual_attributes.rb +24 -0
  41. data/lib/rails_ops/model_mixins/virtual_attributes/virtual_column_wrapper.rb +9 -0
  42. data/lib/rails_ops/model_mixins/virtual_has_one.rb +32 -0
  43. data/lib/rails_ops/operation.rb +215 -0
  44. data/lib/rails_ops/operation/model.rb +168 -0
  45. data/lib/rails_ops/operation/model/create.rb +35 -0
  46. data/lib/rails_ops/operation/model/destroy.rb +26 -0
  47. data/lib/rails_ops/operation/model/load.rb +72 -0
  48. data/lib/rails_ops/operation/model/update.rb +31 -0
  49. data/lib/rails_ops/patches/active_type_patch.rb +52 -0
  50. data/lib/rails_ops/profiler.rb +47 -0
  51. data/lib/rails_ops/profiler/node.rb +64 -0
  52. data/lib/rails_ops/railtie.rb +19 -0
  53. data/lib/rails_ops/scoped_env.rb +20 -0
  54. data/lib/rails_ops/virtual_model.rb +19 -0
  55. data/rails_ops.gemspec +58 -0
  56. data/test/test_helper.rb +3 -0
  57. metadata +252 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c520181f345d34d12b964e8da4b79764c3d352f2
4
+ data.tar.gz: daeceb586c2c8ed3571c5e997fc0af5a590bb908
5
+ SHA512:
6
+ metadata.gz: cc063fc63eac0e8252f098dd6b92245dea2128e84548eeb352cfd3655f866e069474b9fbc06ddef040f51e7147316f852f856cc6149f0739ad0aaaa8c331bd5b
7
+ data.tar.gz: b2e5f85b9c5e3830948028e56cb2a6bf243a4a605291c3087d1b41993b794c378b6f139dbc4f2a14fc73b960e9008df9809dd73bcd2e81fd96c9e6cbe156fe0f
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /bin/ruby
11
+ /vendor/bundle
data/.rubocop.yml ADDED
@@ -0,0 +1,84 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Exclude:
5
+ - 'vendor/**/*'
6
+ - 'tmp/**/*'
7
+ - 'log/**/*'
8
+ - '*.gemspec'
9
+
10
+ DisplayCopNames: true
11
+
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: false
14
+
15
+ Style/DoubleNegation:
16
+ Enabled: false
17
+
18
+ Style/SignalException:
19
+ EnforcedStyle: only_fail
20
+
21
+ Style/ConditionalAssignment:
22
+ Enabled: false
23
+
24
+ Style/IndentArray:
25
+ EnforcedStyle: consistent
26
+
27
+ Metrics/MethodLength:
28
+ Enabled: false
29
+
30
+ Metrics/ClassLength:
31
+ Enabled: false
32
+
33
+ Metrics/ModuleLength:
34
+ Enabled: false
35
+
36
+ Metrics/ParameterLists:
37
+ Max: 5
38
+ CountKeywordArgs: false
39
+
40
+ Metrics/AbcSize:
41
+ Enabled: False
42
+
43
+ Metrics/CyclomaticComplexity:
44
+ Enabled: False
45
+
46
+ Metrics/PerceivedComplexity:
47
+ Enabled: False
48
+
49
+ Metrics/LineLength:
50
+ Max: 160
51
+
52
+ Metrics/BlockNesting:
53
+ Enabled: false
54
+
55
+ Metrics/BlockLength:
56
+ Enabled: false
57
+
58
+ Style/IfUnlessModifier:
59
+ Enabled: false
60
+
61
+ Style/Documentation:
62
+ Enabled: false
63
+
64
+ Style/RedundantReturn:
65
+ Enabled: false
66
+
67
+ Style/AsciiComments:
68
+ Enabled: false
69
+
70
+ Style/GuardClause:
71
+ Enabled: false
72
+
73
+ Style/ClassAndModuleChildren:
74
+ Enabled: false
75
+ EnforcedStyle: compact
76
+ SupportedStyles:
77
+ - nested
78
+ - compact
79
+
80
+ Style/NumericPredicate:
81
+ Enabled: false
82
+
83
+ Style/FormatString:
84
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails_ops.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Sitrox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,1216 @@
1
+ rails_ops
2
+ =========
3
+
4
+ **This Gem is still under development and is not to be used in production yet.**
5
+
6
+ This Gem introduces an additional service layer for Rails: *Operations*. An
7
+ operation is in most cases a *business action* or *use case* and may or may not
8
+ involve one or multiple models. Rails Ops allow creating more modular
9
+ applications by splitting them up into its different operations. Each operation
10
+ is specified in a single, testable class.
11
+
12
+ To achieve this goal, this Gem provides the following building blocks:
13
+
14
+ - Various operation base classes for creating operations with a consistent
15
+ interface and no boilerplate code.
16
+
17
+ - A way of abstracting model classes for a specific business action.
18
+
19
+ Operation Basics
20
+ ----------------
21
+
22
+ ### Placing and naming operations
23
+
24
+ - Operations generally reside in `app/operations` and can be nested using various
25
+ subdirectories. They're all inside of the `Operations` namespace.
26
+
27
+ - Operations operating on a specific model should generally be namespaced with
28
+ the model's class name. So for instance, the operation `Create` for the `User`
29
+ model should generally live under `app/operations/user/create.rb` and therefore
30
+ be called `Operations::User::Create`.
31
+
32
+ - Operations inheriting from other operations should generally be nested within
33
+ their parent operation. See the next section for more details.
34
+
35
+ - Operation classes should always be named after an *action*, such as `Create`,
36
+ `MoveToPosition` and so on. Do not name an operation something like
37
+ `UserCreator` or `CreateUserOperation`.
38
+
39
+ #### Heads-up: Correct namespacing
40
+
41
+ As explained in the previous section, operations should be namespaced properly.
42
+ Operations can either live within a module or within a class. In most cases,
43
+ operations are placed in the `Operation` module or rather one of its
44
+ sub-modules. If, in some special case, operations are nested, they can reside
45
+ inside of another operation class (but not inside of its file) as well.
46
+
47
+ When declaring an operation within a namespace,
48
+
49
+ - Determine whether the namespace you're using is a module or a class. Make sure
50
+ you don't accidentally redefine a module as a class or vice-versa.
51
+
52
+ - If the operation resides within a module, make a module definition on the first
53
+ line and the operation class on the second. Example:
54
+
55
+ ```ruby
56
+ module Operations::Frontend::Navigation
57
+ class DetermineActionsForStructureElement < RailsOps::Operation
58
+ ...
59
+ end
60
+ end
61
+ ```
62
+
63
+ - If the operation resides within a class, use a single-line definition:
64
+
65
+ ```ruby
66
+ class Operations::User::Create::FromApi < Operations::User::Create
67
+ ...
68
+ end
69
+ ```
70
+
71
+ Note that, when defining a namespace of which a segment is already known as a
72
+ (model) class, you cannot just use the model classes name to refer to it:
73
+
74
+ ```ruby
75
+ module Operations::User
76
+ class Create < RailsOps::Operation
77
+ def perform
78
+ # This DOES NOT work as `User` in this case refers to the module of
79
+ # the same name defined on the first line of code.
80
+ User.create(params)
81
+
82
+ # This works as it takes an absolute namespace:
83
+ ::User.create(params)
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ ### Basic operations
90
+
91
+ Every single operation follows a few basic principles:
92
+
93
+ - They inherit from {RailsOps::Operation}.
94
+
95
+ - They are called using the `run` or `run!` methods.
96
+
97
+ - They are parameterized using a `params` hash (and nothing else).
98
+
99
+ - They define a protected `perform` method which actually executes the
100
+ operation. This is usually overridden in each operation and called exclusively
101
+ by `run` or `run!`.
102
+
103
+ - They have a *Context*. See the respective chapter for more information.
104
+
105
+ So, an example of a very simple operation would be:
106
+
107
+ ```ruby
108
+ class Operations::PrintHelloWorld < RailsOps::Operation
109
+ def perform
110
+ puts "Hello #{params[:name]}"
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### Running operations manually
116
+
117
+ There are various ways of instantiating and running an operation. The most
118
+ basic way is the following:
119
+
120
+ ```ruby
121
+ op = Operations::PrintHelloWorld.new(name: 'John Doe')
122
+ op.run
123
+ ```
124
+
125
+ There is even a shortcut for this:
126
+
127
+ ```ruby
128
+ Operations::PrintHelloWorld.run(name: 'John Doe')
129
+ ```
130
+
131
+ ### Validations, `run` and `run!`
132
+
133
+ As you have noticed, there are two methods for running operations: `run` and
134
+ `run!`. They behave exactly like `save` and `save!` of ActiveRecord: While the
135
+ `run!` method raises an exception if there is a validation error, `run` would
136
+ just return `false` (or `true` on success). As not every operation deals with
137
+ models or ActiveRecord models, `run` does not only catch the
138
+ `ActiveRecord::RecordInvalid` exception but also every exception that derives
139
+ from {RailsOps::Exceptions::ValidationFailed}.
140
+
141
+ #### Catching custom exceptions in `run`
142
+
143
+ If you'd like to catch a custom exception if the operation is called using
144
+ `run`, you can either derive this exception from
145
+ {RailsOps::Exceptions::ValidationFailed} or else override the
146
+ `validation_errors` method:
147
+
148
+ ```ruby
149
+ class Operations::PrintHelloWorld < RailsOps::Operation
150
+ # Returns an array of exception classes that are considered as validation
151
+ # errors.
152
+ def validation_errors
153
+ super + [SomeCustomException]
154
+ end
155
+ end
156
+ ```
157
+
158
+ ### Returning data from operations
159
+
160
+ All operations have the same call signatures: `run` always returns `true` or
161
+ `false` while `run!` always returns the operation instance (which allows easy
162
+ chaining). If you need to access data that has been generated / processed /
163
+ fetched in the operation, create custom accessor methods:
164
+
165
+ ```ruby
166
+ class Operations::GenerateHelloWorld < RailsOps::Operation
167
+ attr_reader :result
168
+
169
+ def perform
170
+ @result = "Hello #{params[:name]}"
171
+ end
172
+ end
173
+
174
+ puts Operations::GenerateHelloWorld.run!(name: 'John Doe').result
175
+ ```
176
+
177
+ Params Handling
178
+ ---------------
179
+
180
+ ## Passing params to operations
181
+
182
+ Each single operation can take a `params` hash. Note that it does not have to be
183
+ in any relation with `ActionController`'s params - it's just a plain ruby hash
184
+ called `params`.
185
+
186
+ Params are assigned to the operation via their constructor:
187
+
188
+ ```ruby
189
+ Operations::GenerateHelloWorld.new(foo: :bar)
190
+ ```
191
+
192
+ If no params are given, an empty params hash will be used. If a
193
+ `ActionController::Parameters` object is passed, it will be permitted using
194
+ `permit!` and converted into a regular hash.
195
+
196
+ ## Accessing params
197
+
198
+ For accessing params within an operation, you can use `params` or `osparams`.
199
+ While `params` directly returns the params hash, `osparams` converts them into
200
+ an `OpenStruct` first. This allows easy access using the 'dotted notation':
201
+
202
+ ```ruby
203
+ def perform
204
+ # Access a param using the `params` method
205
+ params[:foo]
206
+
207
+ # Access a param using the `osparams` method
208
+ osparams.foo
209
+ end
210
+ ```
211
+
212
+ Note that both `params` and `osparams` return independent, deep duplicates of
213
+ the original `params` hash to the operation, so the hashes do not correspond.
214
+
215
+ The hash accessed via `params` is a always `Object::HashWithIndifferentAccess`.
216
+
217
+ ## Validating params
218
+
219
+ You're strongly encouraged to perform a validation of the parameters passed to
220
+ an operation. This can be done in several ways:
221
+
222
+ - Manually using a *policy* (see chapter *Policies*):
223
+
224
+ ```ruby
225
+ class Operations::PrintHelloWorld < RailsOps::Operation
226
+ policy do
227
+ unless osparams.name && osparams.name.is_a?(String)
228
+ fail 'You must supply the "name" argument.'
229
+ end
230
+ end
231
+
232
+ def perform
233
+ puts "Hello #{params[:name]}"
234
+ end
235
+ end
236
+ ```
237
+
238
+ - Using a [schemacop](https://github.com/sitrox/schemacop) schema:
239
+
240
+ ```ruby
241
+ class Operations::PrintHelloWorld < RailsOps::Operation
242
+ schema do
243
+ req :name, :string
244
+ end
245
+
246
+ def perform
247
+ puts "Hello #{params[:name]}"
248
+ end
249
+ end
250
+ ```
251
+
252
+ This is the preferred way of performing basic params validation when not using
253
+ model validation (see next item).
254
+
255
+ See documentation of the Gem `schemacop` for more information on how to
256
+ specify schemata.
257
+
258
+ - Using a business model (see chapter *Model Operations*).
259
+
260
+ Policies
261
+ --------
262
+
263
+ Policies are nothing more than blocks of code that run either at operation
264
+ instantiation or before / after execution of the `perform` method and can be
265
+ used to check conditions such as params or permissions.
266
+
267
+ Policies are specified using the static method `policy`, inherited to any
268
+ sub-classes and executed in the order they were defined.
269
+
270
+ ```ruby
271
+ class Operations::PrintHelloWorld < RailsOps::Operation
272
+ policy do
273
+ puts 'This runs first'
274
+ end
275
+
276
+ policy do
277
+ puts 'This runs second'
278
+ end
279
+
280
+ def perform
281
+ puts 'This runs third'
282
+ puts 'Oh, and hello world'
283
+ end
284
+ end
285
+ ```
286
+
287
+ The basic idea of policies is to validate input data (the `params` hash) or
288
+ other conditions such as authorizations or locks.
289
+
290
+ Some checks might still need to be performed directly within the `perform`
291
+ method. Use policies as much as possible though to keep things separated.
292
+
293
+ The return value of the policies is discarded. If a policy needs to fail, raise
294
+ an appropriate exception.
295
+
296
+ ### Policy chains
297
+
298
+ As mentioned above, policies can be executed at 3 points in your operation's
299
+ lifecycle. This is possible using *policy chains*:
300
+
301
+ - `:on_init`
302
+
303
+ Policies in this chain run after the operation class is instantiated.
304
+
305
+ - `:before_perform`
306
+
307
+ Policies in this chain run immediately before the `perform` method is called.
308
+ Obviously this is never called if the operation is just instantiated and never
309
+ run. This is the default chain.
310
+
311
+ - `:after_perform`
312
+
313
+ Policies in this chain run immediately after the `perform` method is called.
314
+ Obviously this is never called if the operation is just instantiated and never
315
+ run.
316
+
317
+ The policy chain (default is `:before_perform`) can be specified as the first
318
+ argument of the `policy` class method:
319
+
320
+ ```ruby
321
+ class MyOp
322
+ policy :on_init do
323
+ puts 'This is run once the operation has been instantiated.'
324
+ end
325
+
326
+ policy do
327
+ puts 'This is run before the operation is performed.'
328
+ end
329
+ end
330
+ ```
331
+
332
+ Calling sub-operations
333
+ ----------------------
334
+
335
+ It is possible and encouraged to call operations within operations if necessary.
336
+ As the basic principle is to create one operation per business action, there are
337
+ cases where nesting operations can be very beneficial.
338
+
339
+ Let's say we have an operation `User::Create` that creates a new user. The
340
+ operation should also assign the newly created user to a default `Group` after
341
+ creation. In this case, we basically have two separate operations that should
342
+ not be combined in one. For this case, use sub-operations:
343
+
344
+ ```ruby
345
+ class Operations::User::Create < RailsOps::Operation
346
+ def perform
347
+ user = User.create(params)
348
+ run_sub! AssignToGroup, user: user, group: Group.default
349
+ end
350
+ end
351
+ ```
352
+
353
+ Every operation offers the methods {RailsOps::Mixins::SubOps.run_sub},
354
+ {RailsOps::Mixins::SubOps.run_sub!} and {RailsOps::Mixins::SubOps.sub_op}. The
355
+ latter one just instantiates and returns a sub operation.
356
+
357
+ So why don't we just create and call the sub-operation directly? The reason lies
358
+ within the context that is automatically adapted and passed to the sub-operation
359
+ and enables to maintain the complete call stack and allows to pass on context
360
+ information such as the current user.
361
+
362
+ ### A note on validations
363
+
364
+ As always when calling operations, you can decide whether an execution should
365
+ raise an exception on validation errors or else just return `false` by using the
366
+ bang or non-bang methods.
367
+
368
+ For nested operations, we must give this fact a little more thought. Consider
369
+ the following case:
370
+
371
+ - Operation *A* is called using `run`.
372
+ - Operation *A* calls operation *B* using `run_sub!`.
373
+ - Operation *B* throws a validation exception.
374
+
375
+ In this case, it is now expected that *A* returns non-gracefully, even though
376
+ it's called using the non-bang method. The reason is that *A* explicitly used
377
+ the bang-method for calling the sub-op.
378
+
379
+ However, as calling *A* catches any validation errors, it will also catch the
380
+ validation errors raised by a sub-operation. For this case, calling `run_sub!`
381
+ catches any validation errors and re-throws them as
382
+ {RailsOps::Exceptions::SubOpValidationFailed} which is not caught by the
383
+ surrounding op.
384
+
385
+ Contexts
386
+ --------
387
+
388
+ Most operations make use of generic parameters like the current user or an
389
+ authorization ability. Sure this could all be passed using the `params` hash,
390
+ but as this would have to be done for every single operation call, it would be
391
+ quite cumbersome.
392
+
393
+ For this reason Rails Ops provides a feature called *Contexts*. Contexts are
394
+ simple instances of {RailsOps::Context} that may or may not be passed to
395
+ operations. Contexts can include the following data:
396
+
397
+ - A user object
398
+
399
+ This is meant to be the user performing the operation. In a controller
400
+ context, this usually referred to as `current_user`.
401
+
402
+ - The session object
403
+
404
+ This is the rails `session` object (can be nil).
405
+
406
+ - An ability object
407
+
408
+ This is an ability object (i.e. cancan(can)) which holds the permissions
409
+ currently available. This is used for authorization within an operation.
410
+
411
+ - The operations chain
412
+
413
+ The operations chain contains the call stack of operations. This is
414
+ automatically generated when calling a sub-op or triggering an op using an
415
+ event (see chapter *Events* for more information on that).
416
+
417
+ TODO: This may induce memory issues. Is keeping the operation's chain worth
418
+ this trade-off?
419
+
420
+ - URL options
421
+
422
+ Rails uses a hash named `url_options` for generating URLs with correct prefix.
423
+ This information usually comes from a request and is automatically passed to
424
+ the operation context when calling an operation from a controller. This hash
425
+ is used by {RailsOps::Mixins::Routes}.
426
+
427
+ - Called via hook
428
+
429
+ `called_via_hook` is a boolean indicating whether or not this operation was
430
+ called by a hook (true) or by a regular method call (false). We will introduce
431
+ hooks below.
432
+
433
+ ### Instantiating contexts
434
+
435
+ Contexts behave like a traditional model object and can be instantiated in
436
+ multiple ways:
437
+
438
+ ```ruby
439
+ context = Context.new(user: current_user, params: { foo: bar })
440
+
441
+ # Another way
442
+ context = Context.new
443
+ context.user = current_user
444
+ ```
445
+
446
+ ### Feeding contexts to operations
447
+
448
+ Contexts are assigned to operations via the operation's constructor:
449
+
450
+ ```ruby
451
+ my_context = RailsOps::Context.new
452
+ op = Operations::PrintHelloWorld.new(my_context, foo: :bar)
453
+ ```
454
+
455
+ For your convenience, contexts also provide `run` and `run!` methods:
456
+
457
+ ```ruby
458
+ my_context.run Operations::PrintHelloWorld, foo: :bar
459
+ ```
460
+
461
+ ### Sub-operations
462
+
463
+ When calling a sub-operation either using the corresponding sub-operation
464
+ methods or else using events, a new context is automatically created and
465
+ assigned to the sub-operation. This context includes all the data from the
466
+ original context. Also, the operations chain is automatically complemented with
467
+ the parent operation.
468
+
469
+ This is called *context spawning* and is performed using the
470
+ {RailsOps::Context.spawn} method.
471
+
472
+ Hooks
473
+ -----
474
+
475
+ In some cases, certain actions must be hooked in after execution of an
476
+ operation. While this can certainly be done with sub-operations, it is not
477
+ always desirable as the triggering operation should not always know of the
478
+ additional ones it's triggering:
479
+
480
+ - `Operations::User::Create` creates a user, but also creates a group object
481
+ using `Operations::Group::Create`. *This is an example for sub-ops*.
482
+
483
+ - `Operations::User::Create` creates a user. Whenever a user is created, another
484
+ part of the application needs to generate a TODO for the admin to approve this
485
+ user. *This would be an example for hooks*.
486
+
487
+ Hooks are pretty simple: Using the file `config/hookup.rb`, you can
488
+ specify which operations should be triggered after which operations. These
489
+ operations are then automatically triggered after the original operation's
490
+ `perform` (in the `run` method).
491
+
492
+ ### Defining hooks
493
+
494
+ Hooks are defined in a file named `config/hookup.rb` in your local application.
495
+ In development mode, this file is automatically reloaded on each request so
496
+ there is no need to restart the application server for this while developing.
497
+
498
+ Defining hooks is as simple as defining a target operation and one or more
499
+ source operations.
500
+
501
+ ```ruby
502
+ RailsOps.hookup.draw do
503
+ run Operations::Notifications::User::SendWelcomeEmail do
504
+ on Operations::User::Create
505
+ end
506
+
507
+ run Operations::Todos::GenerateUserApprovalTodo do
508
+ on Operations::User::Create
509
+ end
510
+
511
+ run Operations::Notification::SendTodoNotification do
512
+ on Operations::Todos::GenerateUserApprovalTodo
513
+ end
514
+ end
515
+ ```
516
+
517
+ Operations hooks are always performed in the order they are defined.
518
+
519
+ ### Events
520
+
521
+ Each operation can throw different *events*. The event `:after_run` is
522
+ automatically triggered after each operation's execution and should be
523
+ sufficient for most cases. However, it is also possible to trigger custom events
524
+ in the `perform` method:
525
+
526
+ ```ruby
527
+ def perform
528
+ trigger :custom_event_name, { some: :params }
529
+ end
530
+ ```
531
+
532
+ This can be hooked by specifying the custom event name in your hookup
533
+ configuration:
534
+
535
+ ```ruby
536
+ on Operations::User::Create, :custom_event_name do
537
+ perform Operations::Notifications::User::SendWelcomeEmail
538
+ end
539
+ ```
540
+
541
+ In most cases though, situations like these should rather be handled by
542
+ explicitly calling a sub-operation.
543
+
544
+ ### Hook parameters
545
+
546
+ For each hook that is called, at set of parameters is passed to the respective
547
+ operations. When calling events manually (see section *Events*), you can
548
+ manually specify the parameters. For the default event `:after_run`, the set of
549
+ parameters is defined by the operation method `after_run_trigger_params`. In the
550
+ default case, this returns an empty array. Some operation base classes, like for
551
+ instance `RailsOps::Operation::Model`, override this method to supply a custom
552
+ set of parameters. See your respective base class for more information.
553
+
554
+ Be advised: It is not usually desirable to provide a very custom param set that
555
+ is tailored to one particular target operation. Trigger parameters should be as
556
+ generic as possible as specific cases should rather be handled using sub-ops.
557
+
558
+ Operations can be used to write adapters (*glue* operations) in order to hook
559
+ into an operation with incompatible parameters. Create a glue operation that
560
+ hooks into the source operation and prepares the params specifically for the
561
+ target operation, which is then called using a sub-operation or the hooking
562
+ system.
563
+
564
+ ### Check if called via hook
565
+
566
+ You can determine whether your operation has been (directly) called via a hook
567
+ using the `called_via_hook` context method:
568
+
569
+ ```ruby
570
+ def perform
571
+ puts 'Called via hook' if context.called_via_hook
572
+ end
573
+ ```
574
+
575
+ Note that this property never propagates, so when calling a sub-operation from
576
+ an operation that has been called using a hook, `called_via_hook` of the
577
+ sub-operation is set to `false` again.
578
+
579
+ Authorization
580
+ -------------
581
+
582
+ Rails Ops offers backend-agnostic authorization using so-called
583
+ *authorization backends*.
584
+
585
+ Authorization basically happens by calling the method `authorize!` (or
586
+ `authorize_only!`, more on that later) within an operation. What exactly this
587
+ method does depends on the *authorization backend* specified.
588
+
589
+ ### Authorization backends
590
+
591
+ Authorization backends are simple classes that supply the method `authorize!`.
592
+ This method, besides the operation instance, can take any number of arguments
593
+ and is supposed to perform authorization and raise if the authorization failed.
594
+
595
+ The authorization backend can be configured globally using the
596
+ `authorization_backend` configuration setting, which can be set to the name of
597
+ your backend class.
598
+
599
+ Example initializer:
600
+
601
+ ```ruby
602
+ RailsOps.configure do |config|
603
+ config.authorization_backend = 'RailsOps::AuthorizationBackends::CanCanCan'
604
+ end
605
+ ```
606
+
607
+ RailsOps ships with the following backend:
608
+
609
+ - `RailsOps::AutorizationBackend::Cancancan`
610
+
611
+ Offers integration of the `cancancan` Gem (which is a fork of the `cancan`
612
+ Gem).
613
+
614
+ ### Performing authorization
615
+
616
+ Authorization is generally performed by calling `authorize!` in an operation.
617
+ The arguments, along with the operation instance, are passed on to the
618
+ `authorize!` method of your authorization backend. Basically, you can call
619
+ `authorize!` anywhere in your operation, but bear in mind that if your
620
+ authorization requires certain data (i.e. the `params` hash), your authorization
621
+ calls should occur *after* that certain data is available.
622
+
623
+ ```ruby
624
+ class MyOp < RailsOps::Operation
625
+ def perform
626
+ authorize! :read, :some_area
627
+ end
628
+ end
629
+ ```
630
+
631
+ Usually though, authorization, as other pre-conditions, are called within
632
+ policies:
633
+
634
+ ```ruby
635
+ class MyOp < RailsOps::Operation
636
+ policy do
637
+ authorize! :read, :some_area
638
+ end
639
+ end
640
+ ```
641
+
642
+ In many cases, you'd like the authorization to run no matter if the operation
643
+ ever runs. For this case, use the `:on_init` policy chain:
644
+
645
+ ```ruby
646
+ class MyOp < RailsOps::Operation
647
+ policy :on_init do
648
+ authorize! :read, osparams.some_record
649
+ end
650
+ end
651
+ ```
652
+
653
+ See section *Policy chains* for more information.
654
+
655
+ ### Ensure that authorization has been performed
656
+
657
+ As it is a very common programming mistake to mistakenly omit calling
658
+ authorization, Rails Ops offers a solution for making sure that authorization
659
+ has been called in every operation.
660
+
661
+ This is done by calling `ensure_authorize_called!` on your operation. This will
662
+ raise an exception if no authorization has been performed. This method is
663
+ automatically called in `run` or `run!` after the execution of the `perform`
664
+ method.
665
+
666
+ This method only applies if authorization is currently enabled (see next
667
+ section), otherwise it does nothing.
668
+
669
+ It is implemented so that every call to `authorize!` sets an instance variable
670
+ of the respective operation to `true`, and `ensure_authorize_called!` checks
671
+ this instance variable on calling.
672
+
673
+ Sometimes you might want to call authorization that should not count for this
674
+ check, i.e. some base authorization that needs to be complemented with some
675
+ specific authorization code. In these cases, use `authorize_only!`:
676
+
677
+ ```ruby
678
+ def perform
679
+ authorize_only! :foo, :bar
680
+
681
+ # The following will fail as authorize_only! calls do not count as authorized.
682
+ ensure_authorize_called!
683
+ end
684
+ ```
685
+
686
+ This method otherwise does exactly the same as `authorize!` (in fact, it's the
687
+ underlying method used by it).
688
+
689
+ ### Disabling authorization
690
+
691
+ Sometimes you don't want a specific operation to perform authorization, or you
692
+ don't want to perform any authorization at all.
693
+
694
+ For this reason, Rails Ops allows you to disable authorization globally, per
695
+ operation or per operation call (i.e. an operation should generally perform
696
+ authorization, but not in a specific case). If authorization is disabled, all
697
+ calls to `authorize!` won't have any effect and will never fail. Also, it is not
698
+ ensured that authorization has been performed as it would always fail (see
699
+ previous section).
700
+
701
+ Rails Ops offers multiple ways of disabling authorization:
702
+
703
+ - By not configuring any authorization backend.
704
+
705
+ - By calling the class method `without_authorization`:
706
+
707
+ ```ruby
708
+ class MyOp < RailsOps::Operation
709
+ without_authorization
710
+ end
711
+ ```
712
+
713
+ TODO: Explain why this can be useful even if there are no authorize! calls
714
+ in this operation.
715
+
716
+ - By invoking one or more operations in a `RailsOps.without_authorization`
717
+ block:
718
+
719
+ ```ruby
720
+ RailsOps.without_authorization do
721
+ # Authorization will be disabled even if `SomeOperation` itself would
722
+ # otherwise perform authorization.
723
+ SomeOperation.run
724
+ end
725
+ ```
726
+
727
+ Within operations, you can also use the instance method
728
+ `without_authorization` which does the same thing as the global one (it is
729
+ just a shortcut and can therefore be used interchangeably):
730
+
731
+ ```ruby
732
+ class MyOp < RailsOps::Operation
733
+ def perform
734
+ without_authorization do
735
+ run_sub! SomeOtherOperation
736
+ end
737
+ end
738
+ end
739
+ ```
740
+
741
+ Note that when calling `without_authorization` this does not only apply to
742
+ other operations called, but also to the operation you're currently in:
743
+
744
+ ```ruby
745
+ class MyOp < RailsOps::Operation
746
+ def perform
747
+ without_authorization do
748
+ # The following line does nothing, as authorization is currently
749
+ # disabled.
750
+ authorize! :read, :some_area
751
+ end
752
+ end
753
+ end
754
+ ```
755
+
756
+ Model Operations
757
+ ----------------
758
+
759
+ One of the key features of RailsOps is model operations. RailsOps provides
760
+ multiple operation base classes which allow convenient manipulation of active
761
+ record models.
762
+
763
+ All of the model operation classes, including more specialized base classes,
764
+ inherit from {RailsOps::Operation::Model} (which in turn inherits from
765
+ {RailsOps::Operation} as every operation base class).
766
+
767
+ The key principle behind these model classes is to associate *one model class*
768
+ and *one model instance* with a particular operation.
769
+
770
+ ## Setting a model class
771
+
772
+ Using the static method `model`, you can assign a model class that is used in
773
+ the scope of this operation.
774
+
775
+ ```ruby
776
+ class SomeOperation < RailsOps::Operation::Model
777
+ model User
778
+ end
779
+ ```
780
+
781
+ You can also directly extend this class by providing a block. If given, this
782
+ will automatically create a new, anonymous class that inherits from the given
783
+ base class and run the given block in the static context of this class:
784
+
785
+ ```ruby
786
+ class SomeOperation < RailsOps::Operation::Model
787
+ model User do
788
+ # This code only runs in a dynamically created subclass of `User` and does
789
+ # not affect the original model class.
790
+ attr_accessible :name
791
+ end
792
+ end
793
+ ```
794
+
795
+ You do not even have to specify a base class. In this case, the class returned
796
+ by the static method `default_model_class` (default: {ActiveType::Object}) will
797
+ be used as base class:
798
+
799
+ ```ruby
800
+ class SomeOperation < RailsOps::Operation::Model
801
+ model do
802
+ # See ActiveType documentation for more information on virtual attributes.
803
+ attribute :name
804
+ end
805
+ end
806
+ ```
807
+
808
+ ## Obtaining a model instance
809
+
810
+ Model instances can be obtained using the *instance* method `model`, which is
811
+ not to be confused with the *class* method of the same name. Other than the
812
+ class method, the instance method instantiates and returns a model object with
813
+ the type / base class specified using the `model` class method:
814
+
815
+ ```ruby
816
+ class SomeOperation < RailsOps::Operation::Model
817
+ model User
818
+
819
+ def perform
820
+ # This returns an instance of the 'User' class. To be precise: This example
821
+ # does not work out-of-the-box as this base class is abstract and does not
822
+ # implement the `build_model` method. But more on that later.
823
+ model
824
+ end
825
+ end
826
+ ```
827
+
828
+ The instance method `model` only instantiates a model once and then caches it in
829
+ the instance variable `@model`. Therefore, you can call `model` multiple times
830
+ and always get back the same instance.
831
+
832
+ If no cached instance is found, one is built using the instance method
833
+ `build_model`. Note that this method is not provided by the `Model` base class
834
+ but only implemented in its subclasses. You can implement and override this
835
+ method to your liking though.
836
+
837
+ ## Loading models
838
+
839
+ Using the base operation class {RailsOps::Operation::Model::Load}, a model can
840
+ be loaded. This is done by implementing the `build_model` mentioned above. In
841
+ this particular case, the `find` method of the statically assigned model class
842
+ is used in conjunction with an ID extracted from the operation's params.
843
+
844
+ ```ruby
845
+ class Operations::User::Load < RailsOps::Operation::Model::Load
846
+ model User
847
+ end
848
+
849
+ op = Operations::User::Load.run!(id: 5)
850
+ op.model.id # => 5
851
+ ```
852
+
853
+ Note that this base class is a bit of a special case: It does not provide a
854
+ `perform` method and does not need to be run at all in order to load a model.
855
+ This is very useful when, for example, displaying a form based on a model
856
+ instance without actually performing any particular action such as updating a
857
+ model.
858
+
859
+ Therefore, the above example would also work as follows:
860
+
861
+ ```ruby
862
+ # The operation does not have to be performed to access the model instance.
863
+ op = Operations::User::Load.new(id: 5)
864
+ op.model.id # => 5
865
+ ```
866
+
867
+ ### Specifying ID field
868
+
869
+ Per default, the model instance is looked up using the field `id` and the ID
870
+ obtained from the method params using `params[:id]`. However, you can customize
871
+ this field name by overriding the method `model_id_field`:
872
+
873
+ ```ruby
874
+ class Operations::User::Load < RailsOps::Operation::Model::Load
875
+ model User
876
+
877
+ def model_id_field
878
+ :some_other_id_field
879
+ end
880
+ end
881
+ ```
882
+
883
+ ### Locking
884
+
885
+ In most cases when you load a model, you might want to lock the corresponding
886
+ database record. RailsOps is configured to automatically perform this locking
887
+ at time of loading. However, you can override the default behavior using
888
+ the option {RailsOps.config.lock_models_at_build}.
889
+
890
+ This behavior can also be overwritten per operation using the
891
+ `lock_model_at_build` class method:
892
+
893
+ ```ruby
894
+ class Operations::User::Load < RailsOps::Operation::Model::Load
895
+ model User
896
+ lock_model_at_build false # Takes `true` if no argument is passed
897
+ end
898
+ ```
899
+
900
+ ## Creating models
901
+
902
+ For creating models, you can use the base class
903
+ {RailsOps::Operation::Model::Create}.
904
+
905
+ This class mainly provides an implementation of the methods `build_model` and
906
+ `perform`.
907
+
908
+ The `build_model` method builds a new record using the operation's parameters.
909
+ See section *Parameter extraction for create and update* for more information on
910
+ that.
911
+
912
+ The `perform` method saves the record using `save!`.
913
+
914
+ As this base class is very minimalistic, it is recommended to fully read and
915
+ comprehend its source code.
916
+
917
+ ## Updating models
918
+
919
+ For creating models, you can use the base class
920
+ {RailsOps::Operation::Model::Update} which is an extension of the `Load` base
921
+ class.
922
+
923
+ This class mainly provides an implementation of the methods `build_model` and
924
+ `perform`.
925
+
926
+ The `build_model` method updates a record using the operation's parameters. See
927
+ section *Parameter extraction for create and update* for more information on
928
+ that.
929
+
930
+ The `perform` method saves the record using `save!`.
931
+
932
+ As this base class is very minimalistic, it is recommended to fully read and
933
+ comprehend its source code.
934
+
935
+ ## Destroying models
936
+
937
+ For destroying models, you can use the base class
938
+ {RailsOps::Operation::Model::Destroy} which is an extension of the `Load` base
939
+ class.
940
+
941
+ This class mainly provides an implementation of the method `perform`, which
942
+ destroys the model using its `destroy!` method.
943
+
944
+ As this base class is very minimalistic, it is recommended to fully read and
945
+ comprehend its source code.
946
+
947
+ ## Parameter extraction for create and update
948
+
949
+ As mentioned before, the `Create` and `Update` base classes provide an
950
+ implementation of `build_model` that assigns parameters to a model.
951
+
952
+ The attributes are determined by the operation instance method
953
+ `extract_attributes_from_params` - the name being self-explaining. See its
954
+ source code for implementation details.
955
+
956
+ ## Model authorization
957
+
958
+ While you can use the standard `authorize!` method (see chapter *Authorization*)
959
+ for authorizing models, RailsOps provides you a more convenient integration.
960
+
961
+ ### Basic authorization
962
+
963
+ Model authorization can be performed via the operation instance methods
964
+ `authorize_model!` and `authorize_model_with_authorize_only!` (see chapter
965
+ *Authorization* for more information on the difference between these two).
966
+
967
+ There two methods provide a simple wrapper around `authorize!` and
968
+ `authorize_only!` that casts the given model class or instance to an active
969
+ record object. This is necessary if the given model class or instance is a
970
+ (possibly anonymous) extension of an active record class for certain
971
+ authorization backends to work. Therefore, use the specific model authorization
972
+ methods instead of the basic authorization methods for authorizing models.
973
+
974
+ If no model is given, the model authorization methods automatically obtain the
975
+ model from the instance method `model`.
976
+
977
+ ### Automatic authorization
978
+
979
+ All model operation classes provide the operation instance method
980
+ `model_authorization` which is automatically run at model instantiation (this is
981
+ done using an `:on_init` policy). The purpose of this method is to perform an
982
+ authorization check based on this model.
983
+
984
+ While you can override this method to perform custom authorization, RailsOps
985
+ provides a base implementation. Using the class method
986
+ `model_authorization_action`, you can specify an action verb that is used for
987
+ authorizing your model.
988
+
989
+ ```ruby
990
+ class Operations::User::Load < RailsOps::Operation::Model::Load
991
+ model User
992
+
993
+ # This automatically calls `authorize_model! :read` after operation
994
+ # instantiation.
995
+ model_authorization_action :read
996
+ end
997
+ ```
998
+
999
+ Note that using the different model base classes, this is already set to a
1000
+ sensible default. See the respective class' source code for details.
1001
+
1002
+ ## Model nesting
1003
+
1004
+ TODO
1005
+
1006
+ Record extension and virtual records
1007
+ ------------------------------------
1008
+
1009
+
1010
+ Transactions
1011
+ ------------
1012
+
1013
+
1014
+
1015
+ Controller Integration
1016
+ ----------------------
1017
+
1018
+ While RailsOps certainly does not have to be used from a controller, it
1019
+ provides a mixin which extends controller classes with functionality that lets
1020
+ you easily instantiate and run operations.
1021
+
1022
+ ## Installing
1023
+
1024
+ Controller integration is designed to be non-intrusive and therefore has to be
1025
+ installed manually. Add the following inclusion to the controllers in question
1026
+ (usually the `ApplicationController` base class):
1027
+
1028
+ ```ruby
1029
+ class ApplicationController
1030
+ include RailsOps::ControllerMixin
1031
+ end
1032
+ ```
1033
+
1034
+ ## Basic usage
1035
+
1036
+ The basic concept behind controller integration is to instantiate and
1037
+ potentially run a single operation per request. Most of this guide refers to
1038
+ this particular use case. See section *Multiple operations per request* for more
1039
+ advanced solutions.
1040
+
1041
+ The following example shows the simplest way of setting and running an
1042
+ operation:
1043
+
1044
+ ```ruby
1045
+ class SomeController < ApplicationController
1046
+ def some_action
1047
+ run! Operations::SomeOperation
1048
+ end
1049
+ end
1050
+ ```
1051
+
1052
+ ## Separating instantiation and execution
1053
+
1054
+ In the previous example, we instantiated and ran an operation in a single
1055
+ statement. While this might be feasible for some "fire-and-forget" controller
1056
+ actions, you might want to separate instantiation from actually running an
1057
+ operation.
1058
+
1059
+ For this reason, RailsOps' controller integration is designed to always use
1060
+ a two-step process: First the operation is instantiated and assigned to the
1061
+ controller instance variable `@op`, and then it's possibly executed.
1062
+
1063
+ In the following example, we do exactly the same thing as in the previous one,
1064
+ but with separate instantiation and execution:
1065
+
1066
+ ```ruby
1067
+ class SomeController < ApplicationController
1068
+ def some_action
1069
+ # The following line instantiates the given operation and assigns the
1070
+ # instance to `@op`.
1071
+ op Operations::SomeOperation
1072
+
1073
+ # The following line runs the operation previously set using `op` using
1074
+ # the operations `run!` method. Note that `run` is available as well.
1075
+ run!
1076
+ end
1077
+ end
1078
+ ```
1079
+
1080
+ The methods `run` and `run!` always require you to previously instantiate an
1081
+ operation using the `op` method.
1082
+
1083
+ This can be particularly useful for "combined" controller methods that either
1084
+ display a form or submit, i.e. based on the HTTP method used.
1085
+
1086
+ ```ruby
1087
+ def update_username
1088
+ # As above operation extends RailsOps::Model, we can already access op.model
1089
+ # (i.e. in a form) without ever running the operation. Therefore, we
1090
+ # instantiate the operation even if it is a GET request.
1091
+ op Operations::User::UpdateUsername
1092
+
1093
+ # In this example, the operation is only run on POST requests.
1094
+ if request.post? && run
1095
+ redirect_to users_path
1096
+ end
1097
+ end
1098
+ ```
1099
+
1100
+ ## Checking for operations
1101
+
1102
+ Using the method `op?`, you can check whether an operation has already been
1103
+ instantiated (using `op`).
1104
+
1105
+ ## Model shortcut
1106
+
1107
+ RailsOps conveniently provides you with a `model` instance method, which is a
1108
+ shortcut for `op.model`. This is particularly useful since this is available as
1109
+ a view helper method as well, see next section.
1110
+
1111
+ ## View helper methods
1112
+
1113
+ The following controller methods are automatically provided as helper methods
1114
+ which can be used in views:
1115
+
1116
+ - `op`
1117
+ - `model`
1118
+ - `op?`
1119
+
1120
+ It is very common to use `model` for your forms:
1121
+
1122
+ ```
1123
+ = form_for model do |f|
1124
+ - # Form code goes here
1125
+ ```
1126
+
1127
+ ## Parameters
1128
+
1129
+ As you've probably noticed in previous examples, we did not provide any
1130
+ parameters to the operation.
1131
+
1132
+ Per default, the `params` hash is automatically provided to the operation at
1133
+ instantiation. To be more precise: The params hash is filtered not to include
1134
+ certain fields (see {RailsOps::ControllerMixin::EXCEPT_PARAMS}) that are most
1135
+ commonly not used by operations (e.g. the `authenticity_token`).
1136
+
1137
+ This is achieved using the private `op_params` method. Overwrite it to your
1138
+ needs if you have to adapt it for the whole controller.
1139
+
1140
+ Alternatively, you can pass entirely custom params to an operation via the `op`
1141
+ method:
1142
+
1143
+ ```ruby
1144
+ op SomeOperation, some_param: 'some_value'
1145
+ ```
1146
+
1147
+ You can also combine these two approaches:
1148
+
1149
+ ```ruby
1150
+ # This example takes the pre-filtered op_params hash and applies another, custom
1151
+ # filter before passing it to the operation.
1152
+ op SomeOperation, some_param: op_params.slice(:some_param, :some_other_param)
1153
+ ```
1154
+
1155
+ ## Authorization ensuring
1156
+
1157
+ For security reasons, RailsOps automatically checks after each action whether
1158
+ authorization has been performed. This is to avoid serving an action's response
1159
+ without ever authorizing.
1160
+
1161
+ The check is run in the `after_action` named
1162
+ `ensure_operation_authorize_called!` and only applies if an operation class has
1163
+ been set.
1164
+
1165
+ Note that this check also doesn't apply if the corresponding operation uses
1166
+ `without_authorization` (see section *Disabling authorization* for more
1167
+ information on this).
1168
+
1169
+ ## Context
1170
+
1171
+ When using the `op` method to instantiate an operation, a context is
1172
+ automatically created. The following fields are set automatically:
1173
+
1174
+ - `params` (as described in subsection *Parameters*)
1175
+ - `user` (uses `current_user` controller method if available, otherwise `nil`)
1176
+ - `ability` (uses `current_ability` controller method if available, otherwise `nil`)
1177
+ - `session` (uses the `session` controller method)
1178
+ - `url_options` (uses the `url_options` controller method)
1179
+
1180
+ ## Multiple operations per request
1181
+
1182
+ RailsOps does not currently support calling multiple operations in a single
1183
+ controller action out-of-the-box. You need to instantiate and run it manually.
1184
+
1185
+ Another approach is to create a parent operation which calls multiple
1186
+ sub-operations, see section *Calling sub-operations* for more information.
1187
+
1188
+ Operation Inheritance
1189
+ ---------------------
1190
+
1191
+ Caveats
1192
+ -------
1193
+
1194
+ ## Eager loading in development mode
1195
+
1196
+ Eager loading operation classes containing models with nested models or
1197
+ operations can be very slow in performance. In production mode, the same process
1198
+ is very fast and not an issue at all. To work around this problem, make sure you
1199
+ exclude your operation classes (i.e. `app/operations`) in your
1200
+ `config.eager_load_paths` of `development.rb`. Make sure not to touch this
1201
+ setting in production mode though.
1202
+
1203
+ Open points
1204
+ -----------
1205
+
1206
+ ## Load model authorization
1207
+
1208
+ It is possible that we can eliminate the load model authorization functionality
1209
+ entirely. Operations using the `Load` base class can authorize using `:read`,
1210
+ while more specific operations can use `:update` or whatever. If we really need
1211
+ to check for `:read` when updating an object, it can be implemented in the
1212
+ ability class.
1213
+
1214
+ ## Copyright
1215
+
1216
+ Copyright (c) 2017 Sitrox. See `LICENSE` for further details.