Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby Build a Todo List Application with Rails 4 Build a Todo List Application with Rails 4 Editing Todo Lists

Need help understanding RSpec "let!" method

So I'm in the "Editing Todo Lists" lesson and understood how everything works until the let! method is introduced

does

let!(:todo_list) {TodoList.create(title: 'Groceries', description: 'Grocery List.')}

create a 'todo_list' variable that contains a new todo list entry? I tested it out by removing the line

todo_list = options[:todo_list]

from my 'update_todo_list' method and no longer referencing 'todo_list:' as an option when calling it, and it worked. So does that mean that let! creates a global variable?

However, if I change the name of the variable in the 'let!' method to something like:

let!(:water) {TodoList.create(title: 'Groceries', description: 'Grocery List.')}

and inside of the update_todo_list method I include

todo_list = options[:todo_list]

and call the method as such

update_todo_list todo_list: water, title: "New Title", description: "New description."

I get an error:

Failure/Error: within "#todo_list_#{todo_list.id}" do
     NoMethodError:
undefined method `id' for nil:NilClass

Which makes no sense to me since Im filling the value of the variable 'todo_list' with the 'water' variable

replacing the above line with

within "#todo_list_#{water.id}" do

then i no longer have an error, but a new error appear saying that todo_list is undefined on the line that contains

todo_list.reload

again, replacing it with

water.reload

will work, but I dont know why including

todo_list = options[:todo_list]

as part of the update function and calling it with

update_todo_list todo_list: water, title: "New Title", description: "New description."

does work. This isn't a particular roadblock to my learning, but its annoying to not understand how things work fully. If anyone here has any understanding of rspec, i would like for you to chime in

1 Answer

Jason Seifer
STAFF
Jason Seifer
Treehouse Guest Teacher

Hey Ulises Reyes! Great question. There are two different versions of rspec's let method: let and let!. The difference is subtle but important. With let the variable is lazily created, which means that Ruby won't create or access the contents of it until it needs to, which is generally when it is called in a test. The let! method with an exclamation point creates the variables immediately. Generally that is done to give you earlier access to the variables or use them earlier in the test.

When you call the method update_todo_list, a new variable is created inside of that method. We are passing in the individual todo list via the options[:todo_list] variable to use inside of that method only. That would let us call the method many times with different todo list variables. When a variable is used in a method without an @ sign, it becomes a local variable to that method.

The distinction is a little bit more strange when we get in to rspec and the way that the let method is handled. The let method will create that todo_list or water variable but it exists more as a method rather than a specific variable. When ruby looks for a variable called todo_list it is actually calling a method which returns our todo list. That method is specified in the block after let(:todo_list) **{ ... }**.

We could have written the update_todo_list method to just use the todo_list variable/method we create with let. However, if we ever wanted to send in a different todo list to that method, we'd have to modify the method body. By using an argument to the method, we can save ourselves some coding later on down the line. This would come in handy if we ever extracted that method out to a helper file.