Ruby/Rails

Flowcoder: Share, refactor, and tweet code snippets

Posted by Trevor in General, Ruby/Rails on February 08, 2010

I just realized that I never posts about our entry into the Rails Rumble last year.

Flowcoder: An Evolution of the Code Snippet Site

Coded and designed by @gbuesing, @scottymac, and @trevorturk for @railsrumble in 2009.

Flowcoder has what you would expect from a code snippet side: multiple language support, raw code view, and support for embedding on other sites.

What was missing for us and what we really wanted was a site that featured not just the code, but the people creating the code. Just as we glean interesting tidbits about people's lives from Twitter, we wanted to learn from our favorite coders by being able to follow the kind of code snippets they create. We wanted to share our own code snippets and have others refactoring them: to fix, optimize, and make them better, and learn in the process. And we wanted to be kept informed: when your code is refactored on Flowcoder, you'll see an @reply from @flowcoderbot with some information and a link. This closes the loop in a casual, low bandwidth fashion and highlights the advantage of using Twitter as both an identity and notification system.

Installing Varnish with nginx, Passenger, and Monit on Ubuntu 8.10 intrepid

Posted by Trevor in Ruby/Rails on October 22, 2009

Varnish is a state-of-the-art, high-performance HTTP accelerator. I first came to know about it thanks to Heroku, where they use it to provide built-in HTTP Caching.

As their docs describe, using Varnish is easy:

# This tells the cache (Varnish) to serve the page for 300 seconds (5 minutes).
 
class MyController < ApplicationController
  def index
    response.headers['Cache-Control'] = 'public, max-age=300'
    render :text => "Rendered at #{Time.now}"
  end
end

Simply setting the "Cache-Control" header like so allows you to serve up a page extremely quickly because requests completely bypass your application logic, database, and all of that related overhead and read straight from the cache.

If you're serving up the same page to all visitors, then setting up Varnish HTTP caching is a no-brainer. If you're serving up pages that are mostly the same for all users but have a custom header or something, you can still take advantage of the caching speed boost if you're willing to investigate the ins and outs of ESI and/or serving partials with Javascript. If you're serving pages that are different for each user, then you're out of luck :)

So, using Varnish for HTTP acceleration is great, but unfortunately for me the version of Ubuntu (8.10 intrepid) that we're using has a painfully out of date package in aptitude. If you're already running Varnish, you can check to make sure you're using a relatively recent release by running varnishd -V. If you see anything less than 2.0.4, you should seriously consider upgrading.

Thanks to the helpful people on the Varnish mailing lists, I was able to get things up and running by doing the following:

Note that there’s a new version of Varnish out now. The 2.0.4 tag is still pretty good, but you should check all the available tags and decide if you’d rather use a newer version. If so, you’ll have to adjust the installation instructions found at the top of this post slightly.

Note that the .deb files generated by the “dpkg-buildpackage” command may be different from those generated at the time this post was written. The basic steps still apply, though.

apt-get update
apt-get install subversion autotools-dev automake1.9 libtool autoconf libncurses-dev xsltproc quilt
cd /tmp
svn co http://varnish.projects.linpro.no/svn/tags/varnish-2.0.4
cd varnish-2.0.4/varnish-cache
dpkg-buildpackage
cd ..
dpkg -i libvarnish1_2.0.4-6_i386.deb
dpkg -i varnish_2.0.4-6_i386.deb

You can then use /etc/init.d/varnish to stop/start/restart the service.

But, we're not done yet. Since it took me a while to get the whole app server stack configured, I thought it might help someone else out if I covered the rest of the steps it took to get Varnish working properly with my Ruby application.

You can get nginx installed with Ruby Enterprise Edition and Passenger by following the excellent documentation on the Phusion website.

I might suggest, however, doing the installation like this in order to get the latest (secure) version of nginx. Even before that, you'll probably want to install Ruby Enterprise Edition, which I like to do the old fashioned way.

You may also be interested in this example nginx init.d config, which allows you to use nginx as you would if installed from a package. Make sure to consider doing some kind of log rotation with this kind of setup as well.

...

Now that you're using the fantastic nginx web server, you'll want to configure it to listen to port 8080, so that Varnish can listen to port 80 and forward any requests that aren't in its cache to the backend server (nginx). I'll just post the complete config we're using, and you can pick out the relevant details for your case.

Some interesting things to note is that this setup is configured to use Sinatra, the lightweight Ruby web framework. If the application throws an exception, we're routing to the /error page. We're also using various other settings that you may or may not agree with. The important part with regard to Varnish and nginx cooperating, however, is that you set nginx to listen to port 8080 in your config file. My config is located in /opt/nginx/conf/nginx.conf:

worker_processes 1;
pid /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  access_log /var/log/nginx_access.log;
  error_log /var/log/nginx_error.log;
  passenger_root /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/passenger-2.2.5;
  passenger_ruby /opt/ruby-enterprise/bin/ruby;
  passenger_max_pool_size 10;
  include mime.types;
  default_type application/octet-stream;
  sendfile on;
  tcp_nopush on;
  keepalive_timeout 65;
  gzip on;
  gzip_comp_level 2;
  gzip_buffers 16 8k;
  gzip_disable "MSIE [1-6]\.";
  gzip_proxied any;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  server {
    listen 8080; # the important part!
    root /var/www/example/current/public;
    passenger_enabled on;
    passenger_use_global_queue on;
    rack_env production; # use rails_env for a rails app
    # serve static files without running more rewrite tests
    if (-f $request_filename) {
      break;
    }
    # disable site via capistrano (cap deploy:web:disable)
    if (-f $document_root/system/maintenance.html) {
      rewrite ^(.*)$ /system/maintenance.html break;
    }
    # expires headers
    location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ {
      expires max;
      break;
    }
  }
}

Now, I'm fairly new to using Varnish, so perhaps someone can advise me about how to better configure things, but I'll cover how I did it. Varnish comes with a few example configs loaded up in /etc/default/varnish. I ended up using the following:

NFILES=131072
MEMLOCK=82000
INSTANCE=$(uname -n)
DAEMON_OPTS="-a :80 -f /etc/varnish/default.vcl -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"

You can review the examples provided and see how you want to go about it, though.

Once you're all set with the general config, you'll need to provide a VCL config. I'm putting ours in /etc/varnish/default.vcl and it looks like this:

backend default {
  .host = "127.0.0.1";
  .port = "8080";
}

# Warning: read the following before using this config:
# http://varnish.projects.linpro.no/wiki/VCLExampleCacheCookies

sub vcl_recv {
  unset req.http.cookie;
}

The first part tells Varnish to forward any requests that aren't in its cache to a webserver running on localhost port 8080. That's nginx!

The second part unsets any cookies that are sent along with any requests. You won't want to do this unless you're serving the same pages for all users for your entire application. If you aren't careful here, you can very easily end up serving pages meant for one user to another. Be careful!

In my case this works fine because we're serving a site with no user-specific actions or views. It's all public. You're case might be different. Perhaps you can cache things like static files (images, etc) or certain areas of your application. Maybe you have a CMS with an admin interface that can't be cached, but with publically viewable pages that could be cached. I dunno. In any case, you should definitely read more about this on the Varnish wiki.

Finally, since we're using Monit to monitor the health of our systems, I'll throw in an example config that covers SSH, nginx, and Varnish. This Monit config would email you using a Google Apps Domain if there was a problem. You probably wouldn't want to use this as-is, but it should serve as a decent starting point for you to create your own. We've got the config in /etc/monit/monitrc:

# Alerts
set daemon 120
set logfile syslog facility log_daemon
set mailserver smtp.gmail.com port 587
    username "noreply@example.com" password "sldkjkfdsj"
    using tlsv1
    with timeout 30 seconds
set alert tech@example.com with reminder on 30 cycles
set httpd port 2812
allow example:slkdjlskdjflskjd

# SSH
check process sshd with pidfile /var/run/sshd.pid
start program "/etc/init.d/ssh start"
stop program "/etc/init.d/ssh stop"
if failed port 22 protocol ssh then restarts
if 5 restarts within 5 cycles then timeout

# nginx
check process nginx with pidfile /var/run/nginx.pid
start program = "/etc/init.d/nginx start"
stop  program = "/etc/init.d/nginx stop"
if failed host 127.0.0.1 port 8080 then restart
if cpu is greater than 40% for 2 cycles then alert
if cpu > 60% for 5 cycles then restart
if 10 restarts within 10 cycles then timeout

# Varnish
check process varnish with pidfile /var/run/varnishd.pid
start program = "/etc/init.d/varnish start"
stop  program = "/etc/init.d/varnish stop"
if failed host 127.0.0.1 port 80 then restart
if cpu is greater than 40% for 2 cycles then alert
if cpu > 60% for 5 cycles then restart
if 10 restarts within 10 cycles then timeout

That's all folks!

Passenger with nginx on Mac OS X

Posted by Trevor in Ruby/Rails on September 16, 2009

Setting up nginx with Passenger support turns out to be fairly easy.

Start by making sure you have the most recent version of Passenger, then install the nginx module. This will actually install and compile nginx with the Passenger module enabled, which is handy. Choose the recommended/default options when the installer prompts you.

sudo gem update passenger
sudo passenger-install-nginx-module

Then, open up the nginx config file:

mate /opt/nginx/conf/nginx.conf

Add the following line to the top of the file:

daemon off;

This will prevent the "502 Bad Gateway" error you may see otherwise. I'm not sure why this is necessary, but I read about it here, and it seems to do the trick. nginx specifies that this should only be used for development, though.

Next, find the http { block, which should start around line 15 or so. You'll want to add a server { block within the http { block for each of your Rails/Rack applications, like so:

server {
   listen 80;
   server_name eldorado.local;
   root /Users/trevorturk/Code/eldorado/public;
   passenger_enabled on;
   rails_env development;
}

You'll need an entry in your hosts file if you don't already have one. Simply open up the file:

mate /etc/hosts

...and add lines for each of the apps you plan to run, like so:

127.0.0.1 eldorado.local 

Now, we can set up a launchd item, so that nginx will start up automatically after a system reboot. Create a new plist file by opening it up in TextMate:

mate /System/Library/LaunchDaemons/nginx.plist

...and paste the following code in, which was kindly provided for us by this helpful person:

Then, run the following command to load it:

launchctl load -F /System/Library/LaunchDaemons/nginx.plist

Now, you can reboot your system and make sure it's all working as expected by visiting http://eldorado.local, or whatever address you've configured your application to be on.

I believe this nginx installation will override the existing Apache installation you may have running. This doesn't bother me, so I opened up my System Preferences -> Sharing prefpane and unchecked the Web Sharing box, so Apache is no longer running. If you have any ideas about how to keep both services running cooperatively, please do let me know.

Update: Here are some additionally configuration options I'm using, which I cobbled together from various sources after Googling for things like "nginx, rails, gzip, expires" and such. Their powers combined, and I seem to have a YSlow-approved setup.

Just above your server { block, around line 40, add the following:

gzip on;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6]\.";
gzip_proxied any;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

That should cover gzip well enough.

Then, amend the aforementioned server { block to include some far future expires goodness that takes advantage of the Rails asset_tag helpers:

server {
  listen 80;
  server_name eldorado.local;
  root /Users/trevorturk/Code/eldorado/public;
  passenger_enabled on;
  rails_env development;
  location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ {
      expires max;
      break;
  }
}

These two configuartion tweeks are, I believe, the rough equivalent of the technique previously discussed on this blog for Apache.

Of course, any additional suggestions, comments, or insights you may have would be most welcome. I'm new to this whole nginx thing, but I'm enjoying it so far.

Update: Here's an easy way to reload nginx, if you need to make a change to your conf. Make sure to have the following in your /opt/nginx/conf/nginx.conf file:

pid /var/run/nginx.pid;

Then, you can make an alias for the reload task in your ~/.bash_profile:

alias nr='sudo kill -HUP `cat /var/run/nginx.pid`'

In case you're interested, you can check out my full nginx.conf file for local development here:

http://gist.github.com/191331

Static: a super simple Rails CMS for Heroku

Posted by Trevor in Ruby/Rails on September 11, 2009

Static is a super simple Rails CMS built for Heroku. It supports file uploads to S3, makes image thumbnails, lets you make pages, has an optional admin password, and a customizable application layout that supports erb. It's really easy to install and deploy to Heroku. If you have an S3 account, you can get up and running in under 5 minutes.

I made this little app a few weekends back because it scratched a personal itch of mine. I maintain a few small "static" or "brochure" sites for friends, which means I get to do boring HTML updates whenever they have a new picture or video they want to add. I figured that I could do a little upfront work and let them to do the rest. So, I looked around for a simple Rails CMS, but I couldn't find anything simple enough. Thusly, Static was born.

It doesn't do much for you, but that's the point. You get a really simple little Rails app that lets you add pages, upload stuff, and wrap everything in a layout that can be updated online. If you ever need to add any cool new functionality (like displaying recent Twitter updates?) you can do it using Ruby/Rails. No more lame old hacked-together PHP sites ;) Static's well-tested, 175 line code base could be the solid foundation for a site that grows over time.

This thing does exactly and only what I need it to do, but I'm happy to accept patches and such. Give it a shot, fork away, and let me know if you have something good for me to pull.

http://github.com/trevorturk/static/

Enjoy!

Poor Man’s Heroku Backups

Posted by Trevor in Code, Ruby/Rails on September 11, 2009

Here's a quick and dirty way to back up all of your Heroku-powered databases using their Taps gem with one easy command. It works well enough for my simple needs, but any improvements you've got would be most welcome!

Just add the following to your ~/.bash_profile, and you're good to go:

El Dorado on Heroku

Posted by Trevor in El Dorado, Ruby/Rails on July 09, 2009

I'm pleased to announce that El Dorado is now compatible with Heroku, the instant Ruby platform.

heroku

This means that deploying El Dorado is no longer a pain in the ass. It also means that you can get started with El Dorado for free. Just follow along with the installation and deployment instructions in the README and you're good to go.

If you need help along the way, drop by the support site: http://eldorado.heroku.com/

Enjoy!

Config vars and Heroku

Posted by Trevor in El Dorado, Ruby/Rails on June 25, 2009

I don't really care for the suggested approach in the Heroku docs for setting configuration variables locally. I have an open-source project that I'm working to get onto Heroku, so I decided to do a little work to come up with a solution that I prefer. I think this would work well for open source projects, as well as projects with multiple developers.

Here's the basic idea:

You have a config file that contains all of your local configuration variables. It looks a lot like database.yml.

 
# config/config.yml
 
development:
  session_key: example_development
  session_secret: ESl6X3oKM1i1RRrD2QLwUUzz9jr1zxNO
  domain: http://example.com
 
test:
  session_key: example_test
  session_secret: vrwPpJTvwnMVLP1wTSgqigSl7PMI7QcE
  domain: http://example.com
 
production:
  session_key: # any string identifying your app
  session_secret: # a random, secret string at least 32 characters long
  domain: # http://example.com
  mailer: # noreply@example.com
 

You perform a little trickery in environment.rb to prefer the Heroku ENV storage of config vars (in the production environment), but you fall back to your config.yml if the config vars aren't found in ENV (in the development and test environments).

 
# config/environment.rb
 
Rails::Initializer.run do |config|
  require 'yaml'
 
  # support yaml and heroku config vars, preferring ENV for heroku
  CONFIG = (YAML.load_file('config/config.yml')[RAILS_ENV] rescue {}).merge(ENV)
 
  config.action_controller.session = {
    :key => CONFIG['session_key'],
    :secret => CONFIG['session_secret']
  }
end
 

Then, you create a rake task (rake heroku:config) that can be used to send all of the config vars for your production environment up to Heroku. This task can be invoked once to set things up, but can also be run again if you need to make any additions or changes.

 
# lib/tasks/heroku.rake
 
namespace :heroku do
  task :config do
    puts "Reading config/config.yml and sending config vars to Heroku..."
    CONFIG = YAML.load_file('config/config.yml')['production'] rescue {}
    command = "heroku config:add"
    CONFIG.each {|key, val| command << " #{key}=#{val} " if val }
    system command
  end
end
 

This way, you've got all of your config vars stored with the project (.gitignored, of course)...

 
# .gitignore
 
/tmp/**/*
/log/*
*.log
/tmp/restart.txt
/config/config.yml
/config/database.yml
/db/*.sqlite3
 

...and you can easily set what you need on Heroku, like so:

 
$ rake heroku:config
Reading config/config.yml and sending config vars to Heroku...
Adding config vars:
  session_key => example_production
  session_secret => 1WlkMkYYi5611vtF...0ZMS2G3Xl67s4lEIK4sj65
  domain => http://example.com
  mailer => noreply@example.com
Restarting app...done.
 

The result is a pretty nice, I think.

You can see the installation and deployment instructions for my open source project El Dorado if you're curious about the overall flow.

I'd love to get some feedback on this approach, but I really like it so far :)

Install Ruby Enterprise, Phusion Passenger and El Dorado on Debian Lenny

Posted by Timothy O'Connell in El Dorado, Ruby/Rails on June 24, 2009

These instructions require and assume the following:

  • You're running Debian Lenny and you've got root access
  • You've got a functioning apache2 installation
  • You know the basics of working on the command line (i.e. how to edit files, execute commands, etc.)

If the above is true of your situation, read on to learn how to install Ruby Enterprise, Phusion Passenger and El Dorado from scratch in a sort of "one-off" setting where you've got one server and you want it to run one site.

NB: These instructions don't use git or capistrano. The instructions contained in the El Dorado README describe how to install El Dorado using those tools. Using them makes for an easier and cleaner installation. It also makes for easier scalability, upgrading and patching: I highly recommend using those tools.

  1. Resolve Dependencies
  2. The first thing you'll need to do, even before installing RE or PP, is make sure that you've got the development files for the databases that RE and PP applications use:

    apt-get install libsqlite3-ruby postgresql-8.3-plruby libmysql-ruby libmysqlclient15-dev postgresql-server-dev-8.3 libsqlite3-dev

    If you don't resolve these dependencies now, you'll get a message during the RE installation that prompts you to install gems for mysql, postgres, etc. and then, when you go to install those gems, you'll get an error like this:

    ERROR:  Error installing mysql:
    	ERROR: Failed to build gem native extension.

    So just go ahead and resolve those dependencies in advance.

  3. Install Ruby Enterprise
  4. The best practice for this, as far as I know, is to install the current stable release of RE in /opt/. First, download the release you plan to use:

    lana:~# cd /opt
    lana:/opt# wget http://rubyforge.org/frs/download.php/58677/ruby-enterprise-1.8.6-20090610.tar.gz

    Once that's down, untar it and execute the installer script:

    lana:/opt# tar -zxvf ruby-enterprise-1.8.6-20090610.tar.gz
    [...]
    lana:/opt# cd ruby-enterprise-1.8.6-20090610/
    lana:/opt/ruby-enterprise-1.8.6-20090610# ./installer

    That should run, after a few tappings of ye olde Enter key, to its error-free conclusion. If, during the installation, the installer finds that you're missing software packages, the installer will bail and you'll be given some commands that fill those holes. Resolve those dependencies and finish the installation.

    At the end of the installation, you'll be given some syntax that will automatically install PP. You'll use that in the next step.

  5. Install Phusion Passenger
  6. Use the automatically generated syntax:

    lana:/opt/ruby-enterprise-1.8.6-20090610# /opt/ruby-enterprise-1.8.6-20090610/bin/passenger-install-apache2-module

    Again, the installer will bail and prompt you to resolve dependencies if you've got any:

    Installation instructions for required software
    
     * To install Apache 2 development headers:
       Please run apt-get install apache2-prefork-dev as root.
    
     * To install Apache Portable Runtime (APR) development headers:
       Please run apt-get install libapr1-dev as root.
    
     * To install Apache Portable Runtime Utility (APU) development headers:
       Please run apt-get install libaprutil1-dev as root.

    Resolve dependencies and finish the installation.

    Once it's finished, you'll be given some lines to add to your "Apache configuration file". The best file to add these lines to is /etc/apache2/httpd.conf.

    Just don't forget that you added them there (as opposed to somewhere else), as you'll need to modify them if you upgrade RE.

    You'll also probably want to go ahead and add the following lines while you've got the file open:

    PassengerPoolIdleTime 14400
    PassengerMaxInstancesPerApp 2

    Those lines do exactly what it looks like they do. They're also very sensible settings to start with, as they'll prevent El Dorado from hogging a bunch of system resources, etc. right off the bat.

    You can find more information here.

    Finally, your /etc/apache2/httpd.conf file should look something like this:

    PassengerPoolIdleTime 14400
    PassengerMaxInstancesPerApp 2
    
    LoadModule passenger_module /opt/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/apache2/mod_passenger.so
    PassengerRoot /opt/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/passenger-2.2.4
    PassengerRuby /opt/ruby-enterprise-1.8.6-20090610/bin/ruby

    Once you've made those changes, you're ready to begin installing El Dorado.

    When it exits, the PP installer will show you some sample syntax for how to write an apache configuration file for your first application. You can ignore that for now, as we're going to come back to it later.

  7. Install El Dorado
  8. First, get the latest release of the software from Trevor's github: http://github.com/trevorturk/eldorado/tree/master

    Once you've got the URL of the latest release, switch from root to a less privileged user, make a folder in your home dir for the site, download the latest release of El Dorado to that directory and untar it:

    toconnell@lana:~$ mkdir example.com
    toconnell@lana:~$ cd example.com
    toconnell@lana:~/example.com$ wget wget http://download.github.com/trevorturk-eldorado-a37d0c71e928f605d111d5f48b5786ff613bf676.tar.gz
    tar -zxvf trevorturk-eldorado-a37d0c71e928f605d111d5f48b5786ff613bf676.tar.gz
    

    Now, get all of those files out of that big, ugly directory and into the current working directory and ditch those old files:

    toconnell@lana:~/example.com$ mv trevorturk-eldorado-a37d0c71e928f605d111d5f48b5786ff613bf676/* .
    toconnell@lana:~/example.com$ rm -rf trevorturk-eldorado-a37d0c71e928f605d111d5f48b5786ff613bf676*

    Now, follow the instructions in the README and copy the example yml files to the places where the application will look for real, non-example files:

    toconnell@lana:~/example.com$ cp config/database.example.yml config/database.yml
    toconnell@lana:~/example.com$ cp config/config.example.yml config/config.yml

    Now, use your favorite editor to edit the last stanza in config/config.yml so that it matches the information of your site:

    production:
      session_key: example_production
      session_secret: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  # Replace these X's and make this string (at least) 32 random alpha-numerics for good site security
      domain: http://example.com
      mailer: noreply@example.com  

    NB: There are "dev" and "test" entries in this default file. If you're not planning on doing anything development related with this installation, you can safely delete those entries.

    Once you've edited that file, that's it, so far as the non-git installation is concerned. To get El Dorado up and running, you'll need to do some minor database tasks. Those are covered in the next section.

  9. Configure the Database
  10. Since MySQL is deprecated, I'll be using PostgreSQL for the remainder of these instructions.

    If you look at config/database.yml, you'll notice that it's essentially a blank template:

    development:
      adapter: sqlite3
      database: db/development.sqlite3
      timeout: 5000
      # adapter: mysql
      # database: eldorado_development
      # username:
      # password:
      # host: localhost
    
    test:
      adapter: sqlite3
      database: db/test.sqlite3
      timeout: 5000
    
    production:
      adapter:
      database:
      username:
      password:
      host:

    First, edit that file:

    production:
      adapter: postgresql
      database: example
      username: example
      password: XXXXXXXXXXXXXXXXXXXX
      host: localhost

    NB: Again: once you've added your "production" entries to this file, you can feel free to delete the "test" and "dev" lines, as they do nothing and could cause confusion down the line.

    Now, create the database and the user:

    toconnell@lana:~/example.com$ sudo su postgres -c "createuser example"
    Shall the new role be a superuser? (y/n) n
    Shall the new role be allowed to create databases? (y/n) n
    Shall the new role be allowed to create more new roles? (y/n) n
    toconnell@lana:~/example.com$ sudo su postgres -c "createdb example"

    Next, start the postgres monitor as the postgres user and make the a few changes:

    toconnell@lana:~/example.com$ sudo su postgres -c psql
    Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
    
    postgres=# ALTER USER example PASSWORD 'XXXXXXXXXXXXXXXXXXXX';
    ALTER ROLE
    postgres=# ALTER DATABASE example OWNER TO example;
    ALTER DATABASE

    Now, if you've got your Postgres database configured correctly and your new user can access your new postgres database, you're ready to rake the El Dorado production database:

    toconnell@lana:~/example.com$ /opt/ruby-enterprise-1.8.6-20090610/bin/rake rake db:schema:load RAILS_ENV=production

    Once the database is successfully raked, all you've got to do to finish up is configure Apache.

  11. Apache Configuration
  12. The following assumes that you're doing apache the "Debian way".

    If this is true, the first thing you'll do is create a symlink in /var/www/ that points at your install directory:

    lana:/var/www# ln -s /home/toconnell/example.com/

    Next, create a file in /etc/apache2/sites-available with the name of your site and then create a symlink to it in /etc/apache2/sites-enabled.

    The file should look something like this:

    #
    # example.com
    #
     
    <VirtualHost *:80>
      ServerName example.com
      ServerAlias www.example.com
      ServerAdmin youremail@example.com
      DocumentRoot /home/toconnell/example.com/public
     
      <Directory "/var/www/example.com">
        Options FollowSymLinks
        AllowOverride None
        Order allow,deny
        Allow from all
      </Directory>
     
      RewriteEngine On
     
      RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
      RewriteRule ^(.*)$ http://example.com$1 [R=301,L]
     
      RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
      RewriteCond %{SCRIPT_FILENAME} !maintenance.html
      RewriteRule ^.*$ /system/maintenance.html [L]
     
      ErrorLog /var/log/apache2/example_error_log
      CustomLog /var/log/apache2/example_access_log combined
      RewriteLog /var/log/apache2/example_rewrite_log
      RewriteLogLevel 9
     
    </VirtualHost>

    NB: I've added some apache custom logging. Logs are good.

    Once you've got the file in /etc/apache2/sites-available and the symlink in /etc/apache2/sites-enabled that points at that file, you should be ready to restart apache and get rolling:

    lana:/etc/logrotate.d# /etc/init.d/apache2 reload

And that, as they say, is that. Once you reload apache, provided that your DNS is set up correctly and you haven't got any system problems beyond the scope of this document, your single instance of El Dorado should be ready for prime time.

Navigate to your site in your browser and create an administrative account: the first user who attempts to login will be the administrator. Once you've got your admin created, you're ready to start tweaking your new El Dorado site's appearance and adding users.

A note on upgrades: if you find you need/want to upgrade an instance of El Dorado that has been installed thus, consult the README. The basic gist is that you're going to want to download/copy the new source/program files over the old ones (while being careful not to erase your user-uploaded files) and then run rake db:migrate RAILS_ENV=production.

Automatically Rotate your Log Files in Development

Posted by Trevor in Ruby/Rails on June 11, 2009

I'm trying to save hard drive space, since I've got this super small (and fast?) SSD hard drive on the way. I noticed that I was using a TON of space to store totally worthless logs for my Rails apps. Now, I know I could set up proper log rotation, but I don't feel like going through the trouble for my local machine.

Here's a quick tip I picked up here that will set your logs to automatically rotate in the test and development environments. Just add the following line to these files:

  • config/development.rb
  • config/test.rb
config.logger = Logger.new(config.log_path, 2, 20.megabytes)

Make sure you've got these in your .gitignore file as well:

/log/*
*.log

That will keep your log files under control, but with plenty of room for digging in if need be.

Speed up your Apache/Passenger Rails app in 2min

Posted by Trevor in Ruby/Rails on June 11, 2009

Here's a quick tip for speeding up your Apache/Passenger powered Rails app. It'll take you about 2 minutes, and I guarantee you'll notice the speed-up.

  • SSH onto your VPS
  • Run the following commands: "a2enmod expires" and "a2enmod deflate"

Now, open up your Apache vhost config for your Rails app. Add the following:

Then, restart Apache by running: "/etc/init.d/apache2 restart"

This will gzip your html, css, and javascript. It'll also add far future expires headers for the appropriate cacheable filetypes. There's no downside, and it only takes a second. Bang for buck.

Edit: Check the comments for some possible downsides... ;)

Time Bomb Test

Posted by Trevor in Ruby/Rails on April 05, 2009

Sometimes you come across something in the Rails changelog that suggests a config change before upgrading to the next version. Sometimes you only have time to put some code together quickly, but you know that you really should go back and refactor it soon. How and where can you remind yourself about this stuff?

I'm not sure where I originally came across this concept, but I think it's worth sharing again anyway. I'll even give it a name this time. Time Bomb Tests: easy cheesy reminders you can put into your test suite. They'll sit there like little time bomb reminders - exploding only when you need them to.

 
# test/integration/time_bomb_test.rb
 
require 'test_helper'
 
class TimeBombTest < ActionController::IntegrationTest
 
  test "stuff to do with next rails upgrade" do
    flunk if Rails.version != '2.2.2'
    # rename application.rb to application_controller.rb
    # etc...
  end
 
  test "stuff I'm putting off today, but really should do eventually" do
    flunk if Time.now > Time.parse('5/1/2009')
    # optimize that thing marked HACK in the user model
    # etc...
  end
end
 

Update: Check out jeremymcanally's deprecate, which appears to have been partially inspired by this post. It allows you to deprecate (primarily) test code after a certain date, version, or other arbitrary condition is met.

Randomize Filename in Paperclip

Posted by Trevor in Ruby/Rails on March 22, 2009

Here's a quick tip that Jonathan Yurek, author of Paperclip, was kind enough to help me with. It's a simple way to have a randomized filename for uploaded content. This is useful for security through obscurity, especially when used with Paperclip's id_partition interpolation helper:

 
class Photo < Asset
 
  has_attached_file :image, :path => ":class/:attachment/:id_partition/:basename_:style.:extension"
 
  before_create :randomize_file_name
 
private
 
  def randomize_file_name
    extension = File.extname(image_file_name).downcase
    self.image.instance_write(:file_name, "#{ActiveSupport::SecureRandom.hex(16)}#{extension}")
  end
 
end
 

That would, for example, change an uploaded image named "DS_100.JPG" into:

http://example.com/photos/images/000/001/204/e15f64f5e7gjdo3e4ae58f4ed9j925f5.jpg

That makes it effectively impossible to guess the location of an image, provided that you don't allow people to browse around the directories on your server. This is the same method of privacy protection that Flickr uses, and it ought to be enough for most non-governmental privacy needs :)

Speeding up Paperclip Tests by… a LOT

Posted by Trevor in Ruby/Rails on March 12, 2009

Here's a quick little trick that I used to speed up my tests involving Paperclip by about 70%.

I posted it over on the Paperclip Google Group, which is a friendly and active place to hang out if you're a Paperclip user.

Here's an example using Test::Unit, which is still my favorite way to test :)

 
require 'test_helper'
class PhotoTest < ActiveSupport::TestCase
  setup do
    Photo.any_instance.stubs(:save_attached_files).returns(true)
    Photo.any_instance.stubs(:delete_attached_files).returns(true)
    Paperclip::Attachment.any_instance.stubs(:post_process).returns(true)
  end
 
  # tests...
end
 

The really important bit is stubbing out the post_process method. That took my unit tests down from 51.77 to 15.14 seconds. That's a HUGE win, especially if you consider slow tests to be a bug.

I'm not sure what kind of impact this has on test coverage, so you may want to consider not stubbing out the Paperclip internals in every case. I've got some separate "remote" tests that I run before deployments that make me feel warm and fuzzy enough. Let me know what you think about it. I've had really good results so far!

The login! test helper for Restful Authentication and Machinist

Posted by Trevor in Ruby/Rails on February 12, 2009

A recent blog post reminded me that I should share a little test helper we've been using quite happily at the office lately. This trick works great with our extra-restful Restful Authentication and fixture-free Machinist setup.

It creates a new user with Machinst, and then logs the user in. We add a touch of convenience by returning the user object, and finish off by allowing you to pass in extra options if necessary.

 
def login!(options = {})
  user = User.make(options)
  @request.session[:user_id] = user.id
  user
end
 

This allows for many little niceties in your tests:

 
# login as a newly created user
login!
 
# login and keep the user around for assertions and whatnot:
user = login!
 
# login with admin privileges
login!(:admin => true)
 

It's a small thing, but give it a try! I'm sure you'll like it.

Quick and Dirty URL Validation

Posted by Trevor in Ruby/Rails on February 09, 2009

I've come across a few different ways to validate URLs in my day, but they all seem a bit more complicated than necessary. Perhaps I'll see the wisdom of these techniques soon, but for now it seems like there's an easy solution to the problem:

 
class Link < ActiveRecord::Base
 
  attr_accessible :url
  validate :validate_url
 
private
 
  def validate_url
    errors.add(:url) unless %w(200 301 302).include?(Link.status_code(self.url))
  end
 
  def self.status_code(url)
    regexp = url.match(/https?:\/\/([^\/]+)(.*)/)
    path = regexp[2].blank? ? '/' : regexp[2]
    Net::HTTP.start(regexp[1]) {|http| http.head(path).code}
  rescue
    nil
  end
 
end
 

Et Voilà.