Metaprogramming Q: Calling an external class method on after_save

28 views
Skip to first unread message

Debajit Adhikary

unread,
Nov 13, 2014, 11:37:40 PM11/13/14
to rubyonra...@googlegroups.com
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

Debajit Adhikary

unread,
Nov 13, 2014, 11:39:43 PM11/13/14
to rubyonra...@googlegroups.com
(In the final code sample, the error I get is: 

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

Sonny Michaud

unread,
Nov 14, 2014, 3:19:06 AM11/14/14
to rubyonra...@googlegroups.com
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
--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-ta...@googlegroups.com.
To post to this group, send email to rubyonra...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/303ca1b9-15f0-478f-9d69-1a25ddadca01%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Jones

unread,
Nov 14, 2014, 7:28:36 AM11/14/14
to rubyonra...@googlegroups.com


On Thursday, 13 November 2014 23:37:40 UTC-5, Debajit Adhikary wrote:
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


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 Jones
Reply all
Reply to author
Forward
0 new messages