AssociationProxy#method_missing now yields?

Hello,

Why does it call send with a block which yields instead of just passing the block to send directly (like it used to)?

        # new way         def method_missing(method, *args)           if load_target             if block_given?               @target.send(method, *args) { |*block_args| yield(*block_args) }             else               @target.send(method, *args)             end           end         end

        # old way         def method_missing(method, *args)           if load_target             @target.send(method, *args, &block)           end         end

This change messes up my implementation of full? which used to allow me to do stuff like this:

puts self => #<Something:0x20e2c3c> post.author.full?{ puts self } # self inside that block will be that post's author. => #<Author:0xc3c2e02>

Now it's like this... puts self => #<Something:0x20e2c3c> post.author.full?{ puts self } # self inside that block will be that post's author. => #< Something:0x20e2c3c>

I'm just curious why the change... and wondering if there is a way to make it compatible with my full? implementation so I won't have to go refactor tons of code.

Thanks for the help, -- Christopher

I'm just curious why the change... and wondering if there is a way to make it compatible with my full? implementation so I won't have to go refactor tons of code.

The change was made to reduce the memory consumption and garbage generated when you have an explicit block parameter. However if it's breaking things we can look at reverting it. It was *meant* to be fully backwards compatible.

If you can add a test for what you're doing, it'd make it easier for us to figure out the best way forward.

Here ya go...

require "cases/helper" require 'models/post' require 'models/author'

class Object   def full?     is_full = !blank?     if is_full and block_given?       instance_eval(&Proc.new)     else       is_full     end   end end

class ProxySendTest < ActiveRecord::TestCase   fixtures :authors, :posts

  def test_send     self_is_author = nil     post = Post.first     post.author.full?{ self_is_author = self == post.author }     assert self_is_author   end

end

That test will pass with the old implementation of AssociationProxy#method_missing, but not the new.

I do realize that that implementation of full? can lead to confusion and there is a good argument for it not to behave that way, but dang it, it makes for some pretty code sometimes...

# dry a.very.long.assocation.chain.full?{ name } || "(no name)" page = params[:page].full?{ to_i } || 1

vs.

# wet a.very.long.assocation.chain.full? ? a.very.long.assocation.chain.name : "(no name)" page = params[:page].full? ? params[:page].to_i : 1

-- C

P.S. I think yall should put this implementation of #full? in Rails, as well as change #blank? to optionally take a block. :slight_smile:

http://github.com/rails/rails/commit/13a60b83a470fb90f91a073b0540f7b02aa11ebd

That test will pass with the old implementation of AssociationProxy#method_missing, but not the new.

I do realize that that implementation of full? can lead to confusion and there is a good argument for it not to behave that way, but dang it, it makes for some pretty code sometimes...

The breakage is caused by the use of instance_eval in your full? method. The simplest fix for your code is to make it yield the object rather than relying on self being transformed:

class Object def full?    is_full = !blank?    if is_full and block_given?   yield self    else      is_full    end end end

params[:page].full? {|i| i.to_i} || 0

Alternatively you can monkeypatch the association proxy as well to avoid that method missing behaviour.

I realise this isn't ideal but to me relying on that instance_eval changing self is just too much of a corner case to be worth reverting the performance patch.