views:

114

answers:

1

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.

We have the following pattern a lot in our application:

class Project << ActiveRecord::Base
  has_many :graph_settings
end

class GraphType << ActiveRecord::Base
  has_many :graph_settings
  #graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end

class GraphSetting << ActiveRecord::Base
  belongs_to :graph_type
  belongs_to :project
  # Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end

This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.

A simple refactor where we get rid of GraphType in favor of using a structure more like this:

class Graph << ActiveRecord::Base
  belongs_to :project
  # Generic methods and settings
end

class SpecificGraph << Graph
  # Default methods and settings hard coded
  # Project implementation specific details stored in db.
end

Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.

We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.

Is 200 STI classes to many? Is there another pattern we should look at?

Thanks for any wisdom and I will answer any questions.

+3  A: 

If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)

But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.

To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:

class SpecificGraph < Graph
  include SpecificGraphDetail::MTI
end

class SpecificGraphDetail < ActiveRecord::Base
  module MTI
    def self.included(base)
      base.class_eval do
        has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
        delegate :extra_column, :extra_column=, :to => :specific_graph_detail
      end
    end
  end
end

The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.

You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Andrew Vit
Thanks for the reply. At this point I do not think we would have many (if any) null values in the database, as the properties are the same, just the values and methods would change. (For instance how it returns the data for the graph to plot).If we do end up with null fields (maybe not this particular instance, but other similar ones) I will certainly keep this in mind.
Alan Peabody
There's another similar approach in http://stackoverflow.com/questions/1634668/multiple-table-inheritance-vs-single-table-inheritance-in-ruby-on-rails/1634734#1634734
Andrew Vit