From 45f1c7a3e16437e517baa6606674f7bbb16dba74 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Tue, 25 Dec 2018 16:35:15 +0200 Subject: Introduce Actionable Errors Actionable errors let's you dispatch actions from Rails' error pages. This can help you save time if you have a clear action for the resolution of common development errors. The de-facto example are pending migrations. Every time pending migrations are found, a middleware raises an error. With actionable errors, you can run the migrations right from the error page. Other examples include Rails plugins that need to run a rake task to setup themselves. They can now raise actionable errors to run the setup straight from the error pages. Here is how to define an actionable error: ```ruby class PendingMigrationError < MigrationError #:nodoc: include ActiveSupport::ActionableError action "Run pending migrations" do ActiveRecord::Tasks::DatabaseTasks.migrate end end ``` To make an error actionable, include the `ActiveSupport::ActionableError` module and invoke the `action` class macro to define the action. An action needs a name and a procedure to execute. The name is shown as the name of a button on the error pages. Once clicked, it will invoke the given procedure. --- activesupport/test/actionable_error_test.rb | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 activesupport/test/actionable_error_test.rb (limited to 'activesupport/test/actionable_error_test.rb') diff --git a/activesupport/test/actionable_error_test.rb b/activesupport/test/actionable_error_test.rb new file mode 100644 index 0000000000..66ba94e0dd --- /dev/null +++ b/activesupport/test/actionable_error_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "active_support/actionable_error" + +class ActionableErrorTest < ActiveSupport::TestCase + class NonActionableError < StandardError + end + + class DispatchableError < StandardError + include ActiveSupport::ActionableError + + class_attribute :flip1, default: false + class_attribute :flip2, default: false + + action "Flip 1" do + self.flip1 = true + end + + action "Flip 2" do + self.flip2 = true + end + end + + test "can get all action of an actionable error" do + assert_equal ["Flip 1", "Flip 2"], ActiveSupport::ActionableError.actions(DispatchableError).keys + assert_equal ["Flip 1", "Flip 2"], ActiveSupport::ActionableError.actions(DispatchableError.new).keys + end + + test "cannot get actions from non-actionable errors" do + assert_raises ActiveSupport::ActionableError::NonActionable do + ActiveSupport::ActionableError.actions(NonActionableError) + end + end + + test "dispatches actions from class and a label" do + assert_changes "DispatchableError.flip1", from: false, to: true do + ActiveSupport::ActionableError.dispatch DispatchableError, "Flip 1" + end + end + + test "dispatches actions from class name and a label" do + assert_changes "DispatchableError.flip2", from: false, to: true do + ActiveSupport::ActionableError.dispatch DispatchableError.name, "Flip 2" + end + end + + test "cannot dispatch errors that do not include ActiveSupport::ActionableError" do + err = assert_raises ActiveSupport::ActionableError::NonActionable do + ActiveSupport::ActionableError.dispatch NonActionableError, "action" + end + + assert_equal <<~EXPECTED.chop, err.to_s + ActionableErrorTest::NonActionableError is non-actionable + EXPECTED + end +end -- cgit v1.2.3