ANN: rails-undo-redo

To this day, very few web apps provide any undo/redo capabilities. This is really not such a good thing for usability. There are a few exceptions like gmail, but it does not go far enough to provide what every one is used on their desktop.

Ever since I read Assaf’s post on the subject, and left hungry for more, as Assaf only helps you on the UI side and leaves you all the heavy lifting, I have been thinking that there must be a way to make it a lot easier to implement undo/redo in a consistent manner, and in a way that is as easy as rails has gotten us used to.

After a few failed attempts, and building on the work of Brian Durand and his Acts As Revisionable and ActsAsTrashable plugins, that I’ve reused (and probably abused as well), I have come to realize a few (obvious?) things, namely that:

for Undo/Redo to work, it needs to

  • Work across all models
  • Capture the list of changed objects (UndoRecords)
  • Group these UndoRecords per User Action (UndoActions)

Then undo and redo are just a simple matter of replaying the UndoRecords in the right order.

All my previous attempts, and most other plugins I got inspiration from focused on one model, and even though you need this as a building block, for any real application, you need to be able to undo changes across multiple models (even beyond belongs_to, has_many type changes).

When you look at the types of changes to a model in details, you really have 3 types of changes Create, Update and Destroy (these are the C-U-D in CRUD, even if you are not doing REST), and undoing each one requires special handling, so this is also tracked and managed by UndoRecord.

To make a long story short, I’ve packaged all these ideas into a new Rails plugin: Rails Undo Redo (and very soon a gem as well, because this can help, and that’s also easy, or very easy), you can now easily transform any Rails Application using Active Record into a full fledged multi level undo/redo application like most desktop application.

For a full how-to, read the Rails Undo Redo project page, or try the Rails Undo Redo demo.

git, submodules and Capistrano

If you use git submodules, as git-rails does, and want to use Capistano, you’ll need to patch patch Capistrano (lib/capistano/recipes/deploy/scm/git.rb) to make sure your submodules get included.

        def checkout(revision, destination)
          git      = command

          branch   = head

          fail "No branch specified, use for example 'set :branch, \"origin/master\"' in your deploy.rb" unless branch

          if depth = configuration[:git_shallow_clone]
            execute  = "#{git} clone --depth #{depth} #{configuration[:repository]} #{destination} && "
          else
            execute  = "#{git} clone #{configuration[:repository]} #{destination} && "
          end

          execute += "cd #{destination} && #{git} checkout -b deploy #{branch}"

          if submodules = configuration[:git_enable_submodules]
            execute += " && git-submodule init &&"
            execute += "git-submodule update"
          end

          execute
        end

then define this in your deploy.rb:

set :git_enable_submodules,1

Or you can use the trunk version (post 2.1), which includes that support already.

Debugging Specs

It is easy enough to debug specs on by one. For this you just run the ruby file that contains the spec with ruby-debug instead of ruby:

rdebug spec/rur_spec.rb

But when running via rake, you can’t do this. A simple solution is to add these lines in the spec file you want to debug (or in spec_helper.rb):

require_library_or_gem 'ruby-debug'
Debugger.start

And you are in business…

git branches, merges and remotes

After releasing the first version of git-rails, Ron Damen created a clone of the repository and started improving on it. And I liked what he was doing, so now was the time to figure out how to get some of his changes back in to the master repo.

Here is the list of steps (and what they mean) to get his changes into the master branch on Gitorious.

First, define a remote branch to track his cloned repository

git remote add ron git://gitorious.org/git-rails/rons-mainline-clone.git

This adds this to .git/config:

[remote "ron"]
        url = git://gitorious.org/git-rails/rons-mainline-clone.git
        fetch = +refs/heads/*:refs/remotes/ron/*

Before doing the actual checkout, make sure you have nothing to commit in the current branch, then checkout a tracking branch:

git checkout -b ron/master


Now get the content from thre remote (remote “ron”, branch “master”)

git pull ron master

Once you’ve reached this point, it is easy to switch back and forth between branches

git checkout master
git checkout ron/master

Next, I created a temporary branch to merge what I like from Ron (not needed really)

git branch merge-ron
git status # does not switch branch!
git checkout merge-ron

This could be used as a shortcut: checkout -b merge-ron

Merge only specific changes (obtained using git log in ron/master branch)

git cherry-pick dc3de57a073e24eeb398e0cacbe52340258e861b
git cherry-pick 61579f92e1bfc95e582728011fcd21c79f08c3ae

Merge from merge-ron branch into local master

git checkout master
git merge merge-ron

And finally, push to origin

git push origin

From now on, the only thing needed to get more changes from Ron is to pull changes into ron/master, cherry-pick changes and merge back into master. Sweet!

Rails Undo Redo

What is RUR (Rails Undo Redo)

RUR (Rails-Undo-Redo) is a rails plugin (and soon a gem as well) to make it so easy to implement multi-level undo redo in your Rails application that you better start thinking of better excuses not to implement Undo/Redo (no, seriously, it is going to be a lot easier).

Try the demo!

To get a feel on how undo/redo can help make your app a lot more difficult for users to make mistake, test drive the RUR demo app

The full source code for that demo app is available here: rur_demo. Or via git:

git clone git://gitorious.org/rur_demo/mainline.git

Installing RUR

Using git-rails (this way, you’ll be able to stay up to date, assuming you use git):

git-rails install git://gitorious.org/rur/mainline.git rur

Using scrip/plugin:

script/plugin install http://svn.nanorails.com/plugins/rur

Then copy the migration from vendor/plugins/rur/migrations to db/migrations (renumber as needed), then run:

rake db:migrate

Using RUR

Define the undoable models

Once you have installed the plugin, for each model that you’d like to have its changes undone, just add act_as_undoable>

class Project < ActiveRecord::Base
  belongs_to :user
  has_many :tasks

  acts_as_undoable
end

Define the undoable controllers:

Add the undo/redo logic to all your controllers:

class TasksController < ApplicationController
  ...

  undoable_methods
  ...
end

Track changes

For all controller methods that make a change (and that are directly called by the user), you need to specify 3 things:

  1. an explanation of the change is
  2. what url to go back to when the user decides to undo the action
  3. what url to go back to when the user decides to redo the action

For this, you enclose the code that will change the records within a “change block”.

For example, here’s a create method:

def create
  @task = @project.tasks.new(params[:task])

  respond_to do |format|
    change("create task #{@task.title}", project_tasks_path(@project), project_tasks_path(@project)) do
      if @task.save
        flash[:notice] = 'Task was successfully created.'
        format.html { redirect_to(project_task_path(@project, @task)) }
        format.xml  { render :xml => @task, :status => :created, :location => @task }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @task.errors, :status => :unprocessable_entity }
      end
    end
  end
end

or for update:

def create
  @task = @project.tasks.new(params[:task])

  respond_to do |format|
    change("create task #{@task.title}", project_tasks_path(@project), project_tasks_path(@project)) do
      if @task.save
        flash[:notice] = 'Task was successfully created.'
        format.html { redirect_to(project_task_path(@project, @task)) }
        format.xml  { render :xml => @task, :status => :created, :location => @task }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @task.errors, :status => :unprocessable_entity }
      end
    end
  end
end

or for destroy:

def destroy
  @task = @project.tasks.find(params[:id])
  change("delete task #{@task.title}", project_task_path(@project, @task), project_tasks_path(@project)) do
    @task.destroy
  end

  respond_to do |format|
    format.html { redirect_to(project_tasks_url(@project)) }
    format.xml  { head :ok }
  end
end

Or even a non REST method:

def move_to
  @task = @project.tasks.find(params[:id])
  @new_project = Project.find(params[:task][:new_project_id])

  unless (@new_project == @project)
    change("move task #{@task.title} to #{@new_project.title}", project_task_path(@project, @task), project_task_path(@new_project, @task)) do
      @task.project.tasks.delete @task
      @new_project.tasks << @task
    end
  end
  flash[:notice] = "Task was successfully reassigned to #{@new_project.title}"
  redirect_to(project_task_path(@new_project, @task))
end

Any change within a change block to a model with an “act_as_undoable” attribute will be recorded and can then later on be undone and redone by calling undo or redo.

Let the user undo and redo

The last piece of the puzzle is to add the following to your views (the layout is probably a good place for it):

<% if undo_redo_links != "" %>
<p><%= undo_redo_links %></p>
<% end %>

Example

For a real life example, check out the rur_demo source code.

How does it work?

Coming up shortly… :)

Contributing

The ruby forge RUR project

Clone away the Git repository: git://gitorious.org/rur/mainline.git

git clone git://gitorious.org/rur/mainline.git

Or, better yet, create your own branch on gitorious.

ANN: git-rails

Git is quickly becoming one of the best option for using a DVCS (Distributed Version Control System). Git main goal is to be quick (most everything you do is local), and has already been battle tested by the Linux Kernel dev team.

Recently, I’ve started experimenting with git after Rick switched Mephisto to git.

Even though things are pretty straightforward, some things are easier than others, especially when you have to take it all in in one shot.

So, over the weekend, I created git-rails, completed with a rubyforge project, an installable gem (gem install git-rails), and a git repository hosted by Gitorious

Currently, git-rails provides 3 functions:

  1. init repository (hookup to remote git repository, rails aware .gitignore)
  2. install plugin managed by git (using git submodules)
  3. update plugin

For now, I’m happy to use giston to manage svn hosted plugins, but I’m considering whether to add that functionality as well. We’ll see how things go.

To get started with git-rails, head over to the new git-rails project page.

Inspired by piston, and its newest sibling: giston/braid

Ruby-debug quick tips: init file and -n option

I use ruby-debug quite a bit (too much? perhaps). And every time I start it, I find myself setting a few options (always the same ones), and it finally got to the point where I thought there had to be a better way. And there was! (Thank you, Kent)

The solution is even documented in some recent release notes:

Create a .rdebugrc in your home directory (it also looks for one in the current directory first):

set autolist
set autoeval
set autoreload
set forcestep

And voila! Each time you start rdebug, it executes this script.

An other good tip: run with -n to avoid stopping on the first instruction, now that you don’t need to run all these commands.

Update: with version 0.10, -n does not seem to be recognized, but —no-stop still works.

My Ruby on Rails Gentoo Setup

I had been hearing about Gentoo for a long time, and finally discovered it in depth about 6 months ago. Gentoo is really a different beast as Linuxes go. You get to build everything from sources. No binaries. That’s right. That means that you get to build everything with compatible flags, get to remove a lot of the unnecessary bloat (Do I hear X windows for example?). It also means that the initial setup could take you several days if your machine is not so powerful. Suffice it to say, I really, really like the approach and that is now what I’m using to run this blog.

Of course, if I need a server setup in a hurry, I’ll still go for a Debian or Ubuntu. But if I want a very fined tune system, Gentoo is it.

Of course, there is an ebuild for rails (ebuild is gentoo-speak for describing a package your can grab and build), but it did bring a lot more that what I cared for, so here’s the step by step instructions for getting Rails+Mongrel+nginx going on Gentoo.

This assumes that you are starting from a Gentoo 2006.1 stage 3 without much else.

A word of warning

Even though I’ve used these notes to successfully setup 3 machines (both on x86 and amd64), your experience may still be different, or you may need slightly different options or settings as the packages are bound to evolve, or I’ve made some assumptions that do not apply in your case, or something was obvious to me that should not have been, or, perish the thought, I just plain forgot something. If that happens, remember, Google is your friend, or don’t be shy, ask a question here, someone might know the answer.

So with that out the way, let’s get started.

Preamble

auto start ssh:

rc-update add sshd default

update portage tree

emerge --sync
emerge portage
emerge -av --update --deep world

At this point, you are going to have a lot of configuration files that out of date. Since we are doing a new setup, we just want to silence the warnings and making sure we use the latest version. So to find each file:

find /etc -iname '._cfg????_*'


But do not overwrite inittab if it is only putting back more terminal login than you need.

Now it is time to add a few very useful tools like sudo, stuff to help you figure out what’s installed, and to find things (namely sudo, gentoolkit and sys-apps/slocate)

emerge -av app-admin/sudo app-portage/gentoolkit sys-apps/slocate

Now it is time to create your user. And set its password.

useradd user
passwd user

Add user to /etc/sudoers with ALL

Add vim (or whatever else you fancy)

emerge -av vim

Change default EDITOR

vi /etc/rc.conf


There, comment out /bin/nano, and uncomment /bin/vim

Install subversion

emerge -av subversion

Even though not strictly necessary for Rails, a few network utilities always come in handy (traceroute, dig, nslookup):

emerge -av traceroute bind-tools

If you want to send/receive emails:

emerge -av mail-client/mailx
emerge --unmerge mail-mta/ssmtp
emerge -av syslog-ng mail-mta/exim
rc-update add exim default

There are lots of options to do SMTP, but I find exim4 the quickest and simplest to setup:

create /etc/exim/exim.conf from /etc/exim/exim.conf.dist

To lockdown mail to localhost (you just want your Rails app to send emails), add this exim.conf:

local_interfaces = 127.0.0.1

And to allow non users to send mails (comment out this line)

##require verify        = sender

And finally, start your mail server:

/etc/init.d/exim start

Rails Setup

My preferred database remains MySQL. I know, some people prefer PostgrSQL.

emerge -av dev-db/mysql
rc-update add mysql default

Then, to configure MySQL and create a first database, first you need to figure out which version you just installed. If you were not paying attention as the logs were flying by (I know I wasn’t), use

equery list mysql

And then

emerge --config =dev-db/mysql-5.0.26-r2

To get rubygems 0.9.x, you need to unmask it. For that, add a new line to /etc/portage/package.keywords with:

dev-ruby/rubygems ~amd64

Replace with ~x86 if you are not on an AMD chip, then run:

emerge -av dev-lang/ruby dev-ruby/mysql-ruby dev-ruby/rubygems  dev-ruby/ruby-termios

For any kind of image manipulation (resizing and such), install Image Magick. First, it it does not already exist create /etc/portage/package.use

dev-ruby/rmagick lcms gif imagemagick jbig jpeg jpeg2k pdf png svg tiff truetype unicode wmf xml xpm pcre
media-gfx/imagemagick lcms gif imagemagick jbig jpeg jpeg2k pdf png svg tiff truetype unicode wmf xml xpm pcre
emerge -av media-gfx/imagemagick dev-ruby/rmagick


The above line represents the options for that ebuild.

Time to get the latest nginx

  1. add a new line to /etc/portage/package.keywords with (Again, on Intel, use ~x86)

"www-servers/nginx ~amd64

Then run emerge again:

emerge -av www-servers/nginx

Ok, now time to install what we actually wanted to install, Ruby on Rails and a few useful gems:

gem install -y rails rake capistrano daemons gem_plugin mongrel mongrel_cluster rmagick BlueCloth RedCloth ruby-debug termios ruby-openid ruby-yadis

Start mysql, create a database for rails test app

/etc/init.d/mysql start
mysqladmin -u root -p create test1_development
mysqladmin -u root -p create test1_production

And finally, install a rails app (we’ll use beast as it is pretty simple to install)

svn checkout http://svn.techno-weenie.net/projects/beast/trunk beast
cd beast/
rake rails:freeze:edge
#create config/database.yml from config/database.example.yml
rake db:schema:load RAILS_ENV=production
./script/server -e production

Test that everything’s fine fine by pointing your browser to port 3000.

Now, let’s configure mongrel (We are almost there, I promise).

cd [app]
sudo useradd -n mongrel
sudo chown -R mongrel:mongrel [app]
mongrel_rails cluster::configure -e production -p 8000 -N 3 -c [app] -a 127.0.0.1 --user mongrel --group mongrel

Later, a few useful commands to use

#start cluster
mongrel_rails cluster::start

#restart cluster
mongrel_rails cluster::restart

#stop cluster
mongrel_rails cluster::stop

To setup nginx, edit /etc/nginx/nginx.conf to match your setup

To setup mongrel_cluster for autostart

mkdir /etc/mongrel_cluster
ln -s [app]/config/mongrel_cluster.yml /etc/mongrel_cluster/[app].yml

#copy from wherever the gem is installed (use locate to figure out the location)

ln -s /usr/lib64/ruby/gems/1.8/gems/mongrel_cluster-0.2.1/resources/mongrel_cluster /etc/init.d/mongrel_cluster
chmod +x /etc/init.d/mongrel_cluster

Add nginx and mongrel_cluster to startup

rc-update add nginx default
rc-update add mongrel_cluster default

For smooth reboot, you may need to delete the pid files
Apply this patch (from http://textsnippets.com)

create file “mongrel_cluster.patch” with:

--- bin/mongrel_rails-orig      2007-05-16 14:41:51.000000000 -0400
+++ bin/mongrel_rails   2007-05-16 14:42:50.000000000 -0400
@@ -83,9 +83,17 @@
       config = Mongrel::Rails::RailsConfigurator.new(settings) do
         if defaults[:daemon]
           if File.exist? defaults[:pid_file]
-            log "!!! PID file #{defaults[:pid_file]} already exists.  Mongrel could be running already.  Check your #{defaults[:log_file]} for errors."
-            log "!!! Exiting with error.  You must stop mongrel and clear the .pid before I'll attempt a start."
-            exit 1
+            # mongrels that crash can leave stale PID files behind, and these
+            # should not stop mongrel from being restarted by monitors…
+            pid = File.new(defaults[:pid_file]).readline
+            unless `ps -ef | grep #{pid} | grep -v grep`.length > 0
+            # use "ps ax" for freebsd
+                log "!!! PID file #{defaults[:pid_file]} exists, but is stale, and will be deleted so that this mongrel can run."
+                File.delete(defaults[:pid_file])
+            else
+                log "!!! PID file #{defaults[:pid_file]} already exists and the process id referred to in it is running.  This mongrel is probably already running.  #{defaults[:log_file]} for errors.  EXITING."
+                exit 1
+            end

           end

                 daemonize

then apply the patch

patch -p0 /usr/lib64/ruby/gems/1.8/gems/mongrel-1.0.1/bin/mongrel_rails < mongrel_cluster.patch

And there you have it. A nice, humming Ruby on Rails+Mongrel+nginx on Gentoo.

Mephisto Theme Gallery

What started as a 5 minutes hack a few weeks ago is now a reality. I’ve put together a new gallery, and unlike many other galleries, you get to see all the existing themes for Mephisto as they would appear in a real Mephisto installation, because that’s where they all run. All sites show the same content, which makes it really easy to find out how each one treats sections, static pages, comments, sidebars, etc.

To the best of my knowledge, this is the most extensive collection of Mephisto designs to date! It contains 34 templates (and would contain more if it was not for the fact that the license did not allow for free redistribution).

Some may require extra Mephisto plugins such as the ERB, Erubis or HAML renderers. Speaking of HAML, I’m going to be releasing a new template within a few days to demonstrate how you can use the HAML renderer for Mephisto.

I’m also officially introducing a new Mephisto theme: Cutline, very accurately translated from the original Cutline Template by Chris Pearson. This is a gorgeous design, with a clean layout, plenty of white space and very precise typography.

I’ll be posting some more details in the following days about why that 5 minutes hack turned out into a much bigger project, but I think the result is quite impressive, and a hats off to Rick for creating an impressive blog engine.

after_method

It started innocently enough. I was writing a Rails Plugin, and needed to call a Class Method on a Model. No problemo, right? In init.rb just call the method (To register a renderer in this case).


  Site.register_template_handler(".erubis", ErubisTemplate)

The method goes on and adds some date to a Class Variable. I try it and it worked…

… For the first request only :( After that, the data is gone from the class! What gives???

Well, there is a very convenient mechanism that reloads all your models, views and controllers so you don’t have to restart your app every time you make a change. Except here. Once the class gets unloaded, BAM! There goes the Class Variable and my carefully registered renderer.

First Solution

The code responsible for unloading and reloading all the models, views, and controllers is Dispatcher.reset_application!, so why not piggy back on reset_application! ? So using alias_method_chain, after a few iteration, here’s what I came up with:


  class << Dispatcher
    def register_erubis_template
      Site.register_template_handler(".erubis", ErubisTemplate)
    end
    def reset_application_with_erubis_registration!
      returning reset_application_without_erubis_registration! do
        register_erubis_template
      end
    end
    alias_method_chain :reset_application!, :erubis_registration
  end
  Dispatcher.register_erubis_template

Since reset_application! is not called till the second request, I had to also call the registration method once, which is why I created a register_erubis_template method.

It now worked. That was nice. But, then I started implementing a second plugin, which needed the same code. Not very DRY.

So I wrote down what ideally I wanted to end up with:


after_reset_application {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

So next time I have the same problem, I can just reuse the same after_reset_application call with any other code I need to run after all the classes get unloaded.

Lots of aborted attempts

Foolishly, I thought it would be quite easy to transform what I had into something reusable. A little dose of Monkey Patching here and there, a bit of Meta Programming, and there you have it. Well, I did not keep all the many iterations I tried, here are just a few that did not work for one reason or an other (I did not try to find out why, just moved on to the next attempt)


  Dispatcher.alias_method_chain :reset_application!, #{feature}
  Dispatcher.class_eval do
    def reset_application_with_#{feature}!
      returning reset_application_without_#{feature}! do
        register_#{feature}
      end
    end
  end

def after_reset_application(feature, &block)
  patch = <<-end_eval
    class << Dispatcher
      def register_#{feature}
        proc
      end
      def reset_application_with_#{feature}!
        returning reset_application_without_#{feature}! do
          register_#{feature}
        end
      end
      alias_method_chain :reset_application!, #{feature}
    end
  end_eval
  eval patch#, &block, __FILE__, __LINE__
  Dispatcher.send "register_#{feature}"
end

Use the singleton-class, Luke! Use the singleton-class!

Then I retrieved a post from Ola Bini that I had read some months back: The ruby singleton class and I started to make some progress after I understood that to define methods, I needed to do it on the singleton class, not the class itself, because even though it did not fail, it did not accomplish much. And finally arrived at:

To retrieve that singleton class:


class << Dispatcher; self end

So now, I finally arrived at this code:


require 'dispatcher'

def after_reset_application(feature, &block)
  class << Dispatcher; self end.class_eval do
    define_method("register_#{feature}", &block)
    define_method("reset_application_with_#{feature}!") {
      returning Dispatcher.send("reset_application_without_#{feature}!") do
        Dispatcher.send("register_#{feature}")
      end
    }
    alias_method_chain :reset_application!, "#{feature}"
  end
  Dispatcher.send("register_#{feature}")
end

after_reset_application("erubis_registration") {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

The key reasons why this works is that it is adding the methods to the singleton class, not the class itself. It is using send to be able to build the method names, and using alias_method_chain to be able to monkey patch reset_application! as many time as needed. I had to add a parameter to after_reset_application for the same reason, so it can be called more than once. And it uses define_method because you can give it a block, plus you can define the method name from variables.

All good, except that it is still not very DRY. What if I want to use the same technique on a different class? A different method?

The final version (for now?)

This one was easy, just add a few parameters, deal safely with ?, ! and = and there you go


def after_method(klass, target, feature, &block)
  # Strip out punctuation on predicates or bang methods since
  # e.g. target?_without_feature is not a valid method name.
  aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
  class << klass; self end.class_eval do
    define_method("register_#{feature}", &block)
    define_method("#{aliased_target}_with_#{feature}#{punctuation}") {
      returning klass.send("#{aliased_target}_without_#{feature}#{punctuation}") do
        klass.send("register_#{feature}")
      end
    }
    alias_method_chain target, "#{feature}"
  end
  klass.send("register_#{feature}")
end

Then, to implement after_reset_application:


require 'dispatcher'

def after_reset_application(feature, &block)
  after_method(Dispatcher, :reset_application!, feature, &block)
end

And to use:


after_reset_application("erubis_registration") {
  Site.register_template_handler(".erubis", ErubisTemplate)
}

Phew! The block gets called once the first time, and every time the classes need to be reloaded.