0

This one is going to be a tinge interesting. After noting the peculiar behavior of let, I've decided to try and make a direct equivalent of the Lisp Let for RSPEC by binding instance variables to example groups.

Here's the code for this so far:

module RSpec
  module Core
    module MemoizedHelpers
      module ClassMethods

        def let_local(name, &block)
          raise "#let_local called without a block" if block.nil?

          # Binding to string instances, fix
          current_example = caller[0]

          # Attempt to find ivar, if not, make it.
          meth = -> {
            current_example.instance_variable_get("@#{name}") || 
            current_example.instance_variable_set("@#{name}", block.call)
          }

          MemoizedHelpers.module_for(self).send(:define_method, name, meth)

          before(:all) { __send__(name) }
        end
      end
    end
  end
end

Problem being, while it technically works for nested examples, I'm throwing ivars on a string. I know why it currently works but man is that hackish... How can I get a hold of the current example group that that function would be run inside? (ie

This is more of a thought exercise to see if it can be done.

There are definite performance reasons for something like this though, when used correctly (and frozen.) The use case is if you write tests in a functional manner, this let_local will not get in the way of running tests in parallel like the original let, and will not try and rebuild the object repeatedly (think expensive instantiations.)

Granted that this can already be done with a before :all ivar, but this may be a cleaner way about it.

Example test code using it:

describe 'Single local, multiple nested example, same local name' do
  let_local(:a) { Person.new('Doctor', 900) }

  it 'will be 900' do
    expect(a.age).to eq(900)
  end

  it 'will be named Doctor' do
    expect(a.name).to eq('Doctor')
  end

  context 'Doc got old' do
    let_local(:a) { Person.new('Doctor', 1000) }

    it 'should now be 1000' do
      expect(a.age).to eq(1000)
    end

    context 'And older still!' do
      let_local(:a) { Person.new('Doctor', 1100) }

      it 'will now be 1100' do
        expect(a.age).to eq(1100)
      end
    end

    it 'will still be 1000' do
      expect(a.age).to eq(1000)
    end
  end

  it 'will still be 900' do
    expect(a.age).to eq(900)
  end
end

The overall intent is to emulate this type of behavior in Lisp:

(let ((x 1))
  (write-line (write-to-string x))    ; prints 1
  (let ((x 2))
    (write-line (write-to-string x))) ; prints 2
  (write-line (write-to-string x)))   ; prints 1

Any tips or ideas?

baweaver
  • 304
  • 1
  • 12

1 Answers1

0

You can already emulate that behavior using standard Ruby. For example,

def let(pr)
  pr.call
end

let->(x=1, y=2) {
  p [x, y]
  let->(x=3, y=4) {
    p [x, y]
  }
  p [x, y]
}

Output:

[1, 2]
[3, 4]
[1, 2]
angus
  • 2,305
  • 1
  • 15
  • 22