文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 制作登录系统
    2. 2.2. 2. 在 groups_controller 限制 “新增讨论群”必须先登录
    3. 2.3. 3. 让这个网站有实际“登录”、“退出”的功能
    4. 2.4. 4. 新增字段到migration中
    5. 2.5. 5. 在新增看板时,记录谁是群组的建立者
    6. 2.6. 6. 路人不应该可以看到“编辑”“删除”按钮
    7. 2.7. 7. 常见的权限控制
    8. 2.8. 8. 给Group内的文章 设立 routing
    9. 2.9. 9. 实作post的 new / create action
    10. 2.10. 11. 文章按照发表时间倒序排列的scope
    11. 2.11. 12. 加入文章分页功能
    12. 2.12. 13. 各种功能的gem一览

课程目标

接下来会以一个“讨论版”为主题,实际让同学了解如何“从零打造”手写出一个网站。

这个讨论版会有:

  • 使用者注册以及登录功能
  • 开新讨论区、留言功能
  • 加入讨论区、退出讨论区功能
  • 权限管理功能
  • 热门文章排序功能
  • 了解什么是 RESTful

当你把这次应用程序实作完成,并上线后,你会学会:

  • 怎么把需求变成会动的 Rails 应用程序
  • 如何在 Rails 套版
  • 如何制作使用者登录功能
  • 重要 Rails 观念以及 API
    • CRUD
    • RESTful
    • member, collection
    • helper
    • partial
    • scope
  • 找到 Rails 第三方 gem 的技巧
  • 要怎么把你的应用程序放到网络上

这几乎是一个 Rails 新手应该学的一切了,有了这些基础技巧之后,你再学更难的技巧,或者是要自己做出复杂的东西,就不会那么容易失败了。

课程复盘

1. 制作登录系统

(1)安装登录系统

Gemfile
1
gem 'devise'

然后执行

1
bundle install

(2)产生会员系统的必要文件

执行

1
2
3
rails g devise:install
rails g devise user
rake db:migrate

2. 在 groups_controller 限制 “新增讨论群”必须先登录

app/controllers/groups_controller.rb
1
2
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new]

before_action 后面加的往往是一个 controller 内的 method,在这里 :authenticate_user! 是 devise 提供的内建功能。

  • before_action :authenticate_user! , only: [:new],表示“只有 new 需要登入”
  • before_action :authenticate_user! 后面不加任何东西,表示这个 controller 下的所有 action 都要登入。
  • 你可以把 before_action 想像成“先过我这一关”的“拦截器”

3. 让这个网站有实际“登录”、“退出”的功能

修改 app/views/common/_navbar.html.erb

app/views/common/_navbar.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- <li>
- <%= link_to("登录", '#') %>
- </li>
+ <% if !current_user %>
+ <li><%= link_to("注册", new_user_registration_path) %> </li>
+ <li><%= link_to("登录", new_user_session_path) %></li>
+ <% else %>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+ Hi!, <%= current_user.email %>
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li> <%= link_to("退出", destroy_user_session_path, method: :delete) %> </li>
+ </ul>
+ </li>
+ <% end %>

记住:new_user_registration_pathnew_user_session_pathdestroy_user_session_path

修改app/assets/javascripts/application.js,
加入 //= require bootstrap/dropdown

app/assets/javascripts/application.js
1
2
//= require bootstrap/alert
+ //= require bootstrap/dropdown

这里还通过 data 属性:向链接或按钮添加 data-toggle=”dropdown” 来切换下拉菜单,使用下拉菜单(Dropdown)插件,可以向任何组件(比如导航栏、标签页、胶囊式导航菜单、按钮等)添加下拉菜单。
更多内容请参考:Bootstrap 下拉菜单(Dropdown)插件

修改 app/assets/javascripts/application.js

加入 //= require bootstrap/dropdown

1
2
git add .
git commit -m "user can login/logout/signup"

4. 新增字段到migration中

执行

1
rails g migration add_user_id_to_group

然后打开刚刚新增的 migration 档,修改让它长得像这样

db/migrate/一串数字_add_user_id_to_group
1
2
3
4
5
class AddUserIdToGroup < ActiveRecord::Migration[5.0]
def change
add_column :groups, :user_id, :integer
end
end

然后,执行

1
rake db:migrate

5. 在新增看板时,记录谁是群组的建立者

修改 app/controllers/groups_controller.rb,在需要“登入验证”的 action 列表再加入 create。

app/controllers/groups_controller.rb
1
2
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new, :create]

修改 app/controllers/groups_controller.rb,在 create 中,多加入一行 @group.user = current_user。

变成以下内容:

app/controllers/groups_controller.rb
1
2
3
4
5
6
7
8
9
10
def create
@group = Group.new(group_params)
@group.user = current_user
if @group.save
redirect_to groups_path
else
render :new
end
end

6. 路人不应该可以看到“编辑”“删除”按钮

加入判断即可。

app/views/groups/index.html.erb
1
2
3
4
5
6
7
<td>
+ <% if current_user && current_user == group.user %>
<%= link_to("Edit", edit_group_path(group), class: "btn btn-sm btn-default")%>
<%= link_to("Delete", group_path(group), class: "btn btn-sm btn-default",
method: :delete, data: { confirm: "Are you sure?" } )%>
+ <% end %>
</td>

7. 常见的权限控制

(1)限制“没登录”的路人不可以“直接输入网址”去存取 edit / update / destroy action

app/controllers/groups_controller.rb
1
2
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]

(2)必须要是 group 拥有人,才能进入 edit / update / destroy action,否则会被重导至首页,并显示错误信息。

app/controllers/groups_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def edit
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
end
def update
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
if @group.update(group_params)
redirect_to groups_path, notice: "Update Success"
else
render :edit
end
end
def destroy
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
@group.destroy
redirect_to groups_path, alert: "Group deleted"
end

(3)使用before_action重构上述代码

app/controllers/groups_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]
before_action :find_group_and_check_permission, only: [:edit, :update, :destroy]
...
def edit
end
def update
if @group.update(group_params)
redirect_to groups_path, notice: "Update Success"
else
render :edit
end
end
def destroy
@group.destroy
redirect_to groups_path, alert: "Group deleted"
end
private
def find_group_and_check_permission
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
end
def group_params
params.require(:group).permit(:title, :description)
end
end

(4)git 存档

1
2
git add .
git commit -m "use before_action to find_group_and_check_permission"

8. 给Group内的文章 设立 routing

config/routes.rb
1
2
3
4
5
6
7
Rails.application.routes.draw do
devise_for :users
resources :groups do
resources :posts
end
root 'groups#index'
end

做了这件事后,执行

1
rake routes

然后你就会发现, routing 列表内产生了 /groups/:group_id/posts/new 这样的网址支援。

9. 实作post的 new / create action

app/controllers/posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class PostsController < ApplicationController
before_action :authenticate_user!, :only => [:new, :create]
def new
@group = Group.find(params[:group_id])
@post = Post.new
end
def create
@group = Group.find(params[:group_id])
@post = Post.new(post_params)
@post.group = @group #这个不要忘了
@post.user = current_user #这个不要忘了,记录谁是文章的建立者
if @post.save
redirect_to group_path(@group)
else
render :new
end
end
private
def post_params
params.require(:post).permit(:content)
end
end

因为对应的app/views/posts/new.html.erb页面需要@group和@post俩个变量(见下面)。所以 new / create action中必须要先找到@group,然后在@group页面新建@post变量。

新增 app/views/posts/new.html.erb

1
touch app/views/posts/new.html.erb
app/views/posts/new.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h2 class="text-center">新增文章</h2>
<div class="col-md-4 col-md-offset-4">
<%= simple_form_for [@group,@post] do |f| %>
<div class="form-group">
#把标签和控件放在一个带有 class .form-group 的 <div> 中。这是获取最佳间距所必需的。
<%= f.input :content, input_html: { class: "form-control"} %>
#向所有的文本元素 <input>、<textarea> 和 <select> 添加 class ="form-control"
</div>
<div class="form-actions">
<%= f.submit "Submit", disable_with: "Submiting...", class: "btn btn-primary"%>
</div>
<% end %>
</div>

该页面设计两个变量@group、@post。

1
<%= simple_form_for [@group,@post] do |f| %>

关于Bootstrap表单

把标签和控件放在一个带有 class .form-group 的

中。这是获取最佳间距所必需的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
向所有的文本元素
```<input>、<textarea> 和 <select>```
添加 class ="form-control" 。
如果想修改输入框(input)的label,也是可以的。
参考:
* [simple_form gem](https://github.com/plataformatec/simple_form)
* [Bootstrap 表单](http://www.runoob.com/bootstrap/bootstrap-forms.html)。
经过上面的操作,文章@post的相应数据就已经写入数据库了。下面就是从数据库中取出这些文章@posts数据,然后在相应的@group中显示出来。请看下面。
### 10. 修改 groups_controller 中的 show action
一系列文章@posts是在@group中显示的。
```ruby app/controllers/groups_controller.rb
def show
@group = Group.find(params[:id])
+ @posts = @group.posts
end

@group负责找到这个页面,@posts是这个页面下所有文章,从数据库中取出这些文章数据。

数据已经有啦,下面是修改 groups/show.html.erb,让文章能够显示出来。我们在这个@group中,用表格来显示这些文章。

app/views/groups/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...(略)
<p><%= @group.description %></p>
+ <table class="table">
+ <thead>
+ <tr>
+ <th>文章内容</th>
+ <th>发表者</th>
+ <th>发表时间</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @posts.each do |post| %>
+ <tr>
+ <td><%= post.content %></td>
+ <td><%= post.user.email %></td>
+ <td><%= post.created_at %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>

可见app/views/groups/show.html.erb这个页面需要@group、@posts。

此外,一般也要限制 post 的 content 不能为空

app/models/post.rb
1
2
3
4
5
6
7
class Post < ApplicationRecord
belongs_to :user
belongs_to :group
+ validates :content, presence: true
end

11. 文章按照发表时间倒序排列的scope

app/models/post.rb
1
2
3
4
5
6
class Post < ApplicationRecord
# ... 略
scope :recent, -> { order("created_at DESC")}
end

scope 是拿来包装“常用 query”的方法。更多 scope 的用法:http://guides.rubyonrails.org/active_record_querying.html

12. 加入文章分页功能

安装 will_paginate,也可以选择其他更漂亮的分页gem

Gemfile
1
gem 'will_paginate'

然后执行

1
bundle install

修改 app/controllers/groups_controller.rb 中的 show

加入分页代码,变成

app/controllers/groups_controller.rb
1
2
3
4
def show
@group = Group.find(params[:id])
@posts = @group.posts.recent.paginate(:page => params[:page], :per_page => 5)
end

也可以写成scope形式。

修改 app/views/groups/show.html.erb 在最下面 下加入以下三行代码:

app/views/groups/show.html.erb
1
2
3
4
5
6
</tbody>
</table>
+ <div class="text-center">
+ <%= will_paginate @posts %>
+ </div>

13. 各种功能的gem一览

各种功能的gem一览: “RubyToolbox

文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 制作登录系统
    2. 2.2. 2. 在 groups_controller 限制 “新增讨论群”必须先登录
    3. 2.3. 3. 让这个网站有实际“登录”、“退出”的功能
    4. 2.4. 4. 新增字段到migration中
    5. 2.5. 5. 在新增看板时,记录谁是群组的建立者
    6. 2.6. 6. 路人不应该可以看到“编辑”“删除”按钮
    7. 2.7. 7. 常见的权限控制
    8. 2.8. 8. 给Group内的文章 设立 routing
    9. 2.9. 9. 实作post的 new / create action
    10. 2.10. 11. 文章按照发表时间倒序排列的scope
    11. 2.11. 12. 加入文章分页功能
    12. 2.12. 13. 各种功能的gem一览