As you know, ruby always tries hard to get out of the programmer’s
way most of the time. One good example is when u invoke a method
that doesn’t require any argument:
123456
defmm:okendmm# >> :okmm()# >> :ok
Almost always, to invoke mm, mm is preferred over mm(), doing
it the mm() way is so un-rubyish. Yet this flexibility tripped us
over today, consider the following:
When we ran the above spec, we get
undefined method 'macho' for "alien":String (NoMethodError) .. why ??
Here’s our intended behaviour:
123456789101112
describeThingdolet(:alien){Factory(:thing,name:'alien')}# defines alien()let(:robot){Factory(:thing,name:'robot')}# defines robot()%w{alien robot}.eachdo|thing|# set local var thingcontext"as #{thing}"dolet(:subject){thing}# defines subject() to return thing()let(:thing){send(thing)}# defines thing() to return the evaluate alien() or robot()it{shouldbe_macho}endendend
This is what happened instead:
123456789101112
describeThingdolet(:alien){Factory(:thing,name:'alien')}# defines alien()let(:robot){Factory(:thing,name:'robot')}# defines robot()%w{alien robot}.eachdo|thing|# set local var thingcontext"as #{thing}"dolet(:subject){thing}# ** defines subject() to return the local var thinglet(:thing){send(thing)}# ** defines thing() which never gets invokedit{shouldbe_macho}endendend
To fix the problem, we can either:
avoid confusing names by renaming the local variable thing to
_thing & amend all its intended usage accordingly (which include
let(:thing) { send(thing) } to let(:thing) { send(_thing) }), OR
be explicte when invoking method by rewriting
let(:subject) { thing } as let(:subject) { thing() }