Navigable
Navigable is a stand-alone tool for isolating business logic from external interfaces and cross-cutting concerns. Navigable composes self-configured command and observer objects to allow you to extend your business logic without modifying it. Navigable is compatible with any Ruby-based application development framework, including Rails, Hanami, and Sinatra.
Installation
Add this line to your application's Gemfile:
gem 'navigable'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install navigable
Usage
We built Navigable to help separate the web adapter and persistence layer from your actual business logic. And, we did it in a composable manner that allows for incredible flexibility. Here's a peek:
# CreateAlert command
class CreateAlert
extend Navigable::Command
corresponds_to :create_alert
corresponds_to :create_alert_and_notify
def execute
return failed_to_validate(new_alert) unless new_alert.valid?
return failed_to_create(new_alert) unless created_alert.persisted?
successfully created_alert
end
private
def new_alert
@new_alert ||= Alert.new(params)
end
def created_alert
@created_alert ||= @new_alert.save
end
end
# AllRecipientsNotifier observer
class AllRecipientsNotifier
extend Navigable::Observer
observes :create_alert_and_notify
def on_success(alert)
NotifyAllRecipientsWorker.perform_async(alert_id: alert.id)
end
end
In these two classes, Navigable enables you to execute multiple use cases. You can create an alert:
Navigable::Dispatcher.dispatch(:create_alert, params: alert_params)
Or, create an alert and notify all recipients:
Navigable::Dispatcher.dispatch(:create_alert_and_notify, params: alert_params)
All without having to add conditional logic about the notifications to the CreateAlert
class.
Similarly, you can add cross-cutting concerns to an application just as easily:
# Monitor observer
class Monitor
extend Navigable::Observer
observes_all_commands
def on_success(*args)
increment_counter(observed_command_key, :success)
end
def on_failed_to_validate(*args)
increment_counter(observed_command_key, :failed_to_validate)
end
def on_failed_to_create(*args)
increment_counter(observed_command_key, :failed_to_create)
end
# ...
end
Here are a few things to look for in the code above:
The DSL in the
execute
method of theCreateAlert
class is built into Navigable. Methods likesuccessfully
tell Navigable the results of the command so that it can notify the observers.The two
corresponds_to
statements inCreateAlert
tell Navigable to execute that command when either key is dispatched. You can register a command under as many different keys as you need.The single
observes
statement inAllRecipientsNotifier
class tells Navigable to send a message to that class only when the:create_alert_with_notifications
key is dispatched. One observer can observe as many commands as you need. And, many observers can observe the same command.Use the
observes_all_commands
statement (as shown in theMonitor
class) for cross-cutting concerns. It tells Navigable to send messages to the observer no matter which command was executed.
For a deeper look at the core concepts introduced by Navigable, please have a look at our wiki.
Rails Usage
The code above can be integrated into Rails like this:
# AlertsController
class AlertsController < ApplicationController
# ...
def create
@alert = Navigable::Dispatcher.dispatch(:create_alert, params: alert_params)
if @alert.persisted?
redirect_to @alert, notice: 'Alert successfully created.'
else
render :new
end
end
# ...
end
# Alert model
class Alert < ApplicationRecord
validates :title, presence: true
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/first-try-software/navigable.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Navigable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.