Module: Invoicing::ClassInfo

Defined in:
lib/invoicing/class_info.rb

Overview

This module is intended for use only internally within this framework. It implements a pattern needed in several other modules: an acts_as_something_or_other method can be called within the scope of an ActiveRecord class, given a number of arguments; including options which define how columns are renamed in a given model object. The information from these arguments needs to be stored in a class variable for later use in instances of that class. It must be possible to call the acts_as_ method multiple times, combining the arguments from the various calls, to make the whole thing look nicely declarative. Subclasses should inherit acts_as_ arguments from their superclass, but should be able to override them with their own values.

This pattern assumes a particular module structure, like the following:

module MyNamespace                    # you may use arbitrarily nested modules for namespacing (optional)
  module Teleporter                   # the name of this module defines auto-generated method names
    module ActMethods
      def acts_as_teleporter(*args)   # should be called "acts_as_#{module_name.underscore}"
        Invoicing::ClassInfo.acts_as(MyNamespace::Teleporter, self, args)
      end
    end

    def transmogrify_the_instance     # will become an instance method of the class on which the
      info = teleporter_class_info    # acts_as_ method is called.
      info.do_transmogrify
    end

    module ClassMethods
      def transmogrify_the_class      # will become a class method of the class on which the
        info = teleporter_class_info  # acts_as_ method is called.
        info.do_transmogrify
      end
    end

    class ClassInfo < Invoicing::ClassInfo::Base
      def do_transmogrify
        case all_options[:transmogrification]
          when :total then "Transmogrified by #{all_args.first}"
        end
      end
    end
  end
end

ActiveRecord::Base.send(:extend, MyNamespace::Teleporter::ActMethods)

ClassInfo is used to store and process the arguments passed to the acts_as_teleporter method when it is called in the scope of an ActiveRecord model class. Finally, the feature defined by the Teleporter module above can be used like this:

class Teleporter < ActiveRecord::Base
  acts_as_teleporter 'Zoom2020', :transmogrification => :total
end

Teleporter.transmogrify_the_class               # both return "Transmogrified by Zoom2020"
Teleporter.find(42).transmogrify_the_instance

Defined Under Namespace

Classes: Base

Class Method Summary collapse

Class Method Details

.acts_as(source_module, calling_class, args) ⇒ Object

Provides the main implementation pattern for an acts_as_ method. See the example above for usage.

source_module

The module object which is using the ClassInfo pattern

calling_class

The class in whose scope the acts_as_ method was called

args

The array of arguments (including options hash) to the acts_as_ method


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/invoicing/class_info.rb', line 64

def self.acts_as(source_module, calling_class, args)
  # The name by which the particular module using ClassInfo is known
  module_name = source_module.name.split('::').last.underscore
  class_info_method = "#{module_name}_class_info"

  previous_info =
    if calling_class.respond_to?(class_info_method, true)
      # acts_as has been called before on the same class, or a superclass
      calling_class.send(class_info_method) || calling_class.superclass.send(class_info_method)
    else
      # acts_as is being called for the first time -- do the mixins!
      calling_class.send(:include, source_module)
      nil # no previous_info
    end

  # Instantiate the ClassInfo::Base subclass and assign it to an instance variable in calling_class
  class_info_class = source_module.const_get('ClassInfo')
  class_info = class_info_class.new(calling_class, previous_info, args)
  calling_class.instance_variable_set("@#{class_info_method}", class_info)

  # Define a getter class method on calling_class through which the ClassInfo::Base
  # instance can be accessed.
  calling_class.class_eval <<-CLASSEVAL
    class << self
      def #{class_info_method}
        if superclass.respond_to?("#{class_info_method}", true)
          @#{class_info_method} ||= superclass.send("#{class_info_method}")
        end
        @#{class_info_method}
      end
      private "#{class_info_method}"
    end
  CLASSEVAL

  # For convenience, also define an instance method which does the same as the class method
  calling_class.class_eval do
    define_method class_info_method do
      self.class.send(class_info_method)
    end
    private class_info_method
  end
end