摘要
rails是使用了ruby语言开发的web应用框架,目的是通过解决快速开发中的共通问题,简化web应用的开发。本文参考:https://ruby-china.github.io/rails-guides
rails哲学包含两大指导思想:
1、不要自我重复(DRY),DRY是软件开发中的一个原则,意思是“系统中的每一个 功能都要具有单一、准确、可信的实现”,不能重复表述同一件事情,写出的代码才更易维护,更具扩展性,也更不容易出问题。
2、多约定、少配置:rails为web应用的大多数需求都提供了更好的解决方法,并且默认使用这些约定,而不是在长长的配置文件中设置某一个细节。
Windows 用户可以使用 Rails Installer,macOS 用户可以使用 Tokaido去安装rails环境。
这里我们跟着文档做一个form表单的CURD,边跟着敲代码边总结学习。
1、创建我们的第一个页面
(1)首先我们去创建我们的第一个rails项目
cd 进入项目文件后
rails new home //创建rails项目,并取名为home
(2)显示文档
(3)创建我们的第一个hello word页面
我们可以通过rails自带的控制器生成器去自动生成我们的控制器文件、视图文件和routes配置
执行命令:
bin/rails generate controller welcome index //创建一个welcome控制器和index动作
’
’
控制器的名称对应视图文件中html文件夹的名称
控制器的动作对应的是视图文件中html文件名称
html后面的扩展名是模板处理器分为erb、builder和coffee
erb:html模板处理器
builder:XML模板处理器
coffee:用CoffeeScript创建JavaScript模板。
这里我们要创建HTML表单,所以应该使用能够在html中嵌入Ruby的ERB语言。
这里我们在app/views/weclome/index.html.erb文件中添加如下代码:
<h1>Hello, world!</h1>
我们的第一个hello world界面就生成了。
这里我们还要做一个改动就是我们的默认的更目录,通常我们都会设置在index.html.erb下,所以我们需要在app/congig/routes.rb文件下添加如下代码:
get “welcome/index”:是告诉rails当对路径http://localhost:3000/welcome/index
进行访问时的请求应该发往welcome控制器下的index动作。
root "welcome#index"告诉rails对根路径http://localhost:3000
的访问请求应该发往welcome下的index动作
所以显示的页面会是这样的:
2、文章的CURD
为了减少在routes.rb中对每一个页面的路由配置,我们可以创建一个资源,
资源是一个术语,表示一系列类似对象的集合,如文章、人或动物。资源中的项目可以被创建、读取、更新和删除,这些操作简称 CRUD(Create, Read, Update, Delete)。
所以routes.rb的文件就变为了:
这里我们就需要创建我们的另一个页面了。
(1)通过控制器生成器去生成我们对应所需要的控制器和视图
bin/rails generate controller articles new
而路由里的get "articles/new"就不需要了,因为我们已经创建了一个articles的资源。
然后我们就可以去创建我们的表单了在new.html.erb里:
<h1>这是一个表单,用于写文章的</h1>
<%= form_for :article, url: articles_path 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 %>
这里用到了表单构建器。rails中最常用的表单构建器是from_for辅助方法,调用 form_for 辅助方法时,需要为表单传递一个标识对象作为参数,这里是 :article 符号。这个符号告诉 form_for 辅助方法表单用于处理哪个对象。在 form_for 辅助方法的块中,f 表示 FormBuilder 对象,用于创建两个标签和两个文本字段,分别用于添加文章的标题和正文。最后在 f 对象上调用 submit 方法来为表单创建提交按钮。不过这个表单还有一个问题,查看 HTML 源代码会看到表单 action 属性的值是 /articles/new,指向的是当前页面,而当前页面只是用于显示新建文章的表单。应该把表单指向其他 URL,为此可以使用 form_for 辅助方法的 :url 选项。在 Rails 中习惯用 create 动作来处理提交的表单,因此应该把表单指向这个动作。
基本的方法顺序是:
action-->index-->show-->new-->edit-->create-->update-->destroy-->private
这里 render 方法接受了一个简单的散列(hash)作为参数,:plain 键的值是 params[:article].inspect。params 方法是代表表单提交的参数(或字段)的对象。params 方法返回 ActionController::Parameters 对象,这个对象允许使用字符串或符号访问散列的键。这里我们只关注通过表单提交的参数。
请确保牢固掌握 params 方法,这个方法很常用。让我们看一个示例 URL:http://www.example.com/?username=dhh&email=dhh@email.com。在这个 URL 中,params[:username] 的值是“dhh”,params[:email] 的值是“dhh@email.com”。
当我们提交表单后
create动作把表单提交的参数都显示出来,但是并没有什么用。所以我们需要创建一个模型,然后实现运行迁移。
(2)创建Article模型
在rails中,模型使用单数名称;对应的数据库表使用复数名称。rails提供了用于创建模型的生成器,大多数rails开发者在新建模型时倾向于使用这个生成器。要想新建模型
bin/rails generate model Article title:string text:text
上面的命令告诉rails创建了Article模型,并使模型具有字符串类型的title属性和文本类型的text属性。这两个属性会自动添加到数据库的articles表中,并映射到Article模板中
他们的关系就如同
在上面的命令下,创建了很多文件但我们要关注的只有两个:app/models/article.rb和db/migrate/20190713115423_create_articles.rb这两个文件,后面的前缀每个人都不一样,后者负责创建数据库结构。
(3)运行迁移
如前文所述,bin/rails generate model 命令会在 db/migrate 文件夹中生成数据库迁移文件。迁移是用于简化创建和修改数据库表操作的 Ruby 类。Rails 使用 rake 命令运行迁移,并且在迁移作用于数据库之后还可以撤销迁移操作。迁移的文件名包含了时间戳,以确保迁移按照创建时间顺序运行。
让我们看一下 db/migrate/YYYYMMDDHHMMSS_create_articles.rb 文件(记住,你的文件名可能会有点不一样),会看到下面的内容:
上面的迁移创建了 change 方法,在运行迁移时会调用这个方法。在 change 方法中定义的操作都是可逆的,在需要时 Rails 知道如何撤销这些操作。运行迁移后会创建 articles 表,这个表包括一个字符串字段和一个文本字段,以及两个用于跟踪文章创建和更新时间的时间戳字段。
现在可以使用 bin/rails 命令运行迁移了:
$ rake db:migrate/bin/rails db:migrate
Rails 会执行迁移命令并告诉我们它创建了 Articles 表。
因为默认情况下我们是在开发环境中工作,所以上述命令应用于 config/database.yml 文件中 development 部分定义的的数据库。要想在其他环境中执行迁移,例如生产环境,就必须在调用命令时显式传递环境变量:bin/rails db:migrate RAILS_ENV=production。
(4)在控制器中保存数据
回到 ArticlesController,修改 create 动作,使用新建的 Article 模型把数据保存到数据库。打开 app/controllers/articles_controller.rb 文件,像下面这样修改 create 动作:
让我们看一下上面的代码都做了什么:Rails 模型可以用相应的属性初始化,它们会自动映射到对应的数据库字段。create 动作中的第一行代码完成的就是这个操作(记住,params[:article] 包含了我们想要的属性)。接下来 @article.save 负责把模型保存到数据库。最后把页面重定向到 show 动作,这个 show 动作我们稍后再定义。
你可能想知道,为什么在上面的代码中 Article.new 的 A 是大写的,而在本文的其他地方引用 articles 时大都是小写的。因为这里我们引用的是在 app/models/article.rb 文件中定义的 Article 类,而在 Ruby 中类名必须以大写字母开头。
之后我们会看到,@article.save 返回布尔值,以表明文章是否保存成功。
运行后
Rails 提供了多种安全特性来帮助我们编写安全的应用,上面看到的就是一种安全特性。这个安全特性叫做 健壮参数(strong parameter),要求我们明确地告诉 Rails 哪些参数允许在控制器动作中使用。
为什么我们要这样自找麻烦呢?一次性获取所有控制器参数并自动赋值给模型显然更简单,但这样做会造成恶意使用的风险。设想一下,如果有人对服务器发起了一个精心设计的请求,看起来就像提交了一篇新文章,但同时包含了能够破坏应用完整性的额外字段和值,会怎么样?这些恶意数据会批量赋值给模型,然后和正常数据一起进入数据库,这样就有可能破坏我们的应用或者造成更大损失。
所以我们只能为控制器参数设置白名单,以避免错误地批量赋值。这里,我们想在 create 动作中合法使用 title 和 text 参数,为此需要使用 require 和 permit 方法。像下面这样修改 create 动作中的一行代码:
@article = Article.new(params.require(:article).permit(:title, :text))
上述代码通常被抽象为控制器类的一个方法,以便在控制器的多个动作中重用,例如在 create 和 update 动作中都会用到。除了批量赋值问题,为了禁止从外部调用这个方法,通常还要把它设置为 private。最后的代码像下面这样:
运行后发现找不到show方法:
(5)显示文章
和前面一样,我们需要在 app/controllers/articles_controller.rb 文件中添加 show 动作,并创建对应的视图文件。
常见的做法是按照以下顺序在控制器中放置标准的 CRUD 动作:index,show,new,edit,create,update 和 destroy。你也可以按照自己的顺序放置这些动作,但要记住它们都是公开方法,如前文所述,必须放在私有方法之前才能正常工作。
添加后
上面的代码中有几个问题需要注意。我们使用 Article.find 来查找文章,并传入 params[:id] 以便从请求中获得 :id 参数。我们还使用实例变量(前缀为 @)保存对文章对象的引用。这样做是因为 Rails 会把所有实例变量传递给视图。
现在新建 app/views/articles/show.html.erb 文件,添加下面的代码:
这个时候我们就可以显示我们刚才创建了文章了。
当我们创建文章之后,作为一个博客,当然要将我们的文章展现出来。
(6)列出文章列表
运行:
rake routes 或者 bin/rails routes
可以从命令行中看到列出文章的路由:
在对应的articles控制器中为上述路由添加index动作,在编写index动作时,常见的做法是把它作为控制器的第一个方法,就像下面这样
最后在视图文件app/view/articles/index.html.erb文件中为index动作添加视图:
这个时候我们就可以去访问url;
有了首页展示界面,列表文章展示界面和创建界面,这个时候我们就需要创建链接,让三个页面彼此联系。
(7)添加链接
在视图文件app/views/welcome/index.html.erb文件,添加一个超链接
link_to 方法是 Rails 内置的视图辅助方法之一,用于创建基于链接文本和地址的超链接。在这里地址指的是文章列表页面的路径。
运行如下
接下来添加指向其他视图的链接。首先在app/views/article/index.html.erb文件中添加“new article”链接,再把这个链接放在

在app/views/article/new.html.erb和show.html.erb文件中添加一个返回按钮放在尾部

链接到当前控制器的动作时不需要指定 :controller 选项,因为 Rails 默认使用当前控制器。
这样所有的页面也都联系在了一起,当然我们也要给我们的文章添加验证
(8)添加验证
app/models/article.rb模型文件简单到只有两行代码:
class Article < ApplicationRecord
end
虽然这个文件中代码很少,但请注意 Article 类继承自 ApplicationRecord 类,而 ApplicationRecord 类继承自 ActiveRecord::Base 类。正是 ActiveRecord::Base 类为 Rails 模型提供了大量功能,包括基本的数据库 CRUD 操作(创建、读取、更新、删除)、数据验证,以及对复杂搜索的支持和关联多个模型的能力。
Rails 提供了许多方法用于验证传入模型的数据。打开 app/models/article.rb 文件,像下面这样修改:
添加的代码用于确保每篇文章都有标题,并且标题长度不少于 5 个字符。在 Rails 模型中可以验证多种条件,包括字段是否存在、字段是否唯一、字段的格式、关联对象是否存在,等等。
现在验证已经添加完毕,如果我们在调用 @article.save 时传递了无效的文章数据,验证就会返回 false。再次打开 app/controllers/articles_controller.rb 文件,会看到我们并没有在 create 动作中检查 @article.save 的调用结果。在这里如果 @article.save 失败了,就需要把表单再次显示给用户。为此,需要像下面这样修改 app/controllers/articles_controller.rb 文件中的 new 和 create 动作:
在上面的代码中,我们在 new 动作中创建了新的实例变量 @article,稍后你就会知道为什么要这样做。
注意在 create 动作中,当 save 返回 false 时,我们用 render 代替了 redirect_to。使用 render 方法是为了把 @article 对象回传给 new 模板。这里渲染操作是在提交表单的这个请求中完成的,而 redirect_to 会告诉浏览器发起另一个请求。
刷新 http://localhost:3000/articles/new,试着提交一篇没有标题的文章,Rails 会返回这个表单,但这种处理方式没有多大用处,更好的做法是告诉用户哪里出错了。为此需要修改 app/views/articles/new.html.erb 文件,添加显示错误信息的代码:
上面我们添加了一些代码。我们使用 @article.errors.any? 检查是否有错误,如果有错误就使用 @article.errors.full_messages 列出所有错误信息。
pluralize 是 Rails 提供的辅助方法,接受一个数字和一个字符串作为参数。如果数字比 1 大,字符串会被自动转换为复数形式。
在 ArticlesController 中添加 @article = Article.new 是因为如果不这样做,在视图中 @article 的值就会是 nil,这样在调用 @article.errors.any? 时就会抛出错误。
当我们提交空的文章的时候会提示错误信息:
(9)更新文章
现在我们可以对文章进行增加删除的操作,现在我们就可以对文章进行更新。
第一步要在articleController中添加edit动作,通常把这个动作放在new动作和create动作之间
接下来我们要给动作一个对应的视图:
上面的代码把表单指向了 update 动作,这个动作稍后我们再来定义。
传入 @article 对象后,会自动为表单创建 URL,用于提交编辑后的文章。
method: :patch 选项告诉 Rails 使用 PATCH 方法提交表单。根据 REST 协议,PATCH 方法是更新资源时使用的 HTTP 方法。
form_for 辅助方法的第一个参数可以是对象,例如 @article,form_for 辅助方法会用这个对象的字段来填充表单。如果传入和实例变量(@article)同名的符号(:article),也会自动产生相同效果,上面的代码使用的就是符号。
接下来在 app/controllers/articles_controller.rb 文件中创建 update 动作,把这个动作放在 create 动作和 private 方法之间:
update 动作用于更新已有记录,它接受一个散列作为参数,散列中包含想要更新的属性。和之前一样,如果更新文章时发生错误,就需要把表单再次显示给用户。
上面的代码重用了之前为 create 动作定义的 article_params 方法。
不用把所有属性都传递给 update 方法。例如,调用 @article.update(title: ‘A new title’) 时,Rails 只更新 title 属性而不修改其他属性。
最后,我们想在文章列表中显示指向 edit 动作的链接。打开 app/views/articles/index.html.erb 文件,在 Show 链接后面添加 Edit 链接:
接着在 app/views/articles/show.html.erb 模板中添加 Edit 链接,这样文章页面也有 Edit 链接了。把这个链接添加到模板底部:
显然编辑文章的页面和新建文章的页面与我们的rails哲理相矛盾,因为他们的代码是重复的,所以我们就要用到局部视图,按照约定,局部视图的文件名以下划线开头。
新建app/views/articles/_form.html.erb文件,将edit.html.erb的代码拷贝下来
然后在app/views/articles/new.html.erb视图,以使用新建的局部视图。把文件内容替换为下面的代码:
然后在app/views/articles/edit.html.erb视图,以使用新建的局部视图。把文件内容替换为下面的代码:
现在增加、修改、查看都有了,就差一个删除的操作了。
(10)删除文章
删除资源的路由应该使用 delete 路由方法。如果在删除资源时仍然使用 get 路由,就可能给那些设计恶意地址的人提供可乘之机,例如:
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
我们用 delete 方法来删除资源,对应的路由会映射到 app/controllers/articles_controller.rb 文件中的 destroy 动作,稍后我们要创建这个动作。destroy 动作是控制器中的最后一个 CRUD 动作,和其他公共 CRUD 动作一样,这个动作应该放在 private 或 protected 方法之前。打开 app/controllers/articles_controller.rb 文件,添加下面的代码:
在 Active Record 对象上调用 destroy 方法,就可从数据库中删除它们。注意,我们不需要为 destroy 动作添加视图,因为完成操作后它会重定向到 index 动作。
最后,在 index 动作的模板(app/views/articles/index.html.erb)中加上“Destroy”链接,这样就大功告成了:
运行后:
这里我们就可以创建、显示、列出、更新和删除文章了!
3、文章评论
第二个模型用于处理文章评论
(1)生成模型
接下来将要使用的生成器,和之前用于创建Article模型的一样。这次我们要创建Commit模型,用于保存文章评论。在终端执行下面命令:
$ bin/rails generate model Comment commenter:string body:text article:references
创建的四个文件分别为:
db/migrate/20190713173605_create_comments.rb //用于在数据库中创建 comments 表的迁移文件(你的文件名会包含不同的时间戳
app/models/comment.rb //comment模型的测试文件
test/models/comment_test.rb //comment模型的测试文件
test/fixtures/comments.yml //用于测试的示例评论
首先看一下 app/models/comment.rb 文件:
可以看到,Comment 模型文件的内容和之前的 Article 模型差不多,仅仅多了一行 belongs_to :article,这行代码用于建立 Active Record 关联。下一节会简单介绍关联。
在上面的 Bash 命令中使用的 :references 关键字是一种特殊的模型数据类型,用于在数据表中新建字段。这个字段以提供的模型名加上 _id 后缀作为字段名,保存整数值。之后通过分析 db/schema.rb 文件可以更好地理解这些内容。
除了模型文件,Rails 还生成了迁移文件,用于创建对应的数据表:
t.references 这行代码创建 article_id 整数字段,为这个字段建立索引,并建立指向 articles 表的 id 字段的外键约束。下面运行这个迁移:
rake db:migrate或者bin/rails db:migrate
Rails 很智能,只会运行针对当前数据库还没有运行过的迁移,运行结果像下面这样:
(1)模型关联
Active Record关联让我们可以轻易地声明两个模型之间的关系。对于评论和文章,我们可以像下面这样声明:
·每一条评论都属于某一篇文章
·一篇文章可以有多条评论
实际上,这种表达方式和rails用于声明模型关联的句法非常接近。前文我们已经看过Comment模型中用于声明模型关联的代码,这行代码用于声明每一条评论都属于某一篇文章:
现在修改app/models/article.rb文件来添加模型关联的另一端:
这两行声明能够启用一些自动行为。例如,如果@article实例变量表示一篇文章,就可以使用@article.comments以数组形式取回这篇文章的所有评论。
(3)为评论添加路由
和welcome控制器一样,在添加路由之后
上面的代码在articles资源中创建comments资源,这种方式被称为嵌套资源。这是表明文章和评论之间层级关系的另一种方式。
(4)生成控制器
有了模型,下面应该创建对应的控制器了。还是使用前面用过的生成器:
bin/rails generate controller comments
上面的命令会创建5个文件和一个空的文件夹:
app/controllers/comments_controller.rb //comments控制器文件
app/views/comments/ //控制器的视图保存的地方
test/controllers/comments_controller_test.rb //控制器的测试文件
app/helpers/comments_helper.rb //视图辅助方法文件
app/assets/javascripts/comments.coffee //控制器的coffeeScript文件
app/assets/stylesheets/comments.scss //控制器的样式表文件
在博客中,读者看完文章后可以直接发表评论,并且马上可以看到这些评论是否在页面上显示出来了。我们的博客采取同样的设计。这里CommentsController需要提供创建评论和删除垃圾评论的方法。
首先对显示文章的模板进行更改app/views/articles/show.html.erb,添加发表评论的功能:
上面的代码在显示文章的页面中添加了用于新建评论的表单,通过调用CommentsController的create动作来发表评论。这里form_for辅助方法以数组为参数,会创建嵌套路由,例如/articles/1/comments
接下来app/controllers/comments_controller.rb文件中添加create动作:
上面的代码比Articles控制器的代码复杂得多,这是嵌套带来得副作用。对于每一个发表评论的请求,都必须记录这条评论属于那篇文章,因此需要Article模型上调用find方法来获取文章对象。
此外,上面的代码还利用了关联特有的方法,在@article.comments上调用create方法来创建和保存评论,同时自动把评论和对应的文章关联起来。
添加评论后,我们使用article_path(@article)辅助方法带回原来的文章页面。如前文所述,这里调用了ArticleController的show动作来渲染show.html.erb模板。因此需要修改app/views/article/show.html.erb文件来显示评论:
这个时候我们就可以看到这样一个画面了。
现在博客的文章的CURD和评论都存在了,这个时候我们有必要去重构我们的代码,让他们变得美观。
(5)渲染局部视图集合
首先创建评论的局部视图,把显示文章的代码抽取出来。创建app/views/comments/_comment.html.erb文件,添加如下代码
在app/views/articles/show.html.erb文件:
这样对于 @article.comments 集合中的每条评论,都会渲染 app/views/comments/_comment.html.erb 文件中的局部视图。render 方法会遍历 @article.comments 集合,把每条评论赋值给局部视图中的同名局部变量,也就是这里的 comment 变量。
(6)渲染局部视图表单
在app/views/comments/_form.html.erb文件,添加下面代码:
在app/views/articles/show.html.erb文件修改如下代码:
上面的代码中第二个 render 方法的参数就是我们刚刚定义的 comments/form 局部视图。Rails 很智能,能够发现字符串中的斜线,并意识到我们想渲染 app/views/comments 文件夹中的 _form.html.erb 文件。
@article 是实例变量,因此在所有局部视图中都可以使用。
(7)删除评论
有了添加评论自然就会有删除评论
为了实现这个功能,我们需要在视图中添加一个链接,并在CommentsController中添加destroy动作。
首先在app/views/comments/_comment.html.erb局部视图上添加删除评论的链接:
运行后发现我们没有创建destroy方法;
destroy 动作首先找到指定文章,然后在 @article.comments 集合中找到指定评论,接着从数据库删除这条评论,最后重定向到显示文章的页面。
(8)删除关联对象
如果要删除一篇文章,文章的相关评论也需要删除,否则这些评论还会占用数据库空间。在rails中可以使用关联的dependent选项来完成这一工作。像下面这样修改app/model/article.rb文件中的Article模型:
现在我们的功能大致完成了。
4、最后我们添加一个基本的身份验证
现在任何人都能对我的博客进行添加、修改、删除评论。
rails为我们提供了一个非常简单的HTTP身份验证系统,可以很好的解决这个问题。
我们需要一种方法来禁止未认证用户访问articleController的动作。这里我们可以使用rails的http_basic_authenticate_with方法,通过这个方法的认证后才能访问所请求的动作。
要使用这个身份验证系统,可以在app/controllers/article_controller文件中的ArticleController的顶部进行指定。这里除了index和show动作,其他动作都要通过身份验证才能访问,为此需要添加这样的代码:
同时只有通过身份验证的用户才能删除评论,为此要在 CommentsController(app/controllers/comments_controller.rb)中像下面这样添加代码:
我们尝试去删除文件
总结:
跟着文档https://ruby-china.github.io/rails-guides把个人博客敲了一边,对rails也有了一个基本的认识,之前没接触过rails,心中并没有一个完整的应用场景,所以并不能理解它的一些基本的原理,现在敲了一边代码,知道了他们的基本协作方式,虽然只是一个入门的教程,但意义重大,万事开头难,争取早日能够磨掉这门web前端框架。加油!