Let Loose?

Today I faced a very interesting problem. I was working on one of the Rails Controllers, and writing specs using RSpec.

This was my controller spec.


let(:expense){
  FactoryGirl.create(:expense, employee: employee)
}

it "assigns all expenses as @expenses" do
  get :index
  expect(assigns(:expenses)).to eq([expense])
end

And the code(assuming @organisation already being assigned by a before_action method):


@expenses = @organisation.expenses.all.reverse

But, strangely, this test case was failing as the value of @expenses was nil. What went wrong? On debugging, I figured out that the value of Expense.all was nil inspite being already created in the let statement.

I put a pry in


it "assigns all expenses as @expenses" do
  get :index
  pry
  expect(assigns(:expenses)).to eq([expense])
end

and executed the following things, and got the respective outputs:


pry(#)> Expense.all
=> []

[3] pry(#)> expense
=> #<Expense:0x4a4979bf id: 354, employee_id: 582, status: 0>

[4] pry(#)> Expense.all
=> [#<Expense:0x5b04224a id: 354, employee_id: 582, status: 0>]

That’s it. That’s the problem. That’s where I did programming by coincidence?

We see that Expense.all gives a nil initially but when I call the expense object, Expense.all starts retrieving the expenses. let actually lazy loads.

Relishapp(https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/helper-methods/let-and-let) says the following:

Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked.

I missed that. Perhaps, didn’t RTFM.

Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed. It can contribute to efficiency in the program’s operation if properly and appropriately used. So, expense is not loaded till it is called.

What can be done about this?

We can use let! to force the method’s invocation before each example.

So, changing


let(:expense){
  FactoryGirl.create(:expense, employee: employee)
}

to


let!(:expense){
  FactoryGirl.create(:expense, employee: employee)
}

fixes the problem.

This is a very simple case of programming by coincidence and assumptions, but I learnt a good concept today.

2 thoughts on “Let Loose?

  1. This could also be done in a before block, unless you care about the actual instance of the object created.

    One more pattern I’ve been following recently is to use less and less instance variables. Makes code more funtional. For e.g.:
    Instead of:

    “`
    class DemoController
    before_action :set_organization
    def show
    render json: @organization
    end
    private
    def set_organization
    @organization = Organization.find(params[:id])
    end
    end
    “`

    “`
    class DemoController
    def show
    render json: organization
    end

    private
    def organization
    @organization ||= Organization.find(params[:id])
    end
    end
    “`

    You could expand the organization method to even initialize a new one on create action.

    Like

  2. Yes, actual instance mattered here. But, before is handy, though before_all is very dangerous until you don’t really understand it. And is there a performance boost you get in your example using the instance variables?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s