Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveRecord Custom Type Naming Scheme

I am dealing with a table that already has a column with natural type names. E.g. there already exists a column called 'provider' that has values of either 'foo' or 'bar'. I want to use STI on this table using the existing type names, as it seems silly to have to add an additional column called 'type' for Active Record to use. Problem is, these type names don't match up exactly the the ruby classes. I was hoping to be able to set up a custom mapping, e.g. Class1 => foo, Class2 => bar.

I tried the following:

# In the base class
set_inheritance_column :provider

# In Class1
def self.sti_name
    'foo'
end

# In Class2
def self.sti_name
    'bar'
end

But this doesn't seem to cut it, I get:

The single-table inheritance mechanism failed to locate the subclass: 'foo'
...
activerecord (3.0.8) lib/active_record/base.rb:923:in `find_sti_class'

It looks like ActiveRecord is only equipped to handle full class names and demodularized class names. I am not sure what the purpose of having a protected sti_name method is at all.

I am about to dig deeper into the framework and override ActiveRecord::Base.compute_type. Before I do that, has anyone done this before? Is this a wild goose chase?

Can rails handle custom type names or am I stuck with having the Ruby class names?

Update

I got this to work with:

# In the base class
def self.find_sti_class(type_name)
    # Do mapping from foo/bar to Class1/Class2
    ...
end

I am still wary of hitting some more issues down the road. Is this the right way to do this?

like image 666
ghempton Avatar asked Jul 28 '11 21:07

ghempton


1 Answers

Looking at the AR code and just focusing on sti_name and instantiate..it seems it might be enough for you to monkey patch those two methods.

I don't have experience with this, but it doesn't look impossibly complicated. Here's how instantiate looks like:

def instantiate(record)
  model = find_sti_class(record[inheritance_column]).allocate
  model.init_with('attributes' => record)
  model
end

Ideally you'd want to scan the code for anything that references something like record[inheritance_column] or just plain inheritance_column and inject your own mapping code in there. Just a quick look and it seems it's not used in that many places.

I'd say what you're attempting IS possible :), but you will need test cases for basic instantiation + stuff where associations are involved to make sure AR can find and build the correct SQL for the different cases.

It's clear though AR was not built with what you're attempting in mind, since the use of inheritance_column in the code is not clearly abstracted away anywhere. So that's why you need to poke around in the code a bit more.

like image 161
Casper Avatar answered Nov 03 '22 01:11

Casper