Ruby On Rails and Emacs

updated (18/07/2007) to incorporate some of the feedback in the comments.

Ruby on Rails is quite a hot topic nowadays and with all the major RoR people using TextMate as their editor there seems little interest in using the one true editor for these tasks.

I started playing with RoR and of course I use Emacs for this. The following describes setting up Emacs for RoR development.

Setting up Emacs

First, you need to get the emacs-rails package for easy RoR development and put it in an directory where you keep your other Emacs packages, I use *.elisp*:

.elisp$ svn checkout svn://rubyforge.org/var/svn/emacs-rails/trunk emacs-rails

Besides the emacs-rails there are some other helper files you need:

.elisp$ wget http://www.kazmier.com/computer/snippet.el
.elisp$ wget http://www.webweavertech.com/ovidiu/emacs/find-recursive.txt
.elisp$ mv find-recursive.txt find-recursive.el

The ruby-mode files are available in the ruby distribution in the *misc* directory.

Ok, so now we have all the components, lets tell Emacs what it needs to know (note I copied the ruby-mode files to my .elisp directory and my .elisp directory is already on the load-path):

(setq load-path (cons "~/.elisp/ruby-mode" load-path))
(autoload 'ruby-mode "ruby-mode" "Load ruby-mode")
(add-hook 'ruby-mode-hook 'turn-on-font-lock)

(setq load-path (cons "~/.elisp/emacs-rails" load-path))
(defun try-complete-abbrev (old)
  (if (expand-abbrev) t nil))

(setq hippie-expand-try-functions-list
      '(try-complete-abbrev
        try-complete-file-name
        try-expand-dabbrev))

(require 'rails)

;; associate ruby-mode with .rb files
(add-to-list 'auto-mode-alist '("\.rb$" . ruby-mode))

;; make #! scripts executable after saving them
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

Cool, so now we have ruby mode. Lets setup rhtml file editing (if you do not have the mmm-mode package, you should install it).

(require 'mmm-mode)
(require 'mmm-mode)
(require 'mmm-auto)
(setq mmm-global-mode 'maybe)
(setq mmm-submode-decoration-level 2)
(set-face-background 'mmm-output-submode-face  "LightGrey")
(set-face-background 'mmm-code-submode-face    "white")
(set-face-background 'mmm-comment-submode-face "lightgrey")
(mmm-add-classes
 '((erb-code
    :submode ruby-mode
    :match-face (("<%#" . mmm-comment-submode-face)
                 ("<%=" . mmm-output-submode-face)
                 ("<%"  . mmm-code-submode-face))
    :front "<%[#=]?"
    :back "-?%>"
    :insert ((?% erb-code       nil @ "<%"  @ " " _ " " @ "%>" @)
             (?# erb-comment    nil @ "<%#" @ " " _ " " @ "%>" @)
             (?= erb-expression nil @ "<%=" @ " " _ " " @ "%>" @))
    )))
(add-hook 'html-mode-hook
          (lambda ()
            (setq mmm-classes '(erb-code))
            (mmm-mode-on)))
(add-to-list 'auto-mode-alist '("\.rhtml$" . html-mode))

;; shortcut to reparse the buffer
(global-set-key [f8] ‘mmm-parse-buffer)

Now when you open a *.rhtml* file you will have access to the full ruby-mode in the appropiate areas.

Alright, so what can you do? Basically everything you would need to do for RoR, only then from Emacs. From creating a project, to running the webrick server to generating models, controllers, scaffold etc. It also has some handy functions to navigate between the different directories.

From a code expansion viewpoint there are many predefined snippets, which you can of course extend all you like. All the standard keybindings and abbreviations are listed here.

An example of abbreviations at work:

  def hello
    flash[press tab]
  end

Expands to, with the cursor on the highlighted ‘:notice’, press tab to go to the second field (text).

  def hello
    flash[:notice] = "Text here..."
  end

Type ‘Invalid product’ in the text field and press tab again

  def hello
    flash[:notice] = "Invalid product"
  end

The statement is now complete

  def hello
    flash[:notice] = "Invalid product"
  end

Shortcuts

  ((kbd "C-c g m") 'rails-nav:goto-models)
  ((kbd "C-c g c") 'rails-nav:goto-controllers)
  ((kbd "C-c g h") 'rails-nav:goto-helpers)
  ((kbd "C-c g l") 'rails-nav:goto-layouts)
  ((kbd "C-c g s") 'rails-nav:goto-stylesheets)
  ((kbd "C-c g j") 'rails-nav:goto-javascripts)
  ((kbd "C-c g g") 'rails-nav:goto-migrate)

  ;; Switch
  ((kbd "C-c <up>") 'rails-lib:run-primary-switch)
  ((kbd "C-c <down>") 'rails-lib:run-secondary-switch)

  ;; Scripts & SQL
  ((kbd "C-c s g c") 'rails-generate-controller)
  ((kbd "C-c s g m") 'rails-generate-model)
  ((kbd "C-c s g s") 'rails-generate-scaffold)
  ((kbd "C-c s g g") 'rails-generate-migration)
  ((kbd "C-c s d c") 'rails-destroy-controller)
  ((kbd "C-c s d m") 'rails-destroy-model)
  ((kbd "C-c s d s") 'rails-destroy-scaffold)
  ((kbd "C-c s c")   'rails-run-console)
  ((kbd "C-c s b")   'rails-run-breakpointer)
  ((kbd "C-c s s")   'rails-run-sql)
  ((kbd "C-c s r")   'rails-rake)
  ((kbd "C-c s w")   'rails-webrick:start)

  ;; Rails finds
  ((kbd "C-c f m") 'rails-find-models)
  ((kbd "C-c f c") 'rails-find-controller)
  ((kbd "C-c f h") 'rails-find-helpers)
  ((kbd "C-c f l") 'rails-find-layout)
  ((kbd "C-c f s") 'rails-find-stylesheets)
  ((kbd "C-c f j") 'rails-find-javascripts)
  ((kbd "C-c f g") 'rails-find-migrate)

  ((kbd "C-c f v") 'rails-find-view)
  ((kbd "C-c f d") 'rails-find-db)
  ((kbd "C-c f p") 'rails-find-public)
  ((kbd "C-c f f") 'rails-find-fixtures)
  ((kbd "C-c f o") 'rails-find-config)

  ;; Navigation
  ((kbd "<C-return>") 'rails-goto-file-on-current-line)
  ((kbd "<M-S-down>") 'rails-goto-file-from-file-with-menu)
  ((kbd "<M-S-up>")   'rails-goto-file-from-file)
  ((kbd "C-c l") 'rails-open-log)

  ;; Tags
  ((kbd "C-c C-t") 'rails-create-tags)

  ;; Browser
  ((kbd "C-c <f5>") 'rails-webrick:auto-open-browser)
  ;;; Doc
  ([f1]  'rails-search-doc)
  ((kbd "<C-f1>")  'rails-browse-api-at-point)
  ((kbd "C-c <f1>")  'rails-browse-api)

  ([f9]  'rails-svn-status-into-root))

All actions are also available through M-x rails-…

The abbreviations per category

General

abbrev inserts
ra
render :action => "action"
ral
render :action => "action", :layout => "layoutname"
rf
render :file => "filepath"
rfu
render :file => "filepath", :use_full_path => false
ri
render :inline => "<%= 'hello' %>"
ril
render :inline => "<%= 'hello' %>", :locals => { name => "value" }
rit
render :inline => "<%= 'hello' %>", :type => :rxml
rl
render :layout => "layoutname"
rn
render :nothing => true
rns
render :nothing => true, :status => 401
rp
render :partial => "item"
rpc
render :partial => "item", :collection => items
rpl
render :partial => "item", :locals => { :name => "value"}
rpo
render :partial => "item", :object => object
rps
render :partial => "item", :status => 500
rt
render :text => "Text here..."
rtl
render :text => "Text here...", :layout => "layoutname"
rtlt
render :text => "Text here...", :layout => true
rts
render :text => "Text here...", :status => 401
rcea
render_component :action => "index"
rcec
render_component :controller => "items"
rceca
render_component :controller => "items", :action => "index"
rea
redirect_to :action => "index"
reai
redirect_to :action => "show", :id => @item
rec
redirect_to :controller => "items"
reca
redirect_to :controller => "items", :action => "list"
recai
redirect_to :controller => "items", :action => "show", :id => @item

Environment

abbrev inserts
flash
flash[:notice] = "Text here..."
logi
logger.info "Text here..."
par
params[:id]
ses
session[:user]

Models

abbrev inserts
bt
belongs_to :model, :class_name => "class", :foreign_key => "key"
hm
has_many :model, :class_name => "class", :foreign_key => "key", :dependent => :destroy
ho
has_one :model, :class_name => "class", :foreign_key => "key", :dependent => :destroy
vp
validates_presence_of :attr
vu
validates_uniqueness_of :attr
vn
validates_numericality_of :attr

Migrations

abbrev inserts
mct
create_table :name do |t|

end
mctf
create_table :name, :force => true do |t|

end
mdt
drop_table :name
mtcl
t.column "name", :type
mac
add_column :table_name, :column_name, :type
mcc
change_column :table_name, :column_name, :type
mrec
rename_column :table_name, :column_name, :new_column_name
mrmc
remove_column :table_name, :column_name
mai
add_index :table_name, :column_name
mait
add_index :table_name, :column_name, :index_type
mrmi
remove_index :table_name, :column_name

ERB

abbrev inserts
ft
<%= form_tag :action => "update" %>

<%= end_form_tag %>
lia
<%= link_to "title", :action => "index" %>
liai
<%= link_to "title", :action => "edit", :id => @item %>
lic
<%= link_to "title", :controller => "items" %>
lica
<%= link_to "title", :controller => "items", :action => "index" %>
licai
<%= link_to "title", :controller => "items", :action => "edit", :id => @item %>
%h
<%=h @item %>
%if
<% if cond -%>

<% end -%>
%ifel
<% if cond -%>

<% else -%>
<% end -%>
%unless
<% unless cond -%>

<% end -%>
%for
<% for elem in @list %>

<% end %>
%
<%  -%>
%%
<%=  %>

And that completes this article.

21 Comments

  1. km
    Posted December 19, 2006 at 12:39 am | Permalink

    How does one disable abbrev expansion in a comment? E.g. in

    # the class is to…

  2. core
    Posted December 20, 2006 at 4:17 am | Permalink

    km,

    This can be done by binding TAB to a function that checks if the face of the characters currently under the cursor are not §font-lock-comment-face’.

    Look at flyspell.el for an example. It’s flyspell-prog-mode does the same thing.

    regards

    Arjen

  3. sam
    Posted January 22, 2007 at 8:06 pm | Permalink

    great stuff! How could you comment out several lines at a time.. any shortcuts? thanks

  4. sam
    Posted January 24, 2007 at 8:41 pm | Permalink

    could you also mention how to edit the shortcuts in emacs? Like for example, to add shortcuts for the javascript and css helpers, or to change the syntax to use Haml?

    thanks!

  5. core
    Posted January 25, 2007 at 1:44 pm | Permalink

    Sam,

    Commenting and uncommenting multiple lines can be done with M-x comment-region and M-x uncomment-region, they are not bound by default, but you can change keyboard shortcuts like this (example defines global key combinations)

    (global-set-key "\C-co" 'occur)
    

    This sets ‘Ctrl-c o’ to the function occur.

    Emacs comes with a great manual which is available by pressing ‘C-h i’ and then going to the Emacs node. There is also a great wiki (http://www.emacswiki.org) which has a lot of information on how to do things with Emacs :)

  6. Posted February 15, 2007 at 8:57 am | Permalink

    I created an .rhtml minor mode that works pretty well for my needs:

    http://journal.dedasys.com/articles/2006/07/02/rhtml-minor-mode

  7. Posted February 15, 2007 at 12:34 pm | Permalink

    I use the following:

    (global-set-key “\C-c;” ‘comment-dwim)

    That gives you one keystroke… select a region and hit it to comment the region, hit it again to uncomment.

  8. core
    Posted February 15, 2007 at 1:29 pm | Permalink

    David,

    Very cool, I will have to check it out some time… I personally like nxml mode for editing xhtml files and it has been a long time since I used psgml :S

    Arjen

  9. core
    Posted February 15, 2007 at 1:29 pm | Permalink

    Alan,

    Thanks! I didn’t know about comment-dwim, you learn something new every day eh?

  10. Posted February 20, 2007 at 12:16 pm | Permalink

    First, thanks for that…really enjoy the MMM in rhtml.

    Question: snippet completion works great in *.rb files, but not at all in *.rhtml.

    I’ve exhausted my options for now. Any ideas?

    Thanks,
    Daniel

  11. Posted February 21, 2007 at 4:37 am | Permalink

    Daniel,

    Which ones do not work for you in .rhtml?…

    Arjen

  12. GramBorder
    Posted March 20, 2007 at 4:14 am | Permalink

    Hi all!

    I want to all of you know, World is mine, and yoursite good

    G’night

  13. Mike Miller
    Posted June 12, 2007 at 8:11 pm | Permalink

    In this bit of your code…

    (setq load-path (cons “~/.elsip/ruby-mode” load-path))

    …you wrote “elsip” instead of “elisp”! That one took a few minutes out of my day because I am no emacs guru.

    What you have done here is fantastic and amazing and you deserve a medal! A million thanks to you!!!

    Mike

  14. Mike Miller
    Posted June 12, 2007 at 8:16 pm | Permalink

    for one line of code you have this…

    http://www.webweavertech.com/ovidiu/emacs/find-recursive.txt

    …but I’m sure you meant to precede that with “wget “.

    I think you should recommend that the user add this to his .emacs in case he doesn’t have it:

    (setq load-path (cons “~/.elisp” load-path))

    He’ll get errors otherwise.

    Mike

  15. Posted June 28, 2007 at 8:40 pm | Permalink

    i’m not a emacs guru, i copied the code, but seems ‘(mmm-add-classes’ is not working, the emacs can not start normally

    and seems i removed this code:

    (defun try-complete-abbrev (old)
    (if (expand-abbrev) t nil))

    (setq hippie-expand-try-functions-list
    ‘(try-complete-abbrev
    try-complete-file-name
    try-expand-dabbrev))

    the ruby mode is still working, can you explain a little bit, great article btw

  16. Nate
    Posted July 7, 2007 at 9:05 am | Permalink

    Has anyone else noticed that flymake runs really, really slowly on occasion?

  17. Posted July 8, 2007 at 6:58 am | Permalink

    mmao:

    What is the error you get when using mmm-add-classes? try running with –debug-init…

    If it is still unclear, create a small .emacs file that just has the relevant stuff and fails for you and send it to me :)

    Arjen

  18. Posted July 8, 2007 at 6:59 am | Permalink

    Nate:

    Very occasionally, but not very often… :S

    Arjen

  19. Posted August 24, 2007 at 9:09 am | Permalink

    useful and cool

  20. spkthed
    Posted November 7, 2007 at 1:06 pm | Permalink

    This tutorial is very helpful, there’s a couple of things that would be very nice though. I know that it’s probably very obvious to someone more advanced with the Linux system but when you start adding commands for emacs:

    (setq load-path (cons “~/.elisp/ruby-mode” load-path))

    Someone that’s new wouldn’t have any idea of what you’re adding it too, also things like having ruby-mode installed, etc are leaps for newbies too have as well. You’ve got probably the best walkthrough that I’ve found on the web for setting this up, with a little bit more information you’ll have a guide that even a newbie can follow!

  21. Posted November 22, 2007 at 8:51 pm | Permalink

    Maybe you would like to test the latest version of nXhtml which at least has a little bit support for Ruby too (in the beta version):

    http://ourcomments.org/Emacs/DL/elisp/nxhtml/beta/

5 Trackbacks

  1. By Dharma Developer » RoR on Emacs on May 27, 2007 at 1:53 pm

    [...]  How to configure Emacs for RoR dev. [...]

  2. By Emacs and Rails « Software bits and pieces on July 3, 2007 at 10:56 am

    [...] have found the documentation to be a bit sketchy, but there is a good summary of the various snippets and some of the other commands [...]

  3. [...] a great post over here that will walk you through the details… don’t forget to read the comments as [...]

  4. [...] Personally, I like a highly portable text editor that is powerful enough to use as an IDE. TextMate, from what I hear, is pretty darn great, but by being limited to only Macs, TextMate doesn’t cut it for me. Emacs being emacs, it is quite possible to set it up to be as nice if not nicer than TextMate for Rails development. A good overview can be found at http://www.credmp.org/index.php/2006/11/28/ruby-on-rails-and-emacs/. [...]

  5. [...] Below there is a table with the Shortcuts that you can use into Emacs. For instance: open a Rails Controller and type the following into the method: flash[press tab], the result will be: flash[:notice] = “Text here…”. You can check a table of the Shortcuts in this link: http://www.credmp.org/2006/11/28/ruby-on-rails-and-emacs/ [...]

Post a Comment

Your email is never shared. Required fields are marked *

*
*