<commit_before># Lev

Lev is an attempt to improve Rails by:

1. Providing a better, more structured, and more organized way to implement code features
2. De-emphasizing the "model is king" mindset when appropriate

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.

When all of the business logic lives in the models, some bad things can happen:

1. your models can become bloated with code that only applies to certain features
2. your models end up knowing way too much about other models, sometimes multiple hops away
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.

Lev introduces two main constructs to get around these issues: **Routines** and **Handlers**.

## Routines

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.

Routines...

1. Can call other routines
2. Have a common error reporting framework
3. Run within a single transaction with a controllable isolation level

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.

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.

A class becomes a routine by calling `lev_routine` in its definition, e.g.:

    class MyRoutine
      lev_routine
      ...

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:

Errors can be recorded in a number of ways.  You can manually add errors to the built-in `errors` object:

    errors.add(true, code: :search_terms_incorrect)

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:

* `:code` A symbol indicating the kind of error that occurred
* `:data` Any data that is useful for understanding the error (`:code`-specific)
* `:kind` If you don't set this, it will default to `:lev` for Lev-generated errors, and `:activerecord` for ActiveRecord-generated errors
* `:message` A human-readable error message
* `: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.

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.

Here's an example setting an error and an output:

    class MyRoutine
      lev_routine

    protected
      def exec(foo, options={})  # whatever arguments you want here
        fatal_error(code: :some_code_symbol) if foo.nil?
        outputs[:bar] = foo * 2
      end
    end

If you'd like the `fatal_error` to raise a `StandardError` immediately instead of bubbling up to the `Lev::Routine#errors` object, you must configure it:

```ruby
Lev.configure do |config|
  config.raise_fatal_errors = true
end
```

Additionally, see below for a discussion on how to transfer errors from ActiveRecord models.

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:
<commit_msg>Update README a bit more<commit_after># Lev

Lev is an attempt to improve Rails by:

1. Providing a better, more structured, and more organized way to implement code features
2. De-emphasizing the "model is king" mindset when appropriate

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.

When all of the business logic lives in the models, some bad things can happen:

1. your models can become bloated with code that only applies to certain features
2. your models end up knowing way too much about other models, sometimes multiple hops away
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.

Lev introduces two main constructs to get around these issues: **Routines** and **Handlers**.

## Routines

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.

Routines...

1. Can call other routines
2. Have a common error reporting framework
3. Run within a single transaction with a controllable isolation level

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.

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.

A class becomes a routine by calling `lev_routine` in its definition, e.g.:

    class MyRoutine
      lev_routine
      ...

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:

Errors can be recorded in a number of ways.  You can manually add errors to the built-in `errors` object:

    errors.add(true, code: :search_terms_incorrect)

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:

* `:code` A symbol indicating the kind of error that occurred
* `:data` Any data that is useful for understanding the error (`:code`-specific)
* `:kind` If you don't set this, it will default to `:lev` for Lev-generated errors, and `:activerecord` for ActiveRecord-generated errors
* `:message` A human-readable error message
* `: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.

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.

Here's an example setting an error and an output:

    class MyRoutine
      lev_routine

    protected
      def exec(foo, options={})  # whatever arguments you want here
        fatal_error(code: :some_code_symbol) if foo.nil?
        outputs[:bar] = foo * 2
      end
    end

If you'd like the `fatal_error` to raise a `StandardError` immediately instead of bubbling up to the `Lev::Routine#errors` object, you must configure it:

```ruby
Lev.configure do |config|
  config.raise_fatal_errors = true
end
```

So if you `fatal_error(name: :is_blank)` it will raise `StandardError: "name is blank"`, or `fatal_error(thing: :is_broken, and: :messed_up)` it will raise `StandardError: "thing is broken - and messed up"`

Additionally, see below for a discussion on how to transfer errors from ActiveRecord models.

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:

{'fraction_non_alphanumeric': 0.0520368718406185, 'fraction_numerical': 0.001784121320249777, 'mean_word_length': 4.358470525756771, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '32468', 'n_tokens_mistral': 2539, 'n_tokens_neox': 2416, 'n_words': 1529}