Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a Rails mixin that spans across model, controller, and view

In an effort to reduce code duplication in my little Rails app, I've been working on getting common code between my models into it's own separate module, so far so good.

The model stuff is fairly easy, I just have to include the module at the beginning, e.g.:

class Iso < Sale
  include Shared::TracksSerialNumberExtension
  include Shared::OrderLines
  extend  Shared::Filtered
  include Sendable::Model

  validates_presence_of   :customer
  validates_associated    :lines

  owned_by :customer

  def initialize( params = nil )
    super
    self.created_at ||= Time.now.to_date
  end

  def after_initialize
  end

  order_lines             :despatched

  # tracks_serial_numbers   :items
  sendable :customer

  def created_at=( date )
    write_attribute( :created_at, Chronic.parse( date ) )
  end
end

This is working fine, now however, I'm going to have some controller and view code that's going to be common between these models as well, so far I have this for my sendable stuff:

# This is a module that is used for pages/forms that are can be "sent"
# either via fax, email, or printed.
module Sendable
  module Model
    def self.included( klass )
      klass.extend ClassMethods
    end

    module ClassMethods
      def sendable( class_to_send_to )
        attr_accessor :fax_number,
                      :email_address,
                      :to_be_faxed,
                      :to_be_emailed,
                      :to_be_printed

        @_class_sending_to ||= class_to_send_to

        include InstanceMethods
      end

      def class_sending_to
        @_class_sending_to
      end
    end # ClassMethods

    module InstanceMethods
      def after_initialize( )
        super
        self.to_be_faxed    = false
        self.to_be_emailed  = false
        self.to_be_printed  = false

        target_class = self.send( self.class.class_sending_to )
        if !target_class.nil?
          self.fax_number     = target_class.send( :fax_number )
          self.email_address  = target_class.send( :email_address )
        end
      end
    end
  end # Module Model
end # Module Sendable

Basically I'm planning on just doing an include Sendable::Controller, and Sendable::View (or the equivalent) for the controller and the view, but, is there a cleaner way to do this? I 'm after a neat way to have a bunch of common code between my model, controller, and view.

Edit: Just to clarify, this just has to be shared across 2 or 3 models.

like image 513
Mike Avatar asked Sep 16 '08 01:09

Mike


3 Answers

You could pluginize it (use script/generate plugin).

Then in your init.rb just do something like:

ActiveRecord::Base.send(:include, PluginName::Sendable)
ActionController::Base.send(:include, PluginName::SendableController)

And along with your self.included that should work just fine.

Check out some of the acts_* plugins, it's a pretty common pattern (http://github.com/technoweenie/acts_as_paranoid/tree/master/init.rb, check line 30)

like image 177
nikz Avatar answered Nov 14 '22 23:11

nikz


If that code needs to get added to all models and all controllers, you could always do the following:

# maybe put this in environment.rb or in your module declaration
class ActiveRecord::Base
  include Iso
end

# application.rb
class ApplicationController
  include Iso
end

If you needed functions from this module available to the views, you could expose them individually with helper_method declarations in application.rb.

like image 31
hoyhoy Avatar answered Nov 14 '22 23:11

hoyhoy


If you do go the plugin route, do check out Rails-Engines, which are intended to extend plugin semantics to Controllers and Views in a clear way.

like image 39
user11058 Avatar answered Nov 15 '22 00:11

user11058