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

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

问题描述

首先,我用 Google 和 Yahoo 进行了深入搜索,我发现了一些关于我的主题的回复,但它们都没有真正涵盖我需要知道的内容.

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.

我的应用中有多个用户模型,现在是客户、设计师、零售商,而且似乎还有更多.他们都有不同的数据存储在他们的表和网站上他们被允许或不允许的几个区域中.所以我想去 devise+CanCan 的方式,并尝试多态关联的运气,所以我得到了以下模型设置:

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!?

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

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.

我还想要一个像 new_user_session_path 这样的路由助手,而不是几个 new_*type*_session_path 等等.事实上,我需要与众不同的只是注册过程......

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?

推荐答案

好的,所以我解决了它并得出了以下解决方案.
我需要稍微修饰一下设计,但这并不复杂.

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.

用户模型

# 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

因此,用户模型具有简单的多态关联,定义它是客户还是设计师.
接下来我要做的是使用 rails g devise:views 生成设计视图,作为我的应用程序的一部分.由于我只需要自定义注册,因此我只保留了 app/views/devise/registrations 文件夹并删除了其余文件夹.

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.

然后我为新注册定制了注册视图,在你生成它们之后可以在 app/views/devise/registrations/new.html.erb 中找到它.

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" %>

对于每个用户类型,我创建了一个单独的部分,其中包含该特定用户类型的自定义字段,即 Designer --> _designer_fields.html

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' }

然后我生成了一个控制器来处理定制的注册过程,从 Devise::RegistrationsController 中的 create 方法复制原始源代码并修改它以工作我的方式(不要忘记将您的视图文件移动到适当的文件夹,在我的情况下 app/views/user_registrations

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

这一切的基本作用是控制器根据 user_type 参数确定必须创建哪个用户类型,该参数通过隐藏字段传递给控制器​​的 create 方法通过 URL 中的简单 GET 参数使用参数的视图.

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.

例如:
如果您转到 /users/sign_up?user[user_type]=designer,您可以创建一个 Designer.
如果你去 /users/sign_up?user[user_type]=customer 你可以创建一个客户.

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.

my_devise_error_messages! 方法是一个辅助方法,它还基于原始 devise_error_messages! 方法处理关联模型中的验证错误

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

更新:

为了能够支持 /designer/sign_up/customer/sign_up 等路由,您可以在路由文件中执行以下操作:

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' }

在内部路由语法中未使用的任何参数都会传递给 params 哈希.所以 :user 被传递给 params 散列.

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.

希望有人觉得它有用.

这篇关于使用 Ruby On Rails 的多个用户模型,并设计有单独的注册路线,但只有一个共同的登录路线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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