Step-by-step demonstration
In this demo we’ll create a blog; because that’s what blogs are for: being demonstrations of web frameworks.
The demonstration uses new features of Rails 2.0 and the snippets in this bundle.
A New App
rails blog cd blog mate .
Add some models
ruby script/generate model Post subject:string body:text
This creates a 001_create_posts migration with a create_table:
create_table :posts do |t| t.string :subject t.text :body t.timestamps end
Sexy Migration support
If you put the cursor on the line after t.text :body
, type t.
and press ⇥. Select “Create boolean column” (by pressing 0), and type “published” into the template field. If nothing happened when you pressed ⇥, check that when you opened the migrations file you’ve selected the bundle “Ruby on Rails”.
Note that another t.
was created on the next line! Press ⇥ and the cursor will be placed after it. You can now press ⇥ again to create another column, or delete this line.
Here, delete the extraneous t.
line (⌃ ⇧ K). And save the file (⌘ S).
Run the migrations, either from the prompt:
rake db:migrate
or directly from the editor with ⌃ | (Ctrl-Pipeline), and choosing option “Migrate to Current”.
Post fixtures
Update the test/fixtures/posts.yml
file as:
published: subject: Some article body: A test article published: true nonpublished: body: Still writing this one
Note, in Rails 2.0 fixtures no longer have explicit ids. Later on we’ll look at snippets for using Foxy Fixtures with auto-completion for associations.
Public blog controller
Create a controller for our blog, either via the command prompt:
ruby script/generate controller blog
or directly from the editor with ⌃ |, and choosing option “Call Generate Script”, choose “Controller”, give it the name “blog”, and empty the list of actions.
Now open blog_controller_test.rb
. To find this file quickly press ⌘ T, enter bct, and select the file.
Note how much cleaner functional tests are now via ActionController::TestCase
.
Let’s do some TDD. First, delete the test_truth
dummy method.
To create a test to show a list of blog articles:
deftg
and ⇥ gives:
def test_should_get_action @model = models(:fixture_name) get :action, :id => @model.to_param assert_response :success end
Type index
to replace action
. Press ⇥, and then ⌫ to remove the first line, then press ⇥ three times and then ⌫ to remove the :id => @model.to_param
part. The press ⇥ again to go to the end of the method. Now we have:
def test_should_get_index get :index assert_response :success end
Now type asg
, press ⇥, and type posts
, and press ⇥ again. This creates an instance variable lookup within an assertion:
assert(posts = assigns(:posts), "Cannot find @posts")
Now, let’s assert the HTML format.
Type ass
and press ⇥. Type div#posts
, press ⇥ and ⌫, then ⇥ twice to place the cursor within the assert_select
block:
assert_select 'div#posts' do end
Now we’ll check that the @posts
objects are represented in the div#posts
element.
With the cursor inside the assert_select
:
Type ass
, press ⇥, type div.post
, press ⇥ twice, and type count
(to replace the text
). Now press ⇥ again, and type posts.size
. Press ⇥ a final time (it will highlight the do...end
block), and press ⌫.
Our test method is now finished:
def test_should_get_index get :index assert_response :success assert(posts = assigns(:posts), "Cannot find @posts") assert_select 'div#posts' do assert_select 'div.post', :count => posts.size end end
NOTE: there is also a deftp
snippet for functional tests to create a POST test stub.
To memorize: deftg
stands for define test get
and deftp
stands for define test post
Controller actions
To navigate to blog_controller.rb
there are three options:
- press ⇧ ⌥ ⌘ ↓, and select “Controller” from the drop-down list
- press ⌥ ⌘ ↓ and you’ll go directly to the controller (toggles between the two files)
- press ⌘ T, type
bc
, choose the file, press ↩.
Add the index
action method:
def index @posts = Post.find_all_by_published(true) end
Action views
To create/navigate to the view, press ⇧ ⌥ ⌘ ↓ and select “View” (like above). Or press ⌥ ⌘ ↓ to toggle between a controller method and it’s view.
As there are no app/views/blog/index*
files, it will prompt you to create a blank view file. By default it guesses index.html.erb
(because the method was named index
), but of course you can change that in the dialog box.
If instead you got the message “blog_controller.rb does not have a view”, note that you first need to save the controller file before hitting ⇧ ⌥ ⌘ ↓ or ⌥ ⌘ ↓. Also note that the cursor must be within the scope of a method for ⇧ ⌥ ⌘ ↓ or ⌥ ⌘ ↓ to work.
Press enter to accept index.html.erb
. You are taken to the new file.
Let’s create HTML to match the earlier tests.
Type div
and press ⇥ twice, then type posts
and press ⇥:
<div id="posts"> </div>
Inside the div
element, type for
and press ⇥. This expands into a large ERb-enabled for-loop. Type @posts
, press ⇥, type post
and press ⇥. The cursor is now inside the for-loop.
Inside the for-loop, type: div
and press ⇥. Press ⌫, and type class='post'
and press ⇥ to enter the div
element.
Create a <%= %>
element (⌃ >). If you press ⌃ > again, it toggles to <% %>
, and then again and it becomes <%- -%>
, and again and it becomes <%# %>
(a Ruby comment). Pressing ⌃ > again starts at <%= %>
again.
Enter post.body
within the ERb template field.
Actually, we’ll need to show the subject too, so above the <%= post.body %>
line (press ↑ followed by ⌘ ↩)
type ‘h3’, and press ⌃ < (LessThan), then ⌃ > (GreatherThan), and post.subject
.
The resulting line is: <h3><%= post.subject %></h3>
Move the cursor down between <% else %>
and <% end %>
.
Create a simple element <p></p>
(⌃ ⇧ W or ⌃ <). You can change the element type here. Just press ⇥ to go inside the element. Type There are no posts available to read. All y'all come back soon, yer hear.
because its funny.
Our index.html.erb
template is now:
<div id="posts"> <% if !@posts.blank? %> <% for post in @posts %> <div class="post"> <h3><%= post.subject %></h3> <%= post.body %> </div> <% end %> <% else %> <p>There are no posts available to read. All y'all come back soon, yer hear.</p> <% end %> </div>
If we run our functional tests they now pass: run either from the command prompt with rake test:functionals
or directly from the editor by pressing ⌃ \ and press 2 for “Test Functionals”
As yet, we have no way for users to leave comments.
Foxy Fixtures
Create a comment model:
ruby script/generate model Comment body:text name:string post:references
Note: here post:references
is effectively the same as post_id:integer
. Within the generated migration it creates t.reference :post
. There is also a t.
and tcr
snippet for references, as for other standard datatypes, which helps setup polymorphic associations.
The generated create_table
in 002_create_comments.rb
is:
create_table :comments do |t| t.text :body t.string :name t.references :post t.timestamps end
Run rake db:migrate
, or directly from the editor with ⌃ | and choose option “Migrate to Current”.
Now create some comment fixtures so we can look at Foxy Fixtures. Open text/fixtures/comments.yml
(⌘ T, type cy
, press ↩).
By default, the generated comments.yml
starts like:
one: body: MyText name: MyString post: two: body: MyText name: MyString post:
The post
fields replace the rails1.2 post_id
fields. Now, we can specify the post.yml
labels for a post. From above we have published
and unpublished
. It can be hard to remember what fixtures we have, so there is a key-combo helper.
Put the cursor after post:
and press ⌥ ⎋. A drop-down box appears with the names of the posts.yml
fixtures. Select published
and press ↩. Repeat for the 2nd fixture. This gives us:
one: body: MyText name: MyString post: published two: body: MyText name: MyString post: published
Associations
To enable the Foxy Fixtures, we need to add associations to the model classes.
You can now quickly go from a fixtures file (we’re in comments.yml) to the model file (⇧ ⌥ ⌘ ↓).
Within comment.rb
model, create a new line within the class, and type bt
and press ⇥. Type post
. This creates a snippet:
belongs_to :post, :class_name => "Post", :foreign_key => "post_id"
The class name and foreign key are now generated from the association name. You can change them by tabbing across. But, we only need the default, so we can delete these options.
Press ⇥ and ⌫ to remove the :class_name
and :foreign_key
options. The Comment
class is now:
class Comment < ActiveRecord::Base belongs_to :post end
Now go to the Post
class. Press ⌘ T and type post
and select the model file, and press ↩.
Create a new line within the Post
class (⌘ ↩). Type hm
and press ⇥ to generate a has_many
association. Type comment
, and the resulting snippet is:
has_many :comments, :class_name => "comment", :foreign_key => "class_name_id"
We don’t need the options. So press ⇥ once and then ⌫.
class Post < ActiveRecord::Base has_many :comments end
Note: there is also a has_many :through
snippet. Type hmt
and ⇥ to activate it.
Finally, we can run our tests since adding the Comment
model + fixtures (⌃ \).
rake test
Routes
Open the routes file (⌘ T, type routes
and press ↩).
Change the routes file to:
ActionController::Routing::Routes.draw do |map| map.resources :posts map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
Creating Posts
From the Post
model class (post.rb
) you can now quickly navigate to a controller
of the same name. It supports either singular or plural controller names, but
will default to the plural name, which is the REST/resources preferred name.
To create a PostsController
, use the ‘Go To’ hot key (as above) ⇧ ⌥ ⌘ ↓ and select ‘Controller’. As there is no post_controller.rb
nor posts_controller.rb
it will create a posts_controller.rb
controller file; which is what we want here.
Note; at this stage you could use the Rails 2.0 scaffold
generator to create the posts_controller.rb
(and tests and routes).
In the blank file, we need to create a controller class.
Typecla
and ⇥, and select “Create controller class”. Type Posts
and ⇥,
post
and ⇥, and finally, Post
and ⇥. This leaves the cursor in the middle
of the generated class:
class PostsController < ApplicationController before_filter :find_post private def find_post @post = Post.find(params[:id]) if params[:id] end end
TDD for Posts controller
Currently there is not a functional test for our posts_controller.rb
. To create it, use the ‘Go To’ hot key (⇧ ⌥ ⌘ ↓) and select ‘Functional Test’. This will create a blank file.
Type cla
and ⇥, and select “Create functional test class”.
Type Posts
and ⇥. (The functional test class name
should match the controller class, with Test
suffixed to it).
The functional test class snippet gives you a deft
stub. If you
press ⇥ now, it creates a generic test method snippet:
def test_case_name end
Instead, we will use the deftg
(GET request) and deftp
(POST request) snippets.
Create a test for the index
, new
and edit
actions. For index
and new
, we can delete the @model = models(:fixture_name)
,
etc parts.
To test for the create
action, type deftp
and ⇥. Type create
and ⇥, type post
and ⇥, type ⌫ and ⇥, and again ⌫ and ⇥. Now enter in a hash of the values to pass in for the test, say :subject => 'Test', :body => 'Some body', :published => '1'
. The result should look like:
def test_should_post_create post :create, :post => { :subject => 'Test', :body => 'Some body', :published => '1' } assert_response :redirect end
On the line after the assert_response
expression, we’ll test
for where we want to be redirected to.
If you type art
you create an old-style assert_redirected_to :action => "index"
snippet.
In addition there are now various assert_redirected_to
snippets that
use resourceful routes:
- artp –
assert_redirected_to model_path(@model)
- artpp –
assert_redirected_to models_path
- artnp –
assert_redirected_to parent_child_path(@parent, @child)
- artnpp –
assert_redirected_to parent_child_path(@parent)
As we’ll see later, this naming scheme is used for other snippets that
use resourceful routes, like link_to
and redirect_to
.
Type artpp
and ⇥, and type post
, to assert that the create
action must redirect to the index page.
The final test_should_post_create
method is:
def test_should_post_create post :create, :post => { :subject => 'Test', :body => 'Some body', :published => '1' } assert_response :redirect assert_redirected_to posts_path end
Running our tests (rake test:functionals
or ⌃ \) shows all these new tests failing.
Views
Go back to the posts_controller.rb
file (⌥ ⌘ ↓).
Now add three actions – index
, new
and edit
. New methods can be created
with the def
snippet:
class PostsController < ApplicationController before_filter :find_post def index @posts = Post.find(:all) end def new @post = Post.new end def edit end private def find_post @post = Post.find(params[:id]) if params[:id] end end
Note: the index
method could be created by typing def
, ⇥, index
, ⇥, @posts = Post.fina
, ⇥, ⌫.
Now we need templates for the index
, new
and edit
actions.
Place the cursor inside the index
method,
and use the ‘Go To’ hot key (⇧ ⌥ ⌘ ↓)
and select ‘View’. A dialog box will pop up asking for the name of the new
template (as there are no app/views/posts/index*
files). By default, the
suffix is now .html.erb
rather than the old .rhtml
. Press ↩,
to accept index.html.erb
as your template name.
Let’s just create a simple table showing the Posts.
Type table
and ⌃ < to generate <table></table>
, and
press ↩ to put the tags on separate lines.
Do the same to create a <tbody></tbody>
element.
Inside the <tbody></tbody>
we want to iterate over the @posts
,
one per <tr></tr>
row.
Press ⌃ >, three times, to create a <%- -%>
tag. Inside it
type @posts.each do |post|
.
On the next line (⌘ ↩), type end
and ⇥, to create <% end -%>
.
We now have a Ruby block within this ERb template.
Inside the block, create a <tr></tr>
element, and within it
create a <td></td>
element. We’ll skip over anything fancy
here, and just put the post’s subject here.
Type post.subject
and select it. Now press ⌃ > to wrap
the selected text inside <%= post.subject %>
.
The resulting index.html.erb
is:
<table> <tbody> <%- @posts.each do |post| -%> <tr> <td><%= post.subject %></td> </tr> <% end -%> </tbody> </table>
Forms
Place the cursor inside the new
method,
and use the ‘Go To’ hot key (⇧ ⌥ ⌘ ↓)
and select ‘View’. Press ↩ to accept new.html.erb
.
Inside the blank new.html.erb
file, type ffe
and press ⇥, and type post
and press ⇥ twice:
<%= error_messages_for :post %> <% form_for @post do |f| -%> <% end -%>
form_for
is the Rails 2.0 preferred helper for managing forms, and
there are now snippets for common form_for helpers. There are ff
and ffe
snippets; the former does not have the error messages section.
To create a label and text field for the subject
attribute:
Create a <p></p>
block (Press ⌃ <, then ⇥, then ↩).
Type f.
and ⇥, and select “Label”. Type subject
, press ⇥ and press ⌫.
Create a <br />
(⌃ ↩).
Type f.
and ⇥, and select “Text Field”. Type subject
.
This gives us:
<%= error_messages_for :post %> <% form_for @post do |f| -%> <p> <%= f.label :subject %><br /> <%= f.text_field :subject %> </p> <% end -%>
Now repeat for body
and published
fields.
Note, for published
, you might change the label to Published yet?
by tabbing
into the default string file.
Finally, add a “Submit” button using the f.
snippet tab completion.
Start script/server
from the prompt and you can now view this form at http://localhost:3000/posts/new
The final form is:
<%= error_messages_for :post %> <% form_for @post do |f| -%> <p> <%= f.label :subject %><br /> <%= f.text_field :subject %> </p> <p> <%= f.label :body %><br /> <%= f.text_area :body %> </p> <p> <%= f.label :published, "Published yet?" %><br /> <%= f.check_box :published %> </p> <p> <%= f.submit "Submit" %> </p> <% end -%>
Note: if you got <br>
when hitting ⌃ ↩ instead of <br />
then you might want to go to the preferences of TextMate (⌘ ,), choose tab “Advanced”, choose “Shell Variables”, click the + sign to add a new shell variable, and give it the name TM_XHTML
and a value of /
Partials
The form we just created is exactly the same as the form required for the edit.html.erb
template.
Instead of copy+pasting it into the edit.html.erb
file, we’ll create a partial
template.
Select the entire form (⌘ A), and press ⌃ ⇧ H and a dialog box appears.
Type in form
and press ↩.
You’ll notice a new file _form.html.erb
has appeared which contains the code you had selected,
while the code in the file new.html.erb
has been replaced by:
<%= render :partial => 'form' %>
Now copy and paste this into the edit.html.erb
file. To create this file,
return to the controller (from the new.html.erb
file, press ⌥ ⌘ ↓), go to the edit
action,
and use ⌥ ⌘ ↓ again to create the edit.html.erb
template file.
Link helpers
At the bottom of thenew.html.erb
we want a link back to the list of all posts
(within the posts controller, not the public blog controller). This
will be the index
action, and will be accessible via the resources route
posts_path
.
There are several link_to
snippets that support the resources routes:
- lip –
<%= link_to "link text...", model_path(@model) %>
- lipp –
<%= link_to "link text...", models_path %>
- linp –
<%= link_to "link text...", parent_child_path(@parent, @child) %>
- linpp –
<%= link_to "link text...", parent_child_path(@parent) %>
- lim –
<%= link_to model.name, model_path(model) %>
The tab stop points are in useful places.
So, to create our link to the posts page, typelipp
and ⇥, type
Show all posts
, press ⇥ twice and type post
.
Controllers: respond_to
and redirect_to
Now we’ll add a create
action to the posts_controller.rb
. Let’s go there (⌥ ⌘ ↓).
edit
method type def
and ⇥, and type
create
and ⇥. Now fill out the create
action like:
def create @post = Post.new(params[:post]) if @post.save else end end
Place the cursor in the true
section of the if
statement.
Type repp
and ⇥ to create a redirect_to
expression. Press ⇥
again and replace the selected text with post
.
Like the various link_to
snippets, there are matching redirect_to
snippets.
- rep –
redirect_to(model_path(@model))
- repp –
redirect_to(models_path)
- renp –
redirect_to(parent_child_path(@parent, @child))
- renpp –
redirect_to(parent_child_path(@parent))
There are tab stops in useful places.
In thefalse
section of the if
expression, we’ll demonstrate the
respond_to
block. There are two ways to generate a respond_to
block.
Type rest
and ⇥, and you get a standard empty block you can work with:
respond_to do |wants| wants.html { } end
Press ⇥ twice to get inside the wants.html
block, type ra
, press ⇥, then type new
. The final block is:
respond_to do |wants| wants.html { render :action => "new" } end
Alternately, there is the “upgrade” hot key (⇧ ⌘ H), where you can convert some
existing selected code, into a respond_to
block.
redirect_to
expression from the
true
section of the if
statement (⇧ ⌘ L).
Press ⇧ ⌘ H and the line is replaced with:
respond_to do |wants| wants.html do redirect_to(posts_path) end wants.js { } end
The js
is the first tab stop. The point of this hot key is to instantly
refactor your existing html respond code, and support a second response
format.
For now remove the line with wants.js
(⌃ ⇧ K).
The completed create
action is:
def create @post = Post.new(params[:post]) if @post.save respond_to do |wants| wants.html do redirect_to(posts_path) end end else respond_to do |wants| wants.html { render :action => "new" } end end end
Yes you’d probably only have one respond_to
block, but this is a
demo so I am taking the scenic route.
Our application so far
In the browser, we can create posts via http://localhost:3000/posts/new and then view them as a blog visitor at http://localhost:3000/blog.
Some more migrations
We’re looking for the following additions:
- rename the column
name
of tablecomments
toauthor
- add a new column
author_url
to tablecomments
- add an index to the column
post_id
of thecomments
table
Let’s try to do this all in one migrations file. Start Quick Migration (⌃ ⇧ M). Let’s name it ModifyComments
. A new migrations file 003_modify_comments.rb
is created and opened, and the cursor is placed behind the mtab
trigger. For now delete mtab
and instead enter mcol
and press ⇥. Choose Rename / Rename Column
(3). Type comments
⇥ name
⇥ author
⇥ ↩.
Again type mcol
and ⇥. This time choose Add / Remove Column
(1). Type comments
⇥ author_url
, then ⇥ twice, and press ↩.
Now type mind
and ⇥. Choose Add / Remove Index
(1). Type comments
⇥ post_id
.
The end result looks like this:
class ModifyComments < ActiveRecord::Migration def self.up rename_column :comments, :name, :author add_column :comments, :author_url, :string add_index :comments, :post_id end def self.down remove_index :comments, :post_id remove_column :comments, :author_url rename_column :comments, :author, :name end end
Notice how the down
method calls are in reversed order of the up
method calls.
Save the file (⌘ S) and migrate to current (⌃ |).
Be sure to modify the comments fixture file. Go there (⌘ T, press cy
, choose comments.yml
). Rename name
to author
and add a row for author_url
for each comment. Check your tests again (⌃ \, choose option 1). All tests should pass.
Futhermore we’d like to know when a post was published. To do this we’ll want the following modifications:
- keep track of the datetime when a post was published.
- remove the column published from the posts table because it can be determined if a post is published by looking at whether or not a value is present for the published date.
Start Quick Migration (⌃ ⇧ M). Let’s name it AddPublishedAtForPosts
. A new migrations file 004_add_published_at_for_posts.rb
is created and opened, and the cursor is placed behind the mtab
trigger. Again delete mtab
and instead enter mcol
and press ⇥. Choose Add / Remove Column
(1). Type posts
⇥ published_at
⇥ datetime
⇥ and ↩.
Again type mcol
and ⇥. Choose Remove / Add Column
(5). Type posts
⇥ published
and press ⇥ twice.
The end result looks like this:
class AddPublishedAtForPosts < ActiveRecord::Migration def self.up add_column :posts, :published_at, :datetime remove_column :posts, :published end def self.down add_column :posts, :published, :boolean remove_column :posts, :published_at end end
Notice how the Remove / Add Column
command automagically determined in the down
method the column type of column published
to be a boolean
. It determines this by looking at the current state of your db/schema.rb
file.
Save the file (⌘ S) and migrate to current (⌃ |).
Now we need to modify the posts fixtures file. Go there (⌘ T, type pyml
, choose posts.yml
). Replace the line published: true
by published_at: 2008-1-1
.
Modify the posts functional test, first go there (⇧ ⌥ ⌘ ↓, choose “Go to Functional Test”). Replace :published => '1'
by :published_at => Date.new(2008, 1, 1)
.
Modify the post model, first go there (⇧ ⌥ ⌘ ↓, choose “Go to Model”). Have the code look like:
class Post < ActiveRecord::Base has_many :comments def published !self.published_at.nil? end def published=(publish) if publish self.published_at = DateTime.now if self.published_at.nil? else self.published_at = nil end end end
Modify the blog_controller.rb
file. Replace Post.find_all_by_published(true)
by Post.find(:all, :conditions => "published_at IS NOT NULL")
.
Finally, check your tests again (⌃ \). All tests should pass.
TODO
- Model snippets (validates_…)
- link_to(model) (ltm)
- RJS demo
Dr Nic Williams, 28th February 2008
Theme extended from Paul Battley