Dustin Frazier
Dustin Frazier

Reputation: 293

avoiding code duplication in Rails 3 models

I'm working on a Rails 3.1 application where there are a number of different enum-like models that are stored in the database. There is a lot of identical code in these models, as well as in the associated controllers and views. I've solved the code duplication for the controllers and views via a shared parent controller class and the new view/layout inheritance that's part of Rails 3.

Now I'm trying to solve the code duplication in the models, and I'm stuck. An example of one of my enum models is as follows:

class Format < ActiveRecord::Base

  has_and_belongs_to_many :videos

  attr_accessible :name

  validates :name, presence: true, length: { maximum: 20 }

  before_destroy :verify_no_linked_videos

  def verify_no_linked_videos
    unless self.videos.empty?
      self.errors[:base] << "Couldn't delete format with associated videos."
      raise ActiveRecord::RecordInvalid.new self
    end
  end

end

I have four or five other classes with nearly identical code (the association declaration being the only difference). I've tried creating a module with the shared code that they all include (which seems like the Ruby Way), but much of the duplicate code relies on ActiveRecord, so the methods I'm trying to use in the module (validate, attr_accessible, etc.) aren't available. I know about ActiveModel, but that doesn't get me all the way there.

I've also tried creating a common, non-persistent parent class that subclasses ActiveRecord::Base, but all of the code I've seen to accomplish this assumes that you won't have subclasses of your non-persistent class that do persist.

Any suggestions for how best to avoid duplicating these identical lines of code across many different enum models?

Upvotes: 1

Views: 739

Answers (1)

Dustin Frazier
Dustin Frazier

Reputation: 293

I found a solution to the code sharing for Rails 3 models, so thought I'd share it with others. It turns out ActiveModel does have everything I need (so far, at least). I created an Enum module using ActiveSupport::Concern, ActiveModel::Validations, and ActiveModel::MassAssignmentSecurity, and I include the module in each of my enum models:

module Enum

  extend ActiveSupport::Concern

  include ActiveModel::Validations
  include ActiveModel::MassAssignmentSecurity

  included do

    attr_accessible :name

    validates :name, presence: true, length: { maximum: 20 }

    before_destroy :verify_no_linked_videos

    private

    def verify_no_linked_videos
      unless self.videos.empty?
        self.errors[:base] << "Couldn't delete object with associated videos."
        raise ActiveRecord::RecordInvalid.new self
      end
    end

  end

end

The way the Rails 3 team pulled out the non-database code from ActiveRecord into ActiveModel really is pretty slick! The following links helped solidify my understanding of how to use this stuff:

http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/

http://asciicasts.com/episodes/237-dynamic-attr-accessible

Upvotes: 2

Related Questions