Announcing graffed.com, the new way to track your body shape, not just your weight

I’ve hinted a few times about a Flex application I’ve been working on. Well now is the time to reveal the app, graffed.com.

Graffed is a site that lets you track your body shape instead of just your weight. You can see in real time your silhouette overlaid with your target body, showing you where you’ve lost weight and shaped up. It was a long hard slog to write but I’ve been very impressed with the Flex technology (Flash for programmers if you will) and it’s taught me a lot. For a limited time, premium accounts are totally free so sign up and give it a go:

The new site in all its glory

The new site in all its glory

Ruby on Rails SMTP Mailer – sending mail

This is a topic that has been covered a few times but took me a while to pickup from a couple of different sources. A note about my tech posts, these are generally designed as references rather than tutorials so they will move fast and assume some basic knowledge.

Environment Settings

The first point of call is setting up your environment variables to tell Ruby your SMTP credentials. Add the following to the bottom of your conf/environment.rb file:


# SMTP configuration
config.action_mailer.delivery_method = :smtp

ActionMailer::Base.server_settings = {
:address => YOUR_MAIL_SERVER,
:port => 25,
:user_name => YOUR_USERNAME,
:password => YOUR_PASSWORD,
:authentication => :login
}

Replacing YOUR_MAIL_SERVER with the address of your mail server, typically mail.DOMAIN.com or smtp.DOMAIN.com. Replace YOUR_USERNAME and YOUR_PASSWORD with the username and password of a valid mail user on the server, the mails will appear to come from that person to your recipients.

Generate the mailer

Next we’ll generate the mailer class, using the very handy generate command. From the root directory of your project run:

script/generate mailer postoffice

This will generate a new class, postoffice.rb under the app/models directory containing the class called Postoffice. This class is responsible for building up email content and setting recipients. For each email you wish to send, you will typically create a method in this class. You can then automatically call Postoffice.deliver_METHOD_NAME and through some Ruby magic it will be sent. So, let’s say we want an email after someone has registered for your new game-changing site. We’ll create a register method which takes an email address and the name of the user in postoffice.rb:


class Postoffice < ActionMailer::Base

def register(email, username)
@recipients   = email
@from         = "mysender@mydomain.com"
headers         "Reply-to" => "mysender@mydomain.com"
@subject      = "Welcome to MyDomain"
@sent_on      = Time.now
@content_type = "text/html"

body[:username]  = username
body[:email] = email
end

end

Adding some templates

This method should self explanatory. The only point to make is the last two lines, they make the values username and email available in the email template files we are about to create. We create two files under app/views/postoffice, register.text.plain.erb and register.text.html.erb. Obviously one is for plain text email clients, the other for HTML. It is best practice to have both formats, there are still plenty of people who only read plain text (mainly sysadmins in my experience, I always though that was ironic). So as an example, register.text.html.erb could like like:


<p>
Welcome <%= @username %>,
You've just joined MyDomain.com, you must be significantly smarter than the average populous.
</p>

So, the final stage, how do we call the method to send the email. Well, in the main register method of your app (not the one we created above), at the point when you want to send the email place:

Postoffice.deliver_register(email, username)

where email and username are variables you have set up of course, referring to the email and username of the recipient of your email. One final note, you will need postfix running to test this locally. To start postfix on your machine (assuming you’re developing on a Mac), run:

sudo postfix start

at a terminal. Hope that helps.

restful_authentication and the Apache ACL

Wow, it didn’t take long after setting up this blog to find a coding issue that warranted blogging about. This is a real slap forehead WTF moment as well. I’ve been using the restful_authentication plugin for rails to handle my user registration and login. This is for a new site which will be announced shortly.

The site is currently sitting on a live server somewhere, so I decided to set up an Apache ACL while we have a closed beta test. An ACL is a way to get Apache to use basic authentication and log people in against a password file on the web server. It’s a quick way to lock down a site for a bit. See the bottom of the post for how to set up an ACL.

I sent around the ACL username / password to a few people, let’s say the username was pingu for argument’s sake. Well, the first person to use the site not only logs in to the ACL with pingu, but used it as a username to register on my rails app. Big deal you might think, I can’t imagine it would hurt. That would be your first (and my) mistake.

Somehow, the ACL interacted with restful_authentication so that anyone logging in to the ACL was also logged in to pingu’s rails account. I clearly don’t know enough about http authentication or the restful_authentication plugin but that is definitely unintended circumstances. Changing the ACL username put everything back to normal. Let’s hope no-one registers with the new ACL username!


edit – Setting up an ACL

I notice a few people got here after searching for ACL details so here’s a quick guide to setting one up. I’ve assumed your Apache instance is installed under /usr/local/apache, if it’s not then please replace /usr/local/apache with the appropriate path in the commands below.

Create a password file using htpasswd

The first step is to create a password file which defines usernames and passwords. This is done using the Apache supplied tool htpasswd (typically this lives in /usr/local/apache/bin). A good place to put your password file is /usr/local/apache/passwd/, although it can go anywhere Apache is able to access. Note that although this file has basic tampering precautions, typically MD5 hashing, it is not entirely secure and should be kept very secret from the rest of the world. So, move into your desired password directory, in our case /usr/local/apache/passwd/ (create it if it does not exist), and run:

/usr/local/apache/bin/htpasswd -c passwords myusername

This will overwrite any existing password files called “passwords” so ensure it doesn’t already exist. The c switch means create, passwords is the name of the file and myusername is the username of a user to add. It will prompt you for a password twice which will be set on the user “username”. To add new users, just remove the c switch (forgetting to do this will overwrite all previous users), i.e. run

/usr/local/apache/bin/htpasswd passwords anotheruser

Configuring a Location with AuthType

Typically an ACL is applied against a Location under a VirtualHost element. So, let’s say you want to lockdown an entire site for beta testers only. In the Apache configuration file httpd.conf (typically under /usr/local/apache/conf), find your VirtualHost element for the site you want to lockdown and add:

<Location "/">
    AuthType Basic
    AuthName "Authorised beta testers only"
    AuthUserFile /usr/local/apache/passwd/passwords
    Require user myusername
</Location>

The “/” specified by location means everything under the root directory of the site is password protected. If you just had some content, e.g. under a directory called protected, you could specify “/protected”. AuthName is the title of the prompt that the browser will bring up, AuthUserFile is the password file we created earlier. Require user specifies which users are allowed through. If you would like to allow all users in the password file through, you can specify Require valid-user instead. Restart Apache and every time you try and access something under the Location specified, your browser will prompt you for your username and password.