class ActiveRecord::Base

Public Class Methods

caches_via_mvcoffee(child, opts = {}) click to toggle source
# File lib/mvcoffee/rails/active_record.rb, line 3
def self.caches_via_mvcoffee(child, opts = {})
  # This is a bunch of crazy metaprogramming!
  
  # Ideally this will work now matter how they supply the child name.
  # It could be a string or symbol, singular or plural.
  # We normalize it to a plural string here, because that's what we need.
  childs = child.to_s.pluralize
  
  # This is the name of the method we need to define on this class to be called
  # when a record of this model is created, and any time any child record is
  # changed in any way
  method = "#{childs}_updated!"
  
  define_method method do
    send "#{childs}_updated_at=", DateTime.now
    save!
  end

  after_create method

  # Okay, done changing this class, let's change the child class
  #
  # There are two cases here, the default, and if we are using :through.
  # In the default case, there is only one descendent, the child, and it
  # belongs_to this class, so its reference back to this class is through
  # a singular method call.
  #
  # In the through case, we have two classes to deal with, the through class
  # which is our direct descendent, and the "child" class we have many through
  # the join table.  In this case, the direct descendent should behave the way
  # the child should in the usual case.  It has a belongs_to singular reference
  # back to this class.  The "child" class should have a has_many through back to
  # this class, so it will be a plural reference.
  
  direct_descendent = child
  if opts[:through]
    direct_descendent = opts[:through]
  end
  
  # Create a reference to the child class definition.
  # It doesn't have to be class_eval here, it could be regular eval, but I've read
  # this is safer from code injection.
  #
  # Plus, doing this as a class_eval puts us in the same namespace as the parent.
  clazz = class_eval "#{direct_descendent.to_s.singularize.camelcase}"

  parent_name = table_name.singularize
  refresh_method = "refresh_#{parent_name}_#{childs}_updated_at"
  clazz.class_eval do
    define_method refresh_method do
      parent = send parent_name
      parent.send method
    end
    
    after_save refresh_method
    after_destroy refresh_method
  end
  
  # Now do the special case of a has_many through
  if opts[:through]
    clazz = class_eval "#{child.to_s.singularize.camelcase}"
    
    parents_name = table_name.pluralize
    clazz.class_eval do
      define_method refresh_method do
        parents = send parents_name
        parents.each { |parent| parent.send method }
      end
    
      after_save refresh_method
      # This may not be strictly necessary if delete cascade, but there's no harm
      # in being thorough
      after_destroy refresh_method
    end
  end
end