使用Ruby On Rails的多个用户模型,并devise为具有单独的注册路线,但具有一个通用的login路线

首先,我对Google和雅虎进行了强烈的search,发现了像我这样的主题的几个回复,但是他们并没有真正涵盖我需要知道的东西。

我的应用中有几个用户模型,现在是客户,devise师,零售商,看起来还有更多。 他们都有不同的数据存储在他们的表格和他们被允许或不允许的网站上的几个区域。 所以我想到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 

为了注册,我已经为每个不同的用户types定制了视图,我的路由设置是这样的:

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

现在注册控制器是标准的(这是“devise/注册”),但我想,因为我有不同的数据存储在不同的模型,我不得不定制这种行为以及!

但是通过这个设置,我得到了像customer_signed_in?这样的助手customer_signed_in?designer_signed_in? ,但是我真正需要的是像user_signed_in?这样的普通帮助user_signed_in? 对于网站上所有用户都可访问的区域,无论是哪种用户types。

我也喜欢像new_user_session_path这样的路由帮助而不是几个new_*type*_session_path等等。 事实上,我需要不同的是注册过程…

所以我想知道如果这是这种方式去解决这个问题? 还是有一个更好/更容易/更less必须定制的解决scheme?

提前致谢,
罗伯特

好的,所以我通过了解决scheme。
我需要花费一些时间,但并不复杂。

用户模型

 # 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 

devise师模型

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

所以User模型有一个简单的多态关联,定义它是一个Customer还是一个Designer。
接下来我要做的是用rails g devise:views生成devise视图rails g devise:views是我的应用程序的一部分。 由于我只需要自定义注册,所以我只保留了app/views/devise/registrations文件夹,并将其余部分删除。

然后我为新的注册定制了注册视图,可以在生成它们之后在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" %> 

对于每个用户types,我创build了一个单独的部分与该特定用户types的自定义字段,即devise器 – > _designer_fields.html

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

然后我设置路线为devise在注册使用自定义控制器

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

然后我生成一个控制器来处理自定义的注册过程,从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 

这一切的基本function是,控制器根据user_type参数创build哪个用户types,该参数通过URL中简单的GET-param使用参数的视图中的隐藏字段传递给控制器​​的create方法。

例如:
如果你去/users/sign_up?user[user_type]=designer你可以创build一个Designer。
如果你去/users/sign_up?user[user_type]=customer你可以创build一个Customer。

my_devise_error_messages! 方法是一个辅助方法,它也基于原始的devise_error_messages!处理关联模型中的validation错误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散列。

就是这样了。 在这里和那里稍微调整一下,我就可以用相当一般的方式工作,这很容易与许多其他共享用户表的用户模型一起扩展。

希望有人认为它有用。

我没有设法find接受答案的任何评论,所以我只是写在这里。

有几件事情不像接受的答案状态那样工作,可能是因为它已经过时了。

无论如何,我必须自己解决一些事情:

  1. 对于UserRegistrationsController, render_with_scope不再存在,只需使用render :new
  2. 创build函数中的第一行,同样在UserRegistrationsController中不起作用。 只是尝试使用

     # 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 

而不是简单的build_resource 。 一些大规模分配的错误在未改变的时候出现了。

  1. 如果你想在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 

我按照上面的指示,发现了一些差距,而且说明在实施时已经过时了。

所以,经过一整天的努力之后,让我跟你分享一下我的工作原理 – 希望能为你节省几个小时的汗水和泪水

  • 首先,如果您不熟悉RoR多态性,请参阅本指南: http : //astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/将有devise和用户模型安装,你将能够开始工作。

  • 之后,请按照Vapire伟大的教程来生成所有partails的视图。

  • 我发现最令人沮丧的是,使用最新版本的Devise(3.5.1),RegistrationController拒绝工作。 下面是将使其重新工作的代码:

     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 
  • 并添加这些覆盖,以便redirect将正常工作:

     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 
  • 为了deviseFlash消息将继续工作,你需要更新config/locales/devise.en.yml而不是由UserRegistraionsControlloer覆盖的RegistraionsControlloer,你需要做的就是添加这个新的部分:

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

希望能为你节省几个小时。

Interesting Posts