宁皓网的付费会员可以查看课程:《Ruby on Rails:起步》http://ninghao.net/course/4101
resource ,资源。一个资源就是一种相似对象的集合,比如文章,用户。你可以对资源执行创建,读取,更新,删除的动作,这些动作被称为 CRUD 操作。
一个博客,里面可能会有文章资源。创建一个文章资源,名字是 articles,先添加一个资源类型的路由。编辑 config/routes.rb,添加一行 resources :articles,像这样:
Rails.application.routes.draw do resources :articles # ... end
执行:
rails routes
返回:
Prefix Verb URI Pattern Controller#Action articles GET /articles(.:format) articles#index POST /articles(.:format) articles#create new_article GET /articles/new(.:format) articles#new edit_article GET /articles/:id/edit(.:format) articles#edit article GET /articles/:id(.:format) articles#show PATCH /articles/:id(.:format) articles#update PUT /articles/:id(.:format) articles#update DELETE /articles/:id(.:format) articles#destroy welcome_index GET /welcome/index(.:format) welcome#index root GET / welcome#index
new
访问一下:
http://localhost:3000/articles/new
会提示:
Routing Error uninitialized constant ArticlesController
创建一个控制器:
rails generate controller Articles
返回:
Running via Spring preloader in process 217 create app/controllers/articles_controller.rb invoke erb create app/views/articles invoke test_unit create test/controllers/articles_controller_test.rb invoke helper create app/helpers/articles_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/articles.coffee invoke scss create app/assets/stylesheets/articles.scss
打开文件:
app/controllers/articles_controller.rb
里面的内容是:
class ArticlesController < ApplicationController end
一个控制器就是一个类,继承了 ApplicationController 类。在这个类里定义的方法会变成控制器的动作,这些动作执行的就是 CRUD 操作。
刷新之前出错的页面,会提示:
Unknown action The action 'new' could not be found for ArticlesController
意思是 Rails 在 ArticlesController 这个控制器里找不到 new 这个动作。我们可以手工的在这个控制器里添加一个 new 方法:
class ArticlesController < ApplicationController def new end end
刷新,又会提示:
ActionController::UnknownFormat in ArticlesController#new ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
没找到跟控制器动作对应的视图。创建一个视图文件,位置是:
app/views/articles/new.html.erb
文件里的内容,输入:
<h1>添加新文章</h1>
刷新页面,页面上会出现 “添加新文章” 这几个字儿。
表单
用 form builder 在模板里面创建表单,用一下 form_for 方法,在 new.html.erb 文件里添加下面代码:
<%= 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 %>
把 :article 交给 form_for 方法,告诉 form_for 这是为谁做的表单。在方法的代码块里,FormBuilder 对象是用 f 表示的。用它创建了两个 label 标签,一个 text 文本框,一个 text_area 文件区域,还有一个 submit 提交按钮。url 选项设置了表单的 action 属性的值,articles_path 相当于 articles#create 动作。
点击表单的 save 按钮,会报错:
Unknown action The action 'create' could not be found for ArticlesController
create
继续编辑 articles 控制器:
class ArticlesController < ApplicationController def new end def create end end
添加了一个 create 方法,再次刷新页面。没有反应,因为我们没有指定要响应的东西,所以 Rails 默认会响应 204 No Content 。改造一下 create 方法:
def create render plain: params[:article].inspect end
给 render 方法一个 hash,key 是 :plain,value 是 params[:article].inspect。params 表示的是表单里的参数(或字段),它返回的是一个 ActionController::Parameters 对象。
刷新,在表单里输入点内容,然后再点一下 Save Article 按钮,这次会返回:
<ActionController::Parameters {"title"=>"第一篇文章", "text"=>"这是文章的内容"} permitted: false>
Article 模型
Rails 里的模型使用单数名字,对应的数据库表用的是复数名字。
创建一个 Article 模型,title 是字符串类型,text 是文本类型,执行:
rails generate model Article title:string text:text
返回:
Running via Spring preloader in process 225 invoke active_record create db/migrate/20160918102316_create_articles.rb create app/models/article.rb invoke test_unit create test/models/article_test.rb create test/fixtures/articles.yml
运行 migration
打开之前创建的 migration 文件,位置:db/migrate/YYYYMMDDHHMMSS_create_articles.rb,里面的内容是:
class CreateArticles < ActiveRecord::Migration[5.0] def change create_table :articles do |t| t.string :title t.text :text t.timestamps end end end
运行 migration:
rails db:migrate
返回:
== 20160918102316 CreateArticles: migrating =================================== -- create_table(:articles) -> 0.0166s == 20160918102316 CreateArticles: migrated (0.0173s) ==========================
save
打开文件:
app/controllers/articles_controller.rb
把里面定义的 create 方法编辑成这样:
def create @article = Article.new(params[:article]) @article.save redirect_to @article end
打开地址:
http://localhost:3000/articles/new
试着发布一篇新文章,你会看到错误:
ActiveModel::ForbiddenAttributesError in ArticlesController#create ActiveModel::ForbiddenAttributesError
这是为了安全报的错,strong parameters,我们需要告诉 Rails 允许哪些参数进入到控制器动作里。允许一下 title 与 text,使用 require 与 permit,改造一下 create 方法:
@article = Article.new(params.require(:article).permit(: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
在发布新文章的页面上,在表单里输入点东西,提交。这次又会出现:
Unknown action The action 'show' could not be found for ArticlesController
show
打开文件:
app/controllers/articles_controller.rb
定义 show 方法:
def show @article = Article.find(params[:id]) end
用 Article.find 找到请求的文章,交给 @article,Rails 会把所有的实例变量传递给视图。
创建视图文件:
app/views/articles/show.html.erb
内容:
<p> <strong>Title: </strong> <%= @article.title %> </p> <p> <strong>Text: </strong> <%= @article.text %> </p>
再去创建一篇文章,打开地址:
http://localhost:3000/articles/new
index
打开文件:
app/controllers/articles_controller.rb
定义 index 方法:
def index @articles = Article.all end
创建视图文件:
app/views/articles/index.html.erb
内容:
<h1>文章列表</h1> <ul> <% @articles.each do |article| %> <li> <p> <strong>Title: </strong> <%= article.title %> </p> <p> <strong>Text: </strong> <%= article.text %> </p> <p> <%= link_to 'show', article_path(article) %> </p> </li> <% end %> </ul>
打开地址:
http://localhost:3000/articles
link_to
打开文件:
app/views/welcome/index.html.erb
修改成:
<h1>hello</h1> <%= link_to '我的博客', controller: 'articles' %>
link_to 是一个视图 helper。上面创建了一个链接是 “我的博客”,打开的地址是文章列表。
打开文件:
app/views/articles/index.html.erb
添加一个新建文章的链接:
<%= link_to '新建文章', new_article_path %>
打开文件:
app/views/articles/new.html.erb
添加一个文章列表的链接:
<%= link_to '返回', articles_path %>
打开文件:
app/views/articles/show.html.erb
添加一个文章列表的链接:
<%= link_to '返回', articles_path %>
验证
打开文件:
app/models/article.rb
编辑成:
class Article < ApplicationRecord validates :title, presence: true, length: { minimum: 5 } end
上面验证的是 title,它的长度至少是 5 个字符。
打开文件:
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
打开地址:
http://localhost:3000/articles/new
输入的标题小于 5 个字符,然后提交表单,你会发现,会返回到新建文章的页面,并且之前在表单里输入的东西仍然会存在。
再显示点错误的提示,打开文件:
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, "个错误") %> </h2> <ul> <% @article.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> ...
这次发布文章的时候,少输入几个字儿,会在页面上显示错误。
edit
打开文件:
app/controllers/articles_controller.rb
添加一个 edit 方法:
def edit @article = Article.find(params[:id]) end
创建新文件:
app/views/articles/edit.html.erb
内容可以这样:
<h1>编辑文章</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, "个错误") %> </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 '返回', articles_path %>
这回我们把表单的动作指向了 update 动作,这个动作还没有定义。
update
打开文件:
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
打开文件:
app/views/articles/index.html.erb
在 show 的下面添加一个编辑按钮:
<p> <%= link_to 'show', article_path(article) %> <%= link_to 'edit', edit_article_path(article) %> </p>
打开文件:
app/views/articles/show.html.erb
也添加一个编辑按钮:
<%= link_to '返回', articles_path %> <%= link_to 'edit', edit_article_path(@article) %>
partials
文章的编辑页面与新建文章页面很像,它们都使用了一样的代码来显示表单。使用视图 partial 可以去掉重复。partial 文件名有一个下划线前缀。
创建新文件:
app/views/articles/_form.html.erb
文件里的内容:
<%= form_for @article do |f| %> <% if @article.errors.any? %> <div id ="error_explanation"> <h2> <%= pluralize(@article.errors.count, "个错误") %> </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 %>
@article 是一个资源,它对应一整套 RESTful 路由。Rails 知道自己应该使用哪个地址与方法。
更新文件:
app/views/articles/new.html.erb
内容:
<h1>添加新文章</h1> <%= render 'form' %> <%= link_to '返回', articles_path %>
更新文件:
app/views/articles/edit.html.erb
内容:
<h1>编辑文章</h1> <%= render 'form' %> <%= link_to '返回', articles_path %>
destroy
用 destroy 方法删除资源。
打开文件:
app/controllers/articles_controller.rb
添加 destroy 方法:
def destroy @article = Article.find(params[:id]) @article.destroy redirect_to articles_path end
打开文件:
app/views/articles/index.html.erb
在模板里添加删除按钮:
<p> <%= link_to 'show', article_path(article) %> <%= link_to 'edit', edit_article_path(article) %> <%= link_to 'delete', article_path(article), method: :delete, data: { confirm: 'are you sure?' } %> </p>
第二个模型
再添加一个处理文章评论的模型。
生成模型
rails generate model Comment commenter:string body:text article:references
返回:
Running via Spring preloader in process 48 invoke active_record create db/migrate/20160918190201_create_comments.rb create app/models/comment.rb invoke test_unit create test/models/comment_test.rb create test/fixtures/comments.yml
打开文件:
app/models/comment.rb
里面的内容是:
class Comment < ApplicationRecord belongs_to :article end
比我们之前创建的 Article 模型多出一个 belongs_to :article ,这里设置的是 Active Record 关联。
执行 migration:
rails db:migrate
关联模型
Active Record 的关联可以声明两个模型之间的关系。这里就是评论与文章:
- 每个评论都属于一篇文章
- 一篇文章可以有多条评论
打开文件:
app/models/article.rb
添加一行代码:
has_many :comments
整体的样子是:
class Article < ApplicationRecord has_many :comments validates :title, presence: true, length: { minimum: 5 } end
为评论添加路由
打开文件:
config/routes.rb
像这样编辑:
resources :articles do resources :comments end
这里给 articles 添加了一个嵌套的资源。
生成控制器
rails generate controller Comments
返回:
Running via Spring preloader in process 65 create app/controllers/comments_controller.rb invoke erb create app/views/comments invoke test_unit create test/controllers/comments_controller_test.rb invoke helper create app/helpers/comments_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/comments.coffee invoke scss create app/assets/stylesheets/comments.scss
打开文件:
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 private def comment_params params.require(:comment).permit(:commenter, :body) end end
评论视图
打开文件:
app/views/articles/show.html.erb
添加评论的表示与评论用的表单:
<h2>评论</h2> <% @article.comments.each do |comment| %> <p> <strong>评论者:</strong> <%= comment.commenter %> </p> <p> <strong>评论:</strong> <%= comment.body %> </p> <% end %> <h2>添加评论</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 %>
Refactoring
创建文件:
app/views/comments/_comment.html.erb
文件里的内容:
<p> <strong>评论者:</strong> <%= comment.commenter %> </p> <p> <strong>评论:</strong> <%= comment.body %> </p>
打开文件:
app/views/articles/show.html.erb
修改显示评论用的代码:
<h2>评论</h2> <%= render @article.comments %>
render 会迭代 @article.comments。
创建文件:
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
修改评论表单:
删除评论
打开文件:
app/views/comments/_comment.html.erb
添加一个删除链接:
<p> <%= link_to 'delete comment', [comment.article, comment], method: :delete, data: { confirm: 'are you sure?' } %> </p>
点击 delete comment,会执行 DELETE /articles/:article_id/comments/:id 。
打开文件:
app/controllers/comments_controller.rb
添加一个 destroy 方法:
def destroy @article = Article.find(params[:article_id]) @comment = @article.comments.find(params[:id]) @comment.destroy redirect_to article_path(@article) end
删除相关对象
删除文章,同时也删除掉属于这篇文章的评论。
打开文件:
app/models/article.rb
编辑一下 has_many 那行代码:
has_many :comments, dependent: :destroyRuby