rails101再复盘-2

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 s

  • 3: 在 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 %> + + <% 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") %>
    + <% @groups.each do |group| %> + <% end %>
    # Title DescriptionCreator
    # <%= 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?" } )%>

    做完之后,打开 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

      1. 设计 Post 的 model 架构
        rails g model post content:text group_id:integer user_id:integer
        rake db:migrate
      1. 建立 Group / Post / User 三者间的关系
        Group has_many posts
    # 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
    
      1. git 存档
      1. 建立 Post Controller
        rails g controller posts
      1. 设立 routing
        修改 config/routes.rb
        resources :posts 加入 resources :groups 内。
    # 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 这样的网址支援。

      1. git 存档
      1. 在 groups#show 新增 “发表文章”按钮,加入一行
    # app/views/groups/show.html.erb    
      <%= link_to("Write a Post", new_group_post_path(@group), class: "btn btn-default pull-right")%>
    
      1. 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
    
      1. 新增 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 %>
      1. 修改 groups_controller 中的 show action
    # app/controllers/groups_controller.rb
      def show
        @group = Group.find(params[:id])
        @posts = @group.posts
      end
    
      1. 修改 groups/show.html.erb
        我们要修改app/views/groups/show.html.erb,让文章能够显示出来,在<%= @group.description %>下,我们多加了一个表格做这件事。
    # app/views/groups/show.html.erb
    ...(略)
      

    <%= @group.description %>

    + + + + + + + + + + <% @posts.each do |post| %> + + + + + + <% end %> + +
    文章内容发表者发表时间
    <%= post.content %><%= post.user.email %><%= post.created_at %>
    • 5: 对 Post 加上 validation
      这时候我们发现,刚刚有文章不小心没填内容,就发出去变成空的。
      为了防止这样的情形,我们还是要限制 post 的 content 要是为空,就不得送出。
    # app/models/post.rb
    class Post < ApplicationRecord
      belongs_to :user
      belongs_to :group
    
      validates :content, presence: true
        
    end
    
      1. git 存档

    6-4 文章应该按照发表时间倒序排列

      1. 修改 groups_controller
    app/controllers/groups_controller.rb
      def show
        @group = Group.find(params[:id])
        @posts = @group.posts.order("created_at DESC")
      end
    
      1. 使用 scope 替代直接 order
        这里的 order("created_at DESC") 是“程序语句”,但不是“功能叙述”。我们作为旁观者,只能猜测这是要按照“最近的时序排列”。

    如果能改成 @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
    
      1. 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 在群组里面判断“是否群组成员”实作

      1. 在 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)
    

    观察结果

      1. 修改 groups/show.html.erb
    app/views/groups/show.html.erb
    
      <% if current_user && current_user.is_member_of?(@group) %>
        
      <% else %>
        
      <% end %>
    
    
      1. 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

      1. 修改 routing
    config/routes.rb
      namespace :account do
        resources :groups
      end
    
    • 3: 修改下拉选项
    # app/views/common/_navbar.html.erb
       
    
      1. 建立 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
    
      1. 新增 “参与群组一览表”
    app/views/account/groups/index.html.erb
    

    我加入的讨论版

    <% @groups.each do |group| %> <% end %>
    # Title Description Post Count Last Update
    # <%= link_to(group.title, group_path(group)) %> <%= group.description %> <%= group.posts.count %> <%= group.updated_at %>
    • 6: git 储存

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

    使用者在下拉选单内,可以看到 "My Posts"
    使用者点选 "My Posts" 可以看到自己发表的所有文章

    • 1: 产生 account 的 namespace 下的 posts_controller
      rails g controller account/posts

      1. 建立 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
    
      1. 修改 routing
    config/routes.rb
      namespace :account do
         resources :groups
    +    resources :posts
      end
    
    • 4: 修改下拉选项
    # app/views/common/_navbar.html.erb
                    
    
      1. 新增 “发表文章一览表”
    # app/views/account/posts/index.html.erb
    

    我发表过的文章

    <% @posts.each do |post| %> <% end %>
    Content Group Name Last Update
    <%= 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") %>
    • 6: git 储存

    9-修饰细节,使用 Helper 与 Partial

    暂略

    你可能感兴趣的:(rails101再复盘-2)