Implementing Abstract Base Model Class, the Rails Way™

12,957

Solution 1

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.

Solution 2

There shouldn't be a downloadable_resources table in this case. Both your books and downloads tables should declare all of the fields they need.

Share:
12,957
Marius Butuc
Author by

Marius Butuc

Ruby & Rails aficionado keen on big data, cloud computing, usable web & photography.

Updated on June 05, 2022

Comments

  • Marius Butuc
    Marius Butuc about 2 years

    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?

  • Marius Butuc
    Marius Butuc over 11 years
    Why? What makes this case unsuited for inheritance? And how to decide when it's worth inheriting and when it's not?