SnFoil::Context
SnFoil Contexts are a simple way to ensure a workflow pipeline can be easily established end extended. It helps by creating a workflow, allowing additional steps at specific intervals, and reacting to success or failure, you should find your code being more maintainable and testable.
Installation
Add this line to your application's Gemfile:
gem 'snfoil-context'
Usage
While contexts are powerful, they aren't a magic bullet. Each function should strive to only contain a single purpose. This also has the added benefit of outlining some basic tests - if it is in a function it should have a related test.
Quickstart Example
require 'snfoil/context'
class TokenContext
include SnFoil::Context
action(:create) { || [:object].save }
action(:expire) { || [:object].update(expired_at: Time.current) }
# inject created_by
setup_create { || [:params][:created_by] = entity }
# initialize token
before_create do ||
[:object] = Token.create([:params])
end
# send token email
after_create_success { || TokenMailer.new(token: option[:object]) }
# log expiration error
after_expire_failure { || ErrorLogger.notify(error: [:object].errors) }
end
Initialize
When you new
up a SnFoil Context you should provide the entity running the actions. This will usually be a user but you can pass in anything. This will be accessible from within the context as entity
.
TokenContext.new(entity: current_user)
Actions
Actions are a group of hookable intervals that create a workflow around a single primary function.
To start you will need to define an action.
Arguments:
name
- The name of this action will also set the name of all the hooks and methods later generated.with
- Keyword Param - The method name of the primary action. Either this or a block is requiredblock
- Block - The block of the primary action. Either this or with is required
# lib/contexts/token_context
require 'snfoil/context'
class TokenContext
include SnFoil::Context
...
action(:expire) { || [:object].update(expired_at: Time.current) }
end
This will generate the intervals of the pipeline. In this example the following get made:
- setup_expire
- before_expire
- after_expire_success
- after_expire_failure
- after_expire
Now you can trigger the workflow by calling the action as a method on an instance of the context.
class TokenContext
include SnFoil::Context
action(:expire) { || [:object].update(expired_at: Time.current) }
end
TokenContext.new(entity: current_user).expire(object: current_token)
If you want to reuse the primary action or just prefer methods, you can pass in the method name you would like to call, rather than providing a block. If a method name and a block are provided, the block is ignored.
# lib/contexts/token_context
require 'snfoil/context'
class TokenContext
include SnFoil::Context
action :expire, with: :expire_token
def expire_token()
[:object].update(expired_at: Time.current)
end
end
Primary Function
The primary function is the function that determines whether or not the action is successful. To do this, the primary function must always return a truthy value if the action was successful, or a falsey one if it failed.
The primary function is passed one argument which is the return value of the closest preceding interval function.
# lib/contexts/token_context
require 'snfoil/context'
class TokenContext
include SnFoil::Context
action :expire, with: :expire_token
before_expire do |options|
options[:foo] = bar
options
end
def expire_token(options)
puts options[:foo] # => logs 'bar' to the console
...
end
end
Action Intervals
The following are the intervals SnFoil Contexts set up in the order they occur. The suggested uses are just very simple examples. You can chain contexts to setup very complex interactions in a very easy-to-manage workflow.
Name | Suggested Use |
---|---|
setup_<action> |
* find or create a model
* setup params needed later in the action
* set scoping
|
before_<action> |
* alter model or set attributes
|
primary action |
* persist database changes
* make primary network call
|
after_<action>success |
* setup additional relationships
* success specific logging
|
after<action>failure |
* cleanup failed remenants
* call bug tracker
* failure specific logging
|
after<action> |
* perform necessary required cleanup
* log outcome
|