设计和多个“用户”模型


73

我正在使用Rails 3.2和devise 2.0,而我对Rails还是很陌生。

要求

我想要实现以下目标:

  • 具有2个或更多“用户”模型,例如。会员,客户,管理员
  • 所有型号共享一些必填字段(例如,电子邮件和密码)
  • 每个模型可能都有一些唯一的字段(例如,公司仅针对客户)
  • 某些字段可能是共享的,但没有相同的验证(例如,“名称”对于“客户”是必需的,对于“成员”是可选的)
  • 在注册过程中必须填写所有字段,因此表格是不同的
  • 登录表单应该唯一

可能的解决方案

我搜索并搜索了StackOverflow很长时间了,但是对我来说似乎没有什么合适的(我是Java人士,很抱歉:),现在我很困惑。提出了两种解决方案:

单一设计用户

这是最常见的答案。只需创建默认的devise用户并在Member-> User和Customer-> User之间创建关系。我在这里关心的是如何为每种型号实现定制的注册过程?我尝试了不同的事情,但一切都糟透了!

多个设计用户

这解决了自定义注册过程,对我来说似乎是正确的,但是唯一的登录表单是阻止程序。我找到了关于SO的答案(Devise-从两个模型登录),该答案建议覆盖Devise :: Models :: Authenticatable.find_for_authentication(conditions)。这似乎很复杂(?),并且由于我是Rails的新手,所以我想知道是否可行?

谢谢你的建议!

Answers:


69

欢迎Java家伙上的=),希望您会喜欢Rails的世界。简单来说,要解决您的问题,您有两种解决方案:

  1. 为每个用户在数据库和相应的模型中创建一个表。
  2. 在数据库中创建一个表,并为每个用户类型创建一个模型。这称为单表继承(STI)。

选择哪一个?这取决于角色的共同属性。如果它们几乎是通用的(例如,它们都有一个名称,电子邮件,手机等),并且一些属性不同,那么我强烈建议您使用STI解决方案。

STI怎么做?1.使用命令简单地创建devise用户模型和表rails generate devise User 。2 type.使用迁移将名为string数据类型的列添加到数据库中的用户表。3.为每个用户类型创建一个模型(例如rails g model admin)4.使Admin类从用户模型继承

class Admin < User
end

就是这样,您完成了=)... Yupeee

要创建管理员,请运行以下命令Admin.create(...),其中点是管理员属性,例如电子邮件,姓名,...

我认为这个问题也可以帮助您


1
我会的,谢谢:)到目前为止,对于一个新来者来说我一直很成功……实际上,客户有很多属性,而会员只有几个属性。您的解决方案是“单用户”还是“多用户”,我不清楚。如果是那样的话,我需要config.scoped_views = true吗?
2012年

它们有很多共同点吗?如果是这样,那么我强烈建议您使用STI解决方案,如果否,则为每种用户类型(您的情况是客户和会员)在数据库中创建一个表并建立一个相应的模型。无论如何,您都可以选择适合您问题的解决方案。
mohamagdy 2012年

据我了解,这是单用户一个(数据库中存储所有用户成员和客户的单个用户表)
mohamagdy 2012年

2
谢谢!旁注:我认为rails g模型也会为该模型创建迁移文件。在这种情况下,我们不会为admin创建新表,所以我认为您只需要在model文件夹中创建一个名为admin.rb的文件即可,而不是使用rails生成器。
strohy1210 '16

27

在尝试了使用单一用户模型的各种方法之后,我和您的处境相似,该模型属于多态角色。这似乎是实现单点登录的最简单方法。

用户模型将仅包含特定于登录的信息。

角色模型将存储特定于每个角色的字段以及特定于该角色的其他关联。

将通过单独的控制器为每种用户类型(角色)自定义新注册,然后为用户构建嵌套属性。

class User < ActiveRecord::Base
    #... devise code ...
    belongs_to :role, :polymorphic => true
end

class Member < ActiveRecord::Base
    attr_accessible :name, :tel, :city  #etc etc....
    attr_accessible :user_attributes #this is needed for nested attributes assignment

    #model specific associations like  
    has_many :resumes

    has_one :user, :as => :role, dependent: :destroy
    accepts_nested_attributes_for :user
end 

路线-只是会员模型的常规内容。

resources :members
#maybe make a new path for New signups, but for now its new_member_path

控制器-您必须拥有build_user的嵌套属性

#controllers/members_controller.rb
def new
    @member = Member.new
    @member.build_user
end

def create
    #... standard controller stuff
end

views / members / new.html.erb

<h2>Sign up for new members!</h2>
<%= simple_form_for @member do |f| %>

    # user fields
    <%= f.fields_for :user do |u| %>
      <%= u.input :email, :required => true, :autofocus => true %>
      <%= u.input :password, :required => true %>
      <%= u.input :password_confirmation, :required => true %>
    <% end %>

    # member fields
    <%= f.input :name %>
    <%= f.input :tel %>
    <%= f.input :city %>

    <%= f.button :submit, "Sign up" %>
<% end %>

我想指出,不需要nested_form gem;因为要求是用户只能属于一种类型的角色。


我在项目中使用了类似的方法,但是在注册后,它不维护用户会话,因此它始终显示不应登录的登录页面。你有什么想法吗?
Ahmad hamza 2014年

1
从数据库的角度来看,更适合has_one在用户模型和belongs_to成员模型中使用,这样就可以避免多态关系。您为什么有相反的理由?
collimarco

@collimarco合法问题,但polymorphic仅适用于belongs_to。还有其他有关“反向多态”的解决方案,但似乎超出了范围。
tw airball'9

18

我找到了路要走,到目前为止我对此很满意。我会在这里为其他人描述。

我参加了单个“用户”课程。我的问题是为每个伪模型实现定制的注册过程。

型号/user.rb:

class User < ActiveRecord::Base
  devise :confirmable,
       :database_authenticatable,
       :lockable,
       :recoverable,
       :registerable,
       :rememberable,
       :timeoutable,
       :trackable,
       :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me, :role

  as_enum :role, [:administrator, :client, :member]
  validates_as_enum :role
  ## Rails 4+ for the above two lines
  # enum role: [:administrator, :client, :member]

end

然后,我对http://railscasts.com/episodes/217-multistep-formshttp://pastie.org/1084054进行了修改,使其具有两个具有覆盖控制器的注册路径:

config / routes.rb:

get  'users/sign_up'   => 'users/registrations#new',        :as => 'new_user_registration'

get  'clients/sign_up' => 'users/registrations#new_client', :as => 'new_client_registration'
post 'clients/sign_up' => 'users/registrations#create',     :as => 'client_registration'

get  'members/sign_up' => 'users/registrations#new_member', :as => 'new_member_registration'
post 'members/sign_up' => 'users/registrations#create',     :as => 'member_registration'

控制器/用户/registrations_controller.rb:

我创建了一个向导类,该向导类知道在每个步骤都要验证的字段

class Users::RegistrationsController < Devise::RegistrationsController

    # GET /resource/sign_up
    def new
        session[:user] ||= { }
        @user = build_resource(session[:user])
        @wizard = ClientRegistrationWizard.new(current_step)

        respond_with @user
    end

    # GET /clients/sign_up
    def new_client
        session[:user] ||= { }
        session[:user]['role'] = :client
        @user = build_resource(session[:user])
        @wizard = ClientRegistrationWizard.new(current_step)

        render 'new_client'
    end

    # GET /members/sign_up
    def new_member
      # same
    end

    # POST /clients/sign_up
    # POST /members/sign_up
    def create
        session[:user].deep_merge!(params[:user]) if params[:user]
        @user = build_resource(session[:user])
        @wizard = ClientRegistrationWizard.new(current_step)

        if params[:previous_button]
            @wizard.previous
        elsif @user.valid?(@wizard)
            if @wizard.last_step?
                @user.save if @user.valid?
            else
                @wizard.next
            end
        end

        session[:registration_current_step] = @wizard.current_step

        if @user.new_record?
            clean_up_passwords @user
            render 'new_client'
        else
            #session[:registration_current_step] = nil
            session[:user_params] = nil

            if @user.active_for_authentication?
                set_flash_message :notice, :signed_up if is_navigational_format?
                sign_in(:user, @user)
                respond_with @user, :location => after_sign_up_path_for(@user)
            else
                set_flash_message :notice, :"signed_up_but_#{@user.inactive_message}" if is_navigational_format?
                expire_session_data_after_sign_in!
                respond_with @user, :location => after_inactive_sign_up_path_for(@user)
            end
        end

    end

    private

    def current_step
        if params[:wizard] && params[:wizard][:current_step]
            return params[:wizard][:current_step]
        end
        return session[:registration_current_step]
    end

end

我的看法是:

  • new.rb
  • new_client.rb 根据向导步骤包括部分:
    • _new_client_1.rb
    • _new_client_2.rb
  • new_member.rb 根据向导步骤包括部分:
    • _new_member_1.rb
    • _new_member_2.rb

6

那怎么了 只需运行rails g devise:views [model_name],自定义每个注册表格config/initializer/devise.rb即可config.scoped_views = true


1
谢谢,但是AFAIK无法解决单一登录表单,对吗?
2012年

1
抱歉,我只是阅读“这样做之后,您将能够基于“用户/会话/新”和“管理员/会话/新”等角色获得视图。如果在范围内未找到任何视图,Devise将使用默认视图位于“ devise / sessions / new”。您还可以使用生成器生成作用域视图:”,这样应该可以工作……
ddidier 2012年

我试图走那条路。我现在有一些针对会员和客户的路线,有些视图是共享的,有些不是。但是单一登录表单问题仍然存在...如何使用唯一表单来认证成员客户?
2012年

您是否打开的scoped_views选项config/initializers/devise.rb?默认情况下,它是disableb以提高性能。
Hauleth

@c_inconnu发现此链接gist.github.com/jeremyw/5319386共享多个模型的登录表单
Jude Calimbas 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.