You may have heard (or even said yourself) that Rails / Ruby has too much magic…
- But where does that idea come from?
- What is Rails magic exactly?
- And what can you do to dispel that magic?
Here’s what I think:
When something feels like magic it’s because there is something you don’t know, there is some information missing.
It’s like a magic trick, if you know the trick there is no magic.
In software development, knowing the tricks is equivalent to having an understanding of how things really work.
Let’s See An Example!
This example is about BCrypt
, a hashing algorithm used to store passwords securely. It is implemented in Ruby via the bcrypt
gem.
Here’s how to use BCrypt
to hash the word “testing”:
require 'bcrypt' BCrypt::Password.create("testing") # "$2a$10$3o.xrISG8fqKfzKqDpgKn.3cwjFV//9C9cZ7MuK5S9sNKFBivB7YG" BCrypt::Password.create("testing") # "$2a$10$BFb/e3cWuAIyelcmkfGtE.GTifXHxig4IzVbdZs9C1zSpF/Jr6c3W" BCrypt::Password.create("testing") # "$2a$10$Q952BVgM783Y4kPvwvxwC.CS2DWUX9jQuDoE6q2R041kQY0ZWPnSe"
Notice how you get a different hash every time, but if you use something like MD5 (don’t!) or SHA1 you will always get the same output for a given string.
Why is BCrypt
behaving in a different way?
I will explain what’s going on in a second, but first let’s look at how you compare two bcrypt hashes, one coming from the database & one from user input (like a form or something like that).
BCrypt::Password.new(@user.hash) == params[:password] # true
The part on the left (BCrypt::Password.new
) is a BCrypt
object, which takes the hash stored in the database as a parameter.
The part on the right (params[:password]
) is the plain-text password that the user is trying to log in with.
In this case we assume that a right user / password combination is being used.
So why is this evaluating to true
?
Well, to understand this you need to know two things:
- BCrypt uses something called a “salt”, which is a random value used to increase security against pre-computed hashes. The salt is stored in the hash itself.
- In Ruby many things that look like syntax are just methods. This is the case for the double equals operator (
==
).
Knowing that, then I can tell you that BCrypt
defines its own ==
method, which knows how to extract that “salt” value so that it can take that into account when comparing the passwords.
In other words:
BCrypt#==
takes the “salt” value from the stored hash.
Then it hashes the plain-text password (the user input) using this salt so that both hashes will be identical if the password is valid.
If you were to look at the source code it would look something like this:
def ==(secret) super( BCrypt::Engine.hash_secret(secret, @salt) ) end
Remember that super
will call the same method (in this case ==
) on the parent class.
The parent class of BCrypt::Password
is String
.
It’s All About Methods
One important thing to understand is that other than a few keywords & a few syntax elements (like parenthesis), its all about classes & methods.
If you know what class you are working with (which you can check using the class
method) you will always be able to know what operations (methods) are available.
But sometimes we just have a method call without an object.
This often means the method is defined in the current class, but that’s not always the case.
Example:
puts 123
Where is puts
defined? Let’s find out:
method(:puts).owner # Kernel
Another thing that may be confusing is metaprogramming, because with metaprogramming you can create, modify or even remove methods during program execution. These things make the code more opaque & obscure.
You can reduce the confusion by being aware of what metaprogramming methods are available & how they work.
Summary
You learned how a deep understanding of how things work can help you dispel any “magic”, write better code & make you a better developer.
If you want to improve your Ruby skills check out my book, Ruby Deep Dive, which is designed to bridge the gap between the fundamentals & more advanced concepts 🙂