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.
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.