Case-insensitive Finder Methods in Rails

When you’re creating a user authentication system you want your usernames to be case-insensitive upon creation and on authentication too. Rails makes this super easy with its built-in validators and has_secure_password but it fails us when authenticating users. We want our passwords to be case-sensitive but we only care that users type in the right characters for their usernames and not whether they capitalized them the same way as when they signed up. I ran across this problem today and decided scopes are the way to go. There are plenty of suggestions on how to do case-insensitive searches out there but in the end all you need is a simple scope. Here’s how and why.

Let’s say you have a user that has signed up for your service as faKeUser with pAssW0rd as the password. When they sign in we want them to be able to enter fakeuser, FAKEUSER, or any other variation of that capitalization so long as all the letters appear in that order. However, Rails find_by and other finder methods are case-sensitive. One way to deal with this is in the database but I want to focus on a pure code solution.

Option 1: Lowercase before save

Don’t do this with usernames. You should assume that what the user entered as their username is what they intend their username to be displayed as. That’s all I have to say about that one.

Option 2: Modify ActiveRecord::Base

You could do this but it’s a pain in the ass. There are simpler ways.

Option 3: User .where()

Now we’re getting warmer. This would be my second choice if it weren’t for scopes. But I won’t go into this one either because we can take this solution and roll it into the next oneā€¦

Option 4: Use a scope

Scopes are ideal for cases like this. Rather than creating modules and messing with ActiveRecord::Base we can just add a simple method to our model. So, we have a users table that is case-insensitive on create which means that if a username fakeUseR exists, another user can’t pick the name FaKeUser. Great. Now when the user logs in we want to be able to verify the username exists regardless of the capitalization they use. So here’s the scope:

1
scope :ci_find, lambda { |attribute, value| where("lower(#{attribute}) = ?", value.downcase).first }

This allows us to use a case-insensitive find for any column of the User model. To use it to find a user, we can do something like this:

1
user = User.ci_find('username', params[:username])

And there you have it. Simple case-insensitive finders for your models. If I started needing this in more than two models then I might consider creating modules or messing with ActiveRecord::Base.

Web Development

« API Sessions with Redis in Rails Authorize Users Based on Roles and Permissions without a Gem »

Comments