lev 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +165 -65
- data/lib/lev.rb +8 -6
- data/lib/lev/errors.rb +9 -5
- data/lib/lev/exceptions.rb +2 -1
- data/lib/lev/object.rb +16 -2
- data/lib/lev/routine.rb +68 -49
- data/lib/lev/transaction_isolation.rb +3 -3
- data/lib/lev/utilities.rb +13 -1
- data/lib/lev/version.rb +1 -1
- data/spec/delegates_to_spec.rb +15 -0
- data/spec/support/delegated_routine.rb +10 -0
- data/spec/support/delegating_routine.rb +5 -0
- metadata +15 -43
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmIyODE2NzY1ZTAwZjA4YzZmY2NmYzYyNzFiZmVmOGJhMWE4MzNkYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MWE4YzcxOWFkYmU2ZTQ5ZjllMDg0MWQ3NTdkZTVhNjZjYjFjZWUzMA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTExN2U3Njg2MmM2OTQ0NTk0NDZmYjk2NDU4NmE1NjBmOTIwNDJlMzY3MGRk
|
10
|
+
MTc5NmRiMjJhNTljMjc3Yzg2OGE2NmU1Y2IwMDQ1ZDlkYTA4MjI4N2QzODA0
|
11
|
+
NzZlMGMyYWYzMzEyM2QyM2UyN2EzY2NlN2FjYjdlMjQyYWU0NjA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MzA2YjM3ZTRiOWRlNzJiZWNjZDRhZWE5NWU1MjI5OGQ0OWZhMDdlZmJkNjFi
|
14
|
+
ZTE5YWRlNjljNDE4NTQ2ZWU3OTIzN2FhZTYyZGM2ODk0N2MyZjBkOGZhODRh
|
15
|
+
NzJhNTk5NDUyYjk3MGE0NGZkYTM0OGViNDA4MTA4MmNhNzhjYjM=
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ Lev introduces two main constructs to get around these issues: **Routines** and
|
|
17
17
|
|
18
18
|
## Routines
|
19
19
|
|
20
|
-
Lev's Routines are pieces of code that have all the responsibility for making one thing (one use case) happen, e.g. "add an email to a user", "register a student to a class", etc), normally acting on objects from more than one model.
|
20
|
+
Lev's Routines are pieces of code that have all the responsibility for making one thing (one use case) happen, e.g. "add an email to a user", "register a student to a class", etc), normally acting on objects from more than one model.
|
21
21
|
|
22
22
|
Routines...
|
23
23
|
|
@@ -30,12 +30,12 @@ In an OO/MVC world, an operation that involves multiple objects might be impleme
|
|
30
30
|
Routines typically don't have any persistent state that is used over and over again; they are created, used, and forgotten. A routine is a glorified function with a special single-responsibility purpose.
|
31
31
|
|
32
32
|
A class becomes a routine by calling `lev_routine` in its definition, e.g.:
|
33
|
-
|
33
|
+
|
34
34
|
class MyRoutine
|
35
|
-
lev_routine
|
35
|
+
lev_routine
|
36
36
|
...
|
37
37
|
|
38
|
-
Other than that, all a routine has to do is implement an "exec" method (typically `protected`) that takes arbitrary arguments and that adds errors to an internal array-like "errors" object and outputs to a "outputs" hash. Two convenience methods are provided for adding errors:
|
38
|
+
Other than that, all a routine has to do is implement an "exec" method (typically `protected`) that takes arbitrary arguments and that adds errors to an internal array-like "errors" object and outputs to a "outputs" hash. Two convenience methods are provided for adding errors:
|
39
39
|
|
40
40
|
Errors can be recorded in a number of ways. You can manually add errors to the built-in `errors` object:
|
41
41
|
|
@@ -62,7 +62,7 @@ Here's an example setting an error and an output:
|
|
62
62
|
outputs[:bar] = foo * 2
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
Additionally, see below for a discussion on how to transfer errors from ActiveRecord models.
|
67
67
|
|
68
68
|
Any `StandardError` raised within a routine will be caught and transformed into a fatal error with `:kind` set to `:exception`. The caller of this routine can choose to reraise this exception by calling `reraise_exception!` on the returned errors object:
|
@@ -79,13 +79,13 @@ By default `raise_exception_if_any!` will raise a `StandardError` with a message
|
|
79
79
|
|
80
80
|
A routine will automatically get both class- and instance-level `call`
|
81
81
|
methods that take the same arguments as the `exec` method. The class-level
|
82
|
-
call method simply instantiates a new instance of the routine and calls
|
83
|
-
the instance-level call method (side note here is that this means that
|
82
|
+
call method simply instantiates a new instance of the routine and calls
|
83
|
+
the instance-level call method (side note here is that this means that
|
84
84
|
routines aren't typically instantiated with state).
|
85
85
|
|
86
86
|
When called, a routine returns a `Result` object, which is just a simple wrapper
|
87
|
-
of the outputs and errors objects.
|
88
|
-
|
87
|
+
of the outputs and errors objects.
|
88
|
+
|
89
89
|
result = MyRoutine.call(42)
|
90
90
|
puts result.outputs[:bar] # => 84
|
91
91
|
|
@@ -93,71 +93,71 @@ of the outputs and errors objects.
|
|
93
93
|
### Nesting Routines
|
94
94
|
|
95
95
|
As mentioned above, routines can call other routines. While this is of course possible just by calling the other routine's call method directly, it is strongly recommended that one routine call another routine using the provided `run` method. This method takes the name of the routine class and the arguments/block it expects in its call/exec methods. By using the `run` method, the called routine will be hooked into the common error and transaction mechanisms.
|
96
|
-
|
96
|
+
|
97
97
|
When one routine is called within another using the `run` method, there is only one transaction used (barring any explicitly made in the code) and its isolation level is sufficiently strict for all routines involved.
|
98
|
-
|
98
|
+
|
99
99
|
It is highly recommend, though not required, to call the `uses_routine` method to let the routine know which subroutines will be called within it. This will let a routine set its isolation level appropriately, and will enforce that only one transaction be used and that it be rolled back appropriately if any errors occur.
|
100
|
-
|
100
|
+
|
101
101
|
Once a routine has been registered with the `uses_routine` call, it can be run by passing run the routine's Class or a symbol identifying the routine. This symbol can be set with the `:as` option. If not set, the symbol will be automatically set by converting the routine class' full name to a symbol. e.g:
|
102
|
-
|
102
|
+
|
103
103
|
uses_routine CreateUser
|
104
104
|
as: :cu
|
105
|
-
|
105
|
+
|
106
106
|
and then you can call this routine with any of the following:
|
107
107
|
|
108
108
|
* `run(:cu, ...)`
|
109
109
|
* `run(:create_user, ...)`
|
110
110
|
* `run(CreateUser, ...)`
|
111
111
|
* `CreateUser.call(...)` (not recommended)
|
112
|
-
|
112
|
+
|
113
113
|
#### Errors from Nested Routines
|
114
114
|
|
115
|
-
`uses_routine` also provides a way to specify how errors relate to routine
|
116
|
-
inputs. Take the following example. A `User` model calls `Routine1` which calls
|
115
|
+
`uses_routine` also provides a way to specify how errors relate to routine
|
116
|
+
inputs. Take the following example. A `User` model calls `Routine1` which calls
|
117
117
|
`Routine2`.
|
118
|
-
|
118
|
+
|
119
119
|
User --> Routine1.call(foo: "abcd4") --> Routine2.call(bar: "abcd4")
|
120
|
-
|
120
|
+
|
121
121
|
An error occurs in `Routine2`, and Routine2 notes that the error is related
|
122
122
|
to its `bar` input. If that error and its metadata bubble up to the `User`,
|
123
123
|
the `User` won't have any idea what `bar` relates to -- the `User` only knows
|
124
124
|
about the interface to `Routine1` and the `foo` parameter it gave it.
|
125
|
-
|
125
|
+
|
126
126
|
`Routine1` knows that it will call `Routine2` and knows what its interface is. It can then specify how to map terminology from `Routine2` into `Routine1`'s context. E.g., in the following class:
|
127
|
-
|
127
|
+
|
128
128
|
class Routine1
|
129
129
|
lev_routine
|
130
130
|
uses_routine Routine2,
|
131
|
-
translations: {
|
131
|
+
translations: {
|
132
132
|
inputs: { map: {bar: :foo} }
|
133
133
|
}
|
134
134
|
def exec(options)
|
135
135
|
run(Routine2, bar: options[:foo])
|
136
136
|
end
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
`Routine1` notes that any errors coming back from the call to `Routine2` related to `:bar` should be transfered into `Routine1`'s errors object as being related to `:foo`. In this way, the caller of `Routine1` will see errors related to the arguments he understands.
|
140
140
|
|
141
141
|
In addition to the `map:` configuration for input transferral, there are three other configurations:
|
142
142
|
|
143
|
-
1. **Scoped** - Appends the provided scoping symbol (or symbol array) to the input symbol.
|
144
|
-
|
143
|
+
1. **Scoped** - Appends the provided scoping symbol (or symbol array) to the input symbol.
|
144
|
+
|
145
145
|
`{scope: SCOPING_SYMBOL_OR_SYMBOL_ARRAY}`
|
146
|
-
|
146
|
+
|
147
147
|
e.g. with `{scope: :register}` and a call to a routine that has an input
|
148
|
-
named `:first_name`, an error in that called routine related to its
|
149
|
-
`:first_name` input will be translated so that the offending input is
|
148
|
+
named `:first_name`, an error in that called routine related to its
|
149
|
+
`:first_name` input will be translated so that the offending input is
|
150
150
|
`[:register, :first_name]`.
|
151
|
-
|
151
|
+
|
152
152
|
2. **Verbatim** - Uses the same term in the caller as the callee.
|
153
|
-
|
153
|
+
|
154
154
|
`{type: :verbatim}`
|
155
|
-
|
155
|
+
|
156
156
|
3. **Mapped** - Give an explicit, custom mapping:
|
157
|
-
|
157
|
+
|
158
158
|
`{map: {called_input1: caller_input1, called_input2: :caller_input2}}`
|
159
|
-
|
160
|
-
4. **Scoped and mapped** - Give an explicit mapping, and also scope the
|
159
|
+
|
160
|
+
4. **Scoped and mapped** - Give an explicit mapping, and also scope the
|
161
161
|
translated terms. Just use `scope:` and `map:` from above in the same hash.
|
162
162
|
|
163
163
|
If an input translation is unspecified, the default is scoped, with `SCOPING_SYMBOL_OR_ARRAY` equal to the `as:` option passed to `uses_routine`, if provided, or if that is not provided then the symbolized name of the routine class. E.g. for:
|
@@ -170,10 +170,10 @@ an errors generated on the `foo` input in `OtherRoutine` will be transferred up
|
|
170
170
|
|
171
171
|
Via the `uses_routine` call, you can also ignore specified errors that occur
|
172
172
|
in the called routine. e.g.:
|
173
|
-
|
173
|
+
|
174
174
|
uses_routine DestroyUser,
|
175
175
|
ignored_errors: [:cannot_destroy_non_temp_user]
|
176
|
-
|
176
|
+
|
177
177
|
ignores errors with the provided code. The `ignore_errors` key must point
|
178
178
|
to an array of code symbols or procs. If a proc is given, the proc will
|
179
179
|
be called with the error that the routine is trying to add. If the proc
|
@@ -187,12 +187,12 @@ In addition to errors being transferred from subroutines to calling routines, a
|
|
187
187
|
class Routine1
|
188
188
|
lev_routine
|
189
189
|
uses_routine Routine2,
|
190
|
-
translations: {
|
190
|
+
translations: {
|
191
191
|
outputs: { type: :verbatim }
|
192
192
|
}
|
193
193
|
|
194
194
|
def exec(options)
|
195
|
-
run(Routine2, bar: options[:foo])
|
195
|
+
run(Routine2, bar: options[:foo])
|
196
196
|
# Assuming Routine2 generates an output named "x", then outputs[:x] will be
|
197
197
|
# available as of this line
|
198
198
|
end
|
@@ -202,7 +202,7 @@ If the output translations are not specified, they will be scoped exactly like h
|
|
202
202
|
|
203
203
|
|
204
204
|
Note if multiple outputs are transferred into the same named output (e.g. by calling the same routine over and over in a loop), an array of those outputs will be stored under that name.
|
205
|
-
|
205
|
+
|
206
206
|
#### Overriding `uses_routine` Options
|
207
207
|
|
208
208
|
Any option passed to uses_routine can also be passed directly to the run
|
@@ -234,20 +234,20 @@ When errors are captured inside an `ActiveRecord` errors object, you can use `tr
|
|
234
234
|
### Specifying Transaction Isolations
|
235
235
|
|
236
236
|
A routine is automatically run within a transaction. The isolation level of the routine can be set by passing a `:transaction` option to the `lev_routine` call (or to the `lev_handler` call, if appropriate). The value must be one of the following:
|
237
|
-
|
237
|
+
|
238
238
|
* `:no_transaction`
|
239
239
|
* `:read_uncommitted`
|
240
240
|
* `:read_committed`
|
241
241
|
* `:repeatable_read`
|
242
242
|
* `:serializable`
|
243
|
-
|
243
|
+
|
244
244
|
Note that by setting an isolation level, you are stating the minimum isolation level at which a routine must be run. When routines are nested inside each other, the highest-specified isolation level from any one of them is used in the one transaction in which all of a routines' subroutines run.
|
245
245
|
|
246
246
|
For example, if you write a routine that does a complex query, you might not need any transaction:
|
247
247
|
|
248
248
|
class MyQueryRoutine
|
249
249
|
lev_routine transaction: :no_transaction
|
250
|
-
|
250
|
+
|
251
251
|
If unspecified, the default isolation is `:repeatable_read`.
|
252
252
|
|
253
253
|
### delegate_to_routine
|
@@ -266,12 +266,112 @@ When `delegate_to_routine` is called, the provided method will call the routine
|
|
266
266
|
|
267
267
|
will alias the old `destroy` method as `destroy_original` and add a new `destroy` method that calls the `DestroyProduct` routine.
|
268
268
|
|
269
|
+
### Express Calling of Routines
|
270
|
+
|
271
|
+
Routines commonly return one output. These routines are often named things like `GetUserEmail` or `IsFinalized`. Particularly
|
272
|
+
for boolean queries like `IsBlahBlah`, it is onerous to say:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
if IsBlahBlah.call(arg1, arg2).outputs.some_output_containing_the_true_false_value
|
276
|
+
```
|
277
|
+
|
278
|
+
As a convenience, routines can be called "expressly" (as in compactly) using the bracket operator. For example with the
|
279
|
+
following routine:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
class AreArgumentsEqual
|
283
|
+
lev_routine
|
284
|
+
|
285
|
+
def exec(arg1, arg2)
|
286
|
+
outputs[:are_arguments_equal] = (arg1 == arg2)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
you could call it in the normal way:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
if AreArgumentsEqual.call(201, 202).outputs.are_arguments_equal
|
295
|
+
# do something
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
or you can call it using brackets:
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
if AreArgumentsEqual[201, 202]
|
303
|
+
# do something
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
When using this bracket style of calling routines, Lev assumes that the value to be returned is named with the underscored
|
308
|
+
version of the routine name, e.g. `AreArgumentsEqual` has a default return value of `are_arguments_equal`. Module names
|
309
|
+
are disregarded when computing the default name.
|
310
|
+
|
311
|
+
The `express_output` can be overriden:
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
class AreArgumentsEqual
|
315
|
+
lev_routine, express_output: :answer
|
316
|
+
|
317
|
+
def exec(arg1, arg2)
|
318
|
+
outputs[:answer] = (arg1 == arg2)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
When calling with the bracket operator, any errors accumulated by the routine are raised in an exception (have to do this
|
324
|
+
since you have no other way to pay attention to the errors).
|
325
|
+
|
326
|
+
### Delegates
|
327
|
+
|
328
|
+
If you have
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class BarRoutine
|
332
|
+
lev_routine
|
333
|
+
|
334
|
+
def exec(alpha:, beta:)
|
335
|
+
# Do work
|
336
|
+
end
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
340
|
+
you might have a reason to wrap this routine inside another, in which case you could write:
|
341
|
+
|
342
|
+
```ruby
|
343
|
+
class FooRoutine
|
344
|
+
lev_routine
|
345
|
+
|
346
|
+
uses_routine BarRoutine,
|
347
|
+
translations: {
|
348
|
+
outputs: { type: :verbatim },
|
349
|
+
inputs: { type: :verbatim }
|
350
|
+
}
|
351
|
+
|
352
|
+
def exec(alpha:, beta:)
|
353
|
+
run(BarRoutine, alpha: alpha, beta: beta)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
```
|
357
|
+
|
358
|
+
or if you use the `delegates_to:` shortcut, you can instead equivalently wrap `BarRoutine` with:
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
class ShorterFooRoutine
|
362
|
+
lev_routine delegates_to: BarRoutine
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
When using `delegates_to`, any `express_output` value set in the delegated routine is automatically
|
367
|
+
used again by the delegating routine.
|
368
|
+
|
269
369
|
### Other Routine Methods
|
270
|
-
|
370
|
+
|
271
371
|
Routine class have access to a few other methods:
|
272
372
|
|
273
373
|
1. a `runner` accessor which points to the routine which called it. If
|
274
|
-
runner is nil that means that no other routine called it (some other
|
374
|
+
runner is nil that means that no other routine called it (some other
|
275
375
|
code did)
|
276
376
|
2. a `topmost_runner` accessor which points to the highest routine in the calling
|
277
377
|
hierarchy (that routine whose 'runner' is nil)
|
@@ -289,7 +389,7 @@ Handlers...
|
|
289
389
|
4. Map one-to-one with controller actions; by keeping the logic in each controller action encapsulated in a Handler, the code becomes independently-testable and also prevents the controller from being "fat" with 7 different actions all containing disparate logic touching different models.
|
290
390
|
|
291
391
|
A class becomes a handler by calling `lev_handler` in its definition, e.g.:
|
292
|
-
|
392
|
+
|
293
393
|
class MyHandler
|
294
394
|
lev_handler
|
295
395
|
...
|
@@ -310,10 +410,10 @@ Additionally, the handler provides attributes to return the `errors` object and
|
|
310
410
|
|
311
411
|
The `handle` method that you define should not return anything; they just set values in the errors and results objects. The documentation for each handler should explain what the results will be and any nonstandard data required to be passed in in the options.
|
312
412
|
|
313
|
-
In addition to the class- and instance-level `call` methods provided by Lev::Routine, Handlers have a class-level `handle` method (an alias of the class-level `call` method). The convention for handlers is that the `call` methods (and this class-level `handle` method) take a hash of options/inputs. The instance-level `handle` method doesn't take any arguments since the arguments have been stored as instance variables by the time the instance-level handle method is called.
|
314
|
-
|
413
|
+
In addition to the class- and instance-level `call` methods provided by Lev::Routine, Handlers have a class-level `handle` method (an alias of the class-level `call` method). The convention for handlers is that the `call` methods (and this class-level `handle` method) take a hash of options/inputs. The instance-level `handle` method doesn't take any arguments since the arguments have been stored as instance variables by the time the instance-level handle method is called.
|
414
|
+
|
315
415
|
Example:
|
316
|
-
|
416
|
+
|
317
417
|
class MyHandler
|
318
418
|
lev_handler
|
319
419
|
protected
|
@@ -327,7 +427,7 @@ In addition to the class- and instance-level `call` methods provided by Lev::Rou
|
|
327
427
|
|
328
428
|
### paramify
|
329
429
|
|
330
|
-
By declaring one or more `paramify` blocks in a handler, you can declare, group, cast, and validate parts of the `params` hash. Think of `paramify` as a way to declare an ad-hoc `ActiveModel` class to wrap incoming parameters. Normally, you only get easy validation of input parameters when those parameters are passed to an application model that is validated during a save. `paramify` lets you do this for any arbitrary collection of incoming parameters without requiring those parameters to live in application models.
|
430
|
+
By declaring one or more `paramify` blocks in a handler, you can declare, group, cast, and validate parts of the `params` hash. Think of `paramify` as a way to declare an ad-hoc `ActiveModel` class to wrap incoming parameters. Normally, you only get easy validation of input parameters when those parameters are passed to an application model that is validated during a save. `paramify` lets you do this for any arbitrary collection of incoming parameters without requiring those parameters to live in application models.
|
331
431
|
|
332
432
|
The first argument to `paramify` is the key in params which points to a hash of params to be paramified. If this first argument is unspecified (or specified as `:paramify`, a reserved symbol), the entire params hash will be paramified. The block passed to paramify looks just like the guts of an ActiveAttr model.
|
333
433
|
|
@@ -338,13 +438,13 @@ For example, when the incoming params includes :search => {:type, :terms, :num_r
|
|
338
438
|
validates :type, presence: true,
|
339
439
|
inclusion: { in: %w(Name Username Any),
|
340
440
|
message: "is not valid" }
|
341
|
-
|
441
|
+
|
342
442
|
attribute :terms, type: String
|
343
443
|
validates :terms, presence: true
|
344
|
-
|
444
|
+
|
345
445
|
attribute :num_results, type: Integer
|
346
446
|
validates :num_results, numericality: { only_integer: true,
|
347
|
-
greater_than_or_equal_to: 0 }
|
447
|
+
greater_than_or_equal_to: 0 }
|
348
448
|
end
|
349
449
|
|
350
450
|
This will result in a `search_params` variable being available. `search_params.num_results` would be guaranteed to be an integer greater than or equal to zero. Note that if you want to use a "Boolean" type, you need to type it with a lowercase (`type: boolean`).
|
@@ -365,9 +465,9 @@ The following is a more complete example using the `paramify` block above:
|
|
365
465
|
|
366
466
|
attribute :num_results, type: Integer
|
367
467
|
validates :num_results, numericality: { only_integer: true,
|
368
|
-
greater_than_or_equal_to: 0 }
|
468
|
+
greater_than_or_equal_to: 0 }
|
369
469
|
end
|
370
|
-
|
470
|
+
|
371
471
|
def handle
|
372
472
|
# By this time, if there were any errors the handler would have
|
373
473
|
# already populated the errors object and returned.
|
@@ -382,23 +482,23 @@ The following is a more complete example using the `paramify` block above:
|
|
382
482
|
### handle_with
|
383
483
|
|
384
484
|
`handle_with` is a utility method for calling handlers from controllers. To use it, call `include Lev::HandleWith` in your relevant controllers (or in your ApplicationController):
|
385
|
-
|
485
|
+
|
386
486
|
class ApplicationController
|
387
487
|
include Lev::HandleWith
|
388
488
|
...
|
389
489
|
end
|
390
|
-
|
490
|
+
|
391
491
|
Then, call `handle_with` from your various controller actions, e.g.:
|
392
|
-
|
492
|
+
|
393
493
|
handle_with(MyFormHandler,
|
394
494
|
params: params,
|
395
495
|
success: lambda { redirect_to 'show', notice: 'Success!'},
|
396
496
|
failure: lambda { render 'new', alert: 'Error' })
|
397
|
-
|
497
|
+
|
398
498
|
`handle_with` takes care of calling the handler and populates a `@handler_result` object with results and errors from running the handler.
|
399
|
-
|
499
|
+
|
400
500
|
The 'success' and 'failure' lambdas are called if there aren't or are errors, respectively. Alternatively, if you supply a 'complete' lambda, that lambda will be called regardless of whether there are any errors. Inside these lambdas (and inside the views they connect to), the @handler_outcome variable containing the errors and results from the handler will be available.
|
401
|
-
|
501
|
+
|
402
502
|
Specifying 'params' is optional. If you don't specify it, `handle_with` will use the entire params hash from the request.
|
403
503
|
|
404
504
|
Handlers help us clean up controllers in our Rails projects. Instead of having a different piece of application logic in every controller action, a Lev-oriented app's controllers just end up being responsible for connecting routes to handlers, normally via a quick call to `handle_with`.
|
@@ -429,7 +529,7 @@ Consider the following example:
|
|
429
529
|
<%= f.submit "Register", id: "register_submit" %>
|
430
530
|
<% end %>
|
431
531
|
|
432
|
-
Here, the form parameters will include
|
532
|
+
Here, the form parameters will include
|
433
533
|
|
434
534
|
:register => {:username => 'bob79', :first_name => 'Bob', :password => 'password', :password_confirmation => 'password'}
|
435
535
|
|
@@ -507,17 +607,17 @@ When these guidelines are followed, model classes end up being very small and si
|
|
507
607
|
|
508
608
|
## Naming Conventions
|
509
609
|
|
510
|
-
As mentioned above, a handler is intended to replace the logic in one controller action. As such, one convention that works well is to name a handler based on the controller name and the action name, e.g. for the `ProductsController#show` action, we would have a handler named `ProductsShow`.
|
610
|
+
As mentioned above, a handler is intended to replace the logic in one controller action. As such, one convention that works well is to name a handler based on the controller name and the action name, e.g. for the `ProductsController#show` action, we would have a handler named `ProductsShow`.
|
511
611
|
|
512
612
|
Routines on the other hand are more or less glorified functions that work with multiple models to get something done, so we typically start their names with verbs, e.g. `CreateUser`, `SetPassword`, `ConfirmEmail`, etc.
|
513
613
|
|
514
614
|
## Differences between Lev and Rails' Concerns
|
515
615
|
|
516
|
-
Both Lev and Concerns remove lines of code from models, but the major difference between the two is that with Concerns, the code still lives logically in the models whereas code in Lev is completely outside of and separate from the models.
|
616
|
+
Both Lev and Concerns remove lines of code from models, but the major difference between the two is that with Concerns, the code still lives logically in the models whereas code in Lev is completely outside of and separate from the models.
|
517
617
|
|
518
|
-
Lev's routines (and handlers) know about models, but the models don't know anything about nor are they dependent on the code in routines*. This makes the models simpler and more stable (a Good Thing).
|
618
|
+
Lev's routines (and handlers) know about models, but the models don't know anything about nor are they dependent on the code in routines*. This makes the models simpler and more stable (a Good Thing).
|
519
619
|
|
520
|
-
Since a Concern's code is essentially embedded in model code, if that Concern breaks it can potentially break other unrelated features, something that can't happen with routines.
|
620
|
+
Since a Concern's code is essentially embedded in model code, if that Concern breaks it can potentially break other unrelated features, something that can't happen with routines.
|
521
621
|
|
522
622
|
Routines are especially good when some use case needs to query or change multiple models. With a routine all of the logic for that use case is in one file. With a concern, that code could be in multiple models and multiple concerns.
|
523
623
|
|
data/lib/lev.rb
CHANGED
@@ -27,12 +27,12 @@ require "lev/transaction_isolation"
|
|
27
27
|
|
28
28
|
module Lev
|
29
29
|
class << self
|
30
|
-
|
30
|
+
|
31
31
|
###########################################################################
|
32
32
|
#
|
33
33
|
# Configuration machinery.
|
34
34
|
#
|
35
|
-
# To configure Lev, put the following code in your applications
|
35
|
+
# To configure Lev, put the following code in your applications
|
36
36
|
# initialization logic (eg. in the config/initializers in a Rails app)
|
37
37
|
#
|
38
38
|
# Lev.configure do |config|
|
@@ -40,7 +40,7 @@ module Lev
|
|
40
40
|
# ...
|
41
41
|
# end
|
42
42
|
#
|
43
|
-
|
43
|
+
|
44
44
|
def configure
|
45
45
|
yield configuration
|
46
46
|
end
|
@@ -53,13 +53,15 @@ module Lev
|
|
53
53
|
# This HTML class is added to form fields that caused errors
|
54
54
|
attr_accessor :form_error_class
|
55
55
|
attr_accessor :security_transgression_error
|
56
|
-
|
57
|
-
|
56
|
+
attr_accessor :illegal_argument_error
|
57
|
+
|
58
|
+
def initialize
|
58
59
|
@form_error_class = 'error'
|
59
60
|
@security_transgression_error = Lev::SecurityTransgression
|
61
|
+
@illegal_argument_error = Lev::IllegalArgument
|
60
62
|
super
|
61
63
|
end
|
62
64
|
end
|
63
|
-
|
65
|
+
|
64
66
|
end
|
65
67
|
end
|
data/lib/lev/errors.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Lev
|
2
2
|
|
3
|
-
# A collection of Error objects.
|
3
|
+
# A collection of Error objects.
|
4
4
|
#
|
5
5
|
class Errors < Array
|
6
6
|
|
7
|
-
def add(fail, args={})
|
7
|
+
def add(fail, args={})
|
8
8
|
args[:kind] ||= :lev
|
9
9
|
error = Error.new(args)
|
10
10
|
return if ignored_error_procs.any?{|proc| proc.call(error)}
|
@@ -16,8 +16,8 @@ module Lev
|
|
16
16
|
proc = arg.is_a?(Symbol) ?
|
17
17
|
Proc.new{|error| error.code == arg} :
|
18
18
|
arg
|
19
|
-
|
20
|
-
raise
|
19
|
+
|
20
|
+
raise Lev.configuration.illegal_argument_error if !proc.respond_to?(:call)
|
21
21
|
|
22
22
|
ignored_error_procs.push(proc)
|
23
23
|
end
|
@@ -31,6 +31,10 @@ module Lev
|
|
31
31
|
self.any? {|error| [error.offending_inputs].flatten.include? input}
|
32
32
|
end
|
33
33
|
|
34
|
+
def raise_exception_if_any!(exception_type = StandardError)
|
35
|
+
raise exception_type, collect{|error| error.message}.join('; ') if any?
|
36
|
+
end
|
37
|
+
|
34
38
|
protected
|
35
39
|
|
36
40
|
def ignored_error_procs
|
@@ -38,4 +42,4 @@ module Lev
|
|
38
42
|
end
|
39
43
|
|
40
44
|
end
|
41
|
-
end
|
45
|
+
end
|
data/lib/lev/exceptions.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
class Lev::IsolationMismatch < StandardError; end
|
2
2
|
class Lev::SecurityTransgression < StandardError; end
|
3
3
|
class Lev::AlgorithmError < StandardError; end
|
4
|
-
class Lev::AbstractMethodCalled < StandardError; end
|
4
|
+
class Lev::AbstractMethodCalled < StandardError; end
|
5
|
+
class Lev::IllegalArgument < StandardError; end
|
data/lib/lev/object.rb
CHANGED
@@ -7,6 +7,20 @@ class Object
|
|
7
7
|
# Routine configuration
|
8
8
|
options[:transaction] ||= Lev::TransactionIsolation.mysql_default.symbol
|
9
9
|
@transaction_isolation = Lev::TransactionIsolation.new(options[:transaction])
|
10
|
+
|
11
|
+
@delegates_to = options[:delegates_to]
|
12
|
+
if @delegates_to
|
13
|
+
uses_routine @delegates_to,
|
14
|
+
translations: {
|
15
|
+
outputs: { type: :verbatim },
|
16
|
+
inputs: { type: :verbatim }
|
17
|
+
}
|
18
|
+
|
19
|
+
@express_output ||= @delegates_to.express_output
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set this after dealing with "delegates_to" in case it set a value
|
23
|
+
@express_output ||= options[:express_output] || self.name.demodulize.underscore
|
10
24
|
end
|
11
25
|
end
|
12
26
|
|
@@ -14,7 +28,7 @@ class Object
|
|
14
28
|
class_eval do
|
15
29
|
include Lev::Handler
|
16
30
|
end
|
17
|
-
|
31
|
+
|
18
32
|
# Do routine configuration
|
19
33
|
options[:skip_routine_include] = true
|
20
34
|
lev_routine(options)
|
@@ -22,4 +36,4 @@ class Object
|
|
22
36
|
# Do handler configuration (none currently)
|
23
37
|
end
|
24
38
|
|
25
|
-
end
|
39
|
+
end
|
data/lib/lev/routine.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Lev
|
2
|
-
|
2
|
+
|
3
3
|
# A "routine" in the Lev world is a piece of code that is responsible for
|
4
4
|
# doing one thing, normally acting on one or more other objects. Routines
|
5
|
-
# are particularly useful when the thing that needs to be done involves
|
5
|
+
# are particularly useful when the thing that needs to be done involves
|
6
6
|
# making changes to multiple other objects. In an OO/MVC world, an operation
|
7
7
|
# that involves multiple objects might be implemented by spreading that logic
|
8
8
|
# among those objects. However, that leads to classes having more
|
9
9
|
# responsibilities than they should (and more knowlege of other classes than
|
10
10
|
# they should) as well as making the code hard to follow.
|
11
11
|
#
|
12
|
-
# Routines typically don't have any persistent state that is used over and
|
12
|
+
# Routines typically don't have any persistent state that is used over and
|
13
13
|
# over again; they are created, used, and forgotten. A routine is a glorified
|
14
14
|
# function with a special single-responsibility purpose.
|
15
15
|
#
|
@@ -22,19 +22,19 @@ module Lev
|
|
22
22
|
#
|
23
23
|
# in its definition.
|
24
24
|
#
|
25
|
-
# Other than that, all a routine has to do is implement an "exec" method
|
25
|
+
# Other than that, all a routine has to do is implement an "exec" method
|
26
26
|
# that takes arbitrary arguments and that adds errors to an internal
|
27
27
|
# array-like "errors" object and outputs to a "outputs" hash.
|
28
28
|
#
|
29
29
|
# A routine returns an "Result" object, which is just a simple wrapper
|
30
|
-
# of the outputs and errors objects.
|
30
|
+
# of the outputs and errors objects.
|
31
31
|
#
|
32
32
|
# A routine will automatically get both class- and instance-level "call"
|
33
33
|
# methods that take the same arguments as the "exec" method. The class-level
|
34
|
-
# call method simply instantiates a new instance of the routine and calls
|
35
|
-
# the instance-level call method (side note here is that this means that
|
34
|
+
# call method simply instantiates a new instance of the routine and calls
|
35
|
+
# the instance-level call method (side note here is that this means that
|
36
36
|
# routines aren't typically instantiated with state).
|
37
|
-
#
|
37
|
+
#
|
38
38
|
# A routine is automatically run within a transaction. The isolation level
|
39
39
|
# of the routine can be set by passing a :transaction option to the lev_routine
|
40
40
|
# call (or to the lev_handler call, if appropriate). The value must be one of
|
@@ -53,9 +53,9 @@ module Lev
|
|
53
53
|
# As mentioned above, routines can call other routines. While this is of
|
54
54
|
# course possible just by calling the other routine's call method directly,
|
55
55
|
# it is strongly recommended that one routine call another routine using the
|
56
|
-
# provided "run" method. This method takes the name of the routine class
|
56
|
+
# provided "run" method. This method takes the name of the routine class
|
57
57
|
# and the arguments/block it expects in its call/exec methods. By using the
|
58
|
-
# run method, the called routine will be hooked into the common error and
|
58
|
+
# run method, the called routine will be hooked into the common error and
|
59
59
|
# transaction mechanisms.
|
60
60
|
#
|
61
61
|
# When one routine is called within another using the run method, there is
|
@@ -65,11 +65,11 @@ module Lev
|
|
65
65
|
# It is highly recommend, though not required, to call the "uses_routine"
|
66
66
|
# method to let the routine know which subroutines will be called within it.
|
67
67
|
# This will let a routine set its isolation level appropriately, and will
|
68
|
-
# enforce that only one transaction be used and that it be rolled back
|
68
|
+
# enforce that only one transaction be used and that it be rolled back
|
69
69
|
# appropriately if any errors occur.
|
70
70
|
#
|
71
71
|
# Once a routine has been registered with the "uses_routine" call, it can
|
72
|
-
# be run by passing run the routine's Class or a symbol identifying the
|
72
|
+
# be run by passing run the routine's Class or a symbol identifying the
|
73
73
|
# routine. This symbol can be set with the :as option. If not set, the
|
74
74
|
# symbol will be automatically set by converting the routine class' full
|
75
75
|
# name to a symbol. e.g:
|
@@ -85,8 +85,8 @@ module Lev
|
|
85
85
|
#
|
86
86
|
# run(:create_user, ...)
|
87
87
|
#
|
88
|
-
# uses_routine also provides a way to specify how errors relate to routine
|
89
|
-
# inputs. Take the following example. A user calls Routine1 which calls
|
88
|
+
# uses_routine also provides a way to specify how errors relate to routine
|
89
|
+
# inputs. Take the following example. A user calls Routine1 which calls
|
90
90
|
# Routine2.
|
91
91
|
#
|
92
92
|
# User --> Routine1.call(foo: "abcd4") --> Routine2.call(bar: "abcd4")
|
@@ -96,14 +96,14 @@ module Lev
|
|
96
96
|
# the User won't have any idea what "bar" relates to -- the User only knows
|
97
97
|
# about the interface to Routine1 and the "foo" parameter it gave it.
|
98
98
|
#
|
99
|
-
# Routine1 knows that it will call Routine2 and knows what its interface is.
|
100
|
-
# It can then specify how to map terminology from Routine2 into Routine1's
|
99
|
+
# Routine1 knows that it will call Routine2 and knows what its interface is.
|
100
|
+
# It can then specify how to map terminology from Routine2 into Routine1's
|
101
101
|
# context. E.g., in the following class:
|
102
102
|
#
|
103
103
|
# class Routine1
|
104
104
|
# lev_routine
|
105
105
|
# uses_routine Routine2,
|
106
|
-
# translations: {
|
106
|
+
# translations: {
|
107
107
|
# inputs: { map: {bar: :foo} }
|
108
108
|
# }
|
109
109
|
# def exec(options)
|
@@ -111,26 +111,26 @@ module Lev
|
|
111
111
|
# end
|
112
112
|
# end
|
113
113
|
#
|
114
|
-
# Routine1 notes that any errors coming back from the call to Routine2
|
114
|
+
# Routine1 notes that any errors coming back from the call to Routine2
|
115
115
|
# related to :bar should be transfered into Routine1's errors object
|
116
116
|
# as being related to :foo. In this way, the caller of Routine1 will see
|
117
117
|
# errors related to the arguments he understands.
|
118
118
|
#
|
119
119
|
# Translations can also be supplied for "outputs" in addition to "inputs".
|
120
|
-
# Output translations control how a called routine's Result outputs are
|
120
|
+
# Output translations control how a called routine's Result outputs are
|
121
121
|
# transfered to the calling routine's outputs. Note if multiple outputs are
|
122
|
-
# transferred into the same named output, an array of those outputs will be
|
123
|
-
# store. The contents of the "inputs" and "outputs" hashes can be of the
|
122
|
+
# transferred into the same named output, an array of those outputs will be
|
123
|
+
# store. The contents of the "inputs" and "outputs" hashes can be of the
|
124
124
|
# following form:
|
125
125
|
#
|
126
126
|
# 1) Scoped. Appends the provided scoping symbol (or symbol array) to
|
127
|
-
# the input symbol.
|
127
|
+
# the input symbol.
|
128
128
|
#
|
129
129
|
# {scope: SCOPING_SYMBOL_OR_SYMBOL_ARRAY}
|
130
130
|
#
|
131
131
|
# e.g. with {scope: :register} and a call to a routine that has an input
|
132
|
-
# named :first_name, an error in that called routine related to its
|
133
|
-
# :first_name input will be translated so that the offending input is
|
132
|
+
# named :first_name, an error in that called routine related to its
|
133
|
+
# :first_name input will be translated so that the offending input is
|
134
134
|
# [:register, :first_name].
|
135
135
|
#
|
136
136
|
# 2) Verbatim. Uses the same term in the caller as the callee.
|
@@ -141,7 +141,7 @@ module Lev
|
|
141
141
|
#
|
142
142
|
# {map: {called_input1: caller_input1, called_input2: :caller_input2}}
|
143
143
|
#
|
144
|
-
# 4) Scoped and mapped. Give an explicit mapping, and also scope the
|
144
|
+
# 4) Scoped and mapped. Give an explicit mapping, and also scope the
|
145
145
|
# translated terms. Just use scope: and map: from above in the same hash.
|
146
146
|
#
|
147
147
|
# Via the uses_routine call, you can also ignore specified errors that occur
|
@@ -170,12 +170,12 @@ module Lev
|
|
170
170
|
# Routine class have access to a few other methods:
|
171
171
|
#
|
172
172
|
# 1) a "runner" accessor which points to the routine which called it. If
|
173
|
-
# runner is nil that means that no other routine called it (some other
|
173
|
+
# runner is nil that means that no other routine called it (some other
|
174
174
|
# code did)
|
175
|
-
#
|
175
|
+
#
|
176
176
|
# 2) a "topmost_runner" which points to the highest routine in the calling
|
177
177
|
# hierarchy (that routine whose 'runner' is nil)
|
178
|
-
#
|
178
|
+
#
|
179
179
|
# References:
|
180
180
|
# http://ducktypo.blogspot.com/2010/08/why-inheritance-sucks.html
|
181
181
|
#
|
@@ -200,13 +200,19 @@ module Lev
|
|
200
200
|
new.call(*args, &block)
|
201
201
|
end
|
202
202
|
|
203
|
+
def [](*args, &block)
|
204
|
+
result = call(*args, &block)
|
205
|
+
result.errors.raise_exception_if_any!
|
206
|
+
result.outputs.send(@express_output)
|
207
|
+
end
|
208
|
+
|
203
209
|
# Called at a routine's class level to foretell which other routines will
|
204
210
|
# be used when this routine executes. Helpful for figuring out ahead of
|
205
211
|
# time what kind of transaction isolation level should be used.
|
206
212
|
def uses_routine(routine_class, options={})
|
207
213
|
symbol = options[:as] || class_to_symbol(routine_class)
|
208
214
|
|
209
|
-
raise
|
215
|
+
raise Lev.configuration.illegal_argument_error, "Routine #{routine_class} has already been registered" \
|
210
216
|
if nested_routines[symbol]
|
211
217
|
|
212
218
|
nested_routines[symbol] = {
|
@@ -221,6 +227,14 @@ module Lev
|
|
221
227
|
@transaction_isolation ||= TransactionIsolation.mysql_default
|
222
228
|
end
|
223
229
|
|
230
|
+
def express_output
|
231
|
+
@express_output
|
232
|
+
end
|
233
|
+
|
234
|
+
def delegates_to
|
235
|
+
@delegates_to
|
236
|
+
end
|
237
|
+
|
224
238
|
def nested_routines
|
225
239
|
@nested_routines ||= {}
|
226
240
|
end
|
@@ -236,9 +250,13 @@ module Lev
|
|
236
250
|
@after_transaction_blocks = []
|
237
251
|
|
238
252
|
in_transaction do
|
239
|
-
catch :fatal_errors_encountered do
|
253
|
+
catch :fatal_errors_encountered do
|
240
254
|
begin
|
241
|
-
|
255
|
+
if self.class.delegates_to
|
256
|
+
run(self.class.delegates_to, *args, &block)
|
257
|
+
else
|
258
|
+
exec(*args, &block)
|
259
|
+
end
|
242
260
|
end
|
243
261
|
end
|
244
262
|
end
|
@@ -261,7 +279,7 @@ module Lev
|
|
261
279
|
|
262
280
|
if other_routine.is_a? Array
|
263
281
|
if other_routine.size != 2
|
264
|
-
raise
|
282
|
+
raise Lev.configuration.illegal_argument_error, "when first arg to run is an array, it must have two arguments"
|
265
283
|
end
|
266
284
|
|
267
285
|
other_routine = other_routine[0]
|
@@ -273,16 +291,16 @@ module Lev
|
|
273
291
|
other_routine
|
274
292
|
when Class
|
275
293
|
self.class.class_to_symbol(other_routine)
|
276
|
-
else
|
294
|
+
else
|
277
295
|
self.class.class_to_symbol(other_routine.class)
|
278
296
|
end
|
279
297
|
|
280
298
|
nested_routine = self.class.nested_routines[symbol] || {}
|
281
299
|
|
282
300
|
if nested_routine.empty? && other_routine == symbol
|
283
|
-
raise
|
301
|
+
raise Lev.configuration.illegal_argument_error,
|
284
302
|
"Routine symbol #{other_routine} does not point to a registered routine"
|
285
|
-
end
|
303
|
+
end
|
286
304
|
|
287
305
|
#
|
288
306
|
# Get an instance of the routine and make sure it is a routine
|
@@ -291,8 +309,9 @@ module Lev
|
|
291
309
|
other_routine = nested_routine[:routine_class] || other_routine
|
292
310
|
other_routine = other_routine.new if other_routine.is_a? Class
|
293
311
|
|
294
|
-
|
295
|
-
|
312
|
+
if !(other_routine.includes_module? Lev::Routine)
|
313
|
+
raise Lev.configuration.illegal_argument_error, "Can only run another nested routine"
|
314
|
+
end
|
296
315
|
|
297
316
|
#
|
298
317
|
# Merge passed-in options with those set in uses_routine, the former taking
|
@@ -308,10 +327,10 @@ module Lev
|
|
308
327
|
|
309
328
|
options[:translations] ||= {}
|
310
329
|
|
311
|
-
input_mapper = new_term_mapper(options[:translations][:inputs]) ||
|
330
|
+
input_mapper = new_term_mapper(options[:translations][:inputs]) ||
|
312
331
|
new_term_mapper({ scope: symbol })
|
313
332
|
|
314
|
-
output_mapper = new_term_mapper(options[:translations][:outputs]) ||
|
333
|
+
output_mapper = new_term_mapper(options[:translations][:outputs]) ||
|
315
334
|
new_term_mapper({ scope: symbol })
|
316
335
|
|
317
336
|
#
|
@@ -357,7 +376,7 @@ module Lev
|
|
357
376
|
errors.add(false, args)
|
358
377
|
end
|
359
378
|
|
360
|
-
# Utility method to transfer errors from a source to this routine. The
|
379
|
+
# Utility method to transfer errors from a source to this routine. The
|
361
380
|
# provided input_mapper maps the language of the errors in the source to
|
362
381
|
# the language of this routine. If fail_if_errors is true, this routine
|
363
382
|
# will throw an error condition that causes execution of this routine to stop
|
@@ -399,33 +418,33 @@ module Lev
|
|
399
418
|
@runner = runner
|
400
419
|
|
401
420
|
if topmost_runner.class.transaction_isolation.weaker_than(self.class.transaction_isolation)
|
402
|
-
raise IsolationMismatch,
|
403
|
-
"The routine being run has a stronger isolation requirement than " +
|
421
|
+
raise IsolationMismatch,
|
422
|
+
"The routine being run has a stronger isolation requirement than " +
|
404
423
|
"the isolation being used by the routine(s) running it; call the " +
|
405
424
|
"'uses' method in the running routine's initializer"
|
406
425
|
end
|
407
426
|
end
|
408
427
|
|
409
|
-
def in_transaction(options={})
|
428
|
+
def in_transaction(options={})
|
410
429
|
if transaction_run_by?(self)
|
411
430
|
isolation_symbol = self.class.transaction_isolation.symbol
|
412
431
|
if ActiveRecord::VERSION::MAJOR >= 4
|
413
432
|
begin
|
414
|
-
ActiveRecord::Base.transaction(isolation: isolation_symbol) do
|
433
|
+
ActiveRecord::Base.transaction(isolation: isolation_symbol) do
|
415
434
|
yield
|
416
435
|
raise ActiveRecord::Rollback if errors?
|
417
436
|
end
|
418
437
|
rescue ActiveRecord::TransactionIsolationError
|
419
438
|
# Silently ignore isolation errors
|
420
|
-
ActiveRecord::Base.transaction do
|
439
|
+
ActiveRecord::Base.transaction do
|
421
440
|
yield
|
422
441
|
raise ActiveRecord::Rollback if errors?
|
423
442
|
end
|
424
443
|
end
|
425
444
|
else
|
426
445
|
ActiveRecord::Base.isolation_level(isolation_symbol) do
|
427
|
-
ActiveRecord::Base.transaction do
|
428
|
-
yield
|
446
|
+
ActiveRecord::Base.transaction do
|
447
|
+
yield
|
429
448
|
raise ActiveRecord::Rollback if errors?
|
430
449
|
end
|
431
450
|
end
|
@@ -443,7 +462,7 @@ module Lev
|
|
443
462
|
when :verbatim
|
444
463
|
return TermMapper.verbatim
|
445
464
|
else
|
446
|
-
raise
|
465
|
+
raise Lev.configuration.illegal_argument_error, "unknown :type value: #{options[:type]}"
|
447
466
|
end
|
448
467
|
end
|
449
468
|
|
@@ -455,4 +474,4 @@ module Lev
|
|
455
474
|
end
|
456
475
|
|
457
476
|
end
|
458
|
-
end
|
477
|
+
end
|
@@ -2,7 +2,7 @@ module Lev
|
|
2
2
|
class TransactionIsolation
|
3
3
|
|
4
4
|
def initialize(symbol)
|
5
|
-
raise
|
5
|
+
raise Lev.configuration.illegal_argument_error, "Invalid isolation symbol" if !@@symbols_to_isolation_levels.has_key?(symbol)
|
6
6
|
@symbol = symbol
|
7
7
|
end
|
8
8
|
|
@@ -36,7 +36,7 @@ module Lev
|
|
36
36
|
def eql?(other)
|
37
37
|
self == other
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
attr_reader :symbol
|
41
41
|
|
42
42
|
protected
|
@@ -56,4 +56,4 @@ module Lev
|
|
56
56
|
attr_writer :symbol
|
57
57
|
|
58
58
|
end
|
59
|
-
end
|
59
|
+
end
|
data/lib/lev/utilities.rb
CHANGED
data/lib/lev/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DelegatingRoutine do
|
4
|
+
|
5
|
+
it "should delegate" do
|
6
|
+
result = DelegatingRoutine.call(1,8)
|
7
|
+
expect(result.outputs[:answer]).to eq 9
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should delegate with assumed express_output" do
|
11
|
+
result = DelegatingRoutine[1,8]
|
12
|
+
expect(result).to eq 9
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
5
|
-
prerelease:
|
4
|
+
version: 4.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- JP Slavinsky
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2015-
|
11
|
+
date: 2015-04-16 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activemodel
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ! '>='
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ! '>='
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: activerecord
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ! '>='
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ! '>='
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,7 +41,6 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: actionpack
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - ! '>='
|
52
46
|
- !ruby/object:Gem::Version
|
@@ -54,7 +48,6 @@ dependencies:
|
|
54
48
|
type: :runtime
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - ! '>='
|
60
53
|
- !ruby/object:Gem::Version
|
@@ -62,7 +55,6 @@ dependencies:
|
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: transaction_isolation
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
59
|
- - ! '>='
|
68
60
|
- !ruby/object:Gem::Version
|
@@ -70,7 +62,6 @@ dependencies:
|
|
70
62
|
type: :runtime
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
66
|
- - ! '>='
|
76
67
|
- !ruby/object:Gem::Version
|
@@ -78,7 +69,6 @@ dependencies:
|
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: transaction_retry
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
73
|
- - ! '>='
|
84
74
|
- !ruby/object:Gem::Version
|
@@ -86,7 +76,6 @@ dependencies:
|
|
86
76
|
type: :runtime
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
80
|
- - ! '>='
|
92
81
|
- !ruby/object:Gem::Version
|
@@ -94,7 +83,6 @@ dependencies:
|
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: active_attr
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
87
|
- - ! '>='
|
100
88
|
- !ruby/object:Gem::Version
|
@@ -102,7 +90,6 @@ dependencies:
|
|
102
90
|
type: :runtime
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
94
|
- - ! '>='
|
108
95
|
- !ruby/object:Gem::Version
|
@@ -110,7 +97,6 @@ dependencies:
|
|
110
97
|
- !ruby/object:Gem::Dependency
|
111
98
|
name: hashie
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
100
|
requirements:
|
115
101
|
- - ! '>='
|
116
102
|
- !ruby/object:Gem::Version
|
@@ -118,7 +104,6 @@ dependencies:
|
|
118
104
|
type: :runtime
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
107
|
requirements:
|
123
108
|
- - ! '>='
|
124
109
|
- !ruby/object:Gem::Version
|
@@ -126,7 +111,6 @@ dependencies:
|
|
126
111
|
- !ruby/object:Gem::Dependency
|
127
112
|
name: bundler
|
128
113
|
requirement: !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
114
|
requirements:
|
131
115
|
- - ! '>='
|
132
116
|
- !ruby/object:Gem::Version
|
@@ -134,7 +118,6 @@ dependencies:
|
|
134
118
|
type: :development
|
135
119
|
prerelease: false
|
136
120
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
121
|
requirements:
|
139
122
|
- - ! '>='
|
140
123
|
- !ruby/object:Gem::Version
|
@@ -142,7 +125,6 @@ dependencies:
|
|
142
125
|
- !ruby/object:Gem::Dependency
|
143
126
|
name: rake
|
144
127
|
requirement: !ruby/object:Gem::Requirement
|
145
|
-
none: false
|
146
128
|
requirements:
|
147
129
|
- - ! '>='
|
148
130
|
- !ruby/object:Gem::Version
|
@@ -150,7 +132,6 @@ dependencies:
|
|
150
132
|
type: :development
|
151
133
|
prerelease: false
|
152
134
|
version_requirements: !ruby/object:Gem::Requirement
|
153
|
-
none: false
|
154
135
|
requirements:
|
155
136
|
- - ! '>='
|
156
137
|
- !ruby/object:Gem::Version
|
@@ -158,7 +139,6 @@ dependencies:
|
|
158
139
|
- !ruby/object:Gem::Dependency
|
159
140
|
name: rspec
|
160
141
|
requirement: !ruby/object:Gem::Requirement
|
161
|
-
none: false
|
162
142
|
requirements:
|
163
143
|
- - ! '>='
|
164
144
|
- !ruby/object:Gem::Version
|
@@ -166,7 +146,6 @@ dependencies:
|
|
166
146
|
type: :development
|
167
147
|
prerelease: false
|
168
148
|
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
149
|
requirements:
|
171
150
|
- - ! '>='
|
172
151
|
- !ruby/object:Gem::Version
|
@@ -174,7 +153,6 @@ dependencies:
|
|
174
153
|
- !ruby/object:Gem::Dependency
|
175
154
|
name: sqlite3
|
176
155
|
requirement: !ruby/object:Gem::Requirement
|
177
|
-
none: false
|
178
156
|
requirements:
|
179
157
|
- - ! '>='
|
180
158
|
- !ruby/object:Gem::Version
|
@@ -182,7 +160,6 @@ dependencies:
|
|
182
160
|
type: :development
|
183
161
|
prerelease: false
|
184
162
|
version_requirements: !ruby/object:Gem::Requirement
|
185
|
-
none: false
|
186
163
|
requirements:
|
187
164
|
- - ! '>='
|
188
165
|
- !ruby/object:Gem::Version
|
@@ -190,7 +167,6 @@ dependencies:
|
|
190
167
|
- !ruby/object:Gem::Dependency
|
191
168
|
name: debugger
|
192
169
|
requirement: !ruby/object:Gem::Requirement
|
193
|
-
none: false
|
194
170
|
requirements:
|
195
171
|
- - ! '>='
|
196
172
|
- !ruby/object:Gem::Version
|
@@ -198,7 +174,6 @@ dependencies:
|
|
198
174
|
type: :development
|
199
175
|
prerelease: false
|
200
176
|
version_requirements: !ruby/object:Gem::Requirement
|
201
|
-
none: false
|
202
177
|
requirements:
|
203
178
|
- - ! '>='
|
204
179
|
- !ruby/object:Gem::Version
|
@@ -206,7 +181,6 @@ dependencies:
|
|
206
181
|
- !ruby/object:Gem::Dependency
|
207
182
|
name: rails
|
208
183
|
requirement: !ruby/object:Gem::Requirement
|
209
|
-
none: false
|
210
184
|
requirements:
|
211
185
|
- - ! '>='
|
212
186
|
- !ruby/object:Gem::Version
|
@@ -214,7 +188,6 @@ dependencies:
|
|
214
188
|
type: :development
|
215
189
|
prerelease: false
|
216
190
|
version_requirements: !ruby/object:Gem::Requirement
|
217
|
-
none: false
|
218
191
|
requirements:
|
219
192
|
- - ! '>='
|
220
193
|
- !ruby/object:Gem::Version
|
@@ -226,6 +199,10 @@ executables: []
|
|
226
199
|
extensions: []
|
227
200
|
extra_rdoc_files: []
|
228
201
|
files:
|
202
|
+
- LICENSE.txt
|
203
|
+
- README.md
|
204
|
+
- Rakefile
|
205
|
+
- lib/lev.rb
|
229
206
|
- lib/lev/better_active_model_errors.rb
|
230
207
|
- lib/lev/delegate_to_routine.rb
|
231
208
|
- lib/lev/error.rb
|
@@ -244,12 +221,9 @@ files:
|
|
244
221
|
- lib/lev/transaction_isolation.rb
|
245
222
|
- lib/lev/utilities.rb
|
246
223
|
- lib/lev/version.rb
|
247
|
-
- lib/lev.rb
|
248
|
-
- LICENSE.txt
|
249
|
-
- Rakefile
|
250
|
-
- README.md
|
251
224
|
- spec/create_sprocket_spec.rb
|
252
225
|
- spec/deep_merge_spec.rb
|
226
|
+
- spec/delegates_to_spec.rb
|
253
227
|
- spec/errors_spec.rb
|
254
228
|
- spec/outputs_spec.rb
|
255
229
|
- spec/paramify_handler_spec.rb
|
@@ -258,6 +232,8 @@ files:
|
|
258
232
|
- spec/sprocket_handler_spec.rb
|
259
233
|
- spec/sprocket_spec.rb
|
260
234
|
- spec/support/create_sprocket.rb
|
235
|
+
- spec/support/delegated_routine.rb
|
236
|
+
- spec/support/delegating_routine.rb
|
261
237
|
- spec/support/paramify_handler_a.rb
|
262
238
|
- spec/support/paramify_handler_b.rb
|
263
239
|
- spec/support/sprocket.rb
|
@@ -265,37 +241,31 @@ files:
|
|
265
241
|
homepage: http://github.com/lml/lev
|
266
242
|
licenses:
|
267
243
|
- MIT
|
244
|
+
metadata: {}
|
268
245
|
post_install_message:
|
269
246
|
rdoc_options: []
|
270
247
|
require_paths:
|
271
248
|
- lib
|
272
249
|
required_ruby_version: !ruby/object:Gem::Requirement
|
273
|
-
none: false
|
274
250
|
requirements:
|
275
251
|
- - ! '>='
|
276
252
|
- !ruby/object:Gem::Version
|
277
253
|
version: '0'
|
278
|
-
segments:
|
279
|
-
- 0
|
280
|
-
hash: 2525822979360039563
|
281
254
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
282
|
-
none: false
|
283
255
|
requirements:
|
284
256
|
- - ! '>='
|
285
257
|
- !ruby/object:Gem::Version
|
286
258
|
version: '0'
|
287
|
-
segments:
|
288
|
-
- 0
|
289
|
-
hash: 2525822979360039563
|
290
259
|
requirements: []
|
291
260
|
rubyforge_project:
|
292
|
-
rubygems_version:
|
261
|
+
rubygems_version: 2.4.6
|
293
262
|
signing_key:
|
294
|
-
specification_version:
|
263
|
+
specification_version: 4
|
295
264
|
summary: Ride the rails but don't touch them.
|
296
265
|
test_files:
|
297
266
|
- spec/create_sprocket_spec.rb
|
298
267
|
- spec/deep_merge_spec.rb
|
268
|
+
- spec/delegates_to_spec.rb
|
299
269
|
- spec/errors_spec.rb
|
300
270
|
- spec/outputs_spec.rb
|
301
271
|
- spec/paramify_handler_spec.rb
|
@@ -304,6 +274,8 @@ test_files:
|
|
304
274
|
- spec/sprocket_handler_spec.rb
|
305
275
|
- spec/sprocket_spec.rb
|
306
276
|
- spec/support/create_sprocket.rb
|
277
|
+
- spec/support/delegated_routine.rb
|
278
|
+
- spec/support/delegating_routine.rb
|
307
279
|
- spec/support/paramify_handler_a.rb
|
308
280
|
- spec/support/paramify_handler_b.rb
|
309
281
|
- spec/support/sprocket.rb
|