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.