具有Ruby On Rails的多个用户模型,并设计具有单独的注册路由,但具有一个常见的登录路由 [英] Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route
问题描述
我的应用程序中有几个用户模型,现在是客户,设计师,零售商,似乎还有更多的未来。他们都有不同的数据存储在他们的表和允许或不允许的网站上的几个区域。所以我想要去设计+ CanCan的方式,并尝试我的运气与多态关联,所以我得到以下模型设置:
类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零售商< AR
has_one:user,:as => :loginable
end
对于注册,我已经为每个不同的用户类型定制了视图我的路线设置如下:
devise_for:customers,:class_name => 'User'
devise_for:designer,: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
等等。事实上,我需要不同的是注册过程...
所以我想知道这是为了这个问题吗?或者有一个更好/更容易/更少/少量的必须定制的解决方案?
提前感谢,
Robert
好的,所以我通过了以下的解决方案。
我需要设计一些设计,但是这并不复杂。
用户模型
#user .rb
class User< ActiveRecord :: Base
devise:database_authenticatable,:可注册,
:可恢复,可记忆,可追踪,可验证
attr_accessible:电子邮件,密码,密码确认,: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
所以用户模型有一个简单的多态关联,定义它是否是客户或设计师。
接下来我要做的是用 rails g devise:views
生成设计视图,作为我应用程序的一部分。由于我只需要注册才能定制,所以我只保留了 app / views / devise / registrationments / / code>文件夹,并删除其余的。
然后,我自定义新注册的注册视图,可以在生成它们后在 app / views / devise / registrations / new.html.erb
中找到。
< h2>注册< / h2>
<%
#自定义代码开始
params [:user] [:user_type] || ='customer'
如果[客户,设计师]包括? 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如果resource.rolable.nil?
#定制代码结束
%>
<%= form_for(resource,:as => resource_name,:url => registration_path(resource_name))do | f | %GT;
<%= my_devise_error_messages! #个自定义代码%>
< 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>
<%#个自定义代码开始%>
<%= fields_for resource.rolable do | rf | %GT;
<%render:partial => #{child_class_name.underscore} _fields,:locals => {:f => rf}%>
<%end%>
<%= hidden_field:user,:user_type,:value => user_type%>
<%#个自定义代码结束%>
< div><%= f.submit注册%>< / div>
<%end%>
<%= render:partial => devise / shared / links%>
对于每个用户类型,我创建了一个单独的部分与该特定用户类型的自定义字段,即设计器 - > _designer_fields.html
< div><% = f.label:label_name%>< br />
<%= f.text_field:label_name%>< / div>
然后我设置了devise的路由,以便在注册时使用自定义控制器
devise_for:users,:controllers => {:registrations => 'UserRegistrations'}
然后我生成一个控制器来处理定制的注册过程,复制了原始的源代码从创建
方法在 Devise :: RegistrationsController
中,并修改它以我的方式工作(不要忘记移动您的视图文件到相应的文件夹,在我的情况下 app / views / user_registrations
class UserRegistrationsController< Devise :: RegistrationsController
def create
build_resource
#定制代码开始
#根据给定的用户类型
child_class = params [:user] [:user_type] .camelize.constantize
resource.rolable = child_class.new(params [child_class.to_s.underscore.to_sym])
#首先检查子实例是否有效
#原因如果是,父实例也是有效的
#这一切都被一次性保存
valid = resource.valid?
valid = resource.rolable.valid? &安培;&安培;有效
#定制代码结束
如果有效&&& resource.save#custom code
如果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
参数通过简单的GET-param使用该参数的视图中的隐藏字段传递给控件的创建
在
例如:
如果你去 / users / sign_up?user [user_type] =设计师
你可以创建一个设计器。
如果你去 / users / sign_up?user [user_type] = customer
创建一个客户。
my_devise_error_messages!
方法是一个辅助方法,它还处理关联模型中的验证错误,基于原始的 devise_error_messages!
m方法
module ApplicationHelper
def my_devise_error_messages!
return如果resource.errors.empty? &安培;&安培; resource.rolable.errors.empty?
message = rolable_messages =
if!resource.errors.empty?
message = 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
message = 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哈希。所以:用户
被传递到params哈希。
所以...就是这样。在这里和我有一点tweeking,我得到它的工作在一个相当普遍的方式,这是很容易扩展与许多其他用户模型共享一个普通的用户表。
希望有人找到它有用的。
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.
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:
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?
and designer_signed_in?
, but what I'd really need is a general helper like 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. 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???
Thanks in advance,
Robert
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.
The next thing I had to do was to generate the devise views with rails g devise:views
to be part of my application. Since I only needed the registration to be customized I kept the app/views/devise/registrations
folder only and removed the rest.
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.
<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>
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
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.
For example:
If you go to /users/sign_up?user[user_type]=designer
you can create a Designer.
If you go to /users/sign_up?user[user_type]=customer
you can create a Customer.
The 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!
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:
# 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. So :user
gets passed to the params hash.
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.
Hope someone finds it useful.
这篇关于具有Ruby On Rails的多个用户模型,并设计具有单独的注册路由,但具有一个常见的登录路由的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!