具有Ruby On Rails的多个用户模型,并设计具有单独的注册路由,但具有一个常见的登录路由 [英] Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route

查看:85
本文介绍了具有Ruby On Rails的多个用户模型,并设计具有单独的注册路由,但具有一个常见的登录路由的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我和Google和雅虎搜索得很激烈,我发现了几个关于我的话题的回复,但都没有真正涵盖我需要知道的内容。



我的应用程序中有几个用户模型,现在是客户,设计师,零售商,似乎还有更多的未来。他们都有不同的数据存储在他们的表和允许或不允许的网站上的几个区域。所以我想要去设计+ 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆