Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericboehs/ccc6b78349fc32e5f1bb to your computer and use it in GitHub Desktop.
Save ericboehs/ccc6b78349fc32e5f1bb to your computer and use it in GitHub Desktop.
Rails: pass friendship model to Popular's after_befriend callback

I wanted to do this with the popular gem:

class User < ActiveRecord::Base
  popular
  
  after_befriend :notify_friend
  
  def notify_friend(friend)
    puts "#{self.username} followed #{friend.username}"
  end
end

but after_befriend doesn't send notify_friend any arguments. This is because it's using ActiveSupport callbacks and they don't send arguments. Or do they...

I started investigating how popular and ActiveSupport callbacks worked. When befriending a user, popular creates a friendship in an ActiveSupport::Callbacks run_callbacks block (See). The return value of the friendship gets passed to run_callbacks then some funky ActiveSupport::Callbacks stuff happens and then my notify_friend method gets called... without the friendship value being passed.

I looked through run_callbacks and it looked to be doing some complicated stuff as soon as it hit the compile method. But essentially it calls apply which calls make_lambda which creates a lambda which sends on my method notify_friend. The lambda is called eventually from simple where it is passed the return value of the block provided by popular.

Unfortunately, make_lambda discards the return value when using a symbol or proc (note the _ on line 424). BUT for some reason, not when you provide a string to after_befriend.

So the simple answer to my long winded adventures:

class User < ActiveRecord::Base
  popular
  
  after_befriend 'notify_friend value'
  
  def notify_friend(friendship)
    puts "#{self.username} followed #{friendship.friend.username}"
  end
end

My suggestions to the rails core would be to pass value to the method if the method accepts arguments:

def make_lambda
  # ...
  when Symbol
    lambda { |target, value, &blk|
      if target.method(filter).arity == 0
        target.send filter, &blk
      else
        target.send filter, value, &blk
      end
    }
  # ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment