Metaprogramming Q: Calling an external class method on after_save

I have the following classes:

class AwardBase
class AwardOne < AwardBase
class Post < ActiveRecord::Base

The Post is an ActiveRecord, and the Award has a can_award? class method
which takes a post object and checks to see if it meets some criteria.
If
yes, it updates post.owner.awards.

I know I can do this using an Observer pattern (I tested it and the code
works fine). However, that requires me to add additional code to the
model.
I’d like not to touch the model at all if possible. What I’d like to do
is
run the Award checks like this (the trigger will be invoked at class
load
time):

class AwardOne < AwardBase
  trigger :post, :after_save

  def self.can_award?(post)
    ...
  end
end

The intention with the above code is that it should automatically add
AwardOne.can_award? to Post’s after_save method

So essentially what I’m trying to do is to get the above code to be
equivalent to:

class Post < ActiveRecord::Base
  after_save AwardOne.can_award?(self)
  ...
end

which is basically:

class Post < ActiveRecord::Base
  after_save :check_award

  def check_award
    AwardOne.can_award?(self)
  end
end

How can I do this without modifying the Post class?


Here’s what I’ve done (which does not appear to work):

class AwardBase

  def self.trigger (klass, active_record_event)
    model_class = klass.to_class

    this = self
    model_class.instance_eval do
      def award_callback
        this.can_award?(self)
      end
    end

    model_class.class_eval do
      self.send(active_record_event, :award_callback)
    end
  end

  def self.can_award? (model)
    raise NotImplementedError
  end
end

(In the final code sample, the error I get is:

NameError (undefined local variable or method `award_callback’ for
#Post:0x002b57c04d52e0):

You can probably do something like this:

class Post < ActiveRecord::Base
after_save &AwardOne.method(:can_award?)
end

This will pass in the method as a block to ::after_save (#method returns
a proc and the & will coerce it to a block).

  • Sonny

On Thursday, 13 November 2014 23:37:40 UTC-5, Debajit Adhikary wrote:

    ...
  end
end

The intention with the above code is that it should automatically add
AwardOne.can_award? to Post’s after_save method

You could do this with code like (not tested):

class AwardOne < AwardBase
def self.trigger(klass, callback, method)
us = self # this may not be needed
klass.to_s.camelize.constantize.send(callback) do |record|
us.send(method, record)
end
end

trigger :post, :after_save, :can_award?

end

But seriously, DON’T DO THIS. Callbacks are already one of the more
confusing features of AR, and confounding that by over-metaprogramming
is
going to obscure things further.

Even re-opening the Post class in award_one.rb and adding the callback
in
the standard style would be clearer than this, but I’d recommend just
using
the version you’ve already written (below).

–Matt J.