views:

6407

answers:

6

Ok, so I've been refactoring my code in my little Rails app in an effort to remove duplication, and in general make my life easier (as I like an easy life). Part of this refactoring, has been to move code that's common to two of my models to a module that I can include where I need it.

So far, so good. Looks like it's going to work out, but I've just hit a problem that I'm not sure how to get around. The module (which I've called sendable), is just going to be the code that handles faxing, e-mailing, or printing a PDF of the document. So, for example, I have a purchase order, and I have Internal Sales Orders (imaginatively abbreviated to ISO).

The problem I've struck, is that I want some variables initialised (initialized for people who don't spell correctly :P ) after the object is loaded, so I've been using the after_initialize hook. No problem... until I start adding some more mixins.

The problem I have, is that I can have an after_initialize in any one of my mixins, so I need to include a super call at the start to make sure the other mixin after_initialize calls get called. Which is great, until I end up calling super and I get an error because there is no super to call.

Here's a little example, in case I haven't been confusing enough:

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

  validates_presence_of   :customer
  validates_associated    :lines

  owned_by                :customer
  order_lines             :despatched # Mixin

  tracks_serial_numbers   :items  # Mixin

  sendable :customer                      # Mixin

  attr_accessor :address

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

So, if each one of the mixins have an after_initialize call, with a super call, how can I stop that last super call from raising the error? How can I test that the super method exists before I call it?

+4  A: 

Rather than checking if the super method exists, you can just define it

class ActiveRecord::Base
 def after_initialize
 end
end

This works in my testing, and shouldn't break any of your existing code, because all your other classes which define it will just be silently overriding this method anyway

Orion Edwards
A: 

Have you tried alias_method_chain? You can basically chained up all your after_initialize calls. It acts like a decorator: each new method adds a new layer of functionality and passes the control onto the "overridden" method to do the rest.

newtonapple
A: 

I actually tested alias_method_chain before posting my previous comment.

It fails for the same reason - at some point there needs to be an after_initialize method in ActiveRecord::Base, or test for one existing.

Orion Edwards
+2  A: 

The including class (the thing that inherits from ActiveRecord::Base, which, in this case is Iso) could define its own after_initialize, so any solution other than alias_method_chain (or other aliasing that saves the original) risks overwriting code. @Orion Edwards' solution is the best I can come up with. There are others, but they are far more hackish.

alias_method_chain also has the benefit of creating named versions of the after_initialize method, meaning you can customize the call order in those rare cases that it matters. Otherwise, you're at the mercy of whatever order the including class includes the mixins.

later:

I've posted a question to the ruby-on-rails-core mailing list about creating default empty implementations of all callbacks. The saving process checks for them all anyway, so I don't see why they shouldn't be there. The only downside is creating extra empty stack frames, but that's pretty cheap on every known implementation.

James A. Rosen
+1  A: 

You can just throw a quick conditional in there:

super if respond_to?('super')

and you should be fine - no adding useless methods; nice and clean.

This seems to work.
James Baker
This does **not** appear to work. respond_to?('super') will always return false.
averell
+1  A: 

You can use this:

super if defined?(super)

Here is an example:

class A
end

class B < A
  def t
    super if defined?(super)
    puts "Hi from B"
  end
end

B.new.t
valo
This worked great for me
Nathan