Dongfeng Gu

【译】自定义Devise

- 8 mins

参考链接

原文链接

文章正文

自定义Devise

在这篇文章当中我们将会介绍如何使用devise的一些自定义功能来满足一些特定的需求,下面列表就是需求列表

用户必须要填写first_name, last_name, roleusername

假设我们已经将first_name, last_name, roleusername添加到users的表中。我们还需要添加一些新的参数,有一个需要注意的地方是,如果我们想要添加一些Devise中没有内置的参数,我们需要将这个参数添加到strong parameters当中,这样Rails才知道这些参数是有用的。

让我们从写一些specs测试开始,如果没有使用任何的自定义的区域(field),那么我们一般来说不需要测试Devise因为Devise本身就有非常完善的测试。但是当我们需要添加一些自定义的行为的时候,那么我们自定义的这些行为就需要进行测试。首先创建一个新的文件user_spec.rb

# spec/features/user_spec.rb

require 'rails_helper'

describe 'user navigation' do
  describe 'creation' do
    it 'can register with full set of user attributes' do
      visit new_user_registration_path

      fill_in 'user[email]', with: "test@test.com"
      fill_in 'user[password]', with: "password"
      fill_in 'user[password_confirmation]', with: "password"
      fill_in 'user[first_name]', with: "Jon"
      fill_in 'user[last_name]', with: "Snow"
      fill_in 'user[username]', with: "watcheronthewall"

      click_on "Sign up"

      expect(page).to have_content("Welcome")
    end
  end
end

运行测试rspec spec/features/user_spec.rb将会出现以下的错误Capybara::ElementNotFound: Unable to find field "user[first_name]"。这个错误是因为我们还没有添加相应的自定义区域(field),现在让我们在registration的模版(template)当中添加我们自定义的区域。

<!-- app/views/devise/registrations/new.html.erb -->

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true %>
  </div>

  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>

  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>

  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "off" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

现在让我们将新属性添加到strong parameters当中。创建一个新的控制器registrations_controller.rb

# app/controllers/registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController

  private

  def sign_up_params
    params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :username)
  end

  def account_update_params
    params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_password, :username)
  end
end

这个将会拓展默认的Devise控制器,然后添加一些新的自定义的属性,我们将会在我们的注册的表中使用到这些属性。最后我们需要修改路由(route)文件

# config/routes.rb

Rails.application.routes.draw do
  devise_for :users, controllers: { registrations: 'registrations' }
  root to: 'static#home'
end

让我们再次运行测试rspec时,所有的测试将会通过。这时我们可以开启服务器rails s然后使用浏览器访问页面localhost:3000/users/sign_up,将会看到新的区域。

Alt text


用户的role属性默认值为student

用户的role属性应该由系统控制,用户将无法做出任何的更改。让我们来通过修改rails生成(rails generate model User )的user_spec.rb文件来添加这个功能的测试:

# spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'creation' do
    before do
      @user = User.create(email: "test@test.com", password: "password", password_confirmation: "password", first_name: "Jon", last_name: "Snow", username: "watcheronthewall")
    end

    it 'should be able to be created if valid' do
      expect(@user).to be_valid
    end

    it 'should have a default role of: student' do
      expect(@user.role).to eq('student')
    end
  end
end

这里面有两个测试,其中一个测试将会正常通过因为我们知道User将会被成功创建,第二个测试将会失败,因为这个测试是来检查User默认的role是否为Student。运行rspec spec/models,我们将会发现以下的错误:NoMethodError: undefined methodrole' for #User:0x007fd2b09991e0。下面让我们来创建一个迁移文件来修复这个错误:

rails g migration add_role_to_users role:string

当我们运行了rake db:migrate之后,将会添加一个role属性到User的数据库当中,现在我们重新运行测试rspec spec/models,我们将会获得一个不同的错误expected: "student", got: nil

Alt text

现在让我们添加一个回调函数到user.rb当中,来默认创建用户的role属性

# app/models/user.rb

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  after_initialize :set_defaults

  private

    def set_defaults
      self.role ||= 'student'
    end
end

当我们再次运行rspec时,所有的测试将会通过。


现在让我们回到用户必须有first namelast nameroleusername的这个需求上来。为了实现这个需求,我们需要添加一些validationsuser.rb文件当中,但是首先让我们添加一些新的rspec测试:

# spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'creation' do
    before do
      @user = User.create(email: "test@test.com", password: "password", password_confirmation: "password", first_name: "Jon", last_name: "Snow", username: "watcheronthewall")
    end

    it 'should be able to be created if valid' do
      expect(@user).to be_valid
    end

    it 'should have a default role of: student' do
      expect(@user.role).to eq('student')
    end

    describe 'validations' do
      it 'should not be valid without a first name' do
        @user.first_name = nil
        expect(@user).to_not be_valid
      end

      it 'should not be valid without a last name' do
        @user.last_name = nil
        expect(@user).to_not be_valid
      end

      it 'should not be valid without a username' do
        @user.username = nil
        expect(@user).to_not be_valid
      end
    end
  end
end

运行rspec spec/models将会给我们三个错误,错误内容为Failure/Error: expect(@user).to_not be_valid。让我们给我们的user.rb文件添加以下的validations

# app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  after_initialize :set_defaults

  validates_presence_of :first_name, :last_name, :username

  private

    def set_defaults
      self.role ||= 'student'
    end
end

再次运行rspec将会发现所有的测试都会通过。


现在让我们回顾一下我们的需求:

现在让我们来完成需求:

用户的username是唯一的

首先让我们来添加一些新的测试到测试文件中来:

# spec/models/user_spec.rb

it 'should ensure that all usernames are unique' do
  duplicate_username_user = User.create(email: "test2@test.com", password: "password", password_confirmation: "password", first_name: "Joni", last_name: "Snowy", username: "watcheronthewall")
  expect(duplicate_username_user).to_not be_valid
end

运行测试我们将会发现以下错误Failure/Error: expect(duplicate_username_user).to_not be_valid。下一步让我们来更新user.rb文件:

# app/models/user.rb

validates_uniqueness_of :username

当我们再次运行rspec时,所有的测试将会通过。


现在让我们实现最后的一个需求。用户的username不可以含有特殊的字符

因为我们的用户名最终将会成为子域名(subdomain),所以用户名中将不能包含任何的特殊字符,类似于&*$&*@。首先我们添加rspec测试:

# spec/models/user_spec.rb

it 'should ensure that usernames do not allow special characters' do
  @user.username = "*#*(@!"
  expect(@user).to_not be_valid
end

运行测试将会给出相应的错误提示消息,现在让我们来修改user.rb文件:

# app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  after_initialize :set_defaults

  validates_presence_of :first_name, :last_name
  validates :username, uniqueness: true, presence: true, format: { with: /\A[a-zA-Z]+([a-zA-Z]|\d)*\Z/ }

  private

    def set_defaults
      self.role ||= 'student'
    end
end

现在当我们再次运行测试时,所有的测试将会通过。

参考代码

comments powered by Disqus
rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium vimeo