簡體   English   中英

使用 Ruby On Rails 的多個用戶模型,並設計為具有單獨的注冊路徑但有一個通用的登錄路徑

[英]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::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

這一切的基本作用是,控制器根據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 模型。

希望有人覺得它有用。

我沒有設法找到對接受的答案發表評論的任何方式,所以我只想寫在這里。

有一些事情與公認的答案狀態不完全一樣,可能是因為它已經過時了。

無論如何,我必須自己解決一些問題:

  1. 對於 UserRegistrationsController, render_with_scope不再存在,只需使用render :new
  2. 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 未更改時會出現一些批量分配錯誤。

  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/跟隨它后你將安裝設計和用戶用戶模型,您將能夠開始工作。

  • 之后,請按照 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM