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 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
- Rails is fantastic and obviously super successful. Lev is an attempt to improve Rails by:
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. I agree that the logic shouldn't live in the controllers, but I also argue that it shouldn't always live in the models either, especially when that logic touches multiple models.
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 "routines" which you can think of as pieces of code that have all the responsibility for making one thing (use case) happen, e.g. "add an email to a user", "register a student to a class", etc).
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
- Handlers are specialized routines that take user input (e.g. form data) and then take an action based on that input.
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. Integrate will with basic routines
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
- In a Lev-oriented Rails app, controllers are just responsible for connecting routes to Handlers. In fact, controller methods just end up being calls to ```handle_with(MyHandler)```, ```handle_with``` being a helper method provided by Lev.
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
- 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.
420
+ <label>First Name</label>
421
+ <%= f.text_field :first_name %>
36
422
 
37
- When using Lev, model classes have the following responsibilities:
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
- 1. Hook into the ORM (i.e., inherit from ActiveRecord::Base)
41
- 2. Establish relationships to other models (e.g. belongs_to, has_many, including dependent: :destroy)
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
- The result of the principles above and below, model classes end up being very small. This is good because a lot of code depends on the models and having the be small normally means they are also stable.
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. This makes the models simpler and more stable (a Good Thing).
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 IllegalArgument, "must supply a :code" if args[:code].blank?
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[:params]
193
- self.request = options[:request]
194
- self.caller = options[:caller]
195
- self.options = options.except(:params, :request, :caller)
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
- exec(*args, &block)
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
@@ -1,3 +1,3 @@
1
1
  module Lev
2
- VERSION = "2.2.2"
2
+ VERSION = "3.0.0"
3
3
  end
@@ -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
@@ -17,6 +17,7 @@ end
17
17
 
18
18
 
19
19
  require 'lev'
20
+ require 'debugger'
20
21
 
21
22
  Dir[(File.expand_path('../support', __FILE__)) + ("/**/*.rb")].each { |f| require f }
22
23
 
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: 2.2.2
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: 2016-06-30 00:00:00.000000000 Z
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: 1.8.23.2
244
+ rubygems_version: 2.2.2
276
245
  signing_key:
277
- specification_version: 3
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