views:

154

answers:

2

I have a User model in my app, which I would like to store basic user information, such as email address, first and last name, phone number, etc.

I also have many different types of users in my system, including sales agents, clients, guests, etc.

I would like to be able to use the same User model as a base for all the others, so that I don't have to include all the fields for all the related roles in one model, and can delegate as necessary (cutting down on duplicate database fields as well as providing easy mobility from changing one user of one type to another).

So, what I'd like is this:

User
-- first name
-- last name
-- email
--> is a "client", so
---- client field 1
---- client field 2
---- client field 3

User
-- first name
-- last name
-- email
--> is a "sales agent", so
---- sales agent field 1
---- sales agent field 2
---- sales agent field 3

and so on...

In addition, when a new user signs up, I want that new user to automatically be assigned the role of "client" (I'm talking about database fields here, not authorization, though I hope to eventually include this logic in my user authorization as well). I have a multi-step signup wizard I'm trying to build with wizardly. The first step is easy, since I'm simply calling the fields included in the base User model (such as first_name and email), but the second step is trickier since it should be calling in fields from the associated model (like--per my example above--the model client with fields client_field_1 or client_field_2, as if those fields were part of User).

Does that make sense? Let me know if that wasn't clear at all, and I'll try to explain it in a different way.

Can anyone help me with this? How would I do this?

A: 

It looks like you have two reasonable approaches here, but it will depend on the nuances of your requirements.

You can use Single Table Inheritance (STI) to do what you want, where User is only the base class for others named SalesAgent or Client and so forth. Each of these sub-classes may define their own validations. All you need for this to work is a string column called "type" and ActiveRecord will do the rest:

class User < ActiveRecord::Base
end

class Agent < User
end

The alternative is to have a number of free-form fields where you store various bits of related data and simply interpret them differently at run-time. You may structure it like this:

class User < ActiveRecord::Base
  has_one :agent_role,
    :dependent => :destroy
end

class AgentRole < ActiveRecord::Base
  belongs_to :user

   # Represents the agent-specific role fields
end

That would have the advantage of allowing for multiple roles, and if you use has_many, then multiple roles of the same type.

tadman
I think STI was exactly what I was looking for. I'll experiment with it and see if it fits... Clarification, though: when you say that the latter method has "the advantage of allowing multiple roles"... what exactly do you mean here? Couldn't I still do `class Agent < User` and `class Client < User` ?? Still a bit confused.
neezer
On further investigation, it looks like Polymorphic Association would be a better fit for me. See this article: http://www.andygoh.net/2008/06/19/ruby-on-rails-polymorphic-association/ Still having trouble wrapping my head around actually using these associations, though... unfortunately, Ryan Bate's screencast on the subject is almost the exact reverse of what I want to accomplish (whereas he has a model that belongs to many other models, I want a model that has many other models)...?
neezer
What I meant by the multiple roles comment was that a single User could have multiple roles associated with it, whereas with STI a user assumes a single role. Polymorphic associations can be good for some circumstances, but since they are impossible to enforce with foreign key constraints, and because joins with them always require a compound key, they are best left on the fringes of your database design. When part of the core they can be a nuisance. STI is "database friendly" since the type information is only relevant to the ORM, not the DB.
tadman
A: 

STI is probably a good fit for your requirements, as suggested by tadman, if you are using ActiveRecord (from Rails 3, it is easy to change ORM). The basic information is available on the AR documentation page, but here is some extra information w.r.t. your target:

  • Define one model per file. Otherwise there are some initialization troubles. Assuming Client inherits from User all in a single file, Client objects cannot be created as long as a User constructor has not been called once. One file per model circumvents the problem.
  • All attributes through the hierarchy are defined one-shot in the top class. This is for performance issues, but it seems disturbing many people in blog posts. In short, the Ruby code is object-oriented and encapsulates properly the attributes. The DB contains everything in a single table, with the extra "type" column to distinguish where they belong in the hierarchy. This is only a "trick" to represent inheritance trees in relational databases. We must be aware that the ORM mapping is not straightforward. The image on this site from Martin Fowler may help understand the situation.

In addition, you would like any new user to be a client, where Client inherits from User. To do so, you may simply instantiate any new user as a client. In your controller for creating users:

user = Client.new
# Do something to user
user.save
#=> <Client id: 1, name: "Michael Bolton", email: "[email protected]", created_at: "2010-05-30 03:27:39", updated_at: "2010-05-30 03:27:39">

All the above is still valid with Rails 3 when using ActiveRecords.

Eric Platon