Ruby on Rails 中的用户认证






4.97/5 (21投票s)
全面了解如何为 Rails 应用程序实现身份验证
目录
- 引言
- 安装基础软件
- 运行一个基本的 Rails 网站
- 创建数据库
- 设置源代码管理
- 熟悉 Rails 项目
- 创建数据库用户表作为迁移
- 创建用户模型
- 控制器,第一步
- 视图:认证页面
- 登录页面:控制器、视图和路由
- 已注销页面
- 注册页面
- 更改密码页面(设置)
- 发送邮件
- 忘记密码
- 记住我
- 使用 Recaptcha
- 使用我们的身份验证
- 最后的润色
- 源代码
- 下一步?
引言
用户认证(登录、注册、更改密码、忘记密码等)是大多数网站的基础。向 Rails 网站添加认证最常见的方法之一是使用 Devise gem。但是,在本文中,我将向您展示如何实现自己的认证,因为这是学习 Ruby on Rails 一些基本功能的好方法。
本文面向 Ruby 和 Ruby on Rails 的初学者,但我期望您对其他技术(如 SQL、HTML、Javascript 以及 C# 或 VB.NET)有一定的了解。
当然,您可以在线找到许多其他优秀的 Ruby on Rails 入门指南。我在这里提供的附加价值是我对这项技术的经验,希望能填补可能遇到的某些空白。在本文中,我们将使用:
- Ruby
- Ruby on Rails
- Git
- RubyMine
- PostgreSQL
- Rails 交互式控制台
- Emails
- Recaptcha
并实现一个完整的认证系统,而不是作为单独的博客或文章条目,该系统将支持:
- 注册
- 登录
- 注销
- 帐户管理
- 密码重置
- 基本用户管理
探索以下功能:
- Gem
- 站点环境变量
- 数据库迁移
- HTML 元数据
- 控制器、模型和视图
- 应用程序布局
- CSS
- Cookie
此外,创建一个基本的认证系统,然后在此基础上构建其他网站功能是大多数网站的基本要求,并为本教程提供了良好的基础。
如果您是 Ruby 新手并熟悉 C#,您可能需要首先阅读我的文章 C# 和 Ruby 类比较,以更熟悉 Ruby 语法。
安装基础软件
首先需要做的是安装一些东西:
- Windows 版 Ruby 和 Rails 运行时
- PostgreSQL,我们将用作后端数据库
- 一个不错的编辑器,例如 RubyMine
完成此操作后,我们可以创建 Rails 网站和认证表单的脚手架。
在 Windows 上安装 Ruby on Rails
首先,访问网站 http://railsinstaller.org/ 并下载并安装 Windows 工具包。正如页面所说,“RailsInstaller 包含您入门所需的一切”,强烈推荐!
安装 PostgreSQL
RailsInstaller 自带 Sqlite,但我们将使用一个更“专业”的数据库,如果您想将应用程序推送到 Heroku 等虚拟主机上,我强烈建议您使用 PostgreSQL。Windows PostgreSQL 下载包含数据库服务器和一个不错的 Windows 应用程序,用于管理数据库,名为 pgAdmin III。它还提供了安装 StackBuilder 的选项,您可以拒绝。
安装 RubyMine
JetBrains 的 RubyMine 是一个优秀的 IDE。我建议下载试用版,该 IDE 提供了出色的编辑器、集成的源代码管理、调试和工具支持。使用 RubyMine,几乎不需要打开命令行提示符,这是我个人喜欢避免的。
其他实用工具
Ansicon
在需要或有益使用命令行时,我建议安装 Ansicon,这是一个控制台应用程序,它知道如何处理 ANSI 转义序列,这些序列用于格式化和着色命令行中许多有用的东西。
Firefox
我还建议安装 Firefox,因为浏览器引擎用于某些集成测试模式。单元测试和集成测试将在下一篇文章中介绍。
SmartGit
这是源代码管理系统 Git 的 Windows 封装,是一个不可或缺的工具,可以消除初学者(以及其他版本控制系统的有经验用户!)在使用 Git 命令行操作时会遇到的困惑。但是,由于 RubyMine 集成了版本控制(SVN 和 Git 是其中几个选项),我们将专注于在 RubyMine 中使用版本控制。
运行一个基本的 Rails 网站
安装完上述各种程序后,启动 Ruby-Mine 并选择“创建新项目”
输入项目名称、位置,并为项目类型选择“Rails 应用程序”
在第二个屏幕“Rails 应用程序设置”中,将“选定的数据库”更改为“postgresql”
RubyMine 将为您的网站创建脚手架
这些文件夹中的大部分内容将在本文中涉及,所以请耐心等待!
测试网站
此时,您可以测试 RubyMine 创建的网站。从菜单中选择“运行”,然后从下拉菜单中选择“运行 'development: basic-auth'”。这将启动 WEBrick 应用程序服务器。初始化后
打开您喜欢的浏览器,在 URL 中(Internet Explorer 除外)输入“localhost:3000”。如果您使用的是 Internet Explorer,请输入“https://:3000/”——“http://”是必不可少的,否则 Internet Explorer(至少版本 9 是这样)会尝试使用 Bing 进行搜索。
然后您将看到欢迎屏幕(其中一部分在此截图中)
停止服务器
要停止 Web 服务器,请单击红色方块
有时可能需要手动清除服务器的 PID
我偶尔会遇到服务器非正常关闭的情况,尤其是在调试时,这需要手动删除 PID 文件,该文件位于项目文件夹的 tmp\pids 中。例如,当服务器运行时,这是 PID 文件
如果 RubyMine 报告服务器实例已在运行,请找到您的 PID 文件并将其删除,然后尝试再次启动服务器。
创建数据库
有几种方法可以做到这一点,但为了我们的目的,我将演示如何(大部分)在 RubyMine 中完成此操作。
检查 database.yml 文件
首先,在 RubyMine 中打开 database.yml 文件,该文件位于 config 文件夹中
YAML 文件有点像 Windows 应用程序过去使用的(现在大多已过时的)INI 文件。它主要由节和键值对组成,并带有一些额外的配置功能。请注意“development:”节,并且用户名与应用程序名称相同
如果您已经安装了 PostgreSQL,则可以将用户名和密码替换为现有用户名和密码,但是,对于本教程,我将假定您是 PostgreSQL 的新手。编辑 yml 文件以指定密码,例如
对其他两个部分执行相同的操作
- 测试
- 生产
这样,下面的步骤,实际创建数据库,将成功创建开发、生产和测试数据库。这是一个重要的注意事项,Ruby on Rails 和 RubyMine IDE 具有复杂的集成功能,可在开发环境中工作,将更改迁移到生产环境,并在测试数据库中创建单元和集成测试。此外,由于这些是单独的部分,因此这三个数据库中的每一个都可以使用不同的数据库引擎。例如,Sqlite 通常用作测试数据库。
在 PostgreSQL 中创建用户角色
打开 pgAdmin III 应用程序(在“开始”菜单下的 PostgreSQL 9.2(或更高版本)中找到)。双击“服务器”树下的“PostgreSQL 9.2”项,然后右键单击“登录角色”。
在第一个选项卡“属性”中,输入角色名称,在本例中为“basic-auth”
单击“定义”选项卡,并在“密码”和“再次输入密码”两个字段中输入密码,在本例中为“mypassword”
点击“角色权限”选项卡并勾选所有复选框,因为我们希望赋予此用户所有权限
点击“确定”创建角色。
运行 Rake 命令创建数据库
回到 RubyMine,从“工具”菜单中选择“运行 Rake 任务...”
Rake 是一个应用程序,它执行许多与创建和维护应用程序状态相关的任务。Rake 的大部分功能都不会在本教程中介绍!
出现一个对话框,您应该从中选择 db:create 任务
如果角色设置正确,RubyMine 的控制台输出将显示
要确认数据库已创建,请在 pgAdmin 工具中右键单击“数据库”树节点,单击“刷新”,并注意已创建开发和测试数据库
请注意,rake db:create 命令不会创建生产数据库。另请注意,红色 X 只是表示我们尚未在 pgAdmin 工具中连接到数据库。
设置源代码管理
在我们深入探讨之前,使用 Git 设置源代码管理会很有用,Git 是 Rails 项目中流行的源代码管理存储库系统。
创建 GitHub 账户
创建远程存储库的一个简单方法是使用 GitHub。在 GitHub 中创建一个帐户。RubyMine 将自动创建一个存储库(请参阅下面的“创建存储库”)。
为 RubyMine 配置 GitHub
从“文件”菜单中,选择“设置”,然后打开“版本控制”节点并选择“GitHub”
输入上面创建的 GitHub 账户的登录名和密码,例如
非常重要:对于登录名,请指定您的_用户名_,而不是您的电子邮件地址。虽然 GitHub 允许您使用用户名或电子邮件地址登录,但存储库本身的身份验证需要您的用户名。
在 GitHub 上分享项目
在 RubyMine 中,单击 VCS 主菜单,然后点击“导入到版本控制”,然后选择“在 GitHub 上共享项目”。
系统将提示您输入项目描述
点击“分享”。假设您的登录凭据(上文)输入正确,存储库将自动创建,并且初始内容将推送到远程存储库。您可以在 GitHub 网站上验证这一点——登录(如果已登录,请导航回 GitHub 主页),您应该会看到
项目现在已配置为使用 GitHub 作为远程存储库!
熟悉 Rails 项目
使用 Rails 意味着您最初很可能会与以下内容打交道:
- gems
- 迁移
然后,一旦插件和数据库模型相当稳定,您会发现自己更多地与以下内容打交道:
- 模型
- 视图
- 控制器
- 路由
我还应该提到测试(在“test”文件夹中),但这将在下一篇文章中介绍。
模型、视图、控制器和测试
Rails 项目将模型、视图和控制器的文件保存在“app”文件夹下
请注意,默认情况下,有一个“application_controller.rb”Ruby 文件以及一个“application.html.erb”视图,前者是后者的控制器。默认的应用程序视图是您指定网站所有常见视觉元素的地方。
如果我们快速浏览一下 application.html.erb,我们会发现这段重要的代码
<body> <%= yield %> </body>
“yield”语句将控制权交给正在渲染的页面特定的视图。您网站的任何共同功能,例如标题、菜单栏、版权声明等,都可以放在 yield 语句之前和之后。yield 最终具体做了什么的幕后实现(以及许多其他事情)是 Rails 提供的。
Gemfile、迁移和路由
一个初始的 Rails 项目通常需要您在“gemfile”中添加插件(gems),在向数据库添加物理表结构时使用数据库迁移,并配置到网页的路由。
gemfile 位于项目文件夹的根目录
数据库迁移通常保存在“db”文件夹下的“migrate”文件夹中
此时,创建“migrate”文件夹——它不会自动为您创建。
routes.rb 文件在 config 文件夹中
如您所见,有很多东西需要跟踪,目前我只介绍了最基本的部分。
创建数据库用户表作为迁移
迁移是为给定版本的网站创建数据库模型的好方法。您可以创建和更改表,以及恢复到模型的先前版本。虽然可以使用“rails g model...”命令从命令行创建模型,但我将尝试避免命令行,以演示如何手动处理迁移而不是从生成的代码中处理。这种方法的优点是您将学习更多 Rails 的内部工作原理,这在面临命令行生成器不支持的更改时很重要。
迁移文件具有以下特点
首先,文件名以开发人员创建的索引开头,可以是增量值或日期时间戳。然后是下划线分隔的迁移名称。这很重要,因为 Ruby 迁移类必须匹配此模式,但没有下划线,并且也是帕斯卡命名法
class CreateUserModel < ActiveRecord::Migration def self.up create_table :users do |t| t.column :username, :string t.column :email, :string t.column :password_hash, :string t.column :password_salt, :string end end def self.down drop_table :users end end
请注意帕斯卡命名法的类名“CreateUserModel”。另请注意,该类派生自 Rails 提供的“ActiveRecord::Migration”。
迁移文件通常包含两个函数
- self.up - 此函数描述对模型所做的更改。
- self.down - 此函数描述如何恢复到以前的状态。
Rails 提供了丰富的功能来创建和修改表。这里只说明了两个函数,“create_table”和“drop_table”。Rails 应用程序的另一个非常常见的特性是它们非常注重_符号_。请注意,我们没有为表和字段名称使用带引号的字符串,而是传递符号,它们总是以冒号 (:) 开头。请注意,符号也用于字段类型。
运行迁移
创建迁移文件后,右键单击迁移文件并选择“运行 'db:migrate'”
如果迁移成功,RubyMine 中的输出视图将显示类似以下内容
请注意,主键由迁移代码自动添加,并且还创建了一个支持序列,以便在添加新行时生成主键。您可以在 pgAdmin III 中检查该表
请注意新的“users”表。Rails 还会创建一个“schema_migrations”表来跟踪已经运行的迁移。请勿编辑此表。
创建用户模型
创建数据库表后,可以创建 Rails 用户模型。在“models”文件夹下,创建“user.rb”。需要注意的一些事项:
- 请注意,支持相应表的模型类名是单数,而表名是复数。此约定由 Rails 强制执行。
- 请注意,类作为类型,以大写字母开头。
- 由物理表支持的模型总是派生自 Rails 提供的“ActiveRecord::Base”。
加密
显然,我们希望加密数据库中的密码。为此,我们将使用“bcrypt-ruby”gem,这意味着将以下行添加到 gemfile 中
gem 'bcrypt-ruby', :require=>'bcrypt'
Bcrypt 是一个插件,提供基本的哈希算法来加密字符串。
Bundler
每当在 gemfile 中添加或删除 gem 时,您都应该运行“bundler”安装程序或更新程序。安装程序将安装任何缺少的 gem,更新程序将安装缺少的 gem 并在现有 gem 有更新时下载更新。要从 RubyMine 执行此操作,请从主菜单中选择“工具”,然后选择“安装”或“更新”
这将更新您系统上安装的 gem。
用户模型代码
在“models”文件夹中创建用户模型
请注意,模型文件名的名称是支持表名的_单数_形式。
用户模型的基本实现如下
class User < ActiveRecord::Base attr_accessible :email, :username, :password, :password_confirmation attr_accessor :password before_save :encrypt_password validates_confirmation_of :password validates_presence_of :password, :on => :create validates_presence_of :email, :on => :create validates_presence_of :username, :on => :create validates_uniqueness_of :email validates_uniqueness_of :username def self.authenticate(email, password) user = find_by_email(email) if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt) user else nil end end def encrypt_password if password.present? self.password_salt = BCrypt::Engine.generate_salt self.password_hash = BCrypt::Engine.hash_secret(password, password_salt) end end end
这里我们有大量的元数据,描述了模型数据的验证、何时执行验证以及在将模型保存到数据库之前应执行的代码(encrypt_password)。我不会详细描述代码——这段代码来自 Railscast #250。稍后我们将对其进行修改,但现在它是一个很好的起点。顺便说一句,Railscasts 是学习如何使用 Ruby on Rails 的出色资源。
使用 RubyMine Rails 控制台
此时,我们可以从 Rails 控制台测试 User 类。在 RubyMine 中,从主菜单中选择“工具”,然后选择“运行 Rails 控制台”
然后我们可以与我们的用户模型交互,创建一个用户
我们还可以验证是否可以使用“静态”authenticate 方法找到用户
请注意,使用正确密码的第一个调用返回一个 User 记录,而使用不正确密码的第二个调用返回 nil。
按用户名认证
现在让我们添加一个方法来通过用户名进行身份验证,并稍微重命名这些方法,以更清楚地表明我们正在使用哪些字段来验证用户
def self.authenticate_by_email(email, password) user = find_by_email(email) if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt) user else nil end end def self.authenticate_by_username(username, password) user = find_by_username(username) if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt) user else nil end end
如果您要在 Rails 控制台中测试这些新方法,则必须重新启动控制台。
我们可以通过使用 pgAdmin III 检查表来验证用户是否已在数据库中创建
控制器,第一步
控制器是 Rails 应用程序不可或缺的一部分——您必须有一个与视图关联的控制器。首先,我们将创建两个控制器作为存根:
- home_controller.rb
- authentication_controller.rb
控制器名称及其功能与视图文件夹和页面之间也存在紧密的集成,我稍后会详细说明。首先,只需在“controllers”文件夹下创建上述两个文件
并且,对于每个控制器,必须创建一个存根类
authentication_controller.rb
class AuthenticationController < ApplicationController end
home_controller.rb
class HomeController < ApplicationController end
请注意文件名和类名之间的 Pascal 命名约定关系。这是 Rails 应用程序的要求。
视图:认证页面
下图说明了我们身份验证系统所需的各种页面以及它们之间的流转。
我总是纠结于是从数据库模型和支持实体开始,还是从视图开始。如果您习惯于测试驱动的环境,您可能更喜欢从测试开始。但是,我将把测试留到第二篇文章中。
在我们的特定情况下,除了主页之外,我们可以在一个控制器中实现身份验证系统的行为,支持所有六个视图(不包括我们假设由单独视图处理的主页)。此外,只需要一个模型,即用户模型。
在“views”文件夹中,创建一个名为“authentication”的子文件夹,并在该文件夹中创建六个视图。另外,在名为“home”的子文件夹(您创建的)中为主页创建一个索引页。结果在“views”文件夹中应如下所示
请注意子文件夹名称如何映射到控制器名称!
删除默认索引页
正如默认 Rails 主页指出的那样,您需要删除 public 文件夹中的 index.html 文件
现在就这么做。
路由
通常,每个视图都需要在 routes.rb 文件中有一个路由条目。
根路由
通过将以下内容添加到 routes.rb 文件(记住,这在 config 文件夹中)来创建您的第一个主页路由
root :to=>"home#index"
请注意上面的语法,将“home”指定为主页控制器,将“index”指定为页面名称!
如果您放入一些简单的 HTML 来标识主页(index.html.erb),例如
<p>Home</p>
然后您可以看到新的主页被渲染
如果服务器尚未运行,请不要忘记启动它——因为 Ruby 是解释型的,大多数情况下您不需要重新启动服务器即可查看更改。RubyMine 会将最后执行的命令放在默认的“运行”菜单项中,因此您可能需要明确指定要运行 Web 服务器。从“运行”主菜单中,从下拉菜单中选择“运行...”,然后选择“开发:basic-auth”
认证路由
为六个认证页面创建认证路由
get "sign_in" => "authentication#sign_in" get "signed_out" => "authentication#signed_out" get "change_password" => "authentication#change_password" get "forgot_password" => "authentication#forgot_password" get "new_user" => "authentication#new_user" get "password_sent" => "authentication#password_sent"
您现在也可以访问这些页面,假设您输入了一些简单的 HTML 来渲染每个页面上的一些文本,您将看到每个页面
本地主机:3000/sign_in
本地主机:3000/signed_out
本地主机:3000/change_password
本地主机:3000/forgot_password
本地主机:3000/new_user
本地主机:3000/password_sent
重要
请注意 URL(例如“sign_in”)如何将 Rails 指向控制器“AuthenticationController”及其方法“sign_in”(如果提供)。
同样重要
每个路由都将为指定的控制器(“/”左侧的名称)执行“/”右侧的函数。如果函数缺失,只要控制器已定义,Rails 就会很高兴。如果实现了函数,它会在页面渲染之前执行,允许您初始化对象并设置页面可能需要的其他数据。控制器实例可用于页面,使页面可以访问控制器的字段,这通常包括控制器接口的模型。
登录页面:控制器、视图和路由
要创建基本的登录页面,我们需要处理视图本身、控制器和 routes.rb 文件。以下渲染结果为
View
让我们从一些基本的东西开始
<p>Sign In</p> <%= form_for @user, :as => :user, :url => sign_in_path(@user) do |f| %> <p> <%= f.label 'username or email:' %><br /> <%= f.text_field :username %> </p> <p> <%= f.label 'password:' %><br /> <%= f.password_field :password %> </p> <p> <%= f.submit 'Sign In' %> <%= f.submit 'Clear Form', :type => 'reset' %> </p> <% end %>
重要
注意“form_for”标签如何指定
- “:user”符号作为支持模型
- 与已知路由匹配的 URL(见下文)
这对于覆盖“form_for”标签的默认行为是必要的,否则它会期望“users”的 post 路由。这会导致登录失败时出现问题,因为“render”命令(见下文)否则会尝试重定向到“localhost:3000/users”,再次从模型名称获取此默认路径。您可以在此处阅读有关 form_for 的更多信息。
另请注意我们如何指定提交按钮“Sign In”的文本。如果我们不这样做,Rails 将默认为文本“Create User”,该文本由“form_for”标签中指定的模型确定。
虽然有时默认行为是可取的,但知道如何覆盖默认行为以特定控制 URL 是非常有用的。form_for 标签中模型名称和路由之间的默认耦合非常神奇,但当某些事情不如预期时,会导致很多“到底发生了什么?”的问题。
路由
我们为 post 命令添加一个路由
post "sign_in" => "authentication#login"
此时应该非常清楚:sign_in 页面上的 HTTP post 命令将把服务器端执行定向到 AuthenticationController 的 login 方法。
控制器
控制器的实现需要两个方法
class AuthenticationController < ApplicationController def sign_in @user = User.new end def login username_or_email = params[:user][:username] password = params[:user][:password] if username_or_email.rindex('@') email=username_or_email user = User.authenticate_by_email(email, password) else username=username_or_email user = User.authenticate_by_username(username, password) end if user redirect_to :root else render :action => "sign_in" end end end
第一个方法“sign_in”由处理 HTTP“get”命令的路由调用
get "sign_in" => "authentication#sign_in"
负责实例化一个空用户。请注意,HTML 中的“form_for”标签能够引用路由指向的控制器的字段。
第二种方法处理 HTTP“post”命令,确定用户是否经过身份验证。
错误消息
上面的代码需要处理显示身份验证错误以及在会话期间保留当前用户。Rails 提供了一种向用户显示消息的机制,称为“flash”。 (在此阅读更多)
application.html.erb
我们可以通过在 application.html.erb 文件中添加一点标记来显示控制器创建的消息,如上所述,该文件是所有页面的全局模板,就在 yield 调用之前
<% flash.each do |name, msg| %> <%= content_tag :div, msg, :id => "flash_#{name}" %> <% end %> <%= yield %>
application.css
在 app\assets\stylesheets 文件夹中是默认的 css 文件。为了让我们的警报和通知更美观,我们可以添加一些 css
#flash_error { border: 1px solid #000066; padding: 4px; margin-bottom: 10px; background-color: #cc3333; } #flash_notice { border: 1px solid #000066; padding: 4px; margin-bottom: 10px; background-color: #A3B15A; }
控制器更改
最后,我们调整控制器以利用此新功能
if user flash[:notice] = 'Welcome.' redirect_to :root else flash.now[:error] = 'Unknown user. Please check your username and password.' render :action => "sign_in" end
现在用户收到一些成功的通知
或失败
会话
我们还需要将会话状态中的用户保留下来,这可以通过以下方式轻松实现:
如果用户
session[:user_id] = user.id
flash[:notice] = '欢迎。'
redirect_to :root
我们可能还想在所有页面上显示登录的用户名。事实上,我们现在可以通过检查会话状态来添加“登录”、“注销”和“注册”链接。首先,我们创建一个视图可访问的方法,这必须在 application_controller.rb 文件中完成,因为每个页面都首先通过此控制器渲染
class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user def current_user # Note: we want to use "find_by_id" because it's OK to return a nil. # If we were to use User.find, it would throw an exception if the user can't be found. @current_user ||= User.find_by_id(session[:user_id]) if session[:user_id] end end
检查用户与数据库,虽然会对性能造成轻微影响,但可确保如果管理员删除了用户,用户下次导航到页面时,将自动注销。我们将在稍后的身份验证测试中将其用作一部分。
最后,我们可以在 application.html.erb 文件中(在显示通知的位置上方)创建链接(全局应用于每个页面),具体取决于登录状态(@current_user 是否为 nil)
<p> <% if current_user %> Logged in as <%= current_user.username %>. <%= link_to "Sign Out", signed_out_path %> <% else %> <%= link_to "Sign up", new_user_path %> or <%= link_to "Sign In", sign_in_path %> <% end %> </p>
我们现在有一个功能更强大的主页
已注销页面
注销页现在实现起来很简单。我们可以将方法添加到控制器,显示通知,并清除会话的用户 ID
def signed_out session[:user_id] = nil flash[:notice] = "You have been signed out." end
注册页面
注册页面会稍微复杂一些,因为我们需要进行字段验证并显示任何错误消息。这需要进行一些更改和添加。
路由
我们添加一个 post 路由
post "new_user" => "authentication#register"
注册页面 HTML
new_user 页面的标记
<p>Sign Up</p> <%= form_for @user, :as => :user, :url => new_user_path, :method => :put do |f| %> <p> <%= f.label 'username:' %><br/> <%= f.text_field :username %> <%= show_field_error(@user, :username) %> </p> <p> <%= f.label 'email:' %><br/> <%= f.text_field :email %> <%= show_field_error(@user, :email) %> </p> <p> <%= f.label 'password:' %><br/> <%= f.password_field :password %> <%= show_field_error(@user, :password) %> </p> <p> <%= f.label 'password confirmation:' %><br/> <%= f.password_field :password_confirmation %> <%= show_field_error(@user, :password_confirmation) %> </p> <p> <%= f.submit 'Sign Up' %> <%= f.submit 'Clear Form', :type => 'reset' %> </p> <% end %>
show_field_error 函数
重要
此函数在 app\helpers 下的 application_helpers.rb 文件中实现。在此文件中,我们添加了可用于所有视图的辅助函数。虽然这看起来与我们在 ApplicationController 类中添加辅助方法“current_user”的方式相似,但我们希望可用于视图的方法应该放入 application_helpers.rb 文件中
module ApplicationHelper def show_field_error(model, field) s="" if !model.errors[field].empty? s = <<-EOHTML <div id="error_message"> #{model.errors[field][0]} </div> EOHTML end s.html_safe end end
这里我们有一个有趣的函数,当存在错误时它会输出一些 HTML,将 HTML 嵌入类似于数据如何使用 CDATA 标签嵌入 XML,但这里我们使用一个名为“EOHTML”的标记。
认证控制器
在控制器中,我们添加了两个函数,一个用于为 HTTP“get”命令实例化用户字段,第二个函数用于处理 HTTP post 命令。
def new_user @user = User.new end def register @user = User.new(params[:user]) if @user.valid? @user.save session[:user_id] = @user.id flash[:notice] = 'Welcome.' redirect_to :root else render :action => "new_user" end end
注意我们如何初始化 User 类,传入一个哈希数组(键值对)。
请注意我们是如何初始化一个字段(@user = User.new...)而不是一个局部变量(user = User.new...),以保留用户在表单渲染出现错误时已经输入的任何参数。
用户构造函数
因此,我们现在需要为 User 模型指定一个构造函数
class User < ActiveRecord::Base # ... def initialize(attributes = {}) super # must allow the active record to initialize! attributes.each do |name, value| send("#{name}=", value) end end # ...
对于每个键值对(哈希),我们通过调用“send”函数(Ruby 中的所有方法调用实际上都是消息)将值赋给属性。
重要
我们实际上不需要为 User 类执行此操作,因为 Rails 提供的构造函数将允许我们从哈希进行“批量赋值”,只要我们正在赋值的字段已被指定为“attr_accessible”,而它们确实如此。但是,在某些情况下,人们希望初始化几个不打算向视图公开但被指定为“attr_accessor”的字段(例如多对多表中的 ID)。上述函数是为内部构造函数提供安全批量赋值功能的简单方法。
测试验证
测试验证最简单的方法之一是尝试使用空白表单注册,结果如下:
我们还可以测试重复的用户名和电子邮件地址是否经过验证,以及密码和密码确认是否匹配。
更改密码页面(设置)
此页面与注册页面非常相似,但它不需要用户名或电子邮件地址,但要求用户输入其现有密码。当我写这篇文章时,我想到一个更有用的实现是允许用户在“帐户设置”页面中更改其用户名、电子邮件地址和/或密码,因此我们将实现这一点。
帐户设置链接
首先,我们将添加一个“帐户设置”链接,供用户登录时访问,该链接将添加到 application.html.erb“所有页面通用”模板中
<% if current_user %> Logged in as <%= current_user.username %>. <%= link_to "Sign Out", signed_out_path %> <%= link_to "Account Settings", account_settings_path %>
我们需要一些路由来处理 HTTP“get”和“post”命令
get "account_settings" => "authentication#account_settings" put "account_settings" => "authentication#set_account_info"
因为当 Rails 处理“form_for”标签时,用户记录已经填充,所以它会发出一个 HTTP“put”命令来更新现有记录,因此上面的路由使用“put”而不是“post”。但奇怪的是,如果页面上出现错误,再次单击“更新”按钮,Rails 会生成一个“post”命令,因此,在“form_for”标签中,我明确定义 HTTP 命令为 put,并带有可选的“:method => :put”。
用户模型
新密码和确认字段需要添加到用户模型中
attr_accessible :email, :username, :password, :password_confirmation, :new_password, :new_password_confirmation attr_accessor :password, :new_password
上述内容确保属性(我一直称之为“字段”)可供视图访问,并由模型中的实际字段支持,即使它们不存在于 User 数据库表中。
我们还希望仅在新密码字段不为空时才确认新密码(否则会弄乱注册页面)
validates_confirmation_of :new_password, :if => Proc.new {|user| !user.new_password.nil? && !user.new_password.empty? }
此验证仅在新密码字段不为 nil 且不为空时运行。
我们必须对电子邮件和用户名进行相同的“presence_of”和“uniqueness_of”验证,以便它们在登录和帐户设置页面上都正常工作
validates_presence_of :email, :if => Proc.new {|user| user.previous_email.nil? || user.email != user.previous_email} validates_presence_of :username, :if => Proc.new {|user| user.previous_username.nil? || user.username != user.previous_username} validates_uniqueness_of :email, :if => Proc.new {|user| user.previous_email.nil? || user.email != user.previous_email} validates_uniqueness_of :username, :if => Proc.new {|user| user.previous_username.nil? || user.username != user.previous_username}
认证控制器
对于 HTTP“get”命令,我们只需将当前登录的用户记录(使用我们已经创建的方法)分配给字段
def account_settings @user = current_user end
这非常简单地使用现有字段填充表单。在上面的截图中,密码字段也已填充,这是浏览器为我的密码设置 cookie 的结果——因为支持数据库表没有密码字段,浏览器没有从我们的应用程序中获取此值。通过在赋值上设置断点并检查 current_user 函数调用返回的记录,可以轻松确认这一点。
HTTP“put”命令的代码有点复杂,因为我们必须根据用户更改的内容更新某些字段。此外,我们必须确认当前用户的密码。
def set_account_info old_user = current_user # verify the current password by creating a new user record. @user = User.authenticate_by_username(old_user.username, params[:user][:password]) # verify if @user.nil? @user = current_user @user.errors[:password] = "Password is incorrect." render :action => "account_settings" else # update the user with any new username and email @user.update(params[:user]) # Set the old email and username, which is validated only if it has changed. @user.previous_email = old_user.email @user.previous_username = old_user.username if @user.valid? # If there is a new_password value, then we need to update the password. @user.password = @user.new_password unless @user.new_password.nil? || @user.new_password.empty? @user.save flash[:notice] = 'Account settings have been changed.' redirect_to :root else render :action => "account_settings" end end end
我们现在有了一个灵活的解决方案,供用户管理其账户设置。
发送邮件
在继续进入最后一个屏幕“忘记密码”之前,我们需要设置发送密码重置说明邮件的功能。这涉及到设置 Rails ActionMailer,如此处所述。打开一个控制台窗口(开始 -> “cmd”),将目录更改为应用程序文件夹,然后运行命令“rails generate mailer UserMailer”
添加欢迎邮件
我们先用一封“欢迎”邮件来试试,当用户首次注册时发送。根据文档,创建一个 welcome_email 方法
def welcome_email(user) @user = user @url = "https://:3000/sign_in" @site_name = "localhost" mail(:to => user.email, :subject => "Welcome to my website.") end
正如文档建议的,创建 HTML 和文本模板(请注意,这两个文件的基本文件名与函数名称匹配!)。在 app\views\user_mailer 中,创建 welcome_email.html.erb
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> </head> <body> <h2>Welcome to <%= @site_name %>, <%= @user.username %></h2> <p> You have successfully signed up to <%= @site_name %>, your username is: <%= @user.username %>.<br/> </p> <p> To login to the site, just follow this link: <%= @url %>. </p> <p>Thanks for joining and have a great day!</p> </body> </html>
还有 welcome_email.text.erb
Welcome to <%= @site_name %>, <%= @user.username %> =============================================== You have successfully signed up to <%= @site_name %>, your username is: <%= @user.username %>. To login to the site, just follow this link: <%= @url %>. Thanks for joining and have a great day!
更新认证控制器
在 authentication_controller.rb 文件中,添加对“register”函数的调用以发送欢迎邮件
def register @user = User.new(params[:user]) if @user.valid? @user.save UserMailer.welcome_email(@user).deliver session[:user_id] = @user.id flash[:notice] = 'Welcome.' redirect_to :root # ...
配置邮件程序
配置邮件程序以通过您的邮件服务器发送电子邮件。对于此示例,我们将使用 GMail(假设您拥有 GMail 帐户)。打开 config\environment\development.rb 文件(因为我们处于开发模式)并添加
config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { :address => "smtp.gmail.com", :port => 587, :domain => '<your domain>', :user_name => '<your username>', :password => '<your password>', :authentication => 'plain', :enable_starttls_auto => true }
如果一切顺利,您应该会收到一封类似于这样的电子邮件
使用环境变量
与其将您的用户名和密码硬编码到 development.rb 环境变量文件中,不如使用环境变量——例如,鉴于我正在将此项目推送到公共 GitHub 存储库,我不想让人们看到我的 GMail 用户名和密码!有多种选项可用,如此处所述。我采用的方法是创建一个被 Git 专门忽略的文件
在 config 文件夹中创建 local_env.yml 文件,内容如下
GMAIL_USERNAME: '<your username>' GMAIL_PASSWORD: '<your password>'
显然,将用户名和密码替换为您自己的。
编辑 .gitignore 文件(位于应用程序文件夹的根目录),并添加
/config/local_env.yml
这确保 Git 不会将此文件提交或推送到远程存储库。
编辑 config 文件夹中的 application.rb 文件,添加
config.before_configuration do env_file = File.join(Rails.root, 'config', 'local_env.yml') YAML.load(File.open(env_file)).each do |key, value| ENV[key.to_s] = value end if File.exists?(env_file) end
就在“config.assets.version = '1.0'”行之后。
重新启动服务器并再次测试您的电子邮件!
忘记密码
现在电子邮件已启用,我们可以添加最后一个网页,为用户提供如何重置密码的说明。我们不想更改他们的密码,以防其他人试图访问他们的帐户,相反,我们只发送一封包含如何重置密码说明的电子邮件。电子邮件地址必须是已注册用户,并且提供的 URL 必须包含一个唯一的令牌,我们还将其存储在数据库中,并且在用户重置密码时必须匹配。关于如何做到这一点,有一个很棒的 Railscast,我从中借鉴了。
首先,我们将使用数据库迁移添加一个密码重置令牌字段和一个日期字段,以便重置机会在 24 小时内过期。我们将迁移文件命名为“0002_add_password_reset_fields.rb”,内容如下
class AddPasswordResetFields < ActiveRecord::Migration def self.up add_column :users, :password_reset_token, :string add_column :users, :password_expires_after, :datetime end def self.down remove_column :users, :password_reset_token remove_column :users, :password_expires_after end end
通过右键单击迁移文件夹并从 RubyMine 文件夹树中选择“db:migrate”来运行迁移
忘记密码视图
我们也将使用用户模型来处理这个表单,但只要求用户输入他们的用户名或电子邮件地址,然后我们将在数据库中验证其是否为活跃用户
<p>Forgot Password</p> <%= form_for @user, :as => :user, :url => forgot_password_path, :method => :put do |f| %> <p> <%= f.label 'username or email:' %><br/> <%= f.text_field :username %> <%= show_field_error(@user, :username) %> </p> <p> <%= f.submit 'Send Password Reset Instructions' %> </p> <% end %>
密码重置视图
我们还需要一个视图来处理密码重置
<p>Account Settings</p> <%= form_for @user, :as => :user, :url => password_reset_path, :method => :put do |f| %> <p> <%= @user.username %> has requested a password reset. Please enter your new password: </p> <p> <%= f.label 'new password:' %><br/> <%= f.password_field :new_password %> <%= show_field_error(@user, :new_password) %> </p> <p> <%= f.hidden_field :username %> <%= f.label 'password confirmation:' %><br/> <%= f.password_field :new_password_confirmation %> <%= show_field_error(@user, :new_password_confirmation) %> </p> <p> <%= f.submit 'Update' %> </p> <% end %>
请注意我们如何使用隐藏字段“username”,以便在 HTTP put 命令上,我们可以重新获取用户名(我们知道它是唯一的),从而获取要更新密码的用户记录。
“忘记密码”链接
我们还需要在登录页面上添加一个链接供用户点击
<p> <%= link_to 'forgot your password?', :forgot_password %> </p>
路由
像往常一样,我们需要一些路由
get "forgot_password" => "authentication#forgot_password" put "forgot_password" => "authentication#send_password_reset_instructions" get "password_reset" => "authentication#password_reset" put "password_reset" => "authentication#new_password"
第一对处理用户请求密码重置的页面。第二对处理允许用户输入新密码的页面。
密码重置邮件
UserMailer 类获得了一个新函数
def reset_password_email(user) @user = user @password_reset_url = 'https://:3000/password_reset?' + @user.password_reset_token mail(:to => user.email, :subject => 'Password Reset Instructions.') end
显然,这里有一些硬编码值,我们希望在生产部署中替换它们,最好使用环境变量或其他方法。
由电子邮件“视图”reset_password_email.html.erb 支持
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> </head> <body> <p> <%= @user.username %> has requested a password reset. </p> <p> To reset your password, click the URL below. </p> <p> <%= @password_reset_url %> </p> <p> If you did not request your password to be reset, just ignore this email and your password will continue to stay the same. </p> </body> </html>
认证控制器
控制器需要函数来处理 HTTP get 和 put 命令,用于请求密码重置和重置密码。
请求密码重置
# HTTP get def forgot_password @user = User.new end # HTTP put def send_password_reset_instructions username_or_email = params[:user][:username] if username_or_email.rindex('@') user = User.find_by_email(username_or_email) else user = User.find_by_username(username_or_email) end if user user.password_reset_token = SecureRandom.urlsafe_base64 user.password_expires_after = 24.hours.from_now user.save UserMailer.reset_password_email(user).deliver flash[:notice] = 'Password instructions have been mailed to you. Please check your inbox.' redirect_to :sign_in else @user = User.new # put the previous value back. @user.username = params[:user][:username] @user.errors[:username] = 'is not a registered user.' render :action => "forgot_password" end end
上述代码中更有趣的部分是处理 HTTP put 命令的函数。在这里,我们验证用户名或电子邮件地址是否与数据库中的用户记录匹配,如果匹配,我们创建一个过期令牌并设置 24 小时后的过期日期,然后发送通知电子邮件。用户收到的电子邮件如下所示
重置密码
用户点击电子邮件中提供的链接后,我们需要处理重置验证。当用户首次进入页面时,我们验证令牌是否与用户关联,并且重置密码的时间尚未过期
# The user has landed on the password reset page, they need to enter a new password. # HTTP get def password_reset token = params.first[0] @user = User.find_by_password_reset_token(token) if @user.nil? flash[:error] = 'You have not requested a password reset.' redirect_to :root return end if @user.password_expires_after < DateTime.now clear_password_reset(@user) @user.save flash[:error] = 'Password reset has expired. Please request a new password reset.' redirect_to :forgot_password end end
当用户更改密码时,我们验证密码不为空且与确认密码匹配。我们需要手动执行此操作,因为用户模型验证例程不会验证空白的“new_password”,因为这是帐户管理页面所需的功能。因此,我们在此处添加自定义验证。
# The user has entered a new password. Need to verify and save. # HTTP put def new_password username = params[:user][:username] @user = User.find_by_username(username) if verify_new_password(params[:user]) @user.update(params[:user]) @user.password = @user.new_password if @user.valid? clear_password_reset(@user) @user.save flash[:notice] = 'Your password has been reset. Please sign in with your new password.' redirect_to :sign_in else render :action => "password_reset" end else @user.errors[:new_password] = 'Cannot be blank and must match the password verification.' render :action => "password_reset" end end
我们还有几个辅助函数
private def clear_password_reset(user) user.password_expires_after = nil user.password_reset_token = nil end def verify_new_password(passwords) result = true if passwords[:new_password].blank? || (passwords[:new_password] != passwords[:new_password_confirmation]) result = false end result end
重要
我遇到很多 Ruby/Rails 代码,其中功能是重复的(在 Ruby 中复制粘贴非常容易),并且函数包含的代码块除了没有文档之外,还在做许多不同的事情。如果代码更清晰,即避免重复代码并将一个函数分解为几个更小的函数,那么对于新进入项目的开发人员来说会更清晰。即使我在这篇文章中提供的代码也可以在这方面进行改进。关于 Ruby on Rails 的另一件事是,通常您不需要编写大量代码来做一件事——通常,那件事可以在一两行中完成。这意味着函数是“做 A、B、C、D、E”的非常紧凑的组合,可能不超过五行。一个完美的例子是这样的
1: user.password_reset_token = SecureRandom.urlsafe_base64 2: user.password_expires_after = 24.hours.from_now 3: user.save 4: UserMailer.reset_password_email(user).deliver 5: flash[:notice] = 'Password instructions have been mailed to you. Please check your inbox.' 6: redirect_to :sign_in
正如您所看到的,这里有很多事情正在发生
第1行和第2行正在初始化负责管理密码重置的字段
第三行正在更新数据库
第四行正在发送电子邮件
第五行正在设置显示屏幕通知
第六行将浏览器重定向到登录页面
基本上,我们在这里与五个独立的“实体”进行对话
- 模型
- ORM
- 电子邮件子系统
- 通知子系统
- 页面管理子系统
这需要大量的思维上下文切换,特别是对于新开发人员来说。代码清晰度(小函数和注释)对于维护 Ruby on Rails 代码的可读性和可维护性至关重要。
记住我
让我们在登录和注册页面添加一个“记住我”复选框。我们将通过创建新的迁移文件 0003_add_authentication_token.rb,向用户表添加一个“authentication_token”
class AddAuthenticationToken < ActiveRecord::Migration def self.up add_column :users, :authentication_token, :string end def self.down remove_column :users, :authentication_token end end
接下来,我们将“remember_me”属性添加到用户模型中
attr_accessible :remember_me, [etc...] attr_accessor :remember_me, [etc...]
接下来,我们将复选框添加到登录和注册页面
<p> <%= f.check_box :remember_me %> </p>
现在我们可以修改我们的控制器,以便在勾选复选框时生成身份验证令牌并将其保存为 cookie 和数据库。如果复选框未勾选或用户注销,我们将清除此令牌。我们修改登录控制器函数
def login username_or_email = params[:user][:username] user = verify_user(username_or_email) if user update_authentication_token(user, params[:user][:remember_me]) user.save
注册用户类似
if @user.valid? update_authentication_token(@user, nil) @user.save
注销会清除认证令牌
def signed_out # clear the authentication toke when the user manually signs out user = User.find_by_id(session[:user_id]) if user update_authentication_token(user, nil) user.save session[:user_id] = nil flash[:notice] = "You have been signed out." else redirect_to :sign_in end end
最后,我们调用了这里的通用方法
def update_authentication_token(user, remember_me) if remember_me == 1 # create an authentication token if the user has clicked on remember me auth_token = SecureRandom.urlsafe_base64 user.authentication_token = auth_token cookies.permanent[:auth_token] = auth_token else # nil or 0 # if not, clear the token, as the user doesn't want to be remembered. user.authentication_token = nil cookies.permanent[:auth_token] = nil end end
现在剩下的就是在应用程序控制器的 current_user 方法中从 cookie 中获取当前用户
def current_user # Note: we want to use "find_by_id" because it's OK to return a nil. # If we were to use User.find, it would throw an exception if the user can't be found. @current_user ||= User.find_by_id(session[:user_id]) if session[:user_id] @current_user ||= User.find_by_authentication_token(cookies[:auth_token]) if cookies[:auth_token] && @current_user.nil? @current_user end
这比必要的要复杂一些,因为我们可以使用非持久性 cookie 来保留身份验证令牌,这样就不需要会话用户 ID,但是,我更喜欢保留现有代码,因为它更清楚地表明我们正在尝试从会话或 cookie 中获取当前用户。
使用 Recaptcha
最后,为了防止恶意分子注册我们的应用程序,我们将在注册页面添加一个 Recaptcha 字段。我们将使用这个 recaptcha gem。首先,添加到您的 gemfile 中
gem 'recaptcha', :require => "recaptcha/rails"
然后,在 RubyMine 中,从“工具”菜单运行 Bundler -> 安装。
从Google recaptcha 网站创建您的密钥。
在 config\initializers 文件夹中创建一个名为 recaptcha.rb 的文件,内容类似于
Recaptcha.configure do |config| config.public_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy' config.private_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx' end
然后,在 new_user.html.erb 视图中,我们在注册按钮之前添加“recaptcha_tags”
<p> <%= recaptcha_tags %> </p> <p> <%= f.submit 'Sign Up' %> <%= f.submit 'Clear Form', :type => 'reset' %> </p>
重新启动服务器后,我们的新用户注册页面现在会出现一个 recaptcha 块
现在我们需要修改身份验证控制器中的 register 方法,以验证 recaptcha 文本并在其不正确时闪烁适当的消息
def register @user = User.new(params[:user]) # Don't use !verify_recaptcha, as this terminates the connection with the server. # It almost seems as if the verify_recaptcha is being called twice with we use "not". if verify_recaptcha if @user.valid? update_authentication_token(@user, nil) @user.save UserMailer.welcome_email(@user).deliver session[:user_id] = @user.id flash[:notice] = 'Welcome.' redirect_to :root else render :action => "new_user" end else flash.delete(:recaptcha_error) # get rid of the recaptcha error being flashed by the gem. flash.now[:error] = 'reCAPTCHA is incorrect. Please try again.' render :action => "new_user" end end
Recaptcha 功能添加完成!
使用我们的身份验证
我们可以使用我们的身份验证系统来验证我们自己的网页!在我们的特定情况下,账户设置页面(HTTP post 和 put 命令)在用户未登录时应无法访问
before_filter :authenticate_user, :only => [:account_settings, :set_account_info]
这告诉 Rails 为“account_settings”和“set_account_info”函数执行“authenticate_user”方法。
重要
“authenticate_user”方法必须在 application_controller.rb 中实现,因为我们所有的控制器都派生自 ApplicationController(理解 application_controller.rb 和 application_helper.rb [见上文] 之间的区别很重要)
def authenticate_user if current_user.nil? flash[:error] = 'You must be signed in to view that page.' redirect_to :root end end
现在,当未登录访问页面时,用户会看到
最后的润色
注册时间和最后登录时间
我们可以添加一些额外功能,例如“注册日期时间”和“上次登录日期时间”,这非常简单——通过数据库迁移修改用户表...
class AddDateInfoFields < ActiveRecord::Migration def self.up add_column :users, :signed_up_on, :datetime add_column :users, :last_signed_in_on, :datetime end def self.down remove_column :users, :signed_up_on remove_column :users, :last_signed_in_on end end
...然后添加适当的字段,然后在控制器中添加几行代码来设置值。在登录函数中,在调用“user.save”之前
user.last_signed_in_on=DateTime.now
并在 register 函数中,在调用“@user.save”之前
@user.signed_up_on = DateTime.now @user.last_signed_in_on = @user.signed_up_on
管理用户
显然,这(以及此处所有其他页面)可以通过样式表和其他有用的功能变得更加美观,但这并不是本文的重点。
我们将添加一个简单的管理屏幕,允许我们删除用户。显然,此屏幕只应供管理员使用,但目前我们将忽略授权(顺便说一下,CanCan 是一个非常流行的 gem,并且与Devise集成良好)。
首先是视图,我在“views”文件夹中创建了一个名为“admin”的新文件夹,其中包含“users.html.erb”文件
<p>Admin Users</p> <table> <tr> <th width="30%">Username</th> <th width="20%">Signed Up On</th> <th width="20%">Last Signed In On</th> </tr> <% for user in @users %> <tr> <td><%= user.username %></td> <td><%= user.signed_up_on %></td> <td><%= user.last_signed_in_on %></td> <td><%= link_to 'Delete', user, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table>
然后我们需要一些路由
get "admin_users" => "admin#users" delete "user/:id" => "admin#delete_user", :as => "user"
最后,新的 admin_controller.rb 文件
class AdminController < ApplicationController def users @users = User.all end def delete_user if params[:id] == current_user.id.to_s flash.now[:error] = 'You cannot delete yourself!' @users = User.all render :action => :users else User.find_by_id(params[:id]).delete @users = User.all render :action => :users end end end
第一个函数“users”由 HTTP“get”命令调用,它简单地将所有用户加载到一个视图可访问的属性(字段)中。视图遍历集合,为每个记录创建行。删除链接被赋予用户记录实例,Rails 会自动将此链接作为,例如:https://:3000/user/20。删除路由处理 HTTP“delete”命令,调用 admin 控制器中的 delete_user 函数。在这里,我们首先验证要删除的用户不是自己,这是不允许的,否则,我们继续,删除用户,刷新列表并重新渲染同一页面。
源代码
源代码可在 GitHub 上获取:https://github.com/cliftonm/basic-auth
下一步?
测试是使用 Ruby on Rails 开发网站的重要组成部分。在下一期中,我们将为我们的基本身份验证系统编写一些测试场景。之后,我将研究实现一个角色授权组件,以完成身份验证/授权教程。一旦这些到位,我将编写一个关于如何在 Rails 中搭建论坛的教程——它不像 Code Project 的论坛那么复杂,但比我在撰写本文时尝试过的 Rails 可用的论坛 gem 要复杂得多。这之后可能会是我对基于社区网站的想法。