undo-manager makes it easy to add undo/redo functionality to a Ruby (on Rails) app. It uses the command pattern to do so. undo-manager provides the handling of undo and redo. It’s up to you to implement the
#undo methods for each of your commands.
gem install undo-manager
or with bundler in your Gemfile:
Let's assume you have an ActiveRecord based
Workflow class that manages commands. It is used to store commands that are executed in a given context. Using
UndoManager and the command pattern allows you to record which commands were executed, and you can undo and redo them. Obviously this is only suitable for commands that are reversible.
Workflow class has a text column named
commands_stack in which you store commands that are executed as part of the workflow. It also has an
#undo_manager getter. This method initializes a new UndoManager on first call and memoizes it. We pass the
commands_stack to the initializer. UndoManager will use it as an
undo_stack. It will be persisted to the database every time you call
save on a
class Workflow < ActiveRecord::Base serialize :commands_stack, Array delegate :record_new_command, :redo_command, :undo_command, to: :undo_manager def undo_manager @undo_manager ||= UndoManager.new(commands_stack) end end
Let's implement your command objects. In order to work with
UndoManager, they need to respond to two methods:
#undo. These methods are called with no arguments. You need to provide all data for the command on initialization. In our example we pass in record ids on which we want to operate. Your command objects will likely be more complex.
class MyCommand < UndoManager::Command def initialize(record_id) @record_id = record_id end def do # do something with record_id end def undo # undo whatever we do to record_id end end
And this is how it's all tied together:
class MyCommandsController < ApplicationController def do @workflow = Workflow.find(id) command = MyCommand.new(params[:record_id]) command.do @workflow.record_new_command(command) @workflow.save end def undo @workflow = Workflow.find(id) @workflow.undo_command @workflow.save end def redo @workflow = Workflow.find(id) @workflow.redo_command @workflow.save end end
- Ruby >= 1.9.3
Copyright (c) 2015 Jo Hund. See (MIT) LICENSE for details.