课程目标
接下来会以一个“讨论版”为主题,实际让同学了解如何“从零打造”手写出一个网站。
这个讨论版会有:
- 使用者注册以及登录功能
- 开新讨论区、留言功能
- 加入讨论区、退出讨论区功能
- 权限管理功能
- 热门文章排序功能
- 了解什么是 RESTful
当你把这次应用程序实作完成,并上线后,你会学会:
- 怎么把需求变成会动的 Rails 应用程序
- 如何在 Rails 套版
- 如何制作使用者登录功能
- 重要 Rails 观念以及 API
- CRUD
- RESTful
- member, collection
- helper
- partial
- scope
- 找到 Rails 第三方 gem 的技巧
- 要怎么把你的应用程序放到网络上
这几乎是一个 Rails 新手应该学的一切了,有了这些基础技巧之后,你再学更难的技巧,或者是要自己做出复杂的东西,就不会那么容易失败了。
课程复盘
1. 制作登录系统
(1)安装登录系统
然后执行
(2)产生会员系统的必要文件
执行
1 2 3
| rails g devise:install rails g devise user rake db:migrate
|
2. 在 groups_controller 限制 “新增讨论群”必须先登录
app/controllers/groups_controller.rb1 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.erb1 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_path和new_user_session_path、destroy_user_session_path。
修改app/assets/javascripts/application.js,
加入 //= require bootstrap/dropdown
app/assets/javascripts/application.js1 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_group1 2 3 4 5
| class AddUserIdToGroup < ActiveRecord::Migration[5.0] def change add_column :groups, :user_id, :integer end end
|
然后,执行
5. 在新增看板时,记录谁是群组的建立者
修改 app/controllers/groups_controller.rb,在需要“登入验证”的 action 列表再加入 create。
app/controllers/groups_controller.rb1 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.rb1 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.erb1 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.rb1 2
| class GroupsController < ApplicationController before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]
|
(2)必须要是 group 拥有人,才能进入 edit / update / destroy action,否则会被重导至首页,并显示错误信息。
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 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.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 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.rb1 2 3 4 5 6 7
| Rails.application.routes.draw do devise_for :users resources :groups do resources :posts end root 'groups#index' end
|
做了这件事后,执行
然后你就会发现, routing 列表内产生了 /groups/:group_id/posts/new 这样的网址支援。
9. 实作post的 new / create action
app/controllers/posts_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 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.erb1 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"> <%= f.input :content, input_html: { 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.erb1 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.rb1 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.rb1 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
然后执行
修改 app/controllers/groups_controller.rb 中的 show
加入分页代码,变成
app/controllers/groups_controller.rb1 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.erb1 2 3 4 5 6
| </tbody> </table> + <div class="text-center"> + <%= will_paginate @posts %> + </div>
|
13. 各种功能的gem一览
各种功能的gem一览: “RubyToolbox”