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
How does one disable abbrev expansion in a comment? E.g. in
# the class is to…
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
great stuff! How could you comment out several lines at a time.. any shortcuts? thanks
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!
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)
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
I created an .rhtml minor mode that works pretty well for my needs:
http://journal.dedasys.com/articles/2006/07/02/rhtml-minor-mode
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.
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
Alan,
Thanks! I didn’t know about comment-dwim, you learn something new every day eh?
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
Daniel,
Which ones do not work for you in .rhtml?…
Arjen
Hi all!
I want to all of you know, World is mine, and yoursite good
G’night
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
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
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
Has anyone else noticed that flymake runs really, really slowly on occasion?
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
Nate:
Very occasionally, but not very often… :S
Arjen
useful and cool
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!
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
[...] How to configure Emacs for RoR dev. [...]
[...] 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 [...]
[...] a great post over here that will walk you through the details… don’t forget to read the comments as [...]
[...] 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/. [...]
[...] 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/ [...]