I’ve recently been working on a project for [client name withheld pending approval from legal dept] where we’re storing a lot of data in SimpleDB and when it came time to add an authentication layer, I naturally turned to Devise because I’ve used it before and it’s a great fit for the needs of this project. Unfortunately Devise doesn’t work with SimpleDB right out of the box. It took a little work and the creation of a new gem to get things working, but now it should be easy to get them to play nicely together in any project.
First let’s generate a new sample project. I’m going to use ‘-O’ to skip ActiveRecord since we’ll be using SimpleDB, and ‘-T’ to skip Test::Unit since I prefer Rspec.
$ rails new simpledevise -O -T
After the project is generated add this to the Gemfile
:
gem "aws-sdk", "~> 1.8.0"
gem "devise", "~> 2.2.0"
gem "orm_adapter_aws", "~> 0.0.1"
Then install the gems by running:
$ bundle install
Then generate a devise config
$ rails generate devise:install
Then you need to edit config/initializers/devise.rb
and remove the following line
require 'devise/orm/false'
The AWS adapter is already being loaded since we have it in the Gemfile
.
We also need to create an initializer so we can setup the AWS library with the correct credentials. Put this into config/initializers/aws.rb
AWS_SECRET_KEY_ID='abc'
AWS_SECRET_KEY ='123'
AWS.config({:access_key_id => AWS_SECRET_KEY_ID, :secret_access_key => AWS_SECRET_KEY})
AWS::Record.domain_prefix = "simpledevise-#{Rails.env}-"
Of course, you’ll need to tweak that with your own keys, and you may want to change the domain prefix.
Before getting straight into the authentication piece we first need to have a resource to protect. Really we just need to route the root_path
to a valid controller action. So, we’ll just create a controller for that.
$ rails g controller static_pages home
Also be sure to delete public/index.html
.
Now edit config/routes.rb
and add this :
root :to => "static_pages#home"
At this point you should be able to start your local server and see some visible progress.
$ rails s
And then when you visit http://localhost:3000/ you should see a very basic home page.
User
model and authentication routesFinally! We can get to the part where we generate a User
and use it to sign up or log in.
$ rails g devise user
If you were paying attention to the output of that last command, you may have noticed that it didn’t generate a model for us, it just added a route. That’s because we don’t have a traditional ORM hooked up to Rails. So, let’s just create a new file at app/models/user.rb
.
class User < AWS::Record::Model # Setup the attributes for the model # Many of these are managed by Devise
## Database authenticatable string_attr :email string_attr :encrypted_password
## Recoverable string_attr :reset_password_token datetime_attr :reset_password_sent_at
## Rememberable datetime_attr :remember_created_at
## Trackable integer_attr :sign_in_count datetime_attr :current_sign_in_at datetime_attr :last_sign_in_at string_attr :current_sign_in_ip string_attr :last_sign_in_ip
## Confirmable # string_attr :confirmation_token # datetime_attr :confirmed_at # datetime_attr :confirmation_sent_at # string_attr :unconfirmed_email # Only if using reconfirmable
## Lockable # integer_attr :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts # string_attr :unlock_token # Only if unlock strategy is :email or :both # datetime_attr :locked_at
## Token authenticatable # string_attr :authentication_token
timestamps
# Include some validation funcitons needed by Devise include ActiveModel::Validations include ActiveModel::Validations::Callbacks
# Some methods to fake out Devise def self.validates_uniqueness_of(arg1,arg2) end
def save(*args) super() end
# Now include devise model methods and 'configure' devise for this model extend Devise::Models # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable # :recoverable, :rememberable, devise :database_authenticatable, :registerable, :trackable, :validatable
end
Whoa! That’s a lot of code. Let’s step through and look at what’s going on.
First we are extending the AWS::Record::Model class to handle the interface with SimpleDB. Then since SimpleDB doesn’t have a static schema we have to declare the attributes and their types in the model itself. I’m only using some of the attributes that you’d normally find in a Devise model, because I only need some of the features. The others are still there, just commented out, and you could use them if you need them.
Next we include some validation methods from ActiveModel and ActiveRecord, then we have a couple of helper functions. One to keep Devise from complaining that User doesn’t respond to validates_uniqueness_of
and one to handle the fact that Devise tries to save the record while skipping callbacks part of the time. (Dont’ worry, in the next installment we’ll look at handing the uniqueness validation.) And finally we include and configure Devise.
At this point we need to do the equivalent of rake db:create; rake db:migrate;
for SimpleDB. Fire up a rails console and run this:
> User.create_domain
Now, we’re finally ready to try to sign up for our app. Visit http://localhost:3000/users/sign_up, fill out the form, and click the ‘Sign up’ button. If everything went according to plan you should have been redirected to the temporary home page, and in a console you should get this:
> User.count
=> 1
You can find the code for this example on Github.
Also on Github is the orm_adapter_aws gem.
In future posts I’ll be looking at adding Rspec and doing some testing, as well as extending this example with a basic user management area, administrative privileges, general cleanup, making it look nicer, and deploying it to Heroku. Big thanks to [client name withheld pending approval from legal dept] for allowing me to build and release the orm_adapter_aws gem in the course of the project that I’m doing for them.