Extending Acts_as_Authenticated


acts_as_authenticated is a pretty sweet plugin for managing users. But extending it proves to be a bit of a challenge. Why? Let’s drill through the details and see.

Imagine your local library contracts you to design their new library system in Rails. Among other things, library users need to have a library card number, name, and an address. Having already decided to use acts_as_authenticated, allowing users to sign up and log in and so on is easy!

But where do you graft on the extra fields?

Your first attempt might be to modify the User class. After all, it’s the core class of the system. So you might write a migration like so:

class ModifyUsers < ActiveRecord::Migration
def self.up
add_column("users", "library_card_number", "integer")
add_column("users", "address", "text")
add_column("users", "address", "first_name")
add_column("users", "address", "last_name")
end

def self.down
remove_column("users", "library_card_number")
remove_column("users", "address")
remove_column("users", "first_name")
remove_column("users", "last_name")
end
end

Since the library database is a legacy system, you also need to create a full_name field that combines the first and last names. So you open up your user.rb model file and add the following:
def full_name
self.first_name + " " + self.last_name
end

You create a few pages, add some test users, and tada! it works!

A few days go by, and the librarians, based on your astounding success in such a short time, decide they want to split the address into a number, street, city, province/state, postal code, and country. And a field to return the full address. You hack up a migration, and add a field to the user class. Tada! Another few days go by, and they decide to add more fields and more “virtual” fields. And more. And more.

A year later, they decide they want to move to restful authentication. When you open up the User class to extract the business-logic fields and methods, you can’t even tell what’s what! Despite all your efforts, all the new fields and “virtual fields” and methods are mixed up with the core acts_as_authenticated user code!

This is high code coupling. Your business-logic class is tightly coupled to your authentication plugin class. Not good. What’s worse, the database fields are also tightly coupled. What’s a Rails developer to do?

Enter single table inheritance. Rails allows you to create subclasses (in fact, a whole list or tree of them) that all share some common attributes, but also have some separate attributes. This won’t fix the database coupling, but at least it’ll help with the code coupling. In this case, it’ll allow you to move all the extended library business logic to its own class.

So how do we do it?

First, write up a separate model class called LibraryPatron that extends user, like so:

class LibraryPatron < User
def full_name
first_name + " " + last_name
end

def full_address
#...
end
end

Next, use our previous migration to add the new fields to the Users table–with a few additions–like so:
class ModifyUsers < ActiveRecord::Migration
def self.up
add_column("users", "type", "text")
add_column("users", "library_card_number", "integer")
add_column("users", "address", "text")
add_column("users", "address", "first_name")
add_column("users", "address", "last_name")
User.connection.execute "UPDATE users SET type = 'LibraryPatron'"
end

def self.down
remove_column("users", "library_card_number")
remove_column("users", "address")
remove_column("users", "first_name")
remove_column("users", "last_name")
end
end

There are two new things here: a type column, and a SQL command. The type column tells Rails that multiple classes (which are all related) are sharing this table. This column specifies if the user is a regular User or a LibraryPatron. The second part, the SQL statement, updates all users to be LibraryPatrons.

That’s it! If you have view code like this, it’ll work (as expected):
<h1><%= current_user.full_name %>'s Details</h1>
<p>Library Card #: <%= current_user.library_card_number %></p>
<p>Address: <%= current_user.full_address %></p>

If you want to be sneaky, you can print out current_user.class. It’ll be set to LibraryPatron. Why? Because all the users have their type set to LibraryPatron! Notice that you didn’t need to modify the plugin to “tell it” that the new class is a LibraryPatron! You leveraged the existing acts_as_authenticated functionality, and you also get to isolate your fields and methods into a separate class. (And you have a migration, so you can easily tell which fields are not part of the plugin).

It’s not the best solution, but it at least decouples the code!

How do you extend acts_as_authenticated? Do you have a solution that also decouples the data coupling? Let us know in the comments!


Forums Discussion: http://www.railsrocket.com/forums/topic.php?id=6

Tags: ,     Posted in Development, Gems and Plugins

Rate this article:
1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 3 out of 5)
Loading ... Loading ...

Further Reading

Leave a Reply