Implementing Abstract Base Model Class, the Rails Way™

Marius Butuc picture Marius Butuc · Dec 30, 2012 · Viewed 12.8k times · Source

I have a Book and Download model that share many attributes, so my goal is to inherit the common attributes from a DownloadableResource model.
Had a look at STI, but I went the abstract base model class way instead:

  • models:

    class DownloadableResource < ActiveRecord::Base
      self.abstract_class = true
    
      attr_accessible :title, :url, :description, :active, :position
      validates :title, :url, :description, presence: true
      scope :active, where(active: true).order(:position)
    end
    
    class Book < DownloadableResource
      attr_accessible :cover_url, :authors
      validates :cover_url, :authors, presence: true
    end
    
    class Download < DownloadableResource
      attr_accessible :icon_url
      validates :icon_url, presence: true
    end
    
  • migrations:

    class CreateDownloadableResources < ActiveRecord::Migration
      def change
        create_table :downloadable_resources do |t|
          t.string    :title
          t.string    :url
          t.text      :description
          t.boolean   :active,      default: false
          t.integer   :position
          t.timestamps
        end
      end
    end
    
    class CreateBooks < ActiveRecord::Migration
      def change
        create_table :books do |t|
          t.string :cover_url
          t.string :authors
          t.timestamps
        end
      end
    end
    
    class CreateDownloads < ActiveRecord::Migration
      def change
        create_table :downloads do |t|
          t.string :icon_url
          t.timestamps
        end
      end
    end
    

After migration, when I create a new Book the result is far from expected:

> Book.new
=> #<Book id: nil, cover_url: nil, authors: nil, created_at: nil, updated_at: nil> 

Can somebody please shed some light on how to implement the Abstract Base Model Class technique so ActiveRecord models can share common code via inheritance yet be persisted to different database tables?

Answer

Jiř&#237; Posp&#237;šil picture Jiří Pospíšil · Dec 30, 2012

By declaring a model as abstract you are actually saying that there's no underlying table and you want to allow subclassing. That means:

  • You don't need the downloadable_resources table
  • Book.table_name prints books instead of downloadable_resources

As @Finbarr already mentioned, this also means that both Book and Download models need to have all of the attributes in their tables.

What is this actually useful for then? In my opinion not for much. You can share validations, scopes etc. but you can achieve all of that more easily by including custom modules.

To solve your problem I would probably go with a different approach. I would create another model called DownloadableContent that would be self contained. It would include validations and the table would have all of the attributes. And finally models Book and Download would have a polymorphic has_one relation to the DownloadableContent model.

You could go with the STI approach but I generally don't like mixing all of the custom attributes together.