Re: Spira: method resolution of has_many resources not working sometimes

This is a quirk of Promise. In Ruby 1.8, there is no BasicObject, so
Promise fakes it by undefining all of the non-critical methods on
Object. When you monkey-patch Object to have a to_zorch, Promise does
not pass the method through to the underlying promised thunk, and you
get Object's version. In Ruby 1.9 (which I tested, which is why I did
not see the same behavior), Promise uses BasicObject, which did not
get #to_zorch added to it by adding it to Object. I do not imagine
this behavior would happen in any case other than monkey-patching
Object itself.

Easy repro in 1.8.7:
require 'promise'
class Object
  def to_zorch
    "promises can't be zorched!"
  end
end

class Fixnum
  def to_zorch
    to_s
  end
end
x = promise { 15 }
x.to_zorch  # => "promises can't be zorched!" in 1.8.7, "15" in 1.9.2

There are two immediate workarounds:
 * Upgrade to 1.9 already ;) RVM makes it easy.
 * Remove to_zorch from Promise:
x.to_zorch  # => "promises can't be inspected!" in 1.8.7, "15" in 1.9.2
Promise.__send__(:undef_method, :to_zorch)
x.to_zorch # => "15" everywhere

I'm not sure that this is worth fixing in promise proper; it's slow
enough already. But I will try and think of a way to document this
gotcha.

Ben

On Fri, May 20, 2011 at 10:57 AM, Christoph Badura <bad@bsd.de> wrote:
> On Thu, May 19, 2011 at 05:28:46PM -0500, Ben Lavender wrote:
>> > Run the following example in irb to see what is happening.  While
>> > writing that test case I also discovered that
>> >    SomeModel.for('123', :has_many_property => other_model_instance)
>> > messes up the subject of the object recoded in the has_many_property.
>>
>> That's to be expected; it wants a Set or Array. Try :has_many_property
>> => [ other_instance]
>
> You expect it not to fail but to mess up the object it is storing?
> I'd expect it to fail or to do something sensible.  If I read the rspec
> specs right you expect it to do something sensible for
>
> some_model_instance.has_many_property = other_model_instance
>
> Check my irb output below.  ab.subject ends up as "http://example.com/a/A"
>
>> > c.bar.first.super
>> > c.bar.first.to_zorch
>> > c.reload
>> > c.bar.first.super
>> > c.bar.first.to_zorch    # XXX fails
>
>> What fails? What is the expected behavior? For me, c.bar.first is an
>> A, which has a defined #to_zorch. When I call it, I get A's subject?
>
> I get different output for both invocations of c.bar.first.to_zorch.
> The first one returns A's subject, the second one "boom!".
>
> The calls to super are just to provoke a backtrace to demonstrate that
> the after the reload c.bar.first.to_zorch goes through the promise's
> method_missing handler.
>
> I guess I was already to tired yesterday.  I wanted to mention that I ran
> this under ruby 1.8.7 and Spira 0.0.12.
>
> Here is the relevant output from my system's irb after the initialisations:
>
> xxx.rb(main):037:0* x = A.for('123', :foo => 'a foo').save!
> => <A:-579815178 @subject: http://example.com/a/123>
> xxx.rb(main):038:0> B.for('456', :foo => 'b foo', :bar => x).save!
> => <B:-579595548 @subject: http://example.com/b/456>
> xxx.rb(main):039:0>
> xxx.rb(main):040:0* a = A.for '123'
> => <A:-578851808 @subject: http://example.com/a/123>
> xxx.rb(main):041:0> a.attributes
> => {:foo=>"a foo"}
> xxx.rb(main):042:0> b = B.for '456'
> => <B:-578860798 @subject: http://example.com/b/456>
> xxx.rb(main):043:0> b.attributes
> => {:bar=>#<Set: {<A:-578865728 @subject: http://example.com/a/A>}>, :foo=>"b foo"}
> xxx.rb(main):044:0> ab = b.bar.first
> => <A:-578865728 @subject: http://example.com/a/A>
> xxx.rb(main):045:0> puts 'boom!' if a.subject != ab.subject # XXX @subject wrong
> boom!
> => nil
> xxx.rb(main):046:0> c = B.for '789'
> => <B:-578885038 @subject: http://example.com/b/789>
> xxx.rb(main):047:0> c.foo = 'c foo'
> => "c foo"
> xxx.rb(main):048:0> c.bar << x
> => #<Set: {<A:-579815178 @subject: http://example.com/a/123>}>
> xxx.rb(main):049:0> c.save!
> => <B:-578885038 @subject: http://example.com/b/789>
> xxx.rb(main):050:0> c.attributes
> => {:bar=>#<Set: {<A:-579815178 @subject: http://example.com/a/123>}>, :foo=>"c foo"}
> xxx.rb(main):051:0> c.bar.first.super
> NoMethodError: undefined method `super' for <A:-579815178 @subject: http://example.com/a/123>:A
>        from xxx.rb:51
> xxx.rb(main):052:0> c.bar.first.to_zorch
> => "http://example.com/a/123"
> xxx.rb(main):053:0> c.reload
> => {:bar=>#<Set: {<A:-578916598 @subject: http://example.com/a/123>}>, :foo=>"c foo"}
> xxx.rb(main):054:0> c.bar.first.super
> NoMethodError: undefined method `super' for <A:-578916598 @subject: http://example.com/a/123>:A
>        from /usr/pkg/lib/ruby/gems/1.8/gems/promise-0.3.0/lib/promise.rb:89:in `__send__'
>        from /usr/pkg/lib/ruby/gems/1.8/gems/promise-0.3.0/lib/promise.rb:89:in `method_missing'
>        from xxx.rb:54
> xxx.rb(main):055:0> c.bar.first.to_zorch        # XXX fails
> => "boom!"
> xxx.rb(main):056:0>
>
> --chris
>



-- 
Ben Lavender | ben@dydra.com | http://dydra.com
twitter/github: bhuga | +15047221016

Received on Friday, 20 May 2011 17:11:57 UTC