登陆

一. Sessions 控制器

登录和退出功能由 Sessions 控制器中相应的 REST 动作处理 : 登录表单在 new 动作中处理, 登录的过程是向 create 动作发送 POST 请求, 退出则是向 destroy 动作发送 DELETE 请求。

1.首先生成 Sessions 控制器, 以及其中的 new 动作:

$ rails generate controller Sessions new

Users 资源使用特殊的 resources 方法自动获得 REST 式路由, 而 Sessions 资源则只能使用具名路由, 处理发给 /login 地址的 GET 和 POST 请求, 以及发给 /logout 地址的 DELETE 请求, 如下图所示(删除了 rails generate controller 生成的无用路由)。

(1).添加一个资源, 获得会话的标准 REST 式动作 RED

打开文件:config/routes.rb

添加上图中的路由规则之后, 还要创建Session时生成的测试, 使用新的登录路由, 如下图所示:

(2).更新 Sessions 控制器的测试, 使用新的登录路由 GREEN

打开文件:test/controllers/sessions_controller_test.rb

Sessions 资源的路由规则会把 URL 和动作对应起来, 如下图所示:

至此, 我们添加了好几个自定义的具名路由, 现在最好看一下完整的路由列表。我们可以执行 rails routes 命令生成路由列表:

注:你没必要完全理解输出的这些路由。像这样查看路由能对应用支持的动作有个整体认识。

二. 登录表单

登录表单和注册表单之间的主要区别是, 会话不是模型, 因此不能创建类似 @user 的变量。所以, 构建登录表单时, 我们要为 form_for 稍微多提供一些信息。form_for(@user)的作用是让表单向 /users 发起 POST 请求。对会话来说, 我们需要指明资源的名称以及相应的 URL:form_for(:session, url: login_path)知道怎么调用 form_for 之后, 参照注册表单编写登陆表单, 如图所示:

1.登录表单的代码

打开文件:app/views/sessions/new.html.erb

注:为了操作方便, 我们还加入了指向“注册”页面的链接。(导航栏中的“Login”还没填写地址,所以你要在地址栏中输入 /login。稍后会修正这个问题。)

2.页面

三. 查找并验证用户的身份

首先,我们要为 Sessions 控制器编写一个最简单的 create 动作, 以及空的 new 动作和 destroy 动作, 如下图所示:

1.Sessions 控制器中 create 动作的初始版本

打开文件:app/controllers/sessions_controller.rb

create 动作现在只渲染 new 视图, 不过这为后续工作做好了准备。提交 /login 页面中的表单后, 显示的页面如下图所示:

2.查找并验证用户的身份

打开文件:app/controllers/sessions_controller.rb

上图中红框内的第一行使用提交的电子邮件地址从数据库中取出相应的用户。(我们前面说过, 电子邮件地址都是以小写字母形式保存的, 所以这里调用了 downcase 方法, 确保提交有效的地址后能查到相应的记录。)高亮显示的第二行看起来很怪, 但在 Rails 中经常使用:

user && user.authenticate(params[:session][:password])

我们使用 && (逻辑与)检测获取的用户是否有效。因为除了 nil 和 false 之外的所有对象都被视作真值, 所以上面这个语句可能出现的结果如下表所示。从表中可以看出, 当且仅当数据库中存在提交的电子邮件地址, 而且对应的密码和提交的密码匹配时, 这个语句才会返回 true 。

3.渲染闪现消息

布局中已经加入了显示闪现消息的局部视图, 所以无需其他修改, flash[:danger] 消息就能显示出来; 而且因为使用了 Bootstrap 提供的 CSS, 消息的样式也很美观,如图所示::

注:就跟代码中标注的一样, 现在的闪现消息有问题问题在于, 闪现消息在一个请求的生命周期内是持续存在的, 而重新渲染页面(使用 render 方法)与重定向不同, 不算是一次新请求, 所以你会发现这个闪现消息存在的时间比预期的要长很多。

例如:提交无效的登录信息, 然后访问首页, 还会显示这个闪现消息:

4.测试闪现消息

首先, 为应用的登录功能生成一个集成测试文件:

$ rails generate integration_test users_login

(1).捕获继续显示闪现消息的测试 RED

打开文件:test/integration/users_login_test.rb

让上图中的测试通过的方法是, 把 flash 换成特殊的 flash.now 。 flash.now 专门用于在重新渲染的页面中显示闪现消息。与 flash 不同的是, flash.now 中的内容会在下次请求时消失——这正是上图中的测试所需的行为。替换之后, 正确的应用代码如下图所示:

(2).处理登录失败正确的代码 GREEN

四. 登录

Rails 生成器很人性化, 生成 Sessions 控制器时自动生成了一个 Ses-sions 辅助模块。而且, 其中的辅助方法会自动引入 Rails 视图。如果在控制器的基类( ApplicationCon-troller )中引入辅助方法模块, 还可以在控制器中使用,如下图所示:

1.在 Application 控制器中引入 Sessions 辅助模块

打开文件:app/controllers/application_controller.rb

注:做好这些准备工作后,现在可以开始编写代码登入用户了。

2.login 方法

有 Rails 提供的 session 方法协助,登入用户很简单。( session 方法与前面生成的 Sessions 控制器没有关系。)我们可以把 session 视作一个散列, 按照下面的方式赋值:

session[:user_id] = user.id

这么做会在用户的浏览器中创建一个临时 cookie, 内容是加密后的用户 ID。在后续的请求中, 可以使用 ses-sion[:user_id] 取回这个 ID。到后面我们会使用的 cookies 方法创建的是持久 cookie, 而 session 方法创建的是临时会话, 浏览器关闭后立即失效。
我们想在多个不同的地方使用这个登录方式, 所以在 Sessions 辅助模块中定义一个名为 log_in 的方法, 如下图所示:

(1).login 方法

打开文件:app/helpers/sessions_helper.rb

session 方法创建的临时 cookie 会自动加密, 所以上图中的代码是安全的, 攻击者无法使用会话中的信息以该用户的身份登录。不过, 只有 session 方法创建的临时 cookie 是这样, cookies 方法创建的持久cookie 则有可能会受到会话劫持(session hijacking)攻击。所以在以后我们会小心处理存入用户浏览器中的信息。
定义好 log_in 方法后, 我们可以完成 Sessions 控制器中的 create 动作, 登入用户, 然后重定向到用户的资料页面, 如下图所示:

(2).登入用户

打开文件:app/controllers/sessions_controller.rb

注:简洁的重定向代码redirect_to user我们在前面节见过。Rails 会自动转换成用户资料页的地址: user_url(user)。

3.当前用户

把用户 ID 安全地存储在临时会话中之后,在后续的请求中可以将其读取出来。我们要定义一个名为 cur-rent_user 的方法,从数据库中取出用户 ID 对应的用户。 current_user 方法可用于编写类似下面的代码:
  <%= current_user.name %>
或是:
  redirect_to current_user
查找用户的方法之一是使用 find 方法,在用户资料页面就是这么做的:
  User.find(session[:user_id])
前面说过,如果用户 ID 不存在, find 方法会抛出异常。在用户的资料页面可以使用这种行为,因为必须有相应的用户才能显示他的信息。但 session[:user_id] 的值经常是 nil (表示用户未登录),所以我们要使用 create 动作中通过电子邮件地址查找用户的 find_by 方法,通过 id 查找用户:
  User.find_by(id: session[:user_id])
如果 ID 无效, find_by 方法返回 nil ,而不会抛出异常。因此,我们可以按照下面的方式定义 current_user 方法:
  def current_user
    User.find_by(id: session[:user_id])
  end
这样定义应该可以,不过如果在一个页面中多次调用 current_user 方法,会多次查询数据库。所以,我们要使用一种 Ruby 习惯写法,把 User.find_by 的结果存储在实例变量中,只在第一次调用时查询数据库,后续再调用直接返回实例变量中存储的值:
  if @current_user.nil?
    @current_user = User.find_by(id: session[:user_id])
  else
    @current_user
  end
使用前面节中介绍的“或”运算符 || ,可以把这段代码改写成:
  @current_user = @current_user || User.find_by(id: session[:user_id])
User 对象是真值,所以仅当 @current_user 没有赋值时才会执行 find_by 方法。
述代码虽然可以使用,但并不符合 Ruby 的习惯。 @current_user 赋值语句的正确写法是这样:
  @current_user ||= User.find_by(id: session[:user_id])
这种写法用到了容易让人困惑的 ||= (或等)运算符, 参见下图的说明:

(1).在会话中查找当前用户

打开文件:app/helpers/sessions_helper.rb

注:定义好 current_user 方法之后,可以根据用户的登录状态修改应用的布局了。

4.修改布局中的链接

修改网站布局中的链接时要在 ERb 中使用 if-else 语句,用户登录时显示一组链接,未登录时显示另一组链接:
<% if logged_in? %>
# 登录用户看到的链接
<% else %>
# 未登录用户看到的链接
<% end %>
为了编写这种代码,我们要定义 logged_in? 方法,返回布尔值。
用户登录后,当前用户存储在会话中,即 current_user 不是 nil 。检测会话中有没有当前用户要使用“非”运算符。“非”运算符写做 ! ,经常读作“bang”。logged_in? 方法的定义如下图所示:

(1).logged_in? 辅助方法

打开文件:app/helpers/sessions_helper.rb

(2).修改布局中的链接

打开文件:app/views/layouts/_header.html.erb

Ruby Rails学习中:登陆的更多相关文章

  1. Ruby Rails学习中:注册表单,注册失败,注册成功

    接上篇 一. 注册表单 用户资料页面已经可以访问了, 但内容还不完整.下面我们要为网站创建一个注册表单. 1.使用 form_for 注册页面的核心是一个表单, 用于提交注册相关的信息(名字.电子邮件 ...

  2. Ruby Rails学习中:调试信息和 Rails 的三种环境,Users 资源,调试器,Gravatar 头像和侧边栏

    注册 一.调试信息和 Rails 环境 现在咱们要实现的用户资料页面是我们这个应用中第一个真正意义上的动态页面.虽然视图的代码不会动态改变, 不过每个用户资料页面显示的内容却是从数据库中读取的.添加动 ...

  3. Ruby Rails学习中:添加安全密码

    接上篇 一. 添加安全密码 我们已经为 name 和 email 字段添加了验证规则, 现在要加入用户所需的最后一个常规属性: 安全密码.每个用户都要设置一个密码(还要二次确认), 数据库中则存储经过 ...

  4. Ruby Rails学习中:User 模型,验证用户数据

    用户建模 一. User 模型 实现用户注册功能的第一步是,创建一个数据结构,用于存取用户的信息. 在 Rails 中,数据模型的默认数据结构叫模型(model,MVC 中的 M).Rails 为解决 ...

  5. Ruby Rails学习中:Ruby内置的辅助方法,基础内容回顾补充

    一. Ruby内置的辅助方法 1.打开文件:app/views/layouts/application.html.erb(演示应用的网站布局) 来咱把注意力放在圈起来的那一行: 这行代码使用 Rail ...

  6. Ruby Rails学习中:Sass 和 Asset Pipeline,布局中的链接(Rails路由,具名路由),用户注册: 第一步

    接上篇: 一.Sass 和 Asset Pipeline Rails 中最有用的功能之一是 Asset Pipeline, 它极大地简化了静态资源文件(CSS.JavaScript 和图像)的生成和管 ...

  7. Ruby Rails学习中:网站导航,Bootstrap和自定义的CSS,局部视图

    添加一些结构 一.网站导航 1.添加一些结构后的网站布局文件 打开文件:app/views/layouts/application.html.erb 简单介绍一下,添加的代码: 我们从上往下看一下这段 ...

  8. Ruby Rails学习中:有点内容的静态页面

    续上篇: 一. 有点内容的静态页面 rails new 命令创建了一个布局文件, 不过现在最好不用.我们重命名这个文件: $ mv app/views/layouts/application.html ...

  9. Ruby Rails学习中:关于测试的补充,MiniTest报告程序,Guard自动测试

    一. 关于测试的补充 1.MiniTest报告程序 为了让 Rails 应用的测试适时显示红色和绿色,我建议你在测试辅助文件中加入以下内容: (1).打开文件:test/test_helper.rb ...

随机推荐

  1. LGU67496 小$s$的玻璃弹珠

    题意 在一幢\(m\)层建筑你将获得\(n\)个一样的鸡蛋,从高于\(x\)的楼层落下的鸡蛋都会碎.如果一个蛋碎了,你就不能再把它掉下去. 你的目标是确切地知道\(x\)的值.问至少要扔几次才能确定. ...

  2. 2017 ZSTU寒假排位赛 #7

    题目链接:https://vjudge.net/contest/149498#overview. A题,水题,直接按照题意模拟一下即可. B题,我用的是线段树.大力用的差分标记(上次听zy说过,下次再 ...

  3. 解决Powershell中不能运行脚本问题

    问题: powershell中不能执行脚本,提示‘because running scripts is disabled on this system’ 原因: powershell中默认的execu ...

  4. LeetCode 41. 缺失的第一个正数(First Missing Positive)

    题目描述 给定一个未排序的整数数组,找出其中没有出现的最小的正整数. 示例 1: 输入: [1,2,0] 输出: 3 示例 2: 输入: [3,4,-1,1] 输出: 2 示例 3: 输入: [7,8 ...

  5. ajax设置头信息,读取头信息

    一.设置头信息 jQuery function GetDateForServiceCustomer(userId) { $.ajax({ url: 'http://*******/api/orders ...

  6. CSS三角形的实现原理及运用

    原理 css盒模型 一个盒子包括: margin+border+padding+content– 上下左右边框交界处出呈现平滑的斜线. 利用这个特点, 通过设置不同的上下左右边框宽度或者颜色可以得到小 ...

  7. 计算机组成原理 — FPGA 现场可编程门阵列

    目录 文章目录 目录 FPGA FPGA 的应用场景 FPGA 的技术难点 FPGA 的工作原理 FPGA 的体系结构 FPGA 的开发 FPGA 的使用 FPGA 的优缺点 参考文档 FPGA FP ...

  8. 错误 MSB6006 CL.exe 已退出,代码为2

    环境 WIN10 + VS2019 社区版 按照其他网友的方法说 解决方法: 1 一个类内部的定义返回类型为double的方法种没有写return语句. 2 变量没有初始化也会导致这种情况. 但是设置 ...

  9. 在Python中使用lambda高效操作列表的教程

    在Python中使用lambda高效操作列表的教程 这篇文章主要介绍了在Python中使用lambda高效操作列表的教程,结合了包括map.filter.reduce.sorted等函数,需要的朋友可 ...

  10. JAVA 基础编程练习题10 【程序 10 自由落体】

    10 [程序 10 自由落体] 题目:一球从 100 米高度自由落下,每次落地后反跳回原高度的一半:再落下,求它在 第 10 次落地时, 共经过多少米?第 10 次反弹多高? package cska ...