将注册添加到 Rails 8' 身份验证

本文最初发表于《使用 Ruby on Rails 构建 SaaS》(作者:Rails Designer)

Rails 8 终于推出了开箱即用的身份验证功能。❤️ 当它发布时,大多数人都惊讶于它没有提供注册(注册)新用户的方法。但这是有意识的决定,因为创建用户通常不是 SaaS 所需的唯一资源。当有人新注册您的产品时,还需要采取许多其他操作并创建资源。

本文探讨了我使用某种东西(通常称为**表单对象**)来实现这一点的方法。让我们来看看。

如果您想继续,请查看此 repo。此提交是一个 vanilla Rails 8 应用程序,其中已运行“bin/rails generate authentication”。

对于新功能,我总是喜欢从外到内开始。这意味着我从 UI 开始,然后再进入内部(更多内容请参阅另一篇文章)。

让我们在**app/views/signups/new.html.erb**创建表单:

<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>

<%= form_with model: @signup do |form| %>
  <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
<%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %>
<%= form.submit "Sign up" %> <% end %>

它与 Rails 的 **sessions/new.html.erb** 非常相似。

**注意**:本节中的文章重点介绍(业务)逻辑,不包含任何样式。我建议查看 Rails Designer 上的大量文章,以提高您的 UI、CSS 和 JavaScript 技能!🧑‍🎓 🎨

下一個路線:

# config/routes.rb
root to: "pages#show"
resource :signups, path: "signup", only: %w[new create]

我还添加了一个根路由,用户成功注册后将被重定向到该路由。

然后是控制器。这很简单:

class SignupsController < ApplicationController
  allow_unauthenticated_access only: %w[new create]

  def new
    @signup = Signup.new
  end

  def create
    @signup = Signup.new(signup_params)

    if user = @signup.save
      start_new_session_for user

      redirect_to root_url
    else
      redirect_to new_signups_path
    end
  end

  private

  def signup_params
    params.expect(signup: [ :email_address, :password ])
  end
end

`allow_unauthenticated_access` 来自 Rails 的身份验证生成器。然后另一个小新东西是 `params.expect(signup: [ :email_address, :password, :terms ])`。以前您可能已经看到过 `params.require(:signup).permit(:email_address, :password)`。这是此 PR 中添加的新 Rails 8+ 语法。

这一切看起来都很熟悉,对吧?但特殊之处在于 **Signup** 类。这不是您的常规 ActiveModel,而是一个所谓的 **Form Object**。

# app/models/signup.rb
class Signup
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :email_address, :string
  attribute :password, :string

  validates :email_address, presence: true
  validates :password, length: 8..128

  def save
    if valid?
      User.create!(email_address: email_address, password: password)
    end
  end

  def model_name
    ActiveModel::Name.new(self, nil, self.class.name)
  end
end

你看,我只是把它存储在 **app/models** 中;不需要其他文件夹。还请注意,它只是一个普通的 Ruby 类(通常称为 **PORO**),也就是说,它不是从 **ApplicationRecord** 继承的,但它看起来(而且听起来很像!)很像。

这是由于 `model_name` 方法和包含的两个模块。这允许您在表单生成器中引用对象(`form_with model: Signup.new`)。

对我来说,这种设置的美妙之处在于你现在有一个可以负责更多事情的类。毕竟,当注册一个新的 SaaS 时,通常会发生更多的事情:

  • 创建工作区
  • 创建一个团队
  • 发送欢迎电子邮件
  • 创建注册活动
  • 等等…
  • 您的特定用例还需要其他任何内容。让我们通过一个例子来稍微扩展一下上面的类:

    # app/models/signup.rb
    class Signup
      include ActiveModel::Model
      include ActiveModel::Attributes
    
      attribute :email_address, :string
      attribute :password, :string
    
      validates :email_address, presence: true
      validates :password, length: 8..128
    
      def save
        if valid?
          User.create!(email_address: email_address, password: password).tap do |user|
            create_workspace_for user
            send_welcome_email_to user
          end
        end
      end
    
      def model_name
        ActiveModel::Name.new(self, nil, self.class.name)
      end
    
      private
    
      def create_workspace_for(user)
        # eg. user.workspaces.create
      end
    
      def send_welcome_email_to(user)
        # eg. WelcomeEmail.perform_later user
      end
    end

    现在,当 `valid?` 返回 true 时,将创建一个工作区并发送一封欢迎电子邮件。它还使用 `tap`:这将传递用户对象并将其作为对象返回(这在 **SignupsController#create** 操作中很有用。

    我喜欢将我的方法编写得易于阅读和理解(类似于 Rails 8 身份验证代码的编写方式,例如“start_new_session_for user”)。

    我现在只添加了两个额外的步骤,但你可以想象工作区创建也会在另一个类中再次发生。你可以随意扩展你需要的任何内容。

    我们不要止步于此,让我们添加一个复选框,让用户接受您的服务条款。首先更新视图:

    <%= form_with model: @signup do |form| %>
      <%# … %>
    
      <%= form.check_box :terms %>
      <%= form.label :terms %>
    <% end %>

    然后验证它是否存在于 Signup 类中。

    class Signup
      # …
      attribute :terms, :boolean, default: false
    
      validates :terms, acceptance: true
      # …
    end

    然后允许该属性在参数中传递。

    class SignupsController < ApplicationController
      # …
    
      def signup_params
        params.expect(signup: [ :email_address, :password, :terms ])
      end
    end

    类似地,你可以添加一个复选框来选择他们加入你的电子邮件通讯(并使用类似本文中描述的保险库之类的工具将其存储为首选项!💡

    为了从头到尾完成此操作,让我们创建一个简单的视图,用户在注册后会被重定向到该视图。

    # app/views/pages/show.html.erb
    

    Sign up Successful

    <%= button_to "Log out", session_path, method: :delete %>

    使用这种方法,您将拥有一个可以包含注册所需的所有步骤的类。易于推理,易于测试!

    当然这只是开始!从 UI 和 CSS(查看 Rails Designer 的 UI 组件库)到添加验证消息等等。

    这样,您就将注册添加到了 Rails 8 身份验证中。此外,此代码不仅限于 Rails 8,还可以轻松添加到旧版本的 Rails。