credmp

Ramblings of a code junkie

November 28, 2006

Ruby On Rails and Emacs

Written by
core

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$ 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 "~/.elsip/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.

Zarro Taags!

9 comments for this post.

  1. Comment from km on December 19th, 2006 :

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

    # the class is to…

  2. Comment from core on December 20th, 2006 :

    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. Comment from sam on January 22nd, 2007 :

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

  4. Comment from sam on January 24th, 2007 :

    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. Comment from core on January 25th, 2007 :

    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. Comment from David N. Welton on February 15th, 2007 :

    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. Comment from Alan Shutko on February 15th, 2007 :

    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. Comment from core on February 15th, 2007 :

    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. Comment from core on February 15th, 2007 :

    Alan,

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

Leave a Comment