Oct 9 2012

Lazy User Registration for Rails Apps

enter here

Requiring a would-be user to go through a sign-up process can often be too much to ask. Instead of jumping through your hoops, many people will just hit the ‘back’ button and continue browsing Reddit without ever discovering how cool your app is. In fact, I do it all the time.

Instead, why not treat your visitor as a valued customer with all the rights and privileges of a full-blown user from the get-go? With soft sign-ups, you can do just that.

Delayed registration, soft sign-up, lazy registration—whatever you like to call it, it has one simple purpose: to remove a barrier between your customer and your product.

But I want my users to register!

Even if you want your content to be as accessible as possible, there are still two basic reasons why you’d require registration:

  1. to prevent unauthorized access to private content
  2. to have something to silo user-created content with

The first is a users’s concern; let’s let them decide when to lock down their account with a password.

The second is our focus. Unless you’re Wikipedia, you can’t just let visitors start creating and editing content without any way to sort out who owns what. However, this is a technical constraint that can easily be overcome with soft sign-ups.

The omnipresent anonymous user

The most basic requirement for a soft sign-up is that there must be a current_user at all times. You also want this user to be persistent (i.e., they’re not someone new with every page load). The example below uses Devise, but could easily be adapted to the auth system of your choice.

There are three methods here:

  • anonymous_user_token returns a unique identifier attached to the visitor’s session. This will persist for the duration of the user’s current session.
  • current_user overrides the default Devise method. It first does what it normally does—returns the currently logged in user—but if one doesn’t exist, it falls back to finding or creating a new AnonymousUser with the unique anonymous_user_token.
  • authenticate_user! behaves like it normally does with Devise, except it’s more lenient and allows an anonymous user.

You may choose to create another before filter method instead of overriding authenticate_user! if you want to restrict an anonymous user in some way other than a registered user.

Another thing you may note is the AnonymousUser class, which inherits from User. This is not necessary, but just one way to differentiate anonymous and registered users. You may prefer to use a role-based system instead.

That’s the meat of it. With those few lines of code, visitors should be able to start using your app like a normal, registered user.

Registering (and keeping all your work)

So now that your users have been able to start using your product right away, they’ve probably found out that it’s really awesome and they want to become a member.

Will they get to keep all the stuff they’ve been working on? Of course!

From a technical perspective, this is more akin to updating their profile in order to change their email and password. But for an end user, it’s magic: They get to transition into registered user-dom whenever they’re ready, while keeping all the stuff there were already doing.

First, we need to tell Devise that we’re going to be overriding the registration controller via routes.rb.

Then we need to override how Devise handles registrations with the RegistrationsController.

Like I said, it’s basically just updating the current_user and re-signing them in. I have a little method on AnonymousUser called register to help do that:

You’ll need to change a little view logic as well. For example, you’ll want to check something like current_user.anonymous? instead of present? to conditionally show your “Sign in” and “Sign up” links.

That’s pretty much all there is to it. With soft sign-ups, visitors can begin using your product right away. If they use your app and want to register, they can transition easily into a full user while retaining all their content.

Image credit: potential past

6 Comments

  1. Michael

    I was giving this a try with Rails 3.2.11 and Devise 2.2.4, but seem to a couple problems…

    first, where is ACCESSIBLE_ATTRS defined and what should it be?

    secondly, find_or_initialize_by_token doesn’t seem to be a valid method on the AnonymousUser model. I get the following:

    NoMethodError (undefined method `find_or_initialize_by_registration_token’ for #):

    • Zac Stewart

      ACCESSIBLE_ATTRS is a constant whose definition is in the imaginary “User” superclass in this example. I use strong_parameters nowadays, but this was a little trick I used to get around having to list out all the common params for each attr_acccessible “role.”

      As for `find_or_initialize_by_token`, it has been deprecated and I would now use `where(…).first_or_initialize`.

  2. Michael

    I decided to put together a fully working demo:

    https://github.com/mwlang/lazy_registration_demos

    Enjoy!

  3. Sam

    It’s a very nice feature but you don’t go deep enough for beginner to understand.

    By exemple you say :
    “You’ll need to change a little view logic as well. For example, you’ll want to check something like current_user.anonymous? instead of present? to conditionally show your “Sign in” and “Sign up” links.”

    How would you implement it?

    I Also had problem with things like : “current_user.has_role? :admin” in my views which where causing exception : undefined method ‘where’ for nil:nilclass in the lib rolify (line 29).

    I solved it by dropping the class AnonymousUser and using the class User instead…

    Could you please elaborate a bit?

    Thank you

    • Zac Stewart

      It’s worth nothing that this is definitely an intermediate to advanced Rails technique, and might be confusing if you don’t have a solid understanding of the framework already. The amount of background knowledge it would take to write this as a beginner tutorial would make it quite a long read.

      That said, I may be able to shed some light on your specific scenario:

      1) By changing via logic I mean that you can no longer check for something like “current_user.present?” to determine whether you have a user signed in, because by nature of what this feature does, there will always be a current_user present. Instead, you will have to determine whether it is an AnonymousUser or a User (the logged in kind), in my case, I include a convenience method, “anonymous?” on both of these classes which responds accordingly.

      2) Because at any given time, a user may be a User or an AnonymousUser, and the point of this feature is that you want to treat them the same, each must have the same interface: if you have a “has_role?” method on your User, you will have to define “has_role?” on AnonymousUser, even if it just always returns “false”.

  4. Sam

    thanks !
    It helps a lot

Leave a Comment

Join the discussion. Do not worry, your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>