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