文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 建立“群成员”数据表
    2. 2.2. 2. 在群组里面判断“是否群组成员”实作
    3. 2.3. 3. “加入群组”或“退出群组”
    4. 2.4. 4. 实际操作“加入群组”或“退出群组”
    5. 2.5. 5. User 在建立 group 后自动成为 group 的一员
    6. 2.6. 6. 产生 account 的 namespace 下的 groups_controller
    7. 2.7. 7. 关于link_to
    8. 2.8. 8.可以看到自己发表的所有文章
    9. 2.9. 9. 系统内建 Helper “simple_format”的使用
    10. 2.10. 10. partial 还可以用在循环上
    11. 2.11. 11. 上传到 Github
    12. 2.12. 12. 其他分支上传到 heroku

课程目标

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

这个讨论版会有:

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

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

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

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

课程复盘

1. 建立“群成员”数据表

(1)建立 GroupRelationship

我们在这里要建立一个新的 model 叫 GroupRelationship。这个数据表里面只有两个栏位:

  • group_id ( integer )
  • user_id ( integer)

记录了“谁”参加了“哪个群组”。

输入

1
rails g model group_relationship group_id:integer user_id:integer

执行

1
rake db:migrate

(2)设定 Group 与 User 之间的关系 ( 使用者参与的所有群)

在这之前,在 User model 已经有一行:has_many :groups

app/models/user.rb
1
2
3
4
5
6
7
8
9
class User < ApplicationRecord
# .. 略
has_many :groups
# .. 略
end

user.groups 会捞出这个使用者“创造过的所有群”。那我们要如何撰写 “参与的所有群”呢?

首先,修改 app/models/user.rb 加入以下两行

app/models/user.rb
1
2
3
4
5
6
7
8
class User < ApplicationRecord
# .. 略
+ has_many :group_relationships
+ has_many :participated_groups, :through => :group_relationships, :source => :group
end

这里使用 :participated_groups代表用户参与的所有群, 数据源是 :group,单数,因为model名都是单数。
注意到这个语法糖::source => :group 和 source: :group 效果相同,不一样的表单而已。

然后修改 app/models/group_relationship.rb,加入这两行

app/models/group_relationship.rb
1
2
3
4
class GroupRelationship < ApplicationRecord
+ belongs_to :group
+ belongs_to :user
end

这样当捞 user.participated_groups 时,就会捞出“参与的所有群”。

(3)设定 Group 与 User 之间的关系 ( 群组内的所有会员 )

修改 app/models/group.rb,加入这两行

app/models/group.rb
1
2
3
4
5
6
7
8
class Group < ApplicationRecord
# ... 略
+ has_many :group_relationships
+ has_many :members, through: :group_relationships, source: :user
end

这里使用 :members 代表该群组的所有成员, 数据源是 :user,单数,因为model名都是单数。

(4)动手测看看

打开

1
rails console

输入

1
2
3
4
5
u = User.first
g = Group.first
g.members << u
g.members
u.participated_groups

这条语句 g.members << u 来让群组g的成员包含用户u,这样u参与了g群组。即向数据中insert插入一笔新的资料。可以看console中:

后面我们可以在user.rb中定义 def join!(group) ,然后就可以使用 u.join!(g),效果和上面一样。

2. 在群组里面判断“是否群组成员”实作

在 user model 内实作判断式“是否为群组的一分子”

app/models/user.rb
1
2
3
4
5
6
7
8
class User < ApplicationRecord
# 略 ...
+ def is_member_of?(group)
+ participated_groups.include?(group)
+ end
end

判断该用户所参与的群组是否包含该群组。

“?”问号在这里有任何意义吗?

你可以暂时把它视为 “method 名字的一部分”,“暂时”没有意义。对于返回值是布尔型的方法,方法名最好加上?。
.include?是rails中内建的方法,它的返回值也是布尔型。
整个方法是传入参数group,并判断participated_groups是否包含group,如果包含,则返回true,否则返回false。

然后“重开”

1
rails console

输入

1
2
3
u = User.first
g = Group.first
u.is_member_of?(g)

在 app/views/groups/show.html.erb 加入这一段

app/views/groups/show.html.erb
1
2
3
4
5
6
7
<span class="pull-right">
<% if current_user && current_user.is_member_of?(@group) %>
<label class="label label-success"> 群组成员 </label>
<% else %>
<label class="label label-warning"> 不是群组成员 </label>
<% end %>
</span>

因为这里是特指这个群组,所以用@group这个变量。

3. “加入群组”或“退出群组”

app/models/user.rb
1
2
3
4
5
6
7
def join!(group)
participated_groups << group
end
def quit!(group)
participated_groups.delete(group)
end

打开

1
rails console

输入

1
2
3
4
5
6
7
u = User.first
g = Group.first
u.join!(g)
u.is_member_of?(g)
u.quit!(g)
u.is_member_of?(g)
exit

4. 实际操作“加入群组”或“退出群组”

model-controller-routes-views

(1)修改controller

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
+ def join
+ @group = Group.find(params[:id])
+
+ if !current_user.is_member_of?(@group)
+ current_user.join!(@group)
+ flash[:notice] = "加入本讨论版成功!"
+ else
+ flash[:warning] = "你已经是本讨论版成员了!"
+ end
+
+ redirect_to group_path(@group)
+ end
+
+ def quit
+ @group = Group.find(params[:id])
+
+ if current_user.is_member_of?(@group)
+ current_user.quit!(@group)
+ flash[:alert] = "已退出本讨论版!"
+ else
+ flash[:warning] = "你不是本讨论版成员,怎么退出 XD"
+ end
+
+ redirect_to group_path(@group)
+ end
private

加入与退出群组必须要是登入状态下才行,修改位於第二行的 before_action ,加入 join 和 quit 也需要验证

app/controllers/groups_controller.rb
1
2
3
4
5
6
class GroupsController < ApplicationController
- before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]
+ before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy, :join, :quit]
...
...

(2)修改routes.rb

config/routes.rb
1
2
3
4
5
6
7
8
resources :groups do
+ member do
+ post :join
+ post :quit
+ end
resources :posts
end

(3)修改view文件

加入两行代码,将这个文件修改成这样:

app/views/groups/show.html.erb
1
2
3
4
5
6
7
8
9
<span class="pull-right">
<% if current_user && current_user.is_member_of?(@group) %>
<label class="label label-success"> 群组成员 </label>
+ <%= link_to("Quit Group", quit_group_path(@group), method: :post, class: "btn btn-default") %>
<% else %>
<label class="label label-warning"> 不是群组成员 </label>
+ <%= link_to("Join Group", join_group_path(@group), method: :post, class: "btn btn-default") %>
<% end %>
</span>

除了默认的method: :get可以省略外,其他的 :method 都必须指定相应的http动作。

5. User 在建立 group 后自动成为 group 的一员

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

6. 产生 account 的 namespace 下的 groups_controller

这里因为已经有group这个model了,这个为共有的。所以,后续只需要controller-action-views-routes三步。

(1)执行

1
rails g controller account/groups

(2)修改 config/routes.rb 加入:

config/routes.rb
1
2
3
namespace :account do
resources :groups
end

(3)建立 account/groups_controller.rb 下的 index action

记得要加入: before_action :authenticate_user!,限制必须要得登录使用者,才能看。

app/controllers/account/groups_controller.rb
1
2
3
4
5
6
7
class Account::GroupsController < ApplicationController
before_action :authenticate_user!
def index
@groups = current_user.participated_groups
end
end

(4) 新增 “参与群组一览表”

1
touch app/views/account/groups/index.html.erb
app/views/account/groups/index.html.erb
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
<div class="col-md-12">
<h2 class="text-center"> 我加入的讨论版 </h2>
<table class="table">
<thead>
<tr>
<th> # </th>
<th> Title </th>
<th> Description </th>
<th> Post Count </th>
<th> Last Update </th>
</tr>
</thead>
<tbody>
<% @groups.each do |group| %>
<tr>
<td> # </td>
<td> <%= link_to(group.title, group_path(group)) %> </td>
<td> <%= group.description %> </td>
<td> <%= group.posts.count %> </td>
<td> <%= group.updated_at %> </td>
</tr>
<% end %>
</tbody>
</table>
</div>
1
2
<%= link_to("My Groups", account_groups_path) %>
<%= link_to(group.title, group_path(group)) %>

上面2句中,link_to与(之间不能有空格。此外因为group.title默认返回的就是字符串,所以没有必要加双引号。

8.可以看到自己发表的所有文章

这里因为已经有post这个model了,这个为共有的。所以,后续只需要controller-action-views-routes三步。

(1)产生 account 的 namespace 下的 posts_controller

执行

1
rails g controller account/posts

(2)建立 account/posts_controller.rb 下的 index action

加入:

:authenticate_user!``` ,限制必须要得登入使用者,才能看。
1
2
3
4
5
6
7
8
```ruby app/controllers/account/posts_controller.rb
class Account::PostsController < ApplicationController
before_action :authenticate_user!
def index
@posts = current_user.posts
end
end

(3) 修改 routing

修改 config/routes.rb 加入 resources :posts

config/routes.rb
1
2
3
4
namespace :account do
resources :groups
+ resources :posts
end

(4)修改下拉选项

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

加入多一个“下拉选单选项”:

app/views/common/_navbar.html.erb
1
2
3
4
5
6
<ul class="dropdown-menu">
<li> <%= link_to("My Groups", account_groups_path) %></li>
+ <li> <%= link_to("My Posts", account_posts_path) %></li>
<li class="divider"> </li>
<li> <%= link_to("退出", destroy_user_session_path, method: :delete) %> </li>
</ul>

(5) 新增 “发表文章一览表”

1
touch app/views/account/posts/index.html.erb
app/views/account/posts/index.html.erb
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
<div class="col-md-12">
<h2 class="text-center"> 我发表过的文章 </h2>
<table class="table">
<thead>
<tr>
<th> Content </th>
<th> Group Name </th>
<th> Last Update </th>
<th colspan="2"></th>
</tr>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td> <%= post.content %> </td>
<td> <%= post.group.title %> </td>
<td> <%= post.updated_at %> </td>
<td> <%= link_to('Edit', edit_group_post_path(post.group, post), class: "btn btn-default btn-xs") %></td>
<td> <%= link_to('Delete', group_post_path(post.group, post), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-default btn-xs") %></td>
</tr>
<% end %>
</tbody>
</table>
</div>

代码中

1
<th colspan="2"></th>

colspan 属性规定单元格可横跨的列数。所以浏览器都支持 colspan 属性。colspan=”0” 指示浏览器横跨到列组的最后一列。

9. 系统内建 Helper “simple_format”的使用

将 group.description 改成 simple_format(group.description),“simple_format”具备断行的功能。

app/views/groups/index.html.erb
1
2
3
4
<td><%= link_to(group.title, group_path(group)) %></td>
- <td><%= group.description %></td>
+ <td><%= simple_format(group.description) %></td>
<td> <%= group.user.email %> </td>

用到 group.description 的地方很多,老是直接写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
simple_format(group.description)
```
不是很好,万一以后产品经理想要加个什么特效,我们可能会改死。
因此发明“自己的 Helper” render_group_description(group)。
修改 app/helpers/groups_helper.rb
加入:
```ruby app/helpers/groups_helper.rb
module GroupsHelper
def render_group_description(group)
simple_format(group.description)
end
end

然后修改 app/views/groups/index.html.erb 与 app/views/account/groups/index.html.erb 里关于 group.description 的部分(最终代码如下):

app/views/groups/index.html.erb
1
2
3
<td><%= link_to(group.title, group_path(group)) %></td>
<td><%= render_group_description(group) %></td>
<td> <%= group.user.email %> </td>

这样的写法更优良,与数据库相关的操作最好不要放在views中。

app/views/account/groups/index.html.erb
1
2
3
4
<td><%= link_to(group.title, group_path(group)) %></td>
<td><%= render_group_description(group) %></td>
<td> <%= group.posts.count %> </td>
<td> <%= group.updated_at %> </td>

10. partial 还可以用在循环上

form 可以放入 partial。

app/views/groups/index.html.erb
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
<div class="col-md-12">
<div class="group">
<%= link_to("New group", new_group_path, class: "btn btn-primary pull-right") %>
</div>
<table class="table table-hover">
<thead>
<tr>
<td>#</td>
<td>Title</td>
<td>Description</td>
<td>Creator </td>
</tr>
</thead>
<tbody>
- <% @groups.each do |group| %>
- <tr>
- <td>#</td>
- <td><%= link_to(group.title, group_path(group)) %></td>
- <td><%= render_group_description(group) %></td>
- <td> <%= group.user.email %> </td>
- <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>
- </tr>
- <% end %>
+ <%= render :partial => "group_item", :collection => @groups, :as => :group %>
</tbody>
</table>
</div>

这里

1
2
然后执行

touch app/views/groups/_group_item.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
加入如下内容:
```ruby app/views/groups/_group_item.html.erb
<tr>
<td>#</td>
<td><%= link_to(group.title, group_path(group)) %></td>
<td><%= render_group_description(group) %></td>
<td><%= group.user.email %></td>
<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>
</tr>

11. 上传到 Github

1
2
git remote add origin git@github.com:你的github名字/rails101-1.git
git push -u origin master

上传“所有进度branch”

1
git push --all origin

12. 其他分支上传到 heroku

在终端输入

1
git push heroku ch08:master

文章目录
  1. 1. 课程目标
  2. 2. 课程复盘
    1. 2.1. 1. 建立“群成员”数据表
    2. 2.2. 2. 在群组里面判断“是否群组成员”实作
    3. 2.3. 3. “加入群组”或“退出群组”
    4. 2.4. 4. 实际操作“加入群组”或“退出群组”
    5. 2.5. 5. User 在建立 group 后自动成为 group 的一员
    6. 2.6. 6. 产生 account 的 namespace 下的 groups_controller
    7. 2.7. 7. 关于link_to
    8. 2.8. 8.可以看到自己发表的所有文章
    9. 2.9. 9. 系统内建 Helper “simple_format”的使用
    10. 2.10. 10. partial 还可以用在循环上
    11. 2.11. 11. 上传到 Github
    12. 2.12. 12. 其他分支上传到 heroku