简体   繁体   English

使用 Ruby On Rails 的多个用户模型,并设计为具有单独的注册路径但有一个通用的登录路径

[英]Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route

First, I've searched intensely with Google and Yahoo and I've found several replies on topics like mine, but they all don't really cover what I need to know.首先,我在 Google 和 Yahoo 上进行了大量搜索,我发现了一些与我类似的主题的回复,但它们都没有真正涵盖我需要知道的内容。

I've got several user models in my app, for now it's Customers, Designers, Retailers and it seems there are yet more to come.我的应用程序中有几个用户模型,现在是客户、设计师、零售商,而且似乎还有更多。 They all have different data stored in their tables and several areas on the site they're allowed to or not.他们都有不同的数据存储在他们的表中,以及他们被允许或不允许的站点上的几个区域。 So I figured to go the devise+CanCan way and to try my luck with polymorphic associations, so I got the following models setup:所以我想采用 devise+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 the registration I've got customized views for each different User type and my routes are setup like this:对于注册,我为每种不同的用户类型定制了视图,我的路由设置如下:

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

For now the registrations controller is left as standard (which is "devise/registrations"), but I figured, since I got different data to store in different models I'd have to customize this behaviour as well!?现在,注册控制器仍然是标准的(即“设计/注册”),但我想,因为我有不同的数据要存储在不同的模型中,所以我也必须自定义这种行为!?

But with this setup I got helpers like customer_signed_in?但是通过这个设置,我得到了像customer_signed_in?这样的助手customer_signed_in? and designer_signed_in?designer_signed_in? , but what I'd really need is a general helper like user_signed_in? ,但我真正需要的是像user_signed_in?这样的通用助手user_signed_in? for the areas on the site that are accessible to all users, no matter which user type.对于网站上所有用户都可以访问的区域,无论哪种用户类型。

I'd also like a routes helper like new_user_session_path instead of the several new_*type*_session_path and so on.我还想要一个像new_user_session_path这样的路由助手,而不是几个new_*type*_session_path等等。 In fact all I need to be different is the registration process...事实上,我需要与众不同的是注册过程......

So I was wondering IF THIS IS THE WAY TO GO for this problem?所以我想知道这是否是解决这个问题的方法? Or is there a better/easier/less must-customize solution for this?或者是否有更好/更简单/更少必须定制的解决方案?

Okay, so I worked it through and came to the following solution.好的,所以我解决了它并得出了以下解决方案。
I needed to costumize devise a little bit, but it's not that complicated.我需要稍微装饰一下设计,但这并没有那么复杂。

The User model用户模型

# 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

The Customer model客户模型

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

The Designer model设计师模型

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

So the User model has a simple polymorphic association, defining if it's a Customer or a Designer.所以 User 模型有一个简单的多态关联,定义它是 Customer 还是 Designer。
The next thing I had to do was to generate the devise views with rails g devise:views to be part of my application.我要做的下一件事是使用rails g devise:views生成设计视图,作为我的应用程序的一部分。 Since I only needed the registration to be customized I kept the app/views/devise/registrations folder only and removed the rest.由于我只需要自定义注册,我只保留app/views/devise/registrations文件夹并删除其余文件夹。

Then I customized the registrations view for new registrations, which can be found in app/views/devise/registrations/new.html.erb after you generated them.然后我为新注册自定义了注册视图,生成后可以在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, ie Designer --> _designer_fields.html对于每个用户类型,我创建了一个单独的部分,其中包含该特定用户类型的自定义字段,即 Designer --> _designer_fields.html

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

Then I setup the routes for devise to use the custom controller on registrations然后我设置了设计路线以在注册时使用自定义控制器

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

Then I generated a controller to handle the customized registration process, copied the original source code from the create method in the Devise::RegistrationsController and modified it to work my way (don't forget to move your view files to the appropriate folder, in my case app/views/user_registrations然后我生成了一个控制器来处理定制的注册过程,从Devise::RegistrationsControllercreate方法复制原始源代码并修改它以按照我的方式工作(不要忘记将您的视图文件移动到适当的文件夹中,在我的案例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

What this all basically does is that the controller determines which user type must be created according to the user_type parameter that's delivered to the controller's create method by the hidden field in the view which uses the parameter by a simple GET-param in the URL.这一切的基本作用是,控制器根据user_type参数确定必须创建哪种用户类型,该参数由视图中的隐藏字段传递给控制器​​的create方法,该字段通过 URL 中的简单 GET-param 使用该参数。

For example:例如:
If you go to /users/sign_up?user[user_type]=designer you can create a Designer.如果您转到/users/sign_up?user[user_type]=designer您可以创建一个设计器。
If you go to /users/sign_up?user[user_type]=customer you can create a Customer.如果您转到/users/sign_up?user[user_type]=customer您可以创建一个 Customer。

The my_devise_error_messages! my_devise_error_messages! method is a helper method which also handles validation errors in the associative model, based on the original devise_error_messages!方法是一个辅助方法,它也处理关联模型中的验证错误,基于原始的devise_error_messages! method方法

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

UPDATE:更新:

To be able to support routes like /designer/sign_up and /customer/sign_up you can do the following in your routes file:为了能够支持/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' }

Any parameter that's not used in the routes syntax internally gets passed to the params hash.内部路由语法中未使用的任何参数都会传递给 params 哈希。 So :user gets passed to the params hash.所以:user被传递给 params 哈希。

So... that's it.就是这样了。 With a little tweeking here and there I got it working in a quite general way, that's easily extensible with many other User models sharing a common User table.在这里和那里稍微调整一下,我就让它以一种非常通用的方式工作,这很容易扩展到许多其他共享公共 User 表的 User 模型。

Hope someone finds it useful.希望有人觉得它有用。

I didn't manage to find any way of commenting for the accepted answer, so I'm just gonna write here.我没有设法找到对接受的答案发表评论的任何方式,所以我只想写在这里。

There are a couple of things that don't work exactly as the accepted answer states, probably because it is out of date.有一些事情与公认的答案状态不完全一样,可能是因为它已经过时了。

Anyway, some of the things that I had to work out myself:无论如何,我必须自己解决一些问题:

  1. For the UserRegistrationsController, render_with_scope doesn't exist any more, just use render :new对于 UserRegistrationsController, render_with_scope不再存在,只需使用render :new
  2. The first line in the create function, again in the UserRegistrationsController doesn't work as stated. create 函数中的第一行,同样在 UserRegistrationsController 中,没有按照说明工作。 Just try using尝试使用

    # Getting the user type that is send through a hidden field in the registration form. user_type = params[:user][:user_type] # Deleting the user_type from the params hash, won't work without this. params[:user].delete(:user_type) # Building the user, I assume. build_resource

instead of simply build_resource .而不是简单的build_resource Some mass-assignment error was coming up when unchanged.未更改时会出现一些批量分配错误。

  1. If you want to have all the user information in Devise's current_user method, make these modifications:如果您想在 Devise 的 current_user 方法中拥有所有用户信息,请进行以下修改:

class ApplicationController < ActionController::Base protect_from_forgery

 # Overriding the Devise current_user method alias_method :devise_current_user, :current_user def current_user # It will now return either a Company or a Customer, instead of the plain User. super.rolable end end

I was following the above instructions and found out some gaps and that instructions were just out of date when I was implementing it.我按照上述说明进行操作,发现了一些差距,并且在我实施它时这些说明已经过时了。

So after struggling with it the whole day, let me share with you what worked for me - and hopefully it will save you few hours of sweat and tears因此,在与它斗争了一整天之后,让我与您分享对我有用的东西-希望它可以为您节省几个小时的汗水和眼泪

  • First of all, if you are not that familiar with RoR polymorphism, please go over this guide: http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/ After following it you will have devise and user users models installed and you will be able to start working.首先,如果您对 RoR 多态性不是很熟悉,请阅读本指南: http : //astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/跟随它后你将安装设计和用户用户模型,您将能够开始工作。

  • After that please follow Vapire's great tutorial for generating the views with all the partails.之后,请按照 Vapire 的精彩教程生成包含所有部分的视图。

  • What I found most frustrating was that dut to using the latest version of Devise (3.5.1), RegistrationController refused to work.我发现最令人沮丧的是,由于使用最新版本的 Devise (3.5.1),RegistrationController 拒绝工作。 Here is the code that will make it work again:这是使其再次工作的代码:

     def create meta_type = params[:user][:meta_type] meta_type_params = params[:user][meta_type] params[:user].delete(:meta_type) params[:user].delete(meta_type) build_resource(sign_up_params) child_class = meta_type.camelize.constantize child_class.new(params[child_class.to_s.underscore.to_sym]) resource.meta = child_class.new(meta_type_params) # first check if child intance 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.meta.valid? && valid # customized code end if valid && resource.save # customized code yield resource if block_given? if resource.persisted? if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_flashing_format? sign_up(resource_name, resource) respond_with resource, location: after_sign_up_path_for(resource) else set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format? expire_data_after_sign_in! respond_with resource, location: after_inactive_sign_up_path_for(resource) end else clean_up_passwords resource set_minimum_password_length respond_with resource end end end
  • and also add these overrides so that the redirections will work fine:并添加这些覆盖,以便重定向可以正常工作:

     protected def after_sign_up_path_for(resource) after_sign_in_path_for(resource) end def after_update_path_for(resource) case resource when :user, User resource.meta? ? another_path : root_path else super end end
  • In order that devise flash messages will keep working you'll need to update config/locales/devise.en.yml instead of the overridden RegistraionsControlloer by UserRegistraionsControlloer all you'll need to do is add this new section:为了使设计 flash 消息继续工作,您需要更新config/locales/devise.en.yml而不是 UserRegistraionsControlloer 覆盖的 RegistraionsControlloer 您需要做的就是添加这个新部分:

     user_registrations: signed_up: 'Welcome! You have signed up successfully.'

Hope that will save you guys few hours.希望这会为你们节省几个小时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM