Reputation: 1267
I'm new to Ruby programming, but I think I more or less get the point of a symbol (an immutable value, takes up less space, sort of an enum, etc.) Here's what's bothering me -- from a Rake test in the Hartl Ruby on Rails tutorial:
describe "something" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in user }
...
end
It makes sense to me to call FactoryGirl.create(:user)
-- here we are passing a sort of flag (:user
) to FactoryGirl to tell it what kind of object to make (or, more accurately, what variables to set, since it only creates User objects). But in let(user)
, user
is a variable, not a symbol (or at least it should be). I can only assume that the let
method is secretly swapping out the symbol :user
for a variable of the same name -- but why? Wouldn't let(user)
be a much more intuitive syntax? What am I missing here?
Also, are there other places where this pattern occurs in Ruby? So far I've only seen it in rake tests.
Upvotes: 0
Views: 462
Reputation: 6419
A few minor corrections / clarifications:
I've glossed over some of the details of the let() stuff. The real docs are here:
https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
In my personal experience this particular pattern is nc, but unit tests tend to be different from regular code since the idea with them is to exercise the code, not to solve an actual problem. To do that you tend to want to vary one thing at a time so things can get repetitive - but ruby provides lots of tools to cut down on typing and still keep things readable.
EDIT: See comment for examples where this pattern is used. It was right under my nose... :)
Upvotes: 0
Reputation: 376
Like Dave already mentioned, the code provided is not a rake task but rather a RSpec specification.
But let me focus on the real problem here.
Symbols are a little hard to grap at first if you never have contact with any other language that have a similar feature. Some language call it Atom.
http://en.wikipedia.org/wiki/Symbol_(programming)
The idea behing a symbol is to provide a primitive type that is humanly readable but computationally cheap.
In Ruby, when the compiler/interpreter sees a symbol, it creates an object of type Symbol and store it in memory. In ruby, symbols are singletons so any other use of the same symbol returns the exact same object, which makes it really cheap in terms of space and also really cheap to compare, since you can just compare the memory address instead of the content.
For example, if you compare two symbols, like this:
:foo == :foo
You're pretty much comparing the same object, meaning only the memory addresses needs to be compared.
Now, when you compare two strings:
"foo" == "foo"
It creates two instances of String with the same content, and needs to compare each byte of the string to make sure they are equal.
This property makes Symbols really good for identifiers or keys in a hash.
Now, to RSpec.
Let's take the following example:
describe Authenticator do
let(:user) { Factory.create(:user) )
it "authenticate" do
auth_user = subject.authenticate(user.login, user.password)
auth_user.should == user
end
end
Factory.create takes a symbol as a identifier of the factory to use. You need to define the factories yourself, so really its just a name. You could use a string but it's cheaper and best practice to use symbols but to be honest, it wouldn't make much difference unless you're calling Factory.create million of times.
The let is not defining a variable, it's actually defining a method that does a few things:
This allow for lazily creating the objects, only when needed, and to allow restricting any state change to the current specification.
So, bottom line is: RSpec is using the symbol as a identifier for a method name that will be generated to abstract away certain things, making your life easier. RSpec is nothing but a BDD domain-specific-language that uses meta-programming to build a test-suite.
The same behavior could be achieved with the following test case:
class AuthenticatorTest < Test::Unit::TestCase
def user
return @user if @user
@user = Factory.create(:user)
end
def subject
return @subject if @subject
@subject = Authenticator.new
end
def teardown
@subject = nil
@user = nil
end
def test_authenticate
auth_user = subject.authenticate(user.login, user.password)
assert_equal auth_user, user
end
end
Note that you probably shouldn't write a test case like this, but it (roughly) illustrates what RSpec does.
I hope that helped.
Upvotes: 2