lev 2.2.2 → 3.0.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 +485 -28
- data/lib/lev/error.rb +1 -1
- data/lib/lev/errors.rb +11 -0
- data/lib/lev/handler.rb +28 -28
- data/lib/lev/routine.rb +9 -2
- data/lib/lev/version.rb +1 -1
- data/spec/errors_spec.rb +27 -0
- data/spec/routine_spec.rb +20 -0
- data/spec/spec_helper.rb +1 -0
- metadata +11 -41
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MzU0MTQ0ODBlZTkwMjQwNGNmNzJkZDRlMmJkYjA1OThkNjM2ODA4ZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmQzOTBhZTczOGQzZDZlNzc4NTNiNWNkZDJiYTc0ZjU4ZWFmNmE5ZA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzBlMDMzMzBiMDIyMDliY2ZhMDM0MDc0MzI2NGQ2NGM3NzAwNmU4N2NkNzhm
|
10
|
+
MGM2YjdmMTQwYzE5MjAyY2E3MDIzM2Y0ODIzYTQwMmRmMDlmOTVjYjY3Zjg3
|
11
|
+
ZmRhMGFkNTVlYjRjODI3MDVjMGE2ZjI5MGU5OGJhMDIzMjhmMzg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDE5MjFmNjliMTRjZTUxYzdhZWFmN2FmN2UwNzc5NDE5MmYzYWQ5MzEwZjI2
|
14
|
+
NWY3NDE0ODA5Y2ViOWE4OGI2YzcwOWFiY2NjZjIzZTYwYWYwOWNkNDgzN2Iy
|
15
|
+
NmE0NWIzOTM1ODg1YzNiOGNjMTY4MDhjZTUzMzE0N2UwNDJjMmU=
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Lev
|
2
2
|
|
3
|
-
|
3
|
+
Lev is an attempt to improve Rails by:
|
4
4
|
|
5
5
|
1. Providing a better, more structured, and more organized way to implement code features
|
6
6
|
2. De-emphasizing the "model is king" mindset when appropriate
|
7
7
|
|
8
|
-
Rails' MVC-view of the world is very compelling and provides a sturdy scaffold with which to create applications quickly. However, sometimes it can lead to business logic code getting a little spread out. When trying to figure out where to best put business logic, you often hear folks recommending "fat models" and "skinny controllers". They are saying that the business logic of your app should live in the model classes and not in the controllers.
|
8
|
+
Rails' MVC-view of the world is very compelling and provides a sturdy scaffold with which to create applications quickly. However, sometimes it can lead to business logic code getting a little spread out. When trying to figure out where to best put business logic, you often hear folks recommending "fat models" and "skinny controllers". They are saying that the business logic of your app should live in the model classes and not in the controllers. While it is a good idea that logic not live in the controllers, it shouldn't always live in the models either, especially when that logic touches multiple models.
|
9
9
|
|
10
10
|
When all of the business logic lives in the models, some bad things can happen:
|
11
11
|
|
@@ -13,7 +13,11 @@ When all of the business logic lives in the models, some bad things can happen:
|
|
13
13
|
2. your models end up knowing way too much about other models, sometimes multiple hops away
|
14
14
|
3. your business logic gets spread all over the place. The execution of one "feature" can jump between bits of code in multiple models, their various ActiveRecord life cycle callbacks (before_create, etc), and their associated observers.
|
15
15
|
|
16
|
-
Lev introduces
|
16
|
+
Lev introduces two main constructs to get around these issues: **Routines** and **Handlers**.
|
17
|
+
|
18
|
+
## Routines
|
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.
|
17
21
|
|
18
22
|
Routines...
|
19
23
|
|
@@ -21,31 +25,485 @@ Routines...
|
|
21
25
|
2. Have a common error reporting framework
|
22
26
|
3. Run within a single transaction with a controllable isolation level
|
23
27
|
|
24
|
-
|
28
|
+
In an OO/MVC world, an operation that involves multiple objects might be implemented by spreading that logic among those objects. However, that leads to classes having more responsibilities than they should (and more knowlege of other classes than they should) as well as making the code hard to follow.
|
29
|
+
|
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
|
+
|
32
|
+
A class becomes a routine by calling `lev_routine` in its definition, e.g.:
|
33
|
+
|
34
|
+
class MyRoutine
|
35
|
+
lev_routine
|
36
|
+
...
|
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:
|
39
|
+
|
40
|
+
Errors can be recorded in a number of ways. You can manually add errors to the built-in `errors` object:
|
41
|
+
|
42
|
+
errors.add(true, code: :search_terms_incorrect)
|
43
|
+
|
44
|
+
The first parameter to the `add` call says whether or not the error should be fatal for the running of the routine (no more work should be done and the transaction should be rolled back). Otherwise, the arguments after that are a hash that can contain values for the following keys:
|
45
|
+
|
46
|
+
* `:code` A symbol indicating the kind of error that occurred
|
47
|
+
* `:data` Any data that is useful for understanding the error (`:code`-specific)
|
48
|
+
* `:kind` If you don't set this, it will default to `:lev` for Lev-generated errors, and `:activerecord` for ActiveRecord-generated errors
|
49
|
+
* `:message` A human-readable error message
|
50
|
+
* `:offending_inputs` An array of symbols indicating which inputs caused the error (if any); if there is only one symbol, you can specify it as a lone symbol instead of a symbol in a one-element array.
|
51
|
+
|
52
|
+
Two convenience methods are also provided for adding errors: `fatal_error` and `nonfatal_error`. These have the same interface as `errors#add` except they provide the first `is_fatal` boolean argument for you. In its current implementation, `nonfatal_error` may still cause a routine higher up in the execution hierarchy to halt running.
|
53
|
+
|
54
|
+
Here's an example setting an error and an output:
|
55
|
+
|
56
|
+
class MyRoutine
|
57
|
+
lev_routine
|
58
|
+
|
59
|
+
protected
|
60
|
+
def exec(foo, options={}) # whatever arguments you want here
|
61
|
+
fatal_error(code: :some_code_symbol) if foo.nil?
|
62
|
+
outputs[:bar] = foo * 2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Additionally, see below for a discussion on how to transfer errors from ActiveRecord models.
|
67
|
+
|
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:
|
69
|
+
|
70
|
+
result = MyRoutine.call(42)
|
71
|
+
result.errors.reraise_exception! # does nothing if there were no exception errors
|
72
|
+
|
73
|
+
Relatedly, a convenience method is provided if the caller wants to raise an exception if there were any errors returned (whether or not they themselves were caused by an exception)
|
74
|
+
|
75
|
+
result = MyRoutine.call(42)
|
76
|
+
result.errors.raise_exception_if_any!(MyFavoriteError)
|
77
|
+
|
78
|
+
By default `raise_exception_if_any!` will raise a `StandardError` with a message containing the concatenated messages of the errors. You can pass a different exception class to this method to use something other than `StandardError`.
|
79
|
+
|
80
|
+
A routine will automatically get both class- and instance-level `call`
|
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
|
84
|
+
routines aren't typically instantiated with state).
|
85
|
+
|
86
|
+
When called, a routine returns a `Result` object, which is just a simple wrapper
|
87
|
+
of the outputs and errors objects.
|
88
|
+
|
89
|
+
result = MyRoutine.call(42)
|
90
|
+
puts result.outputs[:bar] # => 84
|
91
|
+
|
92
|
+
|
93
|
+
### Nesting Routines
|
94
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
103
|
+
uses_routine CreateUser
|
104
|
+
as: :cu
|
105
|
+
|
106
|
+
and then you can call this routine with any of the following:
|
107
|
+
|
108
|
+
* `run(:cu, ...)`
|
109
|
+
* `run(:create_user, ...)`
|
110
|
+
* `run(CreateUser, ...)`
|
111
|
+
* `CreateUser.call(...)` (not recommended)
|
112
|
+
|
113
|
+
#### Errors from Nested Routines
|
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
|
117
|
+
`Routine2`.
|
118
|
+
|
119
|
+
User --> Routine1.call(foo: "abcd4") --> Routine2.call(bar: "abcd4")
|
120
|
+
|
121
|
+
An error occurs in `Routine2`, and Routine2 notes that the error is related
|
122
|
+
to its `bar` input. If that error and its metadata bubble up to the `User`,
|
123
|
+
the `User` won't have any idea what `bar` relates to -- the `User` only knows
|
124
|
+
about the interface to `Routine1` and the `foo` parameter it gave it.
|
125
|
+
|
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
|
+
|
128
|
+
class Routine1
|
129
|
+
lev_routine
|
130
|
+
uses_routine Routine2,
|
131
|
+
translations: {
|
132
|
+
inputs: { map: {bar: :foo} }
|
133
|
+
}
|
134
|
+
def exec(options)
|
135
|
+
run(Routine2, bar: options[:foo])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
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
|
+
|
141
|
+
In addition to the `map:` configuration for input transferral, there are three other configurations:
|
142
|
+
|
143
|
+
1. **Scoped** - Appends the provided scoping symbol (or symbol array) to the input symbol.
|
144
|
+
|
145
|
+
`{scope: SCOPING_SYMBOL_OR_SYMBOL_ARRAY}`
|
146
|
+
|
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
|
150
|
+
`[:register, :first_name]`.
|
151
|
+
|
152
|
+
2. **Verbatim** - Uses the same term in the caller as the callee.
|
153
|
+
|
154
|
+
`{type: :verbatim}`
|
155
|
+
|
156
|
+
3. **Mapped** - Give an explicit, custom mapping:
|
157
|
+
|
158
|
+
`{map: {called_input1: caller_input1, called_input2: :caller_input2}}`
|
159
|
+
|
160
|
+
4. **Scoped and mapped** - Give an explicit mapping, and also scope the
|
161
|
+
translated terms. Just use `scope:` and `map:` from above in the same hash.
|
162
|
+
|
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:
|
164
|
+
|
165
|
+
class MyRoutine
|
166
|
+
lev_routine
|
167
|
+
uses_routine OtherRoutine, as: :jimmy
|
168
|
+
|
169
|
+
an errors generated on the `foo` input in `OtherRoutine` will be transferred up to `MyRoutine` with a `[:jimmy, :foo]` scope. If the `as: :jimmy` option were not specified, the transferred error would have a `[:other_routine, :foo]` scope.
|
170
|
+
|
171
|
+
Via the `uses_routine` call, you can also ignore specified errors that occur
|
172
|
+
in the called routine. e.g.:
|
173
|
+
|
174
|
+
uses_routine DestroyUser,
|
175
|
+
ignored_errors: [:cannot_destroy_non_temp_user]
|
176
|
+
|
177
|
+
ignores errors with the provided code. The `ignore_errors` key must point
|
178
|
+
to an array of code symbols or procs. If a proc is given, the proc will
|
179
|
+
be called with the error that the routine is trying to add. If the proc
|
180
|
+
returns true, the error will be ignored.
|
181
|
+
|
182
|
+
|
183
|
+
#### Outputs from Nested Routines
|
184
|
+
|
185
|
+
In addition to errors being transferred from subroutines to calling routines, a subroutine's outputs are also automatically transferred to the calling routine's "outputs" hash. Exactly how they are transferred is configurable with the same 4 options as input transferals, e.g.:
|
186
|
+
|
187
|
+
class Routine1
|
188
|
+
lev_routine
|
189
|
+
uses_routine Routine2,
|
190
|
+
translations: {
|
191
|
+
outputs: { type: :verbatim }
|
192
|
+
}
|
193
|
+
|
194
|
+
def exec(options)
|
195
|
+
run(Routine2, bar: options[:foo])
|
196
|
+
# Assuming Routine2 generates an output named "x", then outputs[:x] will be
|
197
|
+
# available as of this line
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
If the output translations are not specified, they will be scoped exactly like how input translations are scoped by default.
|
202
|
+
|
203
|
+
|
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
|
+
|
206
|
+
#### Overriding `uses_routine` Options
|
207
|
+
|
208
|
+
Any option passed to uses_routine can also be passed directly to the run
|
209
|
+
method. To achieve this, pass an array as the first argument to "run".
|
210
|
+
The array should have the routine class or symbol as the first argument,
|
211
|
+
and the hash of options as the second argument. Options passed in this
|
212
|
+
manner override any options provided in uses_routine (though those options
|
213
|
+
are still used if not replaced in the run call). For example:
|
214
|
+
|
215
|
+
class ARoutine
|
216
|
+
lev_routine
|
217
|
+
uses_routine BRoutine
|
218
|
+
|
219
|
+
protected
|
220
|
+
def exec(...)
|
221
|
+
run([ BRoutine, {translations: {outputs: {type: :verbatim}}} ])
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
### transfer_errors_from
|
226
|
+
|
227
|
+
|
228
|
+
When errors are captured inside an `ActiveRecord` errors object, you can use `transfer_errors_from` to pull them into the routine errors object. This method takes three arguments:
|
229
|
+
|
230
|
+
1. The ActiveRecord instance that may have errors to transfer.
|
231
|
+
2. A hash describing how to map the error message, using the same options passed to the input translations in a `uses_routine` call, e.g. `transfer_errors_from(myModel, {type: :verbatim})`.
|
232
|
+
3. A flag that if `true` will cause the routine to fail fatally if there are any errors transferred.
|
233
|
+
|
234
|
+
### Specifying Transaction Isolations
|
235
|
+
|
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
|
+
|
238
|
+
* `:no_transaction`
|
239
|
+
* `:read_uncommitted`
|
240
|
+
* `:read_committed`
|
241
|
+
* `:repeatable_read`
|
242
|
+
* `:serializable`
|
243
|
+
|
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
|
+
|
246
|
+
For example, if you write a routine that does a complex query, you might not need any transaction:
|
247
|
+
|
248
|
+
class MyQueryRoutine
|
249
|
+
lev_routine transaction: :no_transaction
|
250
|
+
|
251
|
+
If unspecified, the default isolation is `:repeatable_read`.
|
252
|
+
|
253
|
+
### delegate_to_routine
|
254
|
+
|
255
|
+
Sometimes you'll want to override standard ActiveRecord methods in a model so that they use a routine instead of the default implementation. For this, inside of that ActiveRecord model you can call the class method `delegate_to_routine`, which takes two key-value pairs:
|
256
|
+
|
257
|
+
1. `:method` A symbol for the instance method to override (e.g. `:destroy`)
|
258
|
+
2. `:options` A hash of options including:
|
259
|
+
* `:routine_class` The class of the routine to delegate to; if not given, the class is autocomputed by concatenating the provided `:method` with the model class name.
|
260
|
+
|
261
|
+
When `delegate_to_routine` is called, the provided method will call the routine and the overriden method will be aliased to the original name with `_original` appended to it. For example:
|
262
|
+
|
263
|
+
class Product < ActiveRecord::Base
|
264
|
+
delegate_to_routine method: :destroy
|
265
|
+
end
|
266
|
+
|
267
|
+
will alias the old `destroy` method as `destroy_original` and add a new `destroy` method that calls the `DestroyProduct` routine.
|
268
|
+
|
269
|
+
### Other Routine Methods
|
270
|
+
|
271
|
+
Routine class have access to a few other methods:
|
272
|
+
|
273
|
+
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
|
275
|
+
code did)
|
276
|
+
2. a `topmost_runner` accessor which points to the highest routine in the calling
|
277
|
+
hierarchy (that routine whose 'runner' is nil)
|
278
|
+
|
279
|
+
|
280
|
+
## Handlers
|
281
|
+
|
282
|
+
Handlers are specialized routines that take user input (e.g. form data) and then take an action based on that input. Because all Handlers are Routines, everything discussed above applies to them.
|
25
283
|
|
26
284
|
Handlers...
|
27
285
|
|
28
286
|
1. Help you verify that the calling user is authorized to run the handler
|
29
287
|
2. Provide ways to validate incoming parameters in a very ActiveModel-like way (even when the parameters are not associated with a model)
|
30
|
-
3.
|
288
|
+
3. Can call other Routines using `uses_routine` and `run`
|
31
289
|
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.
|
32
290
|
|
33
|
-
|
291
|
+
A class becomes a handler by calling `lev_handler` in its definition, e.g.:
|
292
|
+
|
293
|
+
class MyHandler
|
294
|
+
lev_handler
|
295
|
+
...
|
296
|
+
|
297
|
+
Additionally, a handler **must** implement two instance methods:
|
298
|
+
|
299
|
+
1. `handle`, which takes no arguments and does the work the handler is charged with
|
300
|
+
2. `authorized?`, which returns true if and only if the caller is authorized to do what the handler is charged with
|
301
|
+
|
302
|
+
Handlers **may**...
|
303
|
+
|
304
|
+
1. implement the `setup` instance method which runs before `authorized?` and `handle`. This method can do anything, and will likely include setting up some instance objects based on the params.
|
305
|
+
2. call the class method `paramify` to declare, cast, and validate parts of the params hash. See below for more on this.
|
306
|
+
|
307
|
+
Any options passed in to a handler's `call` method are made available within the handler via an `options` attribute. If this options hash includes values for `:params`, `:caller`, and `:request` these values will be available within the code you write by accessors with the same names. These values are expected to contain the request params, the caller (whatever your application defines as `current_user`), and the entire HTTP request. See the `handle_with` method below for an easy way to pass these options to your handler.
|
308
|
+
|
309
|
+
Additionally, the handler provides attributes to return the `errors` object and the `results` object.
|
310
|
+
|
311
|
+
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
|
+
|
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
|
+
|
315
|
+
Example:
|
316
|
+
|
317
|
+
class MyHandler
|
318
|
+
lev_handler
|
319
|
+
protected
|
320
|
+
def authorized?
|
321
|
+
# return true iff exec is allowed to be called, e.g. might
|
322
|
+
# check the caller against the params
|
323
|
+
def handle
|
324
|
+
# do the work, add errors to errors object and results to the results hash as needed
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
### paramify
|
329
|
+
|
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.
|
331
|
+
|
332
|
+
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
|
+
|
334
|
+
For example, when the incoming params includes :search => {:type, :terms, :num_results}, the `paramify` block might look like:
|
335
|
+
|
336
|
+
paramify :search do
|
337
|
+
attribute :search_type, type: String
|
338
|
+
validates :search_type, presence: true,
|
339
|
+
inclusion: { in: %w(Name Username Any),
|
340
|
+
message: "is not valid" }
|
341
|
+
|
342
|
+
attribute :search_terms, type: String
|
343
|
+
validates :search_terms, presence: true
|
344
|
+
|
345
|
+
attribute :num_results, type: Integer
|
346
|
+
validates :num_results, numericality: { only_integer: true,
|
347
|
+
greater_than_or_equal_to: 0 }
|
348
|
+
end
|
349
|
+
|
350
|
+
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`).
|
351
|
+
|
352
|
+
The following is a more complete example using the `paramify` block above:
|
353
|
+
|
354
|
+
class MyHandler
|
355
|
+
lev_handler
|
356
|
+
|
357
|
+
paramify :search do
|
358
|
+
attribute :search_type, type: String
|
359
|
+
validates :search_type, presence: true,
|
360
|
+
inclusion: { in: %w(Name Username Any),
|
361
|
+
message: "is not valid" }
|
362
|
+
|
363
|
+
attribute :search_terms, type: String
|
364
|
+
validates :search_terms, presence: true
|
365
|
+
|
366
|
+
attribute :num_results, type: Integer
|
367
|
+
validates :num_results, numericality: { only_integer: true,
|
368
|
+
greater_than_or_equal_to: 0 }
|
369
|
+
end
|
370
|
+
|
371
|
+
def handle
|
372
|
+
# By this time, if there were any errors the handler would have
|
373
|
+
# already populated the errors object and returned.
|
374
|
+
#
|
375
|
+
# Paramify makes a 'search_params' attribute available through
|
376
|
+
# which you can access the paramified params, e.g.
|
377
|
+
x = search_params.num_results
|
378
|
+
...
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
### handle_with
|
383
|
+
|
384
|
+
`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
|
+
|
386
|
+
class ApplicationController
|
387
|
+
include Lev::HandleWith
|
388
|
+
...
|
389
|
+
end
|
390
|
+
|
391
|
+
Then, call `handle_with` from your various controller actions, e.g.:
|
392
|
+
|
393
|
+
handle_with(MyFormHandler,
|
394
|
+
params: params,
|
395
|
+
success: lambda { redirect_to 'show', notice: 'Success!'},
|
396
|
+
failure: lambda { render 'new', alert: 'Error' })
|
397
|
+
|
398
|
+
`handle_with` takes care of calling the handler and populates a `@handler_result` object with results and errors from running the handler.
|
399
|
+
|
400
|
+
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
|
+
|
402
|
+
Specifying 'params' is optional. If you don't specify it, `handle_with` will use the entire params hash from the request.
|
403
|
+
|
404
|
+
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`.
|
405
|
+
|
406
|
+
### lev_form_for
|
407
|
+
|
408
|
+
Lev also provides a `lev_form_for` form builder to replace `form_for`. This builder integrates well with the error reporting infrastructure in routines and handlers, and in general is a nice way to get away from forms that are very single-model-centric.
|
409
|
+
|
410
|
+
The first argument passed to `lev_form_for` is a symbol that scopes the form fields. In a normal `form_for`, the `:url` for the form is autodetermined based on the model instance passed in; since there is no model for `lev_form_for`, you'll need to specify the `:url` option. Beyond that, any options you can pass to `form_for` you can pass to `lev_form_for`.
|
411
|
+
|
412
|
+
Consider the following example:
|
413
|
+
|
414
|
+
<%= lev_form_for :register, url: '/users/register', html: {id: 'register-form'} do |f| %>
|
415
|
+
<p>Please choose a username and password.</p>
|
416
|
+
|
417
|
+
<label>Username</label>
|
418
|
+
<%= f.text_field :username %>
|
34
419
|
|
35
|
-
|
420
|
+
<label>First Name</label>
|
421
|
+
<%= f.text_field :first_name %>
|
36
422
|
|
37
|
-
|
423
|
+
<label>Password</label>
|
424
|
+
<%= f.password_field :password %>
|
38
425
|
|
426
|
+
<label>Password (again)</label>
|
427
|
+
<%= f.password_field :password_confirmation %>
|
39
428
|
|
40
|
-
|
41
|
-
|
42
|
-
3. Validate internal state
|
43
|
-
1. Do not validate state in related models
|
44
|
-
2. Can validate the presence of relationship (can check that a foreign key is present)
|
45
|
-
4. Can perform queries when those queries only use internal model state and are aware of internal model state (e.g. arguments to queries should be in the language of the model state)
|
46
|
-
5. Can create records when those creations only need values internal to this model and take arguments in the language of the internal model state.
|
429
|
+
<%= f.submit "Register", id: "register_submit" %>
|
430
|
+
<% end %>
|
47
431
|
|
48
|
-
|
432
|
+
Here, the form parameters will include
|
433
|
+
|
434
|
+
:register => {:username => 'bob79', :first_name => 'Bob', :password => 'password', :password_confirmation => 'password'}
|
435
|
+
|
436
|
+
A route could direct the URL above to a controller action:
|
437
|
+
|
438
|
+
post '/users/register', to: 'users#register'
|
439
|
+
|
440
|
+
The `UsersController` could then connect this route to a handler:
|
441
|
+
|
442
|
+
class UsersController < ApplicationController
|
443
|
+
include Lev::HandleWith
|
444
|
+
|
445
|
+
def register
|
446
|
+
handle_with(UsersRegister,
|
447
|
+
success: lambda { redirect_to root_path },
|
448
|
+
failure: lambda { render :new })
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
And then the `UsersRegister` handler would exist to process the form parameters and take action.
|
453
|
+
|
454
|
+
class UsersRegister
|
455
|
+
lev_handler
|
456
|
+
|
457
|
+
paramify :register do
|
458
|
+
attribute :username, type: String
|
459
|
+
attribute :first_name, type: String
|
460
|
+
attribute :password, type: String
|
461
|
+
attribute :password_confirmation, type: String
|
462
|
+
|
463
|
+
validates :username, presence: true # simple validation as an example
|
464
|
+
# in this case validation really done
|
465
|
+
# in activerecord User model
|
466
|
+
end
|
467
|
+
|
468
|
+
uses_routine CreateUser,
|
469
|
+
translations: { inputs: {scope: :register} }
|
470
|
+
|
471
|
+
protected
|
472
|
+
|
473
|
+
def authorized?
|
474
|
+
caller.is_anonymous?
|
475
|
+
end
|
476
|
+
|
477
|
+
def handle
|
478
|
+
run(CreateUser, first_name: register_params.first_name,
|
479
|
+
username: register_params.username,
|
480
|
+
password: register_params.password,
|
481
|
+
password_confirmation: register_params.password_confirmation)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
In the above handler, if the `username` is blank, the validation in the `paramify` block will catch it and add a fatal error to the handler's result object. This will cause the `failure` block in `handle_with` to be triggered, and `lev_form_for` will watch for these errors in the @handler_result object and mark offending input fields with a configurable CSS class (default to 'error'). If an error occurs during the run of `CreateUser`, the error will be translated back under a `:register` scope (from the call in `uses_routine`), and the error will also be appropriately traced using `lev_form_for`.
|
486
|
+
|
487
|
+
If the handler runs error free, the `success` block will be triggered.
|
488
|
+
|
489
|
+
## Writing Models in a Lev-enabled Project
|
490
|
+
|
491
|
+
A decision to use Lev means you're interested in following the philosophy of "skinny models, skinny controllers". To achieve "skinny model" zen, we recommend that models obey the following principles:
|
492
|
+
|
493
|
+
1. They should hook into the ORM (i.e., inherit from ActiveRecord::Base)
|
494
|
+
2. They should establish relationships to other models (e.g. belongs_to, has_many, including dependent: :destroy)
|
495
|
+
3. They should validate **internal** state
|
496
|
+
1. They should **not** validate state in related models
|
497
|
+
2. They can run limited validations on associations (e.g. checking the presence of relationship, checking that a foreign key is present, etc)
|
498
|
+
4. They can perform queries when those queries only use internal model state (i.e. arguments to queries should be in the language of the model state)
|
499
|
+
5. The can create records when those creations only need values internal to this model and take arguments in the language of the internal model state.
|
500
|
+
6. They should avoid ActiveRecord lifecycle callbacks (and similarly, Observers) except when the callbacks only work on internal model state. Such callbacks are only good for entangling what should be simple model code in complex code features.
|
501
|
+
7. They should avoid doing any cross-model work.
|
502
|
+
|
503
|
+
When these guidelines are followed, model classes end up being very small and simple. This is good because:
|
504
|
+
|
505
|
+
1. Small, simple code tends to be more stable code (and since a lot of code depends on the models, stable is a very good thing)
|
506
|
+
2. The models are easy to mock and use in feature tests (not worrying about some random `before_create` callback added for some other random feature)
|
49
507
|
|
50
508
|
## Naming Conventions
|
51
509
|
|
@@ -57,10 +515,20 @@ Routines on the other hand are more or less glorified functions that work with m
|
|
57
515
|
|
58
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.
|
59
517
|
|
60
|
-
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
|
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).
|
61
519
|
|
62
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.
|
63
521
|
|
522
|
+
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
|
+
|
524
|
+
(* one small exception is `delegate_to_routine`)
|
525
|
+
|
526
|
+
## Why do we need handlers?
|
527
|
+
|
528
|
+
Ever had a form you wanted to make that didn't map right onto a model? Maybe the form needed to deal with two different models and some random text fields. With a handler, you can pass all of those fields in form_for style, then use active record type validations in the handler to check those inputs (or pass along to the models to have them run their validations).
|
529
|
+
|
530
|
+
Routines and handlers also have a built-in error handling mechanism and they run within a single transaction with a controllable isolation level.
|
531
|
+
|
64
532
|
## Installation
|
65
533
|
|
66
534
|
Add this line to your application's Gemfile:
|
@@ -75,17 +543,6 @@ Or install it yourself as:
|
|
75
543
|
|
76
544
|
$ gem install lev
|
77
545
|
|
78
|
-
## Usage
|
79
|
-
|
80
|
-
For the moment, details on how to use lev live in big sections of comments at the top of:
|
81
|
-
|
82
|
-
* https://github.com/lml/lev/blob/master/lib/lev/routine.rb
|
83
|
-
* https://github.com/lml/lev/blob/master/lib/lev/handler.rb
|
84
|
-
* https://github.com/lml/lev/blob/master/lib/lev/handle_with.rb
|
85
|
-
* https://github.com/lml/lev/blob/master/lib/lev/form_builder.rb
|
86
|
-
|
87
|
-
TBD: talk about ```delegate_to_routine```.
|
88
|
-
|
89
546
|
## Contributing
|
90
547
|
|
91
548
|
1. Fork it
|
data/lib/lev/error.rb
CHANGED
@@ -11,7 +11,7 @@ module Lev
|
|
11
11
|
attr_accessor :offending_inputs
|
12
12
|
|
13
13
|
def initialize(args={})
|
14
|
-
raise
|
14
|
+
raise ArgumentError, "must supply a :code" if args[:code].blank?
|
15
15
|
|
16
16
|
self.code = args[:code]
|
17
17
|
self.data = args[:data]
|
data/lib/lev/errors.rb
CHANGED
@@ -31,6 +31,17 @@ 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
|
+
|
38
|
+
def reraise_exception!
|
39
|
+
exception_error = select{|error| error.kind == :exception}.first
|
40
|
+
return if exception_error.nil?
|
41
|
+
raise exception_error.data
|
42
|
+
end
|
43
|
+
|
44
|
+
|
34
45
|
protected
|
35
46
|
|
36
47
|
def ignored_error_procs
|
data/lib/lev/handler.rb
CHANGED
@@ -14,28 +14,28 @@ module Lev
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
# Common methods for all handlers. Handlers are extensions of Routines
|
18
|
-
# and are responsible for taking input data from a form or other widget and
|
17
|
+
# Common methods for all handlers. Handlers are extensions of Routines
|
18
|
+
# and are responsible for taking input data from a form or other widget and
|
19
19
|
# doing something with it. See Lev::Routine for more information.
|
20
20
|
#
|
21
21
|
# All handlers must:
|
22
22
|
# 2) call "lev_handler"
|
23
|
-
# 3) implement the 'handle' method which takes no arguments and does the
|
23
|
+
# 3) implement the 'handle' method which takes no arguments and does the
|
24
24
|
# work the handler is charged with
|
25
|
-
# 4) implement the 'authorized?' method which returns true iff the
|
25
|
+
# 4) implement the 'authorized?' method which returns true iff the
|
26
26
|
# caller is authorized to do what the handler is charged with
|
27
27
|
#
|
28
28
|
# Handlers may:
|
29
29
|
# 1) implement the 'setup' method which runs before 'authorized?' and 'handle'.
|
30
|
-
# This method can do anything, and will likely include setting up some
|
30
|
+
# This method can do anything, and will likely include setting up some
|
31
31
|
# instance objects based on the params.
|
32
32
|
# 2) Call the class method "paramify" to declare, cast, and validate parts of
|
33
33
|
# the params hash. The first argument to paramify is the key in params
|
34
34
|
# which points to a hash of params to be paramified. If this first argument
|
35
|
-
# is unspecified (or specified as `:paramify`, a reserved symbol), the entire
|
36
|
-
# params hash will be paramified. The block passed to paramify looks just
|
35
|
+
# is unspecified (or specified as `:paramify`, a reserved symbol), the entire
|
36
|
+
# params hash will be paramified. The block passed to paramify looks just
|
37
37
|
# like the guts of an ActiveAttr model.
|
38
|
-
#
|
38
|
+
#
|
39
39
|
# When the incoming params includes :search => {:type, :terms, :num_results}
|
40
40
|
# the Handler class would look like:
|
41
41
|
#
|
@@ -53,9 +53,9 @@ module Lev
|
|
53
53
|
#
|
54
54
|
# attribute :num_results, type: Integer
|
55
55
|
# validates :num_results, numericality: { only_integer: true,
|
56
|
-
# greater_than_or_equal_to: 0 }
|
56
|
+
# greater_than_or_equal_to: 0 }
|
57
57
|
# end
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# def handle
|
60
60
|
# # By this time, if there were any errors the handler would have
|
61
61
|
# # already populated the errors object and returned.
|
@@ -81,23 +81,23 @@ module Lev
|
|
81
81
|
#
|
82
82
|
# These methods are available iff these data were supplied in the call
|
83
83
|
# to the handler (not all handlers need all of this). However, note that
|
84
|
-
# the Lev::HandleWith module supplies an easy way to call Handlers from
|
84
|
+
# the Lev::HandleWith module supplies an easy way to call Handlers from
|
85
85
|
# controllers -- when this way is used, all of the methods above are available.
|
86
86
|
#
|
87
|
-
# Handler 'handle' methods don't return anything; they just set values in
|
87
|
+
# Handler 'handle' methods don't return anything; they just set values in
|
88
88
|
# the errors and results objects. The documentation for each handler
|
89
89
|
# should explain what the results will be and any nonstandard data required
|
90
90
|
# to be passed in in the options.
|
91
91
|
#
|
92
|
-
# In addition to the class- and instance-level "call" methods provided by
|
92
|
+
# In addition to the class- and instance-level "call" methods provided by
|
93
93
|
# Lev::Routine, Handlers have a class-level "handle" method (an alias of
|
94
94
|
# the class-level "call" method). The convention for handlers is that the
|
95
95
|
# call methods take a hash of options/inputs. The instance-level handle
|
96
96
|
# method doesn't take any arguments since the arguments have been stored
|
97
97
|
# as instance variables by the time the instance-level handle method is called.
|
98
|
-
#
|
98
|
+
#
|
99
99
|
# Example:
|
100
|
-
#
|
100
|
+
#
|
101
101
|
# class MyHandler
|
102
102
|
# lev_handler
|
103
103
|
# protected
|
@@ -119,7 +119,7 @@ module Lev
|
|
119
119
|
end
|
120
120
|
|
121
121
|
module ClassMethods
|
122
|
-
|
122
|
+
|
123
123
|
def handle(options={})
|
124
124
|
call(options)
|
125
125
|
end
|
@@ -140,14 +140,14 @@ module Lev
|
|
140
140
|
end
|
141
141
|
|
142
142
|
# Attach a name to this dynamic class
|
143
|
-
const_set("#{group.to_s.capitalize}Paramifier",
|
143
|
+
const_set("#{group.to_s.capitalize}Paramifier",
|
144
144
|
paramify_classes[group])
|
145
145
|
|
146
146
|
paramify_classes[group].class_eval(&block)
|
147
147
|
paramify_classes[group].group = group
|
148
148
|
end
|
149
149
|
|
150
|
-
# Define the "#{group}_params" method to get the paramifier
|
150
|
+
# Define the "#{group}_params" method to get the paramifier
|
151
151
|
# instance wrapping the params. Choose the subset of params
|
152
152
|
# based on the group, choosing all params if the default group
|
153
153
|
# is used.
|
@@ -155,7 +155,7 @@ module Lev
|
|
155
155
|
define_method method_name.to_sym do
|
156
156
|
if !instance_variable_get(variable_sym)
|
157
157
|
params_subset = group == :paramify ? params : params[group]
|
158
|
-
instance_variable_set(variable_sym,
|
158
|
+
instance_variable_set(variable_sym,
|
159
159
|
self.class.paramify_classes[group].new(params_subset))
|
160
160
|
end
|
161
161
|
instance_variable_get(variable_sym)
|
@@ -187,12 +187,12 @@ module Lev
|
|
187
187
|
attr_accessor :auth_error_details
|
188
188
|
|
189
189
|
# This is a method required by Lev::Routine. It enforces the steps common
|
190
|
-
# to all handlers.
|
190
|
+
# to all handlers.
|
191
191
|
def exec(options)
|
192
|
-
self.params = options
|
193
|
-
self.request = options
|
194
|
-
self.caller = options
|
195
|
-
self.options = options
|
192
|
+
self.params = options.delete(:params)
|
193
|
+
self.request = options.delete(:request)
|
194
|
+
self.caller = options.delete(:caller)
|
195
|
+
self.options = options
|
196
196
|
|
197
197
|
setup
|
198
198
|
raise Lev.configuration.security_transgression_error, auth_error_details unless authorized?
|
@@ -203,19 +203,19 @@ module Lev
|
|
203
203
|
# Default setup implementation -- a no-op
|
204
204
|
def setup; end
|
205
205
|
|
206
|
-
# Default authorized? implementation. It returns true so that every
|
206
|
+
# Default authorized? implementation. It returns true so that every
|
207
207
|
# handler realization has to make a conscious decision about who is authorized
|
208
|
-
# to call the handler. To help the common error of forgetting to override this
|
208
|
+
# to call the handler. To help the common error of forgetting to override this
|
209
209
|
# method in a handler instance, we provide an error message when this default
|
210
210
|
# implementation is called.
|
211
211
|
def authorized?
|
212
|
-
self.auth_error_details =
|
212
|
+
self.auth_error_details =
|
213
213
|
"Access to handlers is prevented by default. You need to override the " +
|
214
214
|
"'authorized?' in this handler to explicitly grant access."
|
215
215
|
false
|
216
216
|
end
|
217
217
|
|
218
|
-
|
218
|
+
|
219
219
|
|
220
220
|
# Helper method to validate paramified params and to transfer any errors
|
221
221
|
# into the handler.
|
data/lib/lev/routine.rb
CHANGED
@@ -236,8 +236,15 @@ module Lev
|
|
236
236
|
@after_transaction_blocks = []
|
237
237
|
|
238
238
|
in_transaction do
|
239
|
-
catch :fatal_errors_encountered do
|
240
|
-
|
239
|
+
catch :fatal_errors_encountered do
|
240
|
+
begin
|
241
|
+
exec(*args, &block)
|
242
|
+
rescue StandardError => standard_error
|
243
|
+
fatal_error(kind: :exception,
|
244
|
+
code: standard_error.class.name,
|
245
|
+
message: standard_error.message,
|
246
|
+
data: standard_error)
|
247
|
+
end
|
241
248
|
end
|
242
249
|
end
|
243
250
|
|
data/lib/lev/version.rb
CHANGED
data/spec/errors_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lev::Errors do
|
4
|
+
|
5
|
+
let(:errors) { Lev::Errors.new }
|
6
|
+
|
7
|
+
it "should raise an exception if requested and there are errors" do
|
8
|
+
errors.add(false, kind: :lev, code: 'a code', message: 'a message')
|
9
|
+
expect{errors.raise_exception_if_any!}.to raise_error(StandardError, 'a message')
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not raise an exception if requested and there aren't any errors" do
|
13
|
+
expect{errors.raise_exception_if_any!}.not_to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should reraise an exception if requested and present" do
|
17
|
+
exception = StandardError.new("a message")
|
18
|
+
errors.add(false, kind: :exception, code: 'code', data: exception)
|
19
|
+
expect{errors.reraise_exception!}.to raise_error(StandardError, "a message")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not reraise an exception if requested and not present" do
|
23
|
+
errors.add(false, kind: :lev, code: 'a code')
|
24
|
+
expect{errors.reraise_exception!}.not_to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/routine_spec.rb
CHANGED
@@ -2,6 +2,26 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Lev::Routine do
|
4
4
|
|
5
|
+
before do
|
6
|
+
stub_const 'RaiseArgumentError', Class.new
|
7
|
+
RaiseArgumentError.class_eval {
|
8
|
+
lev_routine
|
9
|
+
def exec
|
10
|
+
raise ArgumentError, 'a message'
|
11
|
+
end
|
12
|
+
}
|
13
|
+
end
|
5
14
|
|
15
|
+
it "should convert exceptions to fatal errors" do
|
16
|
+
outcome = RaiseArgumentError.call
|
17
|
+
expect(outcome.errors.count).to eq 1
|
18
|
+
expect(outcome.errors.first.kind).to eq :exception
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be able to reraise an exception" do
|
22
|
+
outcome = RaiseArgumentError.call
|
23
|
+
expect(outcome.errors.count).to eq 1
|
24
|
+
expect{outcome.errors.reraise_exception!}.to raise_error(ArgumentError, 'a message')
|
25
|
+
end
|
6
26
|
|
7
27
|
end
|
data/spec/spec_helper.rb
CHANGED
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:
|
5
|
-
prerelease:
|
4
|
+
version: 3.0.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:
|
11
|
+
date: 2015-01-14 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
|
@@ -210,6 +185,10 @@ executables: []
|
|
210
185
|
extensions: []
|
211
186
|
extra_rdoc_files: []
|
212
187
|
files:
|
188
|
+
- LICENSE.txt
|
189
|
+
- README.md
|
190
|
+
- Rakefile
|
191
|
+
- lib/lev.rb
|
213
192
|
- lib/lev/better_active_model_errors.rb
|
214
193
|
- lib/lev/delegate_to_routine.rb
|
215
194
|
- lib/lev/error.rb
|
@@ -228,12 +207,9 @@ files:
|
|
228
207
|
- lib/lev/transaction_isolation.rb
|
229
208
|
- lib/lev/utilities.rb
|
230
209
|
- lib/lev/version.rb
|
231
|
-
- lib/lev.rb
|
232
|
-
- LICENSE.txt
|
233
|
-
- Rakefile
|
234
|
-
- README.md
|
235
210
|
- spec/create_sprocket_spec.rb
|
236
211
|
- spec/deep_merge_spec.rb
|
212
|
+
- spec/errors_spec.rb
|
237
213
|
- spec/outputs_spec.rb
|
238
214
|
- spec/paramify_handler_spec.rb
|
239
215
|
- spec/routine_spec.rb
|
@@ -248,37 +224,31 @@ files:
|
|
248
224
|
homepage: http://github.com/lml/lev
|
249
225
|
licenses:
|
250
226
|
- MIT
|
227
|
+
metadata: {}
|
251
228
|
post_install_message:
|
252
229
|
rdoc_options: []
|
253
230
|
require_paths:
|
254
231
|
- lib
|
255
232
|
required_ruby_version: !ruby/object:Gem::Requirement
|
256
|
-
none: false
|
257
233
|
requirements:
|
258
234
|
- - ! '>='
|
259
235
|
- !ruby/object:Gem::Version
|
260
236
|
version: '0'
|
261
|
-
segments:
|
262
|
-
- 0
|
263
|
-
hash: 3271005735947250586
|
264
237
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
265
|
-
none: false
|
266
238
|
requirements:
|
267
239
|
- - ! '>='
|
268
240
|
- !ruby/object:Gem::Version
|
269
241
|
version: '0'
|
270
|
-
segments:
|
271
|
-
- 0
|
272
|
-
hash: 3271005735947250586
|
273
242
|
requirements: []
|
274
243
|
rubyforge_project:
|
275
|
-
rubygems_version:
|
244
|
+
rubygems_version: 2.2.2
|
276
245
|
signing_key:
|
277
|
-
specification_version:
|
246
|
+
specification_version: 4
|
278
247
|
summary: Ride the rails but don't touch them.
|
279
248
|
test_files:
|
280
249
|
- spec/create_sprocket_spec.rb
|
281
250
|
- spec/deep_merge_spec.rb
|
251
|
+
- spec/errors_spec.rb
|
282
252
|
- spec/outputs_spec.rb
|
283
253
|
- spec/paramify_handler_spec.rb
|
284
254
|
- spec/routine_spec.rb
|