用户的属性取决于用户的类型:在Rails中实现的最佳方式

时间:2015-08-14 13:14:40

标签: ruby-on-rails ruby-on-rails-4 model nested

我有一个基本信息和活动特定信息的用户。 假设用户是足球运动员,其简介将如下所示:

User.rb #common infos
  firstname
  lastname
  email
  sex
  address_id

Footballer.rb #or something accurate
  position
  team
  age
  weight
  shoe_size
  ...

但是,如果用户是Boxer,其个人资料将由以下内容组成:

#User.rb #same as footballer
  firstname
  lastname
  email
  sex
  address_id
  avatar

#Boxer.rb
  weight
  handicap
  league
  ...

将此逻辑集成到rails中的最佳方法是什么? 该个人资料将在users#show.html中呈现 我的应用程序具有轻松注册(仅限电子邮件)和由多个配置文件组成的团队,因此反转逻辑(通过创建Footballer has_one :basic_infosBoxer has_one :basic_infos)似乎很复杂,这需要在每个模型上调用基本信息突出显示(如完整的名字和头像)

我坚持这一点,所以任何帮助都会非常受欢迎

2 个答案:

答案 0 :(得分:2)

我在这里看到以下选项。

<强> 1。单表继承(STI

只需将所有字段转储到一个表格中,然后制作class Boxer < Userclass Footballer < User

优点:实施简单。

缺点:表变得臃肿。字段是共享的,即您的足球运动员将拥有weight和其他领域,反之亦然。

<强> 2。将基本信息移至另一个表

这是您已经概述的选项。

优点:干净的桌子,适当的田地分离。

缺点:复杂的实施。您必须确保每个实例始终只有一个基本配置文件。因此,您必须注意关联宏的:delete_all/:destroy_all选项,也可能before_create/after_destroy

以下是有关如何在代码中对其进行整理的示例:

# models

class Profile < ActiveRecord::Base
  # you need to create
  #   t.integer :user_id
  #   t.string  :user_type
  # in a migration for 'profiles' table

  belongs_to :user, polymorphic: true
end

class User < ActiveRecord::Base
  # This will disable STI
  self.abstract_class = true
  has_one :profile, as: :user, dependent: :destroy

  def some_common_user_method
    # ...
  end

  def pick_profile
    self.profile && return self.profile
    if self.persisted?
      self.create_profile
    else
      self.build_profile
    end
  end
end

class Footballer < User
end

# controllers

class UsersController < ApplicationController
  before_action :set_klass
  before_action :set_user, except: [:index, :create]

  # ...

  def create
    @user = @klass.new
    @user.pick_profile
    # etc...
  end

  def destroy
    @user.destroy
  end

  private

  def set_klass
    # white-list search for class
    @klass = User.descendants.find{|k| k == params[:type].camelize.constantize}
  end

  def set_user
    @user = @klass.find(params[:id])
  end
end

# routes

# for URLs like /users/1?type=footballer
resources :users

# for URLs like /users/footballer/1
resources :users, path_prefix: '/users/:type'

第3。添加serialized field

只需添加details字段,其中包含特定运动员详细信息的JSON / YAML,并将公共字段作为单独的数据库字段。

优点:实施简单。数据库结构也很简单。

缺点:您无法对特定运动员的领域进行有效查询,即您需要获取每条记录以了解其详细信息。

<强> 4。使用Postgresql-specific serialized columns

同上,只有没有&#34;缺点&#34;部分。您将能够对序列化数据进行有效查询。

缺点:您将需要学习如何使用这些字段并查询它们。 MySQL无法实现。

答案 1 :(得分:1)

看起来非常适合STI。关于单表继承的Here's a solid tutorial

基本上,您可以为从父类# app/models/user.rb class User < ActiveRecord::Base # validations, associations, methods, etc. end # app/models/footballer.rb class Footballer < User # custom validations, methods, etc. end # app/models/boxer.rb class Boxer < User # custom validations, methods, etc. end 继承的每个不同用户配置文件创建子类。

# == Schema Info
#
# Table name: users
#
#  id                  :integer(11)    not null, primary key
#  firstname           :string(255)
#  lastname            :string(255)
#  email               :string(255)
#  sex                 :string(255)
#  address_id          :integer(11)
#  avatar              :string(255)
#  weight              :integer(11)
#  handicap            :string(255)
#  league              :string(255)
#  position            :string(255)
#  team                :string(255)
#  age                 :integer(11)
#  weight              :float
#  shoe_size           :float
#  weight              :float
#  handicap            :float
#  league              :string(255)
#  type                :string(255)
#

class User < ActiveRecord::Base
  ...
end

我建议使用annotate_models gem添加一条注释,总结每个模型中的当前架构(以及其他文件)。它看起来像这样:

User

注意Footballer模型注释如何包含所有列,包括Boxertype子类的列。这是因为您需要将所有列添加到users表,包括名为{{1}}的保留Rails列,它将是要创建的每个记录的子类的名称。

使用STI将为您提供处理特定于子类的逻辑的灵活性。我建议你查看我链接的教程,或者在Rails中有关STI的任何其他文档。