ror:重设密码

经过一个寒假的休整(浪里个浪)又写了一段时间毕设和论文,终于重启了我的ror(Ruby on rails)作业。感觉已经没法再陌生了,这都是些什么鬼?(WTF ...)


创建及合并分支:略

资源

添加控制器

rails generate controller PasswordResets new edit --no-test-framework

添加路由

# config/routes.rb

... ...
  resources :password_resets, only: [:new, :create, :edit, :update]
  ... ...

添加链接

# app/views/sessions/new.html.erb

... ...
            <%= f.label :password %>
            <%= link_to "(forgot password)", new_password_reset_path %>
            <%= f.password_field :password, class: 'form-control' %>
... ...
ror:重设密码_第1张图片

添加迁移

rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime

执行迁移

rails db:migrate

控制器和表单

重设密码页面视图

# app/views/password_resets/new.html.erb

<% provide(:title, "Forgot password") %>

Forgot password

<%= form_for(:password_reset, url: password_resets_path) do |f| %> <%= f.label :email %> <%= f.text_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %>
ror:重设密码_第2张图片

控制器create动作

# app/controllers/password_resets_controller.rb

... ...

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end
... ...

模型中添加辅助方法

# app/models/user.rb

... ...
  attr_accessor :remember_token, :activation_token, :reset_token
  ... ...

    # 设置密码重设相关属性
    def create_reset_digest
        self.reset_token = User.new_token
        update_attribute(:reset_digest, User.digest(reset_token))
        update_attribute(:reset_sent_at, Time.zone.now)
    end

    # 发送密码重设邮件 
    def send_password_reset_email
        UserMailer.password_reset(self).deliver_now
    end
... ...

邮件程序

发送密码重设链接

# app/mailers/user_mailer.rb

... ...
  def password_reset(user)
    @user = user
    mail to: user.email, subject: "Password reset | From Microblog"
  end

密码重设邮件视图

# app/views/user_mailler/password_reset.html.erb

Password reset

To reset your password click the link below:

<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, email: @user.email, host: 'localhost:3000') %>

This link will expire in two hours.

If you did not request your password to be reset, please ignore this email and your password will stay as it is.

# app/views/user_mailer/password_reset.text.erb

To reset your password click the link below:
<%= edit_password_reset_url(@user.reset_token, email: @user.email, host: 'localhost:3000') %>
This link will expire in two hours.

If you did not request your password to be reset, please ignore this email and your password will stay as it is.

邮件预览

# test/mailers/previews/user_mailer_preview.rb

... ...
  def password_reset
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
  end
... ...
ror:重设密码_第3张图片

邮件测试

# test/mailers/user_mailer_test.rb

... ...
  test "password_reset" do
    user = users(:joshua)
    user.reset_token = User.new_token
    mail = UserMailer.password_reset(user)
    assert_equal "Password reset | From Microblog", mail.subject
    assert_equal [user.email], mail.to
    assert_equal "noreply@localhost:3000", mail.from
    assert_match user.reset_token, mail.body.encoded
    assert_match CGI::escape(user.email), mail.body.encoded
  end
... ...

重设密码

添加表单

# app/views/password_resets/edit.html.erb

<% provide(:title, 'Reset password') %>

Reset password

<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %> <%= render 'shared/error_messages' %> <%= hidden_field_tag :email, @user.email %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Update password", class: "btn btn-primary" %> <% end %>

修改密码

# app/controllers/password_resets_controller.rb

class PasswordResetsController < ApplicationController

  before_action :get_user, only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update]

  def new
  end

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def edit
  end

  def update
    if both_passwords_blank?
      flash.now[:danger] = "Password/confirmation can't be blank"
      render 'edit'
    elsif @user.update_attributes(user_params)
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end

private
  
  def user_params
    params.require(:user).permit(:password, :password_confirmation)
  end
  
  def both_passwords_blank?
    params[:user][:password].blank? && params[:user][:password_confirmation].blank?
  end

  # 检查令牌过期
  def check_expiration
    if @user.password_reset_expired?
      flash[:danger] = "Password reset has expired."
      redirect_to new_password_reset_url
    end
  end

  def get_user
    @user = User.find_by(email: params[:email])
  end

  # 确认用户有效
  def valid_user
    unless (@user && @user.activated? && @user.authenticated?(:reset, params[:id]))
      redirect_to root_url
    end
  end

end

判断令牌失效

# app/models/user.rb

... ...
    def password_reset_expired?
        reset_sent_at < 2.hours.ago
    end
... ...

测试

生成测试文件

rails generate integration_test password_resets

编写测试

# test/integration/password_resets_test.rb

... ...
  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:joshua)
  end

  test "password resets" do
    get new_password_reset_path
    assert_template 'password_resets/new'
    # 邮件地址无效
    post password_resets_path, password_reset: { email: "" }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    # 邮件地址有效
    post password_resets_path, password_reset: { email: @user.email }
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    assert_equal 1, ActionMailer::Base.deliveries.size
    assert_not flash.empty?
    assert_redirected_to root_url
    # 密码重设表单
    user = assigns(:user)
    # 邮件地址错误
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # 用户未激活
    user.toggle!(:activated)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url
    user.toggle!(:activated)
    # 邮件地址正确,令牌不对
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # 邮件地址正确,令牌也对
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_template 'password_resets/edit'
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # 密码和确认不匹配
    patch password_reset_path(user.reset_token),
      email: user.email, user: { password: "foobaz", password_confirmation: "barquux" }
    assert_select 'div#error_explanation'
    # 密码和确认都为空
    patch password_reset_path(user.reset_token),
      email: user.email, user: { password: " ", password_confirmation: " " }
    assert_not flash.empty?
    assert_template 'password_resets/edit'
    # 密码和确认都有效
    patch password_reset_path(user.reset_token),
      email: user.email, user: { password: "foobaz", password_confirmation: "foobaz" }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
  end
  
end

执行测试

rails test:integration

生产环境中发送邮件

添加SendGrid扩展

heroku addons:add sendgrid:starter
Creating sendgrid:starter on ⬢ fathomless-shelf-38262... !
 ▸    Please verify your account to install this add-on plan (please enter a credit card) For more information, see
 ▸    https://devcenter.heroku.com/categories/billing Verify now at https://heroku.com/verify

当然,需要账户填写信用卡信息(由于本人尚未申请信用卡,本部分略过)

你可能感兴趣的:(ror:重设密码)