5-1 本章目标
- 安装“会员系统”(使用 devise)
- 在 navbar 安装登录/退出按钮
- 只有登录了的使用者,才可以建立群组
- 只有群组的建立者,才可以 编辑 / 删除群组
5-2 安装 devise gem
先创建分支
git checkout -b ch04
1: 安装登录系统
Gemfile 新增一行gem 'devise'
, 然后bundle install
2 : 产生会员系统的必要文件
执行
rails g devise:install
rails g devise user
rake db:migrate
之后 重开 rails s3: 在 groups_controller 限制 “新增讨论群”必须先登录
# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new]
- 4: git 存档
5-3 让这个网站有实际“登录”、“退出”的功能
让右上角的“登录”可以实际有:“登录” / “退出的效果”
- 1: 修改 app/views/common/_navbar.html.erb
# app/views/common/_navbar.html.erb
-
- <%= link_to("登录", '#') %>
-
+ <% if !current_user %>
+ <%= link_to("注册", new_user_registration_path) %>
+ <%= link_to("登录", new_user_session_path) %>
+ <% else %>
+
+
+ Hi!, <%= current_user.email %>
+
+
+
+
+ <% end %>
- 2: 修改
app/assets/javascripts/application.js
# app/assets/javascripts/application.js
//= require bootstrap/alert
+ //= require bootstrap/dropdown
- 3: git 储存
5-4 让“群组”与“使用者”产生关联
- 1: 新增 user_id 到 group 的 table 里
执行rails g migration add_user_id_to_group
然后打开刚刚新增的 migration 档,修改让它长得像这样
# db/migrate/一串数字_add_user_id_to_group
class AddUserIdToGroup < ActiveRecord::Migration[5.0]
def change
add_column :groups, :user_id, :integer
end
end
接着执行rake db: migrate
- 2: 连结 user 与 group 的双向关系
修改 app/models/user.rb 加入 has_many :groups
# app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :groups
end
修改 app/models/group.rb 加入 belongs_to :user
# app/models/group.rb
class Group < ApplicationRecord
belongs_to :user
validates :title, presence: true
end
- 3: 在新增看板时,记录谁是群组的建立者
修改app/controllers/groups_controller.rb
,在需要“登入验证”的 action 列表再加入 create。
# app/controllers/groups_controller.rb
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
def create
@group = Group.new(group_params)
@group.user = current_user
if @group.save
redirect_to groups_path
else
render :new
end
end
4 : git 储存
5 : 修改 group 列表,将 user 的名字列上去
修改app/views/groups/index.html.erb
然后把 Creator 的信息加进去。(代码如下)
app/views/groups/index.html.erb
<%= link_to("New group", new_group_path, class: "btn btn-primary pull-right") %>
#
Title
Description
+ Creator
<% @groups.each do |group| %>
#
<%= link_to(group.title, group_path(group)) %>
<%= group.description %>
+ <%= group.user.email %>
<%= 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 %>
做完之后,打开 http://localhost:3000/ 会发现,首页怎么爆炸了。
别担心,这是因为我们之前创造的“群组”都是“无主”的。只要把这些“无主”的群组,通通删掉就行了。
6: 删除所有“无主”的群组
执行rails console
然后输入Group.delete_all
输入exit
退出 console然后再打开 http://localhost:3000/ 就正常了。7 : git 存档
5-5 只有群组的“创始者”可以“编辑”“删除”群组资讯
- 1: 路人不应该可以看到“编辑”“删除”按钮
根据逻辑 只有登入模式下
而且还必须是群组“创始者”
才能看得到这个两个按钮,否则路人是看不到的。
app/views/groups/index.html.erb
+ <% 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 %>
重新刷新首页,路人现在就看不到按钮了。接着 git 保存。
- 3: 路人不应该也可以“直接输入网址”去存取 edit / update / destroy action
除了将按钮对路人隐藏外,我们还要考虑到一个情形,假设这个路人是知道 Rails 规则的,那么他可能输入http://localhost:3000/groups/某笔数据的ID/edit
网址,就直接可以编辑数据。
我们也要在 controller 做权限判断,滤掉这种人。
首先,我们先限定 edit / update / destroy
这三个操作动作,必须要是“登入”的使用者才能存取。
# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]
- 4: 必须要是 group 拥有人,才能进入 edit,否则会被重导至首页,并显示错误信息。
修改app/controllers/groups_controller.rb
加入权限,如果不是“创始者”去存取,会显示没有权限的错误信息。
# app/controllers/groups_controller.rb
def edit
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
end
这样当其他人,试图输入http://localhost:3000/groups/某笔数据的ID/edit
这样的网址,想要编辑数据,就会被挡住。
5: 依样画葫芦的把“权限检查”的代码,套用到 update / destroy 上
6: git 存档
7: 制作
find_group_and_check_permission
我们发现 edit、update、destroy 这三个 action 都有一样的代码,看起来有点冗。
@group = Group.find(params[:id])
if current_user != @group.user
redirect_to root_path, alert: "You have no permission."
end
其实我们可以透过把它包装成一个函式的方式 find_group_and_check_permission
去省略这段冗余代码。
打开 app/controllers/groups_controller.rb
在 private 下,新增一个 find_group_and_check_permission
# app/controllers/groups_controller.rb
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
再修正 edit
# app/controllers/groups_controller.rb
def edit
find_group_and_check_permission
end
修正 update
# app/controllers/groups_controller.rb
def update
find_group_and_check_permission
if @group.update(group_params)
redirect_to groups_path, notice: "Update Success"
else
render :edit
end
end
修正 destroy
# app/controllers/groups_controller.rb
def destroy
find_group_and_check_permission
@group.destroy
redirect_to groups_path, alert: "Group deleted"
end
- 8: 把
find_group_and_check_permission
挂到before_action
开发到这里你会发现一件事,find_group_and_check_permission
其实都是在这三个 action 的最前面开头执行的,所以你甚至可以这样写,把find_group_and_check_permission
挂到before_action
# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
before_action :authenticate_user! , only: [:new, :create, :edit, :update, :destroy]
before_action :find_group_and_check_permission, only: [:edit, :update, :destroy]
然后再把 edit update destroy
里的 find_group_and_check_permission
砍掉。变成这样:
# app/controllers/groups_controller.rb
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
9: git 存档
10 : 修掉 show 里面的 Edit 按钮
做到这里,我们还发现一个地方,我们漏了改,那就是show.html.erb
上面,还有一个 Edit 按钮我们还没有拔掉。
app/views/groups/show.html.erb
<% if current_user && current_user == @group.user %>
<%= link_to("Edit", edit_group_path(@group), class: "btn btn-primary pull-right")%>
<% end %>
- 11: git 存档
6-1 本章目标
可以在群组里面新增 / 编辑 / 删除文章
文章必须要有“内容”,否则不允许被发表
6-2 建立文章的架构
git checkout -b ch05
-
- 设计 Post 的 model 架构
rails g model post content:text group_id:integer user_id:integer
rake db:migrate
- 设计 Post 的 model 架构
-
- 建立 Group / Post / User 三者间的关系
Group has_many posts
- 建立 Group / Post / User 三者间的关系
# app/models/group.rb
class Group < ApplicationRecord
belongs_to :user
has_many :posts
validates :title, presence: true
end
User has_many posts
# app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :groups
has_many :posts
end
Post belongs_to User
Post belongs_to Group
# app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
belongs_to :group
end
-
- git 存档
-
- 建立 Post Controller
rails g controller posts
- 建立 Post Controller
-
- 设立 routing
修改config/routes.rb
将resources :posts
加入resources :groups
内。
- 设立 routing
# config/routes.rb
Rails.application.routes.draw do
devise_for :users
resources :groups do
resources :posts
end
root 'groups#index'
end
之后 rake routes
然后你就会发现, routing 列表内产生了 /groups/:group_id/posts/new
这样的网址支援。
-
- git 存档
-
- 在 groups#show 新增 “发表文章”按钮,加入一行
# app/views/groups/show.html.erb
<%= link_to("Write a Post", new_group_post_path(@group), class: "btn btn-default pull-right")%>
-
- git 存档
6-3 实际发表文章
实际把文章发在群里面
使用者可以在群里面看到文章一览表
- 1: 实作 new / create action
# app/controllers/posts_controller.rb
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
- 新增
# app/views/posts/new.html.erb
新增文章
<%= simple_form_for [@group,@post] do |f| %>
<%= f.input :content, input_html: { class: "form-control"} %>
<%= f.submit "Submit", disable_with: "Submiting...", class: "btn btn-primary"%>
<% end %>
-
- 修改 groups_controller 中的 show action
# app/controllers/groups_controller.rb
def show
@group = Group.find(params[:id])
@posts = @group.posts
end
-
- 修改 groups/show.html.erb
我们要修改app/views/groups/show.html.erb
,让文章能够显示出来,在<%= @group.description %>
下,我们多加了一个表格做这件事。
- 修改 groups/show.html.erb
# app/views/groups/show.html.erb
...(略)
<%= @group.description %>
+
+
+
+ 文章内容
+ 发表者
+ 发表时间
+
+
+
+ <% @posts.each do |post| %>
+
+ <%= post.content %>
+ <%= post.user.email %>
+ <%= post.created_at %>
+
+ <% end %>
+
+
- 5: 对 Post 加上 validation
这时候我们发现,刚刚有文章不小心没填内容,就发出去变成空的。
为了防止这样的情形,我们还是要限制 post 的 content 要是为空,就不得送出。
# app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
belongs_to :group
validates :content, presence: true
end
-
- git 存档
6-4 文章应该按照发表时间倒序排列
-
- 修改 groups_controller
app/controllers/groups_controller.rb
def show
@group = Group.find(params[:id])
@posts = @group.posts.order("created_at DESC")
end
-
- 使用 scope 替代直接 order
这里的 order("created_at DESC") 是“程序语句”,但不是“功能叙述”。我们作为旁观者,只能猜测这是要按照“最近的时序排列”。
- 使用 scope 替代直接 order
如果能改成 @group.posts.recent 可能在维护性上更直观。
这里我们还可以用 Rails 里面的一个内建 API scope 让代码更直观一些。
修改app/models/post.rb
,加入一行 scope :recent, -> { order("created_at DESC")}
# app/models/post.rb
class Post < ApplicationRecord
# ... 略
scope :recent, -> { order("created_at DESC")}
end
然后修改 app/controllers/groups_controller.rb
中的 show
# app/controllers/groups_controller.rb
def show
@group = Group.find(params[:id])
@posts = @group.posts.recent
end
-
- git 储存
6-5 加入文章分页功能
1: 安装 will_paginate
gem 'will_paginate'
bundle install
rails s
2: 修改 groups_controller
# app/controllers/groups_controller.rb
def show
@group = Group.find(params[:id])
@posts = @group.posts.recent.paginate(:page => params[:page], :per_page => 5)
end
- 3: 修改 groups/show.html.erb
+
+ <%= will_paginate @posts %>
+
- 4: git 储存
7-1 本章目标
一个使用者可以选择“加入”、“退出”讨论群
群的创始者,创群一开始就应该加在群组里
7-2 建立“群成员”数据表
git checkout -b ch06
1: 建立 GroupRelationship
输入rails g model group_relationship group_id:integer user_id:integer
执行rake db:migrate
2: 设定 Group 与 User 之间的关系 ( 使用者参与的所有群)
首先,修改app/models/user.rb
加入以下两行
# app/models/user.rb
class User < ApplicationRecord
# .. 略
+ has_many :group_relationships
+ has_many :participated_groups, :through => :group_relationships, :source => :group
end
然后修改 app/models/group_relationship.rb
,加入这两行
# app/models/group_relationship.rb
class GroupRelationship < ApplicationRecord
+ belongs_to :group
+ belongs_to :user
end
这样当捞 user.participated_groups
时,就会捞出“参与的所有群”。
- 3: 设定 Group 与 User 之间的关系 ( 群组内的所有会员 )
# app/models/group.rb
class Group < ApplicationRecord
# ... 略
+ has_many :group_relationships
+ has_many :members, through: :group_relationships, source: :user
end
- 4: 动手测看看
打开rails console
依次输入
u = User.first
g = Group.first
g.members << u
g.members
u.participated_groups # 看看会出现什么结果,最后执行exit退出
- 5: git 储存
7-3 在群组里面判断“是否群组成员”实作
-
- 在 user model 内实作判断式“是否为群组的一分子”
# app/models/user.rb
class User < ApplicationRecord
# 略 ...
+ def is_member_of?(group)
+ participated_groups.include?(group)
+ end
end
然后“重开” rails console
依次输入
u = User.first
g = Group.first
u.is_member_of?(g)
观察结果
-
- 修改 groups/show.html.erb
app/views/groups/show.html.erb
<% if current_user && current_user.is_member_of?(@group) %>
<% else %>
<% end %>
-
- git 存档
7-4 “加入群组”或“退出群组”
实作 model 层 “加入群组”
实作 model 层“退出群组”
# app/models/user.rb
def join!(group)
participated_groups << group
end
def quit!(group)
participated_groups.delete(group)
end
打开 rails console
终端依次输入
u = User.first
g = Group.first
u.join!(g)
u.is_member_of?(g)
u.quit!(g)
u.is_member_of?(g)
exit # 观察结果, 记得 git
7-5 实际操作“加入群组”或“退出群组”
app/controllers/groups_controller.rb
+ 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
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]
...
# config/routes.rb
resources :groups do
+ member do
+ post :join
+ post :quit
+ end
resources :posts
end
# app/views/groups/show.html.erb
<% if current_user && current_user.is_member_of?(@group) %>
+ <%= link_to("Quit Group", quit_group_path(@group), method: :post, class: "btn btn-default") %>
<% else %>
+ <%= link_to("Join Group", join_group_path(@group), method: :post, class: "btn btn-default") %>
<% end %>
最后 git 保存
7-6 User 在建立 group 后自动成为 group 的一员
app/controllers/groups_controller.rb
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
还得记得 git 保存
8-使用者可以在“自己的后台”看过曾经发表的文章、以及创立的社团
新增一个下拉选单
可以看到自己过去曾经发表的文章
可以看到自己过去曾经参与的社团
8-2 可以看到自己参与的所有群组
git checkout -b ch07
1: 产生 account 的 namespace 下的 groups_controller
rails g controller account/groups
-
- 修改 routing
config/routes.rb
namespace :account do
resources :groups
end
- 3: 修改下拉选项
# app/views/common/_navbar.html.erb
-
- 建立
account/groups_controller.rb
下的 index action
- 建立
app/controllers/account/groups_controller.rb
class Account::GroupsController < ApplicationController
before_action :authenticate_user!
def index
@groups = current_user.participated_groups
end
end
-
- 新增 “参与群组一览表”
app/views/account/groups/index.html.erb
我加入的讨论版
#
Title
Description
Post Count
Last Update
<% @groups.each do |group| %>
#
<%= link_to(group.title, group_path(group)) %>
<%= group.description %>
<%= group.posts.count %>
<%= group.updated_at %>
<% end %>
- 6: git 储存
8-3 可以看到自己发表的所有文章
使用者在下拉选单内,可以看到 "My Posts"
使用者点选 "My Posts" 可以看到自己发表的所有文章
1: 产生 account 的 namespace 下的 posts_controller
rails g controller account/posts
-
- 建立
account/posts_controller.rb
下的 index action
- 建立
# app/controllers/account/posts_controller.rb
class Account::PostsController < ApplicationController
before_action :authenticate_user!
def index
@posts = current_user.posts
end
end
-
- 修改 routing
config/routes.rb
namespace :account do
resources :groups
+ resources :posts
end
- 4: 修改下拉选项
# app/views/common/_navbar.html.erb
-
- 新增 “发表文章一览表”
# app/views/account/posts/index.html.erb
我发表过的文章
Content
Group Name
Last Update
<% @posts.each do |post| %>
<%= post.content %>
<%= post.group.title %>
<%= post.updated_at %>
<%= link_to('Edit', edit_group_post_path(post.group, post), class: "btn btn-default btn-xs") %>
<%= link_to('Delete', group_post_path(post.group, post), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-default btn-xs") %>
<% end %>
- 6: git 储存
9-修饰细节,使用 Helper 与 Partial
暂略