启动快速迁移(shift+control+M),给我们的迁移起个名字叫做ModifyComments,自动生成了003_Modify_comments.rb并打开,光标放在了mtab后面,删除它,键入mcol,然后tab,选择
Rename / Rename Column
(3)
,键入
comments
⇥
name
⇥
author
⇥
↩
.
要记住修改comments的fixture文件。先打开文件(command+T,键入cy,回车),把name字段改名为author字段,并在每一节增加author_url,然后测试,应该都顺利通过。
Remove/Add Column命令自动在down方法中决定published字段为布尔字段,这取决于当前数据库db/schema.rb文件的状态。
这篇文章的原文在ruby on rails bundle的帮助里,在
TextMate里面打开rails项目的一个rb文件,使用快捷键Control+H,然后选择2,就可以看到这篇文章了。最新的ror bundle更新于上个月底,还是很具有参考价值的,对于如何快速使用
TextMate进行rails开发是一个不错的教程。
不过还有一些没完成。不是特别完整。但是基本的快捷键都包含在里面了。
---------英文原版教程
Step-by-step demonstration
Get Version
1.90.0
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, type 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
[email protected]? %>
<% 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.
Type cla 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 �C assert_redirected_to model_path(@model)
artpp �C assert_redirected_to models_path
artnp �C assert_redirected_to parent_child_path(@parent, @child)
artnpp �C 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 �C 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 [url]http://localhost:3000/posts/new[/url]
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 the new.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 �C <%= link_to "link text...", model_path(@model) %>
lipp �C <%= link_to "link text...", models_path %>
linp �C <%= link_to "link text...", parent_child_path(@parent, @child) %>
linpp �C <%= link_to "link text...", parent_child_path(@parent) %>
lim �C <%= link_to model.name, model_path(model) %>
The tab stop points are in useful places.
So, to create our link to the posts page, type lipp 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 (⌥ ⌘ ↓).
Below the 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 �C redirect_to(model_path(@model))
repp �C redirect_to(models_path)
renp �C redirect_to(parent_child_path(@parent, @child))
renpp �C redirect_to(parent_child_path(@parent))
There are tab stops in useful places.
In the false 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.
Select the whole line containing the 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 [url]http://localhost:3000/posts/new[/url] and then view them as a blog visitor at [url]http://localhost:3000/blog.[/url]
Some more migrations
We’re looking for the following additions:
rename the column name of table comments to author
add a new column author_url to table comments
add an index to the column post_id of the comments 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.