smallroomsoftware.com

Testing your SSL requirements with Webrick in development mode

Posted on August 18, 2008

Testing SSL pages (login pages etc) in development mode doesn't seem to be supported out of the box with Rails. Turns out that webrick includes an SSL server and it isn't too hard to set. You need this script (mentioned here). I changed the :ip setting and then saved it in my project as script/ssl_server. The trick is to run both the script/server and the script/ssl_server scripts at the same time. The problem is that you'll be using non-standard ports so the ssl_requirement plugin will fail to provide the correct redirects (it assumes ports 80 and 443). There's a ticket and patch about this but it doesn't look like it's going to be applied anytime soon. Let's just patch the code ourselves, replacing ssl_requirement's ensure_proper_protocol method.

def ensure_proper_protocol
  return true if ssl_allowed?

  if ssl_required? && !request.ssl?
    redirect_to "https://" + request.host + (RAILS_ENV == 'development' ? ':3001' : '') + request.request_uri
    return false
  elsif request.ssl? && !ssl_required?
    redirect_to "http://" + request.host + (RAILS_ENV == 'development' ? ':3000' : '') + request.request_uri
    return false
  end
end

So I ran into another problem with my SSL pages, this time in production. If you use the assets host feature, you'll find your SSL protected pages reference assets with normal unsecured http URLs. This will cause a lot of security warning dialogs for IE users so you have to deal with it. You need all referenced media to be server over SSL. Luckily, you can supply a Proc to generate asset URLs and this Proc receives the current 'request' object which we can interrogate. Turns out that this is such a common situation, the example in the rails docs gives the solution. Let's get our four hostname pipelining back too:

ActionController::Base.asset_host = Proc.new { |source, request|
  if request.ssl?
    "#{request.protocol}#{request.host_with_port}"
  else
    "#{request.protocol}asset#{(source.hash % 4)}.rentability.com"
  end
}

mod_rails makes life easier

Posted on April 25, 2008

Every six months there seems to be a new, preferred way to deploy a Rails application. Mongrel+modproxy has worked OK for me for a while, but it's a bit fiddly to set up and quite tricky to make robust across reboots or crashes. Even normal restarts of my mongrel clusters sometimes fails. Now there is modrails which seems to provide most of the advantages of a mongrel cluster with the simplicity of an apache module that spawns rails processes on demand. This is definitely becoming my preferred deployment method. I might even move rentability to mod_rails when I get the chance as it'll simplify our server configuration a lot.

Reuse existing partials in ActionMailer HTML emails

Posted on November 18, 2007

This might be useful for anyone wanting to reuse action/controller templates/partials in HTML emails sent with ActionMailer. It causes templates/partials to be resolved relative to the 'views' directory rather than relative to the relevant mailer templates directory (which is the default).

module ActionMailer
  class Base
    # We have to go root relative here because of the change below
    def render_message(method_name, body)
      render :file => "#{mailer_name}/#{method_name}", :body => body
    end

    # This allows root relative partials to be located correctly
    def initialize_template_class(assigns)
      ActionView::Base.new(template_root, assigns, self)
    end
  end
end

Simple java properties style rails localisation with Gibberish

Posted on July 23, 2007

I had a session preparing the Rentability front-end for localization the other day. Luckily I found the Gibberish rails plugin which makes localizing the static text fairly painless - you can store the translated strings in yml files, and the syntax is very simple. I made a slight patch to gibberish so it'll print the key next to each string on the live app so that the human translator can see where each key is used on the running site:

module Gibberish
  module Localize
    alias_method :old_translate, :translate
    def translate(string, key, *args)
      str = old_translate(string, key, *args)
      "#{str} <span class=\"translation-key\">#{key}</span>"
    end
  end
end

You can put that in environment.rb (maybe if RAILS__ENV == 'development'). Here's some CSS:

span.translation-key {
  font-size: 11px;
  font-weight: normal;
  color: black;
  background-color: yellow;
  font-family: geneva;
  border: 1px solid #444;
  padding: 0 1px;
  text-transform: none;
  font-style: normal;
}

Internationalisation with the globalize-rails plugin

Posted on November 02, 2006

I've been using the globalize-rails plugin for translation and localisation of my webapp. I'm fairly happy with with the plugin - it makes a good attempt to globalise most aspects of your application including strings in views and strings used anywhere else (uses the same mechanism, a .t method on instances of the String class) and also data in the database. This is the complicated bit - efficiently loading translations when records are loaded using a non-default locale. Seems to work though pretty well though but with two limitations:

  • Doesn't work when you use an :include option on associations or when calling ActiveRecord::Base::find. Instead, an :includetranslated option can be used but that only works with :belongsto associations which seems like the least useful behaviour.
  • Data isn't translated if you used find_by_sql. I guess this isn't really surprising but as my main 'search' function has to use findby_sql I suspect it might cause me some problems. Maybe there is a way to join with the globalizetranslations table and have it load translated attributes when it unmarshals to ActiveRecord objects.

I've created a reasonable UI for translating data:

In my view (say _form.rhtml) I have something like:

<%= text_field 'space_decl', 'name' %>
<%= translation_links 'space_decl', 'name' %>

Here's the helper module. The css just sets the alpha for untranslated languages:

span.flags img {
  margin-right: 4px;
}

span.flags img.todo {
  filter: alpha(opacity=50);
  -moz-opacity: .50;
  opacity: .50;
}

Graceful degredation of ajax partial page loading without much hassle

Posted on July 19, 2006

Okay, this is pretty obvious when you think about it but I think it's the kind of thing you have to read somewhere to actually hammer it home. There's a very straightforward principle you can use to make your fancy AJAX powered partial page reloads degrade nicely when javascript isn't available (or just fails).

Say you have an "add to cart" link and you want it to use an AJAX request to inform the server that the item is to be added to the cart and to also get some html to update a "mini-cart" div. Well, what you should do is code this without any AJAX/javascript first. Have the link just target an action, e.g. "/cart/add_product?id=123". The action should then redirect to the cart details page or wherever. Fine, that works nicely for non-javascript browsers.

Now, write your link onClick handler so it pulls the same URL from the link (using the DOM) and makes an XMLHttpRequest. This time though, the server should return just the html fragment to be updated. How does the server know to return a fragment? Most AJAX libraries add a special header to the request identifying it as an AJAX request.

Something like Ruby on Rails should of course be able to do all of this for you! But, by default, the link_to_remote function inserts "#" into the link's href parameter and you have to repeat the URL parameters to get the desired result.

Configuring postfix on OS X Server for receiving email with rails

Posted on June 07, 2006

So ActiveMailer has the ability to receive mail using a 'runner' to which you pass raw email (or rather, postfix, sendmail etc pass raw mail). The message is decoded and turned into a TMail object that is then passed to a 'receive' method on your ActiveMailer::Base subclass. I wanted my app to receive all emails sent to addresses that match a certain regular expression. The explanation on the rails wiki uses a regular expression and got me some of the way but it didn't mention the final step for getting this running on the OS X Server postfix installation. You need to add:

virtual_alias_maps = regexp:/etc/postfix/virtual

to /etc/postfix/main.cf. Just add it at the end. Here are my full notes for configuring postfix for receiving mail with rails:

--- In /etc/postfix/aliases add (all on one line):

my-app: "|/path/to/app/script/runner
  -e production 'MyMailer.receive(STDIN.read)'"

--- In /etc/postfix/main.cf add to the end:

virtual_alias_maps = regexp:/etc/postfix/virtual

--- In /etc/postfix/virtual add:

/^.*@mydomain.com$/  my-app

What's on in Cambridge? now live!

Posted on March 13, 2006

My first Ruby on Rails web-app, What's on in Cambridge? is now up and running and open to the public. The aim is to create a useful directory of interesting events in Cambridge (talks, exhibitions, classes, concerts etc) and to do it with some style! Users can add events, tag events, comment on them, express interest in them etc. Eventually I hope to records for different entities such as venue, band, person, organisation with some kind of wiki engine behind it. Currently, there are still a few glaring feature omissions (email reminders being one of them) and several interface issues to sort out but I'm pleased with how the site looks and works so far.

Setting up ruby on rails and fastcgi on Mac OS X Server

Posted on February 26, 2006

Today I set up a Mac OS X Server (Tiger) box to run rails applications with fastcgi, the stock apache (1.3.33) and the mysql gem. So these are the exact steps I took...


Update 25/09/06 : I'm now pretty much converted to using Darwin Ports for my ruby/rails/lighttpd/apache2 installation. It seems easier to get a capistrano-friendly fastcgi setup working with lighttpd than apache (although that's probably more to do with my bad understanding of apache). The trick with Darwin Ports is to make sure that nothing ever uses the original apple ruby installation (especially remote Capistrano tasks). This is an easy problem to solve once you know how.

Firstly you should edit the PATH declaration in /etc/profile so it looks like this:

PATH="/opt/local/bin:/opt/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin"

This will update all users default paths to see at Darwin Ports installed binaries.

Then there's the fix to get Capistrano's remote tasks to work with the Darwin Ports environment. These tasks execute the command directly via ssh (they don't log in to a full shell, I'm not sure what the term is for that). It's like executing ssh tom@blah.com "pwd" and it doesn't get the full environment you'd get when logging into a shell. It gets a very restricted environment that you can't normally change. So you must change a setting for sshd to allow yourself to setup extra environment variables for these ssh'ed commands.

So open /etc/sshd_config and make sure the line:

PermitUserEnvironment yes

...exists and is uncommented. Now set up the custom environment for your user (the user you deploy as) by creating the file ~/.ssh/environment and adding:

PATH=/opt/local/bin:/opt/local/sbin:/opt/local/apache2/bin:/bin:/sbin:/usr/bin

That's it! Although I think you have to restart sshd by toggling Remote Login off and on in Sharing preferences or rebooting hte machine.


First you'll need gcc installed. So download the install the developer tools (now just called XCode) from apple and install.

You'll want to fix up Apple's ruby installation now. First download the latest rubygems:

cd ~  
curl -O http://rubyforge.org/frs/download.php/5207/rubygems-0.8.11.tgz
tar -xvzf rubygems-0.8.11.tgz 
cd rubygems-0.8.11
sudo ruby setup.rb

Now we have rubygems installed, we'll fix the OS X ruby configuration by installing the fixrbconfig gem:

sudo gem install fixrbconfig
sudo fixrbconfig

If you're using Tiger (I think it's okay under Panther) the last command will have resulted in an error:

/usr/lib/ruby/1.8/powerpc-darwin8.0/ruby.h does not exist.
This probably means you haven't yet installed Xcode from the
Tiger DVD.  You won't be able to compile Ruby extensions without
it. Please install it then rerun this program.

The widely reported fix this error looks like this:

cd /usr/lib/ruby/1.8/
ln -s universal-darwin8.0/* powerpc-darwin8.0

Though I suspect there is a neater way to fix the problem, Oh well. Now we re-run:

sudo fixrbconfig

Now lets install the rails gem:

sudo gem install rails

Now install the mysql ruby bindings from source to avoid the various problems associated with installed it via gem:

cd ~
curl -O http://tmtm.org/downloads/mysql/ruby/mysql-ruby-2.7.tar.gz
tar xzvf mysql-ruby-2.7.tar.gz
cd mysql-ruby-2.7
ruby extconf.rb --with-mysql-config
make
sudo make install

Now we'll install fastcgi. Download the main fastcgi package:

cd ~
curl -O http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
tar -xvzf fcgi-2.4.0.tar.gz

Build it!

cd fcgi-2.4.0
./configure --prefix=/usr/local
make
sudo make install

Now install the ruby fcgi bindings:

sudo gem install fcgi

Don't worry about the "No definition for xxx" output - I don't think that matters (just rdoc warnings). Now we need to install the apache fastcgi module, mod_fastcgi:

cd ~
curl -O http://fastcgi.com/dist/mod_fastcgi-2.4.2.tar.gz
tar xzvf mod_fastcgi-2.4.2.tar.gz
cd mod_fastcgi-2.4.2
apxs -o mod_fastcgi.so -c *.c
sudo apxs -i -a -n fastcgi mod_fastcgi.so

This will add the necessary lines to http.conf to load mod_fastcgi. You will still need to configure the module. Open up /etc/httpd.conf and add:

<IfModule mod_fastcgi.c>
    FastCgiIpcDir /tmp/fcgi_ipc/
    AddHandler fastcgi-script .fcgi
</IfModule>

... somewhere near the end of the file.

Assuming you want to run your rails application on a virtual server (which you have added via the Server Admin GUI app) you'll need to slightly edit the apache configuration for the virtual server so that settings can be overriden with the .htaccess file present in your rails app's public directory.

Find the configuration file for your virtual server in /etc/httpd/sites/, e.g. 0019any80_www.myserver.com.conf. Find the line 'AllowOverride None' and change it to 'AllowOverride All'.

Then deploy your rails application in whatever way you wish. I recommend using SwitchTower.

Future of Web Apps Summit (be a snowflake)

Posted on February 14, 2006

I was quite pleased with the future of web apps carson summit. Everything ran pretty smoothly. Most of the speakers were very good, although there was a lot of repetition (I think we were told to use clean URLs 3 or 4 times). There was also a lot of talk about building APIs. These are obviously good points but I think the benefits are well understood and I got the feeling that the speakers knew they were addressing a fairly clued up audience. Some case studies might have been more useful than lists of dos and don'ts. I would have liked to have heard about the problems that the guys writing delicious/flickr/google maps etc ran into along the way. Ryan Cason's talk was a bit more like this - an explanation of how Drop Send came about, what the budget was and how he spent his cash. I got the impression he hadn't been coding for a while - almost all the development was contracted out. The emphasis was on building web apps on a budget. In fact, I think the main thing I learnt was that you can't make much money doing contracted web development these days - he got some pretty good deals!

David Heinemeier Hansson's talk was very good. It was the most technical (code snippets!) but also the most philosophical. He tried to get to the heart of what makes programmers happy. Of course, we all know that using Ruby On Rails is the answer to this! :-) But what exactly is it about rails that makes us happy? It has a lot to do with how often you can be a beautiful and unique snowflake. That is, how often you can feel that you are doing something that hasn't been done a million times before or isn't being done by a million other people. David also talked about convention over configuration - one of rails' biggest strengths in my opinion - trade off some unnecessary flexibility for speed of development (and in-built encouragement to 'do the right thing'). Combine that, and many other great features, with all the potential of the Ruby language to produce very elegant code, and everyone can spend more time being snowflakes!

Update: All of the talks including the panel discussion are now available for download (as mp3s).

Carson Workshop Summit - The Future of Web Apps

Posted on February 02, 2006

Next Wednesday I'll be in London at the 'Future of Web Apps' summit. There will be 800 attendees (which I think is an increase on the original 500), and you can check out some of them in advance! It should be good. I'm hoping for some nice insight into the world of flickr, google, yahoo, delicious etc. Kim is coming along too which I am glad about although I have yet to totally convince him we aren't headed for a sales pitch!

Using bbclone with typo

Posted on January 19, 2006

I've used the bbclone stats package for a while now and it pretty much does exactly what I want. It is, however, pretty much geared towards PHP driven websites or static HTML sites. Getting it working nicely with typo (or any other rails app for that matter) seems to be a little trickier. The hacky (and probably temporary) solution I've come up with involves a hidden iframe on every typo generated page that links to the PHP script below:

<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s")
       . " GMT");
header("Cache-Control: no-store, no-cache, "
       ."must-revalidate");
header("Cache-Control: post-check=0, pre-check=0",
       false);
header("Pragma: no-cache");

define("_BBC_PAGE_NAME", $_GET['page']);
define("_BBCLONE_DIR", "/full/path/to/bbclone/dir/");
define("COUNTER", _BBCLONE_DIR."mark_page.php");
if (is_readable(COUNTER)) include_once(COUNTER);
?>

The markup for the iframe looks like this and is added to the bottom of the default.rhtml file in your current theme:

1
2
<iframe frameborder="0" scrolling="no" width="0" height="0" src="/bbclone_hit.php?page=<%=h params %>">
</iframe>

Passing <%=h params %> as the page name is, I admit, pretty lame - I'll fix that later. At least I can now see some stats!

Hosting by site5.com