%w(hashrockets spaceships bangbangs)

> non-optimized bits & pieces <

Tripping Over Method & Local Variable

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:

1
2
3
4
5
6
def mm
  :ok
end

mm   # >> :ok
mm() # >> :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:

1
2
3
4
5
6
7
8
9
10
11
12
describe Thing do
  let(:alien) { Factory(:thing, name: 'alien') }
  let(:robot) { Factory(:thing, name: 'robot') }

  %w{alien robot}.each do |thing|
    context "as #{thing}" do
      let(:subject) { thing }
      let(:thing) { send(thing) }
      it { should be_macho }
    end
  end
end

When we ran the above spec, we get undefined method 'macho' for "alien":String (NoMethodError) .. why ??

Here’s our intended behaviour:

1
2
3
4
5
6
7
8
9
10
11
12
describe Thing do
  let(:alien) { Factory(:thing, name: 'alien') } # defines alien()
  let(:robot) { Factory(:thing, name: 'robot') } # defines robot()

  %w{alien robot}.each do |thing|  # set local var thing
    context "as #{thing}" do
      let(:subject) { thing }      # defines subject() to return thing()
      let(:thing) { send(thing) }  # defines thing() to return the evaluate alien() or robot()
      it { should be_macho }
    end
  end
end

This is what happened instead:

1
2
3
4
5
6
7
8
9
10
11
12
describe Thing do
  let(:alien) { Factory(:thing, name: 'alien') } # defines alien()
  let(:robot) { Factory(:thing, name: 'robot') } # defines robot()

  %w{alien robot}.each do |thing| # set local var thing
    context "as #{thing}" do
      let(:subject) { thing }     # ** defines subject() to return the local var thing
      let(:thing) { send(thing) } # ** defines thing() which never gets invoked
      it { should be_macho }
    end
  end
end

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() }

ruby, tips

Comments