undo-manager makes it easy to add undo/redo functionality to a Ruby (on Rails) app.
Ruby
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
doc/how_to
lib
spec
.gitignore
.travis.yml
CHANGELOG.md
Gemfile
MIT-LICENSE
README.md
Rakefile
undo-manager.gemspec

README.md

undo-manager

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 #do and #undo methods for each of your commands.

Installation

gem install undo-manager

or with bundler in your Gemfile:

gem 'undo-manager'

Usage

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.

The 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 Workflow object.

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: #do and #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

Dependencies

  • Ruby >= 1.9.3

Resources

Build Status

Code Climate

License

MIT licensed.

Copyright

Copyright (c) 2015 Jo Hund. See (MIT) LICENSE for details.