Single table inheritance and where to use it in Rails
Asked Answered
C

4

53

I am stuck in a weird Design problem,

I am working on a two type of profiles Models,

  • User profile (belongs to User)
  • others that are maintain in-site as "bots" (doesn't belong to anybody)

The typical OO behaviour of these two types of Profiles is same but only the important attributes/properties are common ( the very important ones 5-6 in number), others properties like "interests etc"(almost 10-15 properties) are not there for bot profiles

The coder who worked on this earlier created separate models/Controllers for bot profiles / User profiles which creates a lot of redundancy everywhere and also as expected hard to maintain, write tests etc.I wanted to DRY this up, atleast to solve some/all of these redundancy problems.

Somebody suggested Single Table Inheritance as a solution

Somebody suggested Use Polymorphic Associations instead.

what is the better approach. When do we actually use STI?

My own thought was STI is used best when attributes are same for Models and they differ in behaviour.

Thoughts about what can I do?

Chimene answered 17/2, 2009 at 6:2 Comment(0)
M
35

Characterising STI as mostly useful when attributes are the same but behaviour differs is "about right", but possibly a little limiting. I like to use STI when there is, as the name suggests, a clear OO-style inheritance relationship, rather than the database-style relationship between objects of different types.

If there is common code between bots and users, I'd say STI sounds like a winner. If there's just some common attributes, it's probably less applicable but still worth having a go at.

I'm a pretty experimental person, so my recommendation is to give it a go. Branch your code and refactor the models into an STI relationship. See if it really does dry things up, or just swaps one set of headaches for some other problem.

One thing I think you won't see much benefit from is drying up your controllers. In my experience, STI models don't often translate into similarly related controllers. But that would be something else to experiment with. Sometimes there's a win, sometimes there isn't.

Monzon answered 17/2, 2009 at 6:26 Comment(5)
I am going to experiment with STI, I think. But what are problems that I might face if I have only a few common attributes ?Chimene
Another way to deal with the controllers is to subclass them, like the models... refactor to make common methods shareable between them.Bruch
@Andrew: IME, that doesn't work nearly as well as subclassing for models, except in the (relatively rare) bare-bones scaffoldy CRUD case.Monzon
@rishavrastogi If there's lots of different attributes, the chances of the semantics of the attributes and actions being the same is reduced. If you've got an obvious inheritance relationship, of course, that risk is greatly reduced.Monzon
For all those other, dissimilar attributes, one technique I have used quite successfully is STI+properties association. You various STI models can define their own properties association (has_one), and just keep truely shared attributes in the STI modelEmmeram
T
29

I've written an article on this very topic, including some tips for working with STI:

Single Table Inheritance in Rails

In short: there needs to be a clear OO-style inheritance relationship among objects (as eloquently stated by womble), not just some shared data. If there isn't a natural and obvious class hierarchy, a STI design may become difficult to maintain as your application evolves.

Secondly, you should consider if it's important to have all the data in one table. With polymorphic associations, your database queries will become more complex, and probably slower. If you're planning on listing all the objects together on the site (eg, in a table) then STI may be the way to go.

Thirdly, make sure your child classes don't have too many unique attributes. With all the data in one table, you don't want a lot of non-global columns. Not only do these take up space (not a major concern), but they make the data structure confusing. If you do have "special" columns you should explain them explicitly in your code.

Lastly, if you do use STI, I strongly recommend using a single controller for all of your child models. The main function of a controller is to provide access to objects, and if the objects need to be accessed in very different ways, then STI may not have been the correct design choice to begin with.

Check out my article (link above) for some more useful tips.

Tiler answered 11/12, 2009 at 16:16 Comment(0)
T
5

I would probably use either STI or no special features at all. You might be able to call everything a Profile and you'd know if it was a "bot" if its user was nil. You could also store a "type" field without using STI.

Certain things would affect my decision to use STI:

  • Whether there is bot-specific logic
  • How many bots there are versus users profiles (small number of bots means STI is OK - lots of bots and I might store them somewhere else)

The reason to avoid STI is sometimes it can get in your way. For instance it can be fairly annoying to change an object from one type to another (a Bot to a Profile in this case). Sometimes a simple "type" field is better.

Its worth noting that you'll probably want a common base class if you use STI. So you may want Profile, BotProfile, and UserProfile. The names are up to you. :)

Tonnage answered 17/2, 2009 at 6:37 Comment(3)
I agree, was thinking about a similar approach.Chimene
Just wondering, why is it annoying to change an object from one type to another? bot.becomes(Profile) is pretty clear and easy...Bruch
@andrew: I was not aware of #becomes, but that does not actually change the type of the object, just its classname, and actually has nothing to do with STI.Tonnage
W
4

One gotcha of Rails STI - most plugins (and so forth) don't support it fully. You will find yourself patching many of the common ones.

Waterford answered 18/2, 2009 at 0:23 Comment(1)
Have you had any problems with STI and activeadmin?Agbogla

© 2022 - 2024 — McMap. All rights reserved.