首先,我用谷歌和雅虎进行了大量搜索,找到了一些关于我的主题的回复,但它们都没有真正涵盖我需要知道的内容.

我的应用程序中有几个用户模型,现在是客户、设计师、零售商,似乎还有更多.他们都有不同的数据存储在他们的表和网站上的几个区域,他们被允许或不允许.所以我想走Desive+CanCan的路,试试多态关联的运气,所以我设置了以下模型:

class User < AR
  belongs_to :loginable, :polymorphic => true
end

class Customer < AR
  has_one :user, :as => :loginable
end

class Designer < AR
  has_one :user, :as => :loginable
end

class Retailer < AR
  has_one :user, :as => :loginable
end

对于注册,我 for each 不同的用户类型提供了自定义视图,我的路由设置如下:

devise_for :customers, :class_name => 'User'
devise_for :designers, :class_name => 'User'
devise_for :retailers, :class_name => 'User'

现在注册控制器仍然是标准的(即"设计/注册"),但我想,因为我有不同的数据存储在不同的模型中,所以我也必须定制这种行为!?

但在这个设置中,我得到了customer_signed_in?designer_signed_in?这样的助手,但我真正需要的是一个通用的助手,比如user_signed_in?,用于站点上所有用户都可以访问的区域,无论是哪种用户类型.

我还想要一个路由助手,比如new_user_session_path,而不是几个new_*type*_session_path等等.事实上,我需要改变的只是注册过程...

所以我想知道这是否是解决这个问题的方法?还是有更好/更容易/更少的定制解决方案?

推荐答案

好吧,我仔细研究了一下,得出了以下解决方案

用户模型

# user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  attr_accessible :email, :password, :password_confirmation, :remember_me

  belongs_to :rolable, :polymorphic => true
end

客户模式

# customer.rb
class Customer < ActiveRecord::Base
  has_one :user, :as => :rolable
end

设计师模型

# designer.rb
class Designer < ActiveRecord::Base
  has_one :user, :as => :rolable
end

因此,用户模型有一个简单的多态关联,定义它是客户还是设计师

然后我为新的注册定制了注册视图,在生成它们后,可以在app/views/devise/registrations/new.html.erb中找到它们.

<h2>Sign up</h2>

<%
  # customized code begin

  params[:user][:user_type] ||= 'customer'

  if ["customer", "designer"].include? params[:user][:user_type].downcase
    child_class_name = params[:user][:user_type].downcase.camelize
    user_type = params[:user][:user_type].downcase
  else
    child_class_name = "Customer"
    user_type = "customer"
  end

  resource.rolable = child_class_name.constantize.new if resource.rolable.nil?

  # customized code end
%>

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
  <%= my_devise_error_messages!    # customized code %>

  <div><%= f.label :email %><br />
  <%= f.email_field :email %></div>

  <div><%= f.label :password %><br />
  <%= f.password_field :password %></div>

  <div><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></div>

  <% # customized code begin %>
  <%= fields_for resource.rolable do |rf| %>
    <% render :partial => "#{child_class_name.underscore}_fields", :locals => { :f => rf } %>
  <% end %>

  <%= hidden_field :user, :user_type, :value => user_type %>
  <% # customized code end %>

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

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

For each User type I created a separate partial with the custom fields for that specific User type, i.e. Designer --> _designer_fields.html

<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>

然后我为Desive设置了注册时使用自定义控制器的路径

devise_for :users, :controllers => { :registrations => 'UserRegistrations' }

然后我生成了一个控制器来处理定制的注册过程,从Devise::RegistrationsController中的create方法复制了原始源代码,并对其进行了修改以适应我的工作方式(在我的例子app/views/user_registrations中,不要忘记将视图文件移动到适当的文件夹中)

class UserRegistrationsController < Devise::RegistrationsController
  def create
    build_resource

    # customized code begin

    # crate a new child instance depending on the given user type
    child_class = params[:user][:user_type].camelize.constantize
    resource.rolable = child_class.new(params[child_class.to_s.underscore.to_sym])

    # first check if child instance is valid
    # cause if so and the parent instance is valid as well
    # it's all being saved at once
    valid = resource.valid?
    valid = resource.rolable.valid? && valid

    # customized code end

    if valid && resource.save    # customized code
      if resource.active_for_authentication?
        set_flash_message :notice, :signed_up if is_navigational_format?
        sign_in(resource_name, resource)
        respond_with resource, :location => redirect_location(resource_name, resource)
      else
        set_flash_message :notice, :inactive_signed_up, :reason => inactive_reason(resource) if is_navigational_format?
        expire_session_data_after_sign_in!
        respond_with resource, :location => after_inactive_sign_up_path_for(resource)
      end
    else
      clean_up_passwords(resource)
      respond_with_navigational(resource) { render_with_scope :new }
    end
  end
end

这一切基本上都是这样做的,控制器根据视图中的隐藏字段传递给控制器create方法的user_type参数确定必须创建哪个用户类型,该字段通过URL中的简单GET参数使用该参数.

例如:

my_devise_error_messages!方法是一种辅助方法,它基于原始devise_error_messages!方法处理关联模型中的验证错误

module ApplicationHelper
  def my_devise_error_messages!
    return "" if resource.errors.empty? && resource.rolable.errors.empty?

    messages = rolable_messages = ""

    if !resource.errors.empty?
      messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
    end

    if !resource.rolable.errors.empty?
      rolable_messages = resource.rolable.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
    end

    messages = messages + rolable_messages   
    sentence = I18n.t("errors.messages.not_saved",
                      :count => resource.errors.count + resource.rolable.errors.count,
                      :resource => resource.class.model_name.human.downcase)

    html = <<-HTML
    <div id="error_explanation">
    <h2>#{sentence}</h2>
    <ul>#{messages}</ul>
    </div>
    HTML

    html.html_safe
  end
end

更新:

要支持/designer/sign_up/customer/sign_up等路由,可以在路由文件中执行以下操作:

# routes.rb
match 'designer/sign_up' => 'user_registrations#new', :user => { :user_type => 'designer' }
match 'customer/sign_up' => 'user_registrations#new', :user => { :user_type => 'customer' }

路由语法内部未使用的任何参数都会被传递到params散列.所以:user被传递给params散列.

所以就这样.我在这里和那里用了一点tweeking,使它以一种非常通用的方式工作,这很容易扩展到共享一个公共用户表的许多其他用户模型.

希望有人觉得有用.

Ruby-on-rails相关问答推荐

Gemfile需要较新版本的依赖项

Ubuntu Webhooks无法读取安装的正确RVM Ruby

Puma 工作人员无法在 Ubuntu 20.04 VM 上的 Rails 5.2 中启动

Ruby - 如何删除目录中早于 X 天的所有文件?

如何在生成的 HTML 本身中显示部分名称

线程如何在 Web 应用程序中工作?

Restful Rails 编辑与更新

ActiveRecord 包括.指定包含的列

2个空格或1个制表符,Rails社区的缩进标准是什么?

Rails 3 - Select 包含?

如何在 Rails 上生成 AuthenticityToken

在 Rails 中,如何处理多个选中的复选框,只需拆分 , 或?

Rails,获取模型中的资源路径

在 ruby​​ 中构建公钥时,是什么导致既不是 PUB key 也不是 PRIV key::nested asn1 错误?

Rails Activeadmin - 自定义关联 Select 框

在 Rails 应用程序中处理大文件上传的最佳方法是什么?

Ruby 根据属性在数组中查找并返回对象

Coffeescript ||= 模拟?

Rails - 如何在用户登录时覆盖设计 SessionsController 以执行特定任务?

按照给定 ID 数组的顺序按 ID 查找模型记录