[英]Multiple user models with Ruby On Rails and devise to have separate registration but one common login
[英]Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route
首先,我在 Google 和 Yahoo 上进行了大量搜索,我发现了一些与我类似的主题的回复,但它们都没有真正涵盖我需要知道的内容。
我的应用程序中有几个用户模型,现在是客户、设计师、零售商,而且似乎还有更多。 他们都有不同的数据存储在他们的表中,以及他们被允许或不允许的站点上的几个区域。 所以我想采用 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
对于注册,我为每种不同的用户类型定制了视图,我的路由设置如下:
devise_for :customers, :class_name => 'User'
devise_for :designers, :class_name => 'User'
devise_for :retailers, :class_name => 'User'
现在,注册控制器仍然是标准的(即“设计/注册”),但我想,因为我有不同的数据要存储在不同的模型中,所以我也必须自定义这种行为!?
但是通过这个设置,我得到了像customer_signed_in?
这样的助手customer_signed_in?
和designer_signed_in?
,但我真正需要的是像user_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
所以 User 模型有一个简单的多态关联,定义它是 Customer 还是 Designer。
我要做的下一件事是使用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" %>
对于每个用户类型,我创建了一个单独的部分,其中包含该特定用户类型的自定义字段,即 Designer --> _designer_fields.html
<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>
然后我设置了设计路线以在注册时使用自定义控制器
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
这一切的基本作用是,控制器根据user_type
参数确定必须创建哪种用户类型,该参数由视图中的隐藏字段传递给控制器的create
方法,该字段通过 URL 中的简单 GET-param 使用该参数。
例如:
如果您转到/users/sign_up?user[user_type]=designer
您可以创建一个设计器。
如果您转到/users/sign_up?user[user_type]=customer
您可以创建一个 Customer。
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 哈希。
就是这样了。 在这里和那里稍微调整一下,我就让它以一种非常通用的方式工作,这很容易扩展到许多其他共享公共 User 表的 User 模型。
希望有人觉得它有用。
我没有设法找到对接受的答案发表评论的任何方式,所以我只想写在这里。
有一些事情与公认的答案状态不完全一样,可能是因为它已经过时了。
无论如何,我必须自己解决一些问题:
render_with_scope
不再存在,只需使用render :new
create 函数中的第一行,同样在 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
。 未更改时会出现一些批量分配错误。
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/跟随它后你将安装设计和用户用户模型,您将能够开始工作。
之后,请按照 Vapire 的精彩教程生成包含所有部分的视图。
我发现最令人沮丧的是,由于使用最新版本的 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
并添加这些覆盖,以便重定向可以正常工作:
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
为了使设计 flash 消息继续工作,您需要更新config/locales/devise.en.yml
而不是 UserRegistraionsControlloer 覆盖的 RegistraionsControlloer 您需要做的就是添加这个新部分:
user_registrations: signed_up: 'Welcome! You have signed up successfully.'
希望这会为你们节省几个小时。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.