Rolling Windows and Sticky Bytes

<tagline type="clever"/>

Magic Timestamps and Join Tables

…don’t mix.

If you’re creating a join table in Rails for a has_and_belongs_to_many relation, be sure that you don’t include t.timestamps to set up magic timestamps. When setting up the timestamps fields in the database (created_at, etc.), Rails establishes a NOT NULL constraint and since the timestamp magic doesn’t happen, adds across the join will fail.

No Comments »

Devise I18n Localization

Turns out that even if you install a translation for Devise, it only translates e-mail strings and flash messages — it does not include translations for the views (e.g. the Sign In page, the Register page, etc.). You have to use rails:generate to dump the built-in views to file so you can override them and replace all the strings with I18n t() calls yourself.

Not impressed right now.

I wonder if I should take some time to update the views in Devise itself, move the English translations into the locale file and submit a pull request…

No Comments »

Automating Elastic IP Assignment Redux

As a follow-up to my previous post on Automating Elastic IP Assignment, I went back and installed the package from my distro’s repository and updated my script with the new binary locations. I’m using Ubuntu on my development instance so this post will be very Ubuntu-centric.

The ec2-api-tools package is in the multiverse repository which is not enabled by default on Ubuntu so before I could get started, I had to enable the multiverse repository as documented in the Ubuntu documentation on Repositories:

  1. Edit /etc/apt/sources.list
  2. Uncomment the four lines for the multiverse repository
  3. Run apt-get update to fetch the repository index

Once I had the multiverse repository enabled, installing the package was as easy as apt-get install ec2-api-tools. The package payload is deployed into /usr.

The updated elastic-ip script is below.

/etc/init.d/elastic-ip

#!/bin/bash

# Set environment variables
export JAVA_HOME=/usr

export EC2_HOME=/usr
export EC2_BIN=$EC2_HOME/bin

export EC2_PRIVATE_KEY=/root/.ec2/keyfile.pem
export EC2_CERT=/root/.ec2/certfile.pem

export EC2_URL=https://ec2.us-east-1.amazonaws.com

# Associate the elastic IP with the instance
$EC2_BIN/ec2-associate-address -i i-deadbeef 10.99.99.5
No Comments »

Automating Elastic IP Assignment on Amazon EC2

For those unfamiliar, an Amazon Elastic IP is basically a static IP address for Amazon virtual machines (EC2 or VPC). It’s a dedicated IP for your instance so you don’t have to muck around with updating the IP address you want to connect to every time.

The only catch? If, like me, you shut down your dev machine when it’s not in use to avoid paying for idle CPU time, you will have noticed that the association between the instance and the Elastic IP evaporates on shutdown.

The solution in my case was to install the AWS API SDK on my instance (I put it into /usr/local/aws by hand, not realizing there’s a package for it in my Linux distro’s repository and was too lazy to go back and fix it), download my X.509 certificate to the instance and drop the following script into /etc/init.d:

/etc/init.d/elastic-ip

#!/bin/bash

# Set environment variables
export JAVA_HOME=/usr

export EC2_HOME=/usr/local/aws
export EC2_BIN=$EC2_HOME/bin

export EC2_PRIVATE_KEY=/root/.ec2/keyfile.pem
export EC2_CERT=/root/.ec2/certfile.pem

export EC2_URL=https://ec2.us-east-1.amazonaws.com

# Associate the elastic IP with the instance
$EC2_BIN/ec2-associate-address -i i-deadbeef 10.99.99.5

The script sets up the API environment, then calls ec2-associate-address to associate my instance with its elastic IP. Once the script was installed, I added a call to it in /etc/rc.local and now whenever my instance boots up, it automatically associates itself with its elastic IP.

Of course there are lots of ways you could implement equivalent functionality (I saw another post on the EC2 forums where they used route53 for dynamic DNS updates instead of using an Elastic IP) but this was the way I decided to approach it and it works well.

References:

https://forums.aws.amazon.com/message.jspa?messageID=229486

http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/SettingUp_CommandLine.html

http://docs.amazonwebservices.com/AWSEC2/latest/CommandLineReference/ApiReference-cmd-AssociateAddress.html

No Comments »

Devise Authentication, Internationalization and Mailed URIs

For those who aren’t familiar with it, Devise is an authentication framework for Ruby on Rails. I was initially hesitant to use a plug-in for authentication for a few reasons.

  1. I was worried that the various authentication frameworks represent a “kitchen sink” approach to authentication and I wasn’t sure if I needed all the features. More complexity means higher probability of coding errors.
  2. Rails 3 makes it easy to roll your own via ActiveModel::SecurePassword[1]. I had initially decided based on reading up on Rails authentication strategies that this would be the best approach since it would give me the most flexibility.

Eventually I changed my mind and went for Devise because of the 11 features provided by the Devise toolkit, my site required 5 of them (Database Authenticable, Token Authenticable, Recoverable, Rememberable, Validatable) and most of the rest would be “nice to have”. Rather than spending time and effort rolling my own version of each of these, it seemed prudent to re-use existing, well tested code.

Having made the leap to Devise, I figured it would be worth writing down my experience.

I was able to install the gem without issue by adding it to my Gemfile and using bundler in the usual way (as documented in the “Getting Started” section on the Devise github page). The “devise install” run went equally smoothly (n.b.: only because I followed the instructions closely).

At this point, the next step is to add Devise to your model and this is where I started to run into some difficulties. This wasn’t because of any problem with Devise itself but because in my original design, I had specified the database field for e-mail addresses as “email_address” instead of “email” (which is what Devise expects). When I created a migration to rename the database field, I found that the indexes on the table had names too long to rename!

Lesson Learned #1: Do not accept the generated index names for database migrations if you are indexing a lot of fields in a table.

After I determined that I would have to fix up the index names, I modified the migration for the database field rename so that it would drop the index, rename the email_address field to email, then re-add the index with a shorter name. This worked out reasonably well.

I had to futz with the generated model attributes a bit (editing some attr_accessible entries and customizing the devise features) but the modifications were minor.

The next step was to try to add an authentication call to a page. There are two (easy) ways to do this:

  1. Apply authentication to all methods in your controller. To do this, add before_filter :authenticate_user! to the controller (somewhere inside class UsersController < ApplicationController but outside of any individual controller method).
  2. Apply authentication to individual methods in your controller. To do this, add authenticate_user! to the controller method you want to protect.

Which strategy you use will depend on whether the whole object needs to be behind the “authentication wall” or if some actions on an object can be run by anyone (e.g. if your site has a “posts” controller, you may want the software to allow anybody to view a post but only allow authenticated users to comment on one).

Note that (as stated in the documentation), the name of the method will be authenticate_model!, so if you have devise on your users model, the call will be authenticate_user!, if you are using an “admins” model, it will be authenticate_admin!, if you are using a “commenters” model, it will be authenticate_commenter!, etc.

This bit me at least once during my experimentation, with the software throwing a missing method error.

Lesson Learned #2: Make sure you’ve specified the right model name in authenticate_model! calls

Once I had the dev site putting up an authentication wall, I moved on to getting a reset link sent out for a test user and getting the password set… and that’s when all hell broke loose.

I clicked the “reset password” link and the e-mail was sent. All good!

When I visited the generated reset link I was… forwarded to a login page. I spent a couple of hours trying to diagnose the problem, struggling with it until I checked the logs and remembered that my application is internationalized. This turned out to be important.

On each page visit, the site will do its best to make sure it serves you in the appropriate language. It does this by taking the following steps:

  1. Enumerate the available locales in the application
  2. Ask your web browser for your most preferred locale based on the list of available locales
  3. Set the locale to a) the locale specified on the URI (to ensure all URIs are RESTful), b) the preferred locale specified by your browser or if all else fails, c) the default locale
  4. If the locale has changed, redirect the browser to the current URI with the correct locale specified as a parameter

Naturally it’s important to make sure that the locale is set correctly on every internal link to avoid a constant barrage of phony redirects. Back in the early days of development on the application, I made sure this would happen by setting the default url options in my application controller to include the current locale like so:

def default_url_options(options={})
  { :locale => I18n.locale }
end

Essentially what was happening when a user was hitting the “reset” URI provided in the e-mail message was that they were reaching a page without a locale set and being redirected. Because the software strips single-use security tokens from redirect URIs, they were being forwarded to a login page instead of the password reset page.

So why didn’t the URIs being sent by Devise have the locale set? Essentially because ActionMailer (which is used to generate the mail messages) does not inherit from the application controller, so the default url options specified there do not apply.

Lesson Learned #3: You have to set the default locale for ActionMailer URIs if you have internationalized your application

To fix the locale problem in my e-mailed URIs, I had to add a line to set the locale to the ActionMailer settings for my environments:

config/environments/development.rb
config.action_mailer.default_url_options = { :host => 'dev.example.com:3000', :locale => I18n.locale }
config/environments/production.rb
config.action_mailer.default_url_options = { :host => 'example.com', :locale => I18n.locale }

Once I had set the ActionMailer default URL options and restarted the application, password reset links worked like a charm and I was able to reset the password for a test account and log into the site with it.

3 Comments »