-->

Single Table Inheritance to refer to a child class

2020-03-06 18:41发布

问题:

I am using Ruby on Rails 3 and I implemented a working Single Table Inheritance like the following:

class User < ActiveRecord::Base

  # Schema Information
  #
  # Table name: User
  #
  # id              : integer
  # type            : string
  # children_user_id: integer

  ...
end

class UserAdmin < User

  # Schema Information
  #
  # Table name: UserAdmin
  #
  # id             : integer
  # special_field1 : string
  # special_field2 : string
  # ...

  ...
end

class UserCommon < User

  # Schema Information
  #
  # Table name: UserCommon
  #
  # id             : integer
  # another_field1 : string
  # another_field2 : string
  # ...

  ...
end

I would like to know if, on creating an UserAdmin record (or an UserCommon record) in the User table running the following

UserAdmin.create(:children_user_id => "1")
# or UserCommon.create(:children_user_id => "1")

it is possible to "automatically" create in someway (possibly the "Rails Way"!) a new record also in the UserAdmin table (or UserCommon table) that has its own fields (at the database level, those fields are columns). I would like to do that in order to "better handle" UserAdmin because this class has different and more attributes of the UserCommon class.

If it is possible, how can I do that (maybe using association model statements, callbacks, polymorphism, ...)? Do you have some advice on this issue?

回答1:

The thing with single table inheritance is it is based on a single table model, not surprisingly, so you can't have different classes using different tables.

Typically the "Rails Way" is to bundle together all the possible fields that are required into a single table and use STI to handle the data mapping and validation issues for you. There are limits on what it can hide from the application, though, as generally a field being defined means it's usable by any of the classes bound to that table. Most people do not consider this to be an issue.

What you probably want to do is make a record that's joined in depending on the user type, for instance:

class User < ActiveRecord::Base
end

class AdminUser < User
  belongs_to :admin_profile
end

class CommonUser < User
  belongs_to :common_profile
end

This would require the users table having an admin_profile_id and common_profile_id column where the admin_profiles and common_profiles tables contain the required additional fields.

The attributes in these tables can be mapped back into the base class using the delegate method as required.

Moving the extra fields into a separate table may help compartmentalize things, but it also means reads will be slower because of the required join and the possibility of inconsistent records, due to one part being missing or out of date, is increased.

Generally you are okay loading all of the user-related fields into a single table even if many of these fields are not frequently used. The storage cost of a NULL field is typically low in the scheme of things and unless there are hundreds of these fields, the additional complexity exposed to the developer is minimal, a smaller price to pay than to have to create and reference records in pairs constantly.