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.
Add this line to your application's Gemfile:
And then execute:
$ bundle install
Or install it yourself as:
$ gem install navigable
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
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
executemethod of the
CreateAlertclass is built into Navigable. Methods like
successfullytell Navigable the results of the command so that it can notify the observers.
CreateAlerttell Navigable to execute that command when either key is dispatched. You can register a command under as many different keys as you need.
AllRecipientsNotifierclass tells Navigable to send a message to that class only when the
:create_alert_with_notificationskey is dispatched. One observer can observe as many commands as you need. And, many observers can observe the same command.
observes_all_commandsstatement (as shown in the
Monitorclass) 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.
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
Bug reports and pull requests are welcome on GitHub at https://github.com/first-try-software/navigable.
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.