Single Table Inheritance--base type doesn't have a "type"

I am very much a rails newbie, so this could be user error, but...

I am trying to implement Single Table Inheritance, and what I find is
that for the base class, and only for that class, the "type" field is
never written out into the database.

To check this out, I created a new project, and created three classes
with scaffold (Rails 2.0.2): Manager < Employee < Person, following
the example in Agile Web Development with Rails. I then edited the
files to set up Single Table Inheritance, which I think I did
correctly. In any event, the controllers appear to work correctly and
it passes the scaffold-generated tests (after necessary fixes).

But I find that when I save an Employee or Manager, the "type" field
is written correctly, but when I save a Person, the type field is
empty.

Is this normal, or is this a bug? I can supply a sample project if
that helps.

TIA

PS--if this is a bug, where would be the best place to fix it?
Overriding Person.new, or adding a call-back?

I've been studying the ActiveRecord source code, and that's what I
saw, too.

///ark

alanterra wrote:

I am very much a rails newbie, so this could be user error, but...

I am trying to implement Single Table Inheritance, and what I find is
that for the base class, and only for that class, the "type" field is
never written out into the database.

To check this out, I created a new project, and created three classes
with scaffold (Rails 2.0.2): Manager < Employee < Person, following
the example in Agile Web Development with Rails. I then edited the
files to set up Single Table Inheritance, which I think I did
correctly. In any event, the controllers appear to work correctly and
it passes the scaffold-generated tests (after necessary fixes).

But I find that when I save an Employee or Manager, the "type" field
is written correctly, but when I save a Person, the type field is
empty.

Is this normal, or is this a bug? I can supply a sample project if
that helps.

That's as expected -- types aren't written for classes that are
direct subclasses of ActiveRecord::Base, because it isn't needed
for disambiguation. Why would you like the Person type written?

That's as expected -- types aren't written for classes that are
direct subclasses of ActiveRecord::Base, because it isn't needed
for disambiguation.

Thanks for the response.

There are a couple of reasons one would want it. Often, eg the code at
http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance, one
needs to case on the [:type] column. It makes much more sense to case
on the known types, and then report an error if no match (like a
subclass was added). It seems unintuitive and unsafe to have ".nil?

.empty?" be the test used for the base class.

Second, and again, it might be user error, I set up a test case using
Rails 2.0.2, and scaffolding, and then edited to introduced STI. What
I found was that the call People.find(:all) returned an array of
objects, of which the ".type" field (which I believe is the real type
internal to Ruby?) was NOT set for elements of the array which should
have been type "Person", EXCEPT THE FIRST ONE. I.e., in the array
returned, all Employees and Managers had .type set correctly, and the
first Person also, but other People had .type either nil or empty
(didn't check which). Setting [:type] to Person before writing solved
this problem.

I don't know enough Ruby to muck around in the internals to figure out
why Rails might be acting this way.

Alan

I don’t know enough Ruby to muck around in the internals to figure out

why Rails might be acting this way.

Alan

Wasn’t this already answered?

That’s as expected – types aren’t written for classes that are

direct subclasses of ActiveRecord::Base, because it isn’t needed
for disambiguation. Why would you like the Person type written?

Perhaps make a subclass of the original Person class, or change the main class to something called “Party” and have another subclass called “Person”. Rails will not put the type into the table if you’re creating an object of the base model.

Why not just switch on the class? This would be the "Ruby way," I
think, since Class's === operator is designed for it.

Stop me if you've heard this before, but it's best to avoid doing this
kind of think (switching on Class) because every time you add a new
kind of Person, you have to remember to search your code for all the
other places that care about all Person descendents. If possible, it's
to let classes take care of themselves.

///ark

Why not just switch on the class? This would be the "Ruby way," I
think, since Class's === operator is designed for it.

That would be fine, of course. Thx.

On a related subject, can anyone explain to me the difference between
the .type and the .class methods? My Ruby book says that they are
synonyms, but I am seeing different values for them in rails. For some
People, I get .type == NIL, and .class == Person. A quick perusal of
the documentation doesn't show me that .type was overridden, but I
suspect it is somewhere.

Stop me if you've heard this before, but it's best to avoid doing this
kind of thing (switching on Class)...

I have heard that, and I do know it. I was quoting somebody else's
code, and s/he thought it was necessary. I am not smart enough to
really grok how object oriented design and rails really work
together,and whether you can avoid bad code like casing on the class,
but I'm working on it.

Alan

Whoops, I meant for the Person class itself and all of its subclasses.

Well, that explains things. Thanks.

Much of my confusion came from not understanding what the .type method
does.

It turns out, if you care, that if you call Person.find(:all),
the .type method of the elements of the returned array can return the
following values

* Person (the Class) if the first object in the array is a Person
* NIL if the object is a Person and not the first Person in the array
* "Employee", "Manager", ... (strings) if the object is a subclass of
Person

I will just avoid using .type from now on, and use .class and [:type]
only.

Over and out

I wouldn't reference .class, but just do this:

case manager_or_employee
when Manager
     #...
when Employee
    #...
  end