课程目标
接下来会以一个“讨论版”为主题,实际让同学了解如何“从零打造”手写出一个网站。
这个讨论版会有:
- 使用者注册以及登录功能
- 开新讨论区、留言功能
- 加入讨论区、退出讨论区功能
- 权限管理功能
- 热门文章排序功能
- 了解什么是 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
|
执行
(2)设定 Group 与 User 之间的关系 ( 使用者参与的所有群)
在这之前,在 User model 已经有一行:has_many :groups
app/models/user.rb1 2 3 4 5 6 7 8 9
| class User < ApplicationRecord has_many :groups end
|
user.groups 会捞出这个使用者“创造过的所有群”。那我们要如何撰写 “参与的所有群”呢?
首先,修改 app/models/user.rb 加入以下两行
app/models/user.rb1 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.rb1 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.rb1 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 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.rb1 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 2 3
| u = User.first g = Group.first u.is_member_of?(g)
|
在 app/views/groups/show.html.erb 加入这一段
app/views/groups/show.html.erb1 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.rb1 2 3 4 5 6 7
| def join!(group) participated_groups << group end def quit!(group) participated_groups.delete(group) end
|
打开
输入
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.rb1 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.rb1 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.rb1 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.erb1 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.rb1 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.rb1 2 3
| namespace :account do resources :groups end
|
(3)建立 account/groups_controller.rb 下的 index action
记得要加入: before_action :authenticate_user!,限制必须要得登录使用者,才能看。
app/controllers/account/groups_controller.rb1 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.erb1 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> Title </th> <th> Description </th> <th> Post Count </th> <th> Last Update </th> </tr> </thead> <tbody> <% @groups.each do |group| %> <tr> <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>
|
7. 关于link_to
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.rb1 2 3 4
| namespace :account do resources :groups + resources :posts end
|
(4)修改下拉选项
然后修改 app/views/common/_navbar.html.erb
加入多一个“下拉选单选项”:
app/views/common/_navbar.html.erb1 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.erb1 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>
|
代码中
colspan 属性规定单元格可横跨的列数。所以浏览器都支持 colspan 属性。colspan=”0” 指示浏览器横跨到列组的最后一列。
将 group.description 改成 simple_format(group.description),“simple_format”具备断行的功能。
app/views/groups/index.html.erb1 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.erb1 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.erb1 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.erb1 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>
|
这里
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”
12. 其他分支上传到 heroku
在终端输入
1
| git push heroku ch08:master
|