3

I'm amazed at how hard it is to still find a definitive answer to this: it seems so common that I must be looking at it all wrong.

We have Users, who's authorization roles run something like ROLES = %w[admin moderator teacher student banned]

It's commonly recommended to use a ROLES field and Single Table Inheritance to (as here)

class User < ActiveRecord::Base
end

class Student < User
end

class Teacher < User
end

But this puts all the data in one table. What if we have a good bit of data unique to each user type?

student
  year:integer
  request_id:integer
  portfolio_id:integer
  status:string
  ...

teachers
  user_id:integer
  district:string
  school:string
  subject1:string
  subject2:string
  specialty:string
  bio:text
  ...

STI gives things like student.specialty and teacher.portfolio_id, which we don't want and would have to block.

The Ruby Way suggests Abstract Base Model Class to handle separate tables:

class User < ActiveRecord::Base
  self.abstract = true
end

class Student < User
end

class Teacher < User
end

Which will allow unique tables for both Student and Teacher. However, he warns, User.find(:all) won't work. Plus, there's the common attributes we wanted, which was the whole point of a User model:

User
  username:string
  email:string
  password:string
  role:string

Since there's no User table, there's no common attributes?

Various other answers hint at using :polymorphic => true, :class_name => 'User', or as: but all explained more like adding a Comments to both a post and an image. Which doesn't seem a good parallel.

I seem to recall at least one language (and maybe a couple OODB's) which simply used the IS-A relationship to inherit attributes.

What's the RAILS way?

Community
  • 1
  • 1
Ed Jones
  • 653
  • 1
  • 4
  • 19

1 Answers1

0

I think STI is the wrong to do here. You have too many things going on that would be shoved into one table.

I would rather do a generic User model, holding the common stuff like e-mail, name and for each user type have a separate model (and table). So, teachers and students would reference users, but have their own fields.

Roles should also be in their own table separate from users.

Reference the user record like this:

class Teacher < AR::Base
  belongs_to :user
end
class Student < AR::Base
  belongs_to :user
end
class User < AR::Base
  has_one :teacher
  has_one :student
end
Srdjan Pejic
  • 8,152
  • 2
  • 28
  • 24
  • user_id in the teachers and students tables. Editing the answer to show you the code. – Srdjan Pejic Jan 25 '12 at 17:37
  • I implemented this, and did lots of cool things. @teacher.user.avatar seems awkward, but doable. My new challenge is how to put these into one "New Teacher" form. <%= form_for(@teacher) do |f| %> – Ed Jones Jan 27 '12 at 00:46
  • To address the awkwardness, you can use the delegate method, http://apidock.com/rails/v3.1.0/Module/delegate. It'll let you do things like teacher.avatar or teacher.user_avatar, easily. – Srdjan Pejic Jan 27 '12 at 02:26
  • As for the form stuff, you can make a partial out of the user forms, and reference that partial inside the teacher form_for. You'll need to do manual updating of the user record. – Srdjan Pejic Jan 27 '12 at 02:27
  • delegate method: sweet. I wonder why teacher.avatar? doesn't work, though? – Ed Jones Jan 27 '12 at 15:10
  • You have to delegate it separately from :avatar, I'm guessing – Srdjan Pejic Jan 27 '12 at 17:00