What’s rails?
Rails 是使用 Ruby 语言编写的网页程序开发框架,目的是为开发者提供常用组件,简化网页程序的开发。只需编写较少的代码,就能实现其他编程语言或框架难以企及的功能。经验丰富的 Rails 程序员会发现,Rails 让程序开发变得更有乐趣。
1.安装 Rails
执行命令,确认是否已经安装 Ruby
和 Sqlite3
。Mac 默认安装了 Ruby,很多类 Unix
系统都自带了版本尚新的 SQLite3
。
$ ruby -v
ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin14]
$ sqlite3 --version
3.8.5 2014-08-15 22:37:57 c8ade949d4a2eb3bba4702a4a0e17b405e9b6ace
然后执行命令安装 rails :
$ sudo gem install rails
检查所有软件是否都正确安装了,可以执行下面的命令:
rails --version
如果显示的结果类似“Rails 4.2.1”,那么就可以继续往下读了。
1.1新建 Blog
执行命令:
$rails new blog
这个命令会在文件夹 blog
中新建一个 Rails
程序,然后执行 bundle install
命令安装 Gemfile
中列出的 gem
。
注意的是把 Gemfile
里面的第一行改成 http://ruby.taobao.org
。
生成 blog 程序后,进入该文件夹:
$ cd blog
1.2启动服务器
在 blog 文件夹中执行下面的命令:
$ rails server
会看到
上述命令会启动 WEBrick
,这是 Ruby
内置的服务器。要查看程序,请打开一个浏览器窗口,访问 http://localhost:3000。应该会看到默认的 Rails 信息页面:
来到这里,整个搭建流程就基本成功搭建完成了。停止服务器,请在命令行中按 Ctrl+C
键。
2.显示“Hello, Rails!”
要在 Rails
中显示 “Hello, Rails!”
,需要新建一个控制器和视图。
控制器用来接受向程序发起的请求。路由决定哪个控制器会接受到这个请求。一般情况下,每个控制器都有多个路由,对应不同的动作。动作用来提供视图中需要的数据。
视图的作用是,以人类能看懂的格式显示数据。有一点要特别注意,数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来。默认情况下,视图使用 eRuby
(嵌入式 Ruby
)语言编写,经由 Rails
解析后,再发送给用户。
控制器可用控制器生成器创建,你要告诉生成器,我想要个名为 “welcome”
的控制器和一个名为 “index”
的动作,如下所示:
$ rails generate controller welcome index
运行上述命令后,Rails 会生成很多文件,以及一个路由。
在这些文件中,最重要的当然是控制器,位于 app/controllers/welcome_controller.rb
,以及视图,位于 app/views/welcome/index.html.erb
。
使用文本编辑器打开 app/views/welcome/index.html.erb
文件,删除全部内容,写入下面这行代码:
<h1>Hello, Rails!</h1>
2.1设置首页
在编辑器中打开 config/routes.rb
文件。
这是程序的路由文件,使用特殊的 DSL
( domain-specific language
,领域专属语言)编写,告知 Rails
请求应该发往哪个控制器和动作。文件中有很多注释,举例说明如何定义路由。其中有一行说明了如何指定控制器和动作设置网站的根路由。找到以 root
开头的代码行,去掉注释,变成这样:
root 'welcome#index'
告知 Rails
,访问程序的根路径时,交给 welcome
控制器中的 index
动作处理。get 'welcome/index'
告知 Rails
,访问 http://localhost:3000/welcome/index
时,交给 welcome
控制器中的 index
动作处理。get 'welcome/index'
是运行 rails generate controller welcome index
时生成的。
打开服务器后,就能看到:
3开始使用
3.1 添加资源
Rails
提供了一个 resources
方法,可以声明一个符合 REST
架构的资源。创建文章资源后,config/routes.rb
文件的内容如下:
Rails.application.routes.draw do
resources :articles
root 'welcome#index'
end
执行 rake routes
任务,会看到定义了所有标准的 REST
动作。输出结果中各列的意义稍后会说明,现在只要留意 article
的单复数形式,这在 Rails
中有特殊的含义。
程序中要有个页面用来新建文章。一个比较好的选择是 /articles/new
。这个路由前面已经定义了,可以访问。打开 http://localhost:3000/articles/new
,会看到如下的路由错误:
产生这个错误的原因是,没有定义用来处理该请求的控制器。解决这个问题的方法很简单:创建名为 ArticlesController
的控制器。执行下面的命令即可:
$ rails g controller articles
如下图:
现在刷新 http://localhost:3000/articles/new
,会看到一个新错误:
手动创建动作只需在控制器中定义一个新方法。打开 app/controllers/articles_controller.rb
文件,在 ArticlesController
类中,定义 new
方法,如下所示:
class ArticlesController < ApplicationController
def new
end
end
在 ArticlesController
中定义 new
方法后,再刷新 http://localhost:3000/articles/new
,看到的还是个错误:
错误信息解释:
第一部分说明找不到哪个模板,这里,丢失的是 articles/new
模板。Rails
首先会寻找这个模板,如果找不到,再找名为 application/new
的模板。之所以这么找,是因为 ArticlesController
继承自 ApplicationController
。
后面一部分是个 Hash
。:locale
表示要找哪国语言模板,默认是英语("en")
。:format
表示响应使用的模板格式,默认为 :html
,所以 Rails
要寻找一个 HTML
模板。:handlers
表示用来处理模板的程序,HTML
模板一般使用 :erb
,XML
模板使用 :builder
,:coffee
用来把 CoffeeScript
转换成 JavaScript
。
让这个程序正常运行,最简单的一种模板是 app/views/articles/new.html.erb
。新建文件 app/views/articles/new.html.erb
,写入如下代码:
<h1>New Article</h1>
3.1创建表单
要在模板中编写表单,可以使用“表单构造器”。Rails
中常用的表单构造器是 form_for
。在 app/views/articles/new.html.erb
文件中加入以下代码:
<%= form_for :article do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
显示结果如下:
但是这个表单还有个问题。如果查看这个页面的源码,会发现表单 action
属性的值是 /articles/new
。这就是问题所在,因为其指向的地址就是现在这个页面,而这个页面是用来显示新建文章表单的。
要想转到其他地址,就要使用其他的地址。这个问题可使用 form_for
方法的 :url
选项解决。在 Rails
中,用来处理新建资源表单提交数据的动作是 create
,所以表单应该转向这个动作。
修改 app/views/articles/new.html.erb
文件中的 form_for
,改成这样:
<%= form_for :article, url: articles_path do |f| %>
3.2创建文章
在 ArticlesController
类中定义 create
方法。在 app/controllers/articles_controller.rb
文件中 new
方法后面添加以下代码:
class ArticlesController < ApplicationController
def new
end
def create
render plain: params[:article].inspect
end
end
render
方法接受一个简单的 Hash
为参数,这个 Hash
的键是 plain
,对应的值为 params[:article].inspect
。params
方法表示通过表单提交的参数,返回 ActiveSupport::HashWithIndifferentAccess
对象,可以使用字符串或者 Symbol
获取键对应的值。
3.3创建 Article 模型
在 Rails 中,模型的名字使用单数,对应的数据表名使用复数。
创建模型,执行命令:
$ rails generate model Article title:string text:text
如下图:
这个命令告知 Rails,我们要创建 Article 模型,以及一个字符串属性 title 和文本属性 text。这两个属性会自动添加到 articles 数据表中,映射到 Article 模型。执行这个命令后,Rails 会生成一堆文件。现在我们只关注 app/models/article.rb
和 db/migrate/20150528013459_create_articles.rb
(你得到的文件名可能有点不一样)这两个文件。后者用来创建数据库结构。
3.4运动迁移
执行命令:
$ rake db:migrate
Rails 会执行迁移操作,告诉你创建了 articles 表。
3.5在控制器中保存数据
打开 app/controllers/articles_controller.rb
文件,把 create
动作修改成这样:
def create
@article = Article.new(params[:article])
@article.save
redirect_to @article
end
在 Rails 中,每个模型可以使用各自的属性初始化,自动映射到数据库字段上。create
动作中的第一行就是这个目的(还记得吗,params[:article]
就是我们要获取的属性)。@article.save
的作用是把模型保存到数据库中。保存完后转向 show
动作。稍后再编写 show
动作。
再次访问 http://localhost:3000/articles/new,填写表单提交后,出现下面错误:
Rails 提供了很多安全防范措施保证程序的安全,你所看到的错误就是因为违反了其中一个措施。这个防范措施叫做“健壮参数”
,我们要明确地告知 Rails 哪些参数可在控制器中使用。这里,我们想使用 title 和 text 参数。请把 create 动作修成成:
def create
@article = Article.new(article_params)
@article.save
redirect_to @article
end
private
def article_params
params.require(:article).permit(:title, :text)
end
3.6显示文章
在 rake routes
的输出中看到,show
动作的路由是:
article GET /articles/:id(.:format) articles#show
:id
的意思是,路由期望接收一个名为 id
的参数,在这个例子中,就是文章的 ID
。
在 app/controllers/articles_controller.rb
文件中添加 show
动作,以及相应的视图文件。
def show
@article = Article.find(params[:id])
end
注意的是,我们调用 Article.find
方法查找想查看的文章,传入的参数 params[:id]
会从请求中获取 :id
参数。我们还把文章对象存储在一个实例变量中(以 @
开头的变量),只有这样,变量才能在视图中使用。
然后,新建 app/views/articles/show.html.erb
文件,写入下面的代码:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
3.7列出所有文章
列出所有文章,对应的路由是:
articles GET /articles(.:format) articles#index
在 app/controllers/articles_controller.rb
文件中,为ArticlesController
控制器添加 index
动作:
def index
@articles = Article.all
end
然后编写这个动作的视图,保存为 app/views/articles/index.html.erb
。
3.8添加链接
打开 app/views/welcome/index.html.erb
文件,改成这样:
<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>
link_to
是 Rails
内置的视图帮助方法之一,根据提供的文本和地址创建超链接。这上面这段代码中,地址是文章列表页面。
接下来添加到其他页面的链接。先在 app/views/articles/index.html.erb
中添加 “New Article”
链接,放在 <table>
标签之前:
<%= link_to 'New article', new_article_path %>
点击这个链接后,会转向新建文章的表单页面。
然后在 app/views/articles/new.html.erb
中添加一个链接,位于表单下面,返回到 index
动作:
<%= form_for :article do |f| %>
...
<% end %>
<%= link_to 'Back', articles_path %>
最后,在 app/views/articles/show.html.erb
模板中添加一个链接,返回 index
动作,这样用户查看某篇文章后就可以返回文章列表页面了:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
如下图显示:
3.9更新文章
首先,要在 ArticlesController
中添加 edit
动作:
def edit
@article = Article.find(params[:id])
end
视图中要添加一个类似新建文章的表单。新建 app/views/articles/edit.html.erb
文件,写入下面的代码:
<h1>Editing article</h1>
<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
method: :patch
选项告诉 Rails
,提交这个表单时使用 PATCH
方法发送请求。根据 REST
架构,更新资源时要使用 HTTP PATCH
方法。
form_for 的第一个参数可以是对象,例如 @article
,把对象中的字段填入表单。如果传入一个和实例变量(@article)同名的 Symbol(:article)
,效果也是一样。
然后,要在 app/controllers/articles_controller.rb
中添加 update
动作:
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render 'edit'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
新定义的 update
方法用来处理对现有文章的更新操作,接收一个 Hash
,包含想要修改的属性。和之前一样,如果更新文章出错了,要再次显示表单。
最后,我们想在文章列表页面,在每篇文章后面都加上一个链接,指向 edit
动作。打开 app/views/articles/index.html.erb
文件,在 Show
链接后面添加Edit
链接:
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="2"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
</tr>
<% end %>
</table>
还要在 app/views/articles/show.html.erb
模板的底部加上 Edit
链接:
...
<%= link_to 'Back', articles_path %>
<%= link_to 'Edit', edit_article_path(@article) %>
3.10删除文章
从数据库中删除文章。按照 REST 架构的约定,删除文章的路由是:
DELETE /articles/:id(.:format) articles#destroy
删除资源使用 DELETE
方法,路由会把请求发往 app/controllers/articles_controller.rb
中的 destroy
动作。destroy
动作现在还不存在,下面来添加:
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path
end
最后,在 index
动作的模板 (app/views/articles/index.html.erb)
中加上 Destroy
链接:
<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
如下图显示:
3.11添加数据验证
打开 app/models/article.rb
文件,修改成:
class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
end
添加的这段代码可以确保每篇文章都有一个标题,而且至少有五个字符。
添加数据验证后,如果把不满足验证条件的文章传递给 @article.save
,会返回 false
。打开 app/controllers/articles_controller.rb
文件,会发现,我们还没在 create
动作中检查 @article.save
的返回结果。如果保存失败,应该再次显示表单。为了实现这种功能,请打开 app/controllers/articles_controller.rb
文件,把 new
和 create
动作改成:
打开 app/controllers/articles_controller.rb
文件,把 new
和 create
动作改成:
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
在 app/views/articles/new.html.erb
文件中检测错误消息:
<%= form_for :article, url: articles_path do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited
this article from being saved:</h2>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
再次访问 http://localhost:3000/articles/new,尝试发布一篇没有标题的文章,会看到一个很有用的错误提示。
4添加评论模型
4.1生成模型
在终端执行下面的命令:
$ rails generate model Comment commenter:string body:text article:references
如下图:
然后运行迁移,执行命令:
$ rake db:migrate
Rails 相当智能,只会执行还没有运行的迁移,在命令行中会看到以下输出:
4.2模型关联
使用 Active Record
关联可以轻易的建立两个模型之间的关系。评论和文章之间的关联是这样的:
- 评论属于一篇文章
- 一篇文章有多个评论
我们要编辑 app/models/article.rb
文件,加入这层关系的另一端:
class Article < ActiveRecord::Base
has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
end
4.3添加评论的路由
打开 config/routes.rb
文件,按照下面的方式修改:
resources :articles do
resources :comments
end
4.4生成控制器
$ rails generate controller Comments
这个命令生成六个文件和一个空文件夹:
评论发布后,会转向文章显示页面,查看自己的评论是否显示出来了。所以,CommentsController
中要定义新建评论的和删除垃圾评论的方法。
首先,修改显示文章的模板 (app/views/articles/show.html.erb)
,允许读者发布评论:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>
5重构
5.1渲染局部视图中的集合
看一下 app/views/articles/show.html.erb
模板,内容太多。下面使用局部视图重构。
新建 app/views/comments/_comment.html.erb
文件,写入下面的代码:
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
然后把 app/views/articles/show.html.erb
修改成:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Comments</h2>
<%= render @article.comments %>
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>
这个视图会使用局部视图 app/views/comments/_comment.html.erb
渲染 @article.comments
集合中的每个评论。render
方法会遍历 @article.comments
集合,把每个评论赋值给一个和局部视图同名的本地变量,在这个例子中本地变量是 comment
,这个本地变量可以在局部视图中使用。
5.2渲染局部视图中的表单
新建 app/views/comments/_form.html.erb
文件,写入:
<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
然后把 app/views/articles/show.html.erb
改成:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<h2>Comments</h2>
<%= render @article.comments %>
<h2>Add a comment:</h2>
<%= render "comments/form" %>
<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>
第二个 render
方法的参数就是要渲染的局部视图,即 comments/form
。Rails
很智能,能解析其中的斜线,知道要渲染 app/views/comments
文件夹中的 _form.html.erb
模板。
@article
变量在所有局部视图中都可使用,因为它是实例变量。
5.3删除评论
博客还有一个重要的功能是删除垃圾评论。为了实现这个功能,要在视图中添加一个连接,并在 CommentsController
中定义 destroy
动作。
先在 app/views/comments/_comment.html.erb
局部视图中加入删除评论的链接:
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<p>
<%= link_to 'Destroy Comment', [comment.article, comment],
method: :delete,
data: { confirm: 'Are you sure?' } %>
</p>
点击 Destroy Comment
链接后,会向 CommentsController
控制器发起 DELETE /articles/:article_id/comments/:id
请求。我们可以从这个请求中找到要删除的评论。下面在控制器中加入 destroy
动作 (app/controllers/comments_controller.rb)
:
class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
redirect_to article_path(@article)
end
def destroy
@article = Article.find(params[:article_id])
@comment = @article.comments.find(params[:id])
@comment.destroy
redirect_to article_path(@article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
destroy
动作先查找当前文章,然后在 @article.comments
集合中找到对应的评论,将其从数据库中删掉,最后转向显示文章的页面。
5.4删除关联对象
如果删除一篇文章,也要删除文章中的评论,不然这些评论会占用数据库空间。在 Rails
中可以在关联中指定 dependent
选项达到这一目的。把 Article
模型(app/models/article.rb)
修改成:
class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
end
6安全认证
Rails
提供了一种简单的 HTTP
身份认证机制可以避免出现这种情况。
在 ArticlesController
中,我们要用一种方法禁止未通过认证的用户访问其中几个动作。我们需要的是 http_basic_authenticate_with
方法,通过这个方法的认证后才能访问所请求的动作。
要使用这个身份认证机制,需要在 ArticlesController
控制器的顶部调用 http_basic_authenticate_with
方法。除了 index
和 show
动作,访问其他动作都要通过认证,所以在 app/controllers/articles_controller.rb
中,要这么做:
class ArticlesController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
def index
@articles = Article.all
end
# snipped for brevity
同时,我们还希望只有通过认证的用户才能删除评论。修改 CommentsController
控制器(app/controllers/comments_controller.rb)
:
class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
@article = Article.find(params[:article_id])
...
end
# snipped for brevity
现在,如果想新建文章,会看到一个 HTTP 基本认证对话框。
总结
通过利用 Ruby on Rails 搭建一个简单的博客系统,学习到 Ruby 这门语言该怎么用,对 Rails 也有了一个深刻的实际理解。加油,继续努力!