authlogic plugin error/fix

I’ve seen this error lots and lots of times dealing with authlogic plugins:

rake aborted!
undefined method `add_acts_as_authentic_module' for ActiveRecord::Base:Class

And I’ve searched on the web for answers and there’s not a really good source as to why it happens or what can be done to fix it.  Yesterday, I put an end to the error in authlogic_facebook_connect (great plugin, BTW) and wanted to post here so the next schlub who comes along googling for a solution will find it.

The conversational explanation is that if you’re attempting to use rake to install, build, unpack or otherwise effect gems, its possible you’ll have issues with the plugins that depend on those not-yet-working gems.  The exact use-case for me is that I have authlogic installed as a gem on my local box.  I have authlogic_facebook_connect installed as a plugin.  I setup capistrano to install the gems on every update of the code to the server (to pickup new gem dependencies in environment.rb).  When the code is deployed to the server and the rake gems:install task is run, authlogic_facebook_connect would cause an error because of its dependence on the yet-to-be-installed authlogic, preventing the installation of authlogic.  The fix is to simply guard the integration code with a check to make sure authlogic is installed.  That way, there will be no errors while installing authlogic and when the server is started immediately after the gems:install, authlogic_facebook_connect will integrate correctly (upon writing this, perhaps a warning would be in order instead of silent ignorance).

The code:

if ActiveRecord::Base.respond_to?(:add_acts_as_authentic_module)
  ActiveRecord::Base.send(:include, AuthlogicFacebookConnect::ActsAsAuthentic)
  Authlogic::Session::Base.send(:include, AuthlogicFacebookConnect::Session)
  ActionController::Base.helper AuthlogicFacebookConnect::Helper
end

I imagine the exact same fix would work for all the various authlogic plugins.

Thanks goes out to the developers of authlogic, authlogic_facebook_connect, and all the other authlogic plugin developers.  You guys have built a really great-to-use, modular, extendable authentication system that I enjoy very much.


Posted
5 October 2009 @ 9am

Tagged
, , , ,

Share and Enjoy
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Digg
  • Twitter
  • Reddit
  • MySpace
  • Technorati
  • StumbleUpon
  • Tumblr
  • Slashdot
  • email
  • Print

Comments Off

prototype-based image preloader

Preloading images is often a final optimization step when writing a javascript heavy web app. I think its generally because its seen as a pita, so it keep getting pushed as the site is developed until you get to the very end, and something has to be re-engineered to make it look right. I think that’s a shame, because a simple preloader is pretty easy to write and use and can really improve the look & feel of a website. Here’s mine:

var Preloader = {
    load: function() {
        var args = $A(arguments);
        var callback = Object.isFunction(args.last()) ? args.pop() : Prototype.emptyFunction;
        var urls = Object.isArray(args[0]) ? $A(args[0]) : args;
        var loaded = 0;
        var images = $A();

        var onload = function() {
            if (++loaded == urls.length) {
                callback();

                // cleanup
                images.each(function(i) { delete i });
                images = callback = urls = null;
            }
        };

        urls.each(function(url) {
            var image = new Image();
            image.onload = image.onerror = onload;
            image.src = url;
            images.push(image);
        });
    }
};

To use you load it up with url’s like:

// to load a single image
Preloader.load('http://futureadapter.com/wp-content/uploads/2008/08/fa_blog3-1.jpg');

// to load a bunch of images
Preloader.load('http://futureadapter.com/wp-content/uploads/2008/08/fa_blog3-1.jpg', 'http://futureadapter.com/another_image.jpg');

// or, alternately, if it works better for loading a bunch of images
Preloader.load(['http://futureadapter.com/wp-content/uploads/2008/08/fa_blog3-1.jpg', 'http://futureadapter.com/another_image.jpg']);

And the final argument can be a callback which executes after all the images have been loaded.  You’d commonly use this for something like:

// show an ajax spinner in your front end over the updating area
$('target').startWaiting();

new Ajax.Request('/images', {
    method: 'get',
    onSuccess: function(response) {
        // grab the image data from the back end
        var images = response.responseJSON['images'];
        var urls = images.invoke('public_filename');

        // load up the images
        Preloader.load(urls, function() {
            // when all the images have been loaded, replace the
            // existing ones with the new page (for instance)
            $$('#target .image').each(function(image, index) {
                image.writeAttribute('src', urls[index]);
            });

            // and reveal the updated area with the new images
            $('target').stopWaiting();
        });
    }
});

Have fun!


counter_cache as an optimization step

I had a debate with a programmer friend of mine once about optimization. I argued that once you get past adding indexes to a relational database, every attempt at increasing its speed or scaling is an act of denormalization. Whether its double storing information in the database in such a way that it can be read more efficiently by different queries or its adding in a cacheing layer—be it key-value-esque or page/fragment caching your view layer—it’s all essentially doing the same thing: making your DBA’s head explode.

Anyways, Rails has a great mechanism for doing exactly this, and its called counter_cache.  Its an attribute on a has_many/belongs_to association (it’s actually on the belongs_to) which keeps track of the number of belonging to objects in a count column on the having many table.

In the application I’m working on there are categories, tags, and images.  Tags and images have a many to many relationship; you can tag an image multiple times and a tag can be re-used on multiple images.  Tags also belong to categories.  When trying to improve the load times of a page, I was immediately struck by the code counting the images for a catogory:

class Category < ActiveRecord::Base
  has_many :tags

  def image_count
    tags.inject(0) { |tag, memo| memo + tag.images.count }
  end
end

To find the count for each category, we have to instantiate every single tag object associated with the category and run a separate SQL count for each to grab the images. And this could be worse depending on what else ActiveRecord decides to load with your tags (maybe you have a default_scope that includes images or an after_find callback). Worst case, we’re running 20 SQL queries if there are 20 tags … and if there are 20 categories, with 20 tags, all on the same page …

To fix the problem I had to start with the tags and images themselves. The way they were defined was:

class Tag < ActiveRecord::Base
  has_and_belongs_to_many :images
end

class Image < ActiveRecord::Base
  has_and_belongs_to_many :tags
end

but I wanted to take advantage of the counter_cache. Without changing the join table at all, I simply added a model and changed the assocations:

class ImagesTag < ActiveRecord::Base
  belongs_to :image, :counter_cache => true
  belongs_to :tag, :counter_cache => true
end

class Tag < ActiveRecord::Base
  has_many :images_tags, :dependent => :destroy
  has_many :images, :through => :images_tags
end

class Image < ActiveRecord::Base
  has_many :images_tags, :dependent => :destroy
  has_many :tags, :through => :images_tags
end

and with the following migration:

class AddCounterCachesToImagesAndTags < ActiveRecord::Migration
  def self.up
    add_column :images, :tag_count, :integer, :default => 0
    add_column :tags, :image_count, :integer, :default => 0

    # this is necessary because in production w/ cache_classes on the
    # definition of the Image and Tag objects won't reload to reflect
    # the new columns unless you do it explicitly
    Image.reset_column_information
    Tag.reset_column_information

    Image.all.each do |image|
      Image.update_counters image.id, :tag_count => image.tags.count
    end

    Tag.all.each do |tag|
      Tag.update_counters tag.id, :image_count => tag.images.count
    end

    add_column :images_tags, :id, :integer unless columns.detect { |column| column.name == 'id' }
  end

  def self.down
    remove_column :images, :tag_count
    remove_column :tags, :image_count
    remove_column :images_tags, :id if columns.detect { |column| column.name == 'id' }
  end
end

I now had denormalized image_count and tag_count columns in my database available as attributes on objects (which I definitely needed in my view anyways).

To take advantage of this data for categories, I rewrote the image_count method:

class Category < ActiveRecord::Base
  has_many :tags

  def image_count
    tags.sum(:image_count)
  end
end

Now no matter how many tags belong to a category and regardless of any extra loading a tag might do, there are no extra objects instantiated by the single SQL query to find the number of images belonging to a category.

Rails does all the work to keep these count columns in sync without any extra effort on my part.  It tries (hard enough in most cases) to keep the overhead as low as possible using SQL without going through ActiveRecord, which is nice because you don’t have to put that SQL straight into your model.

There’s lots of built-in magic in Rails and unless you happen to be on top of new code being pushed into Edge (and there are great blogs to help!), you can miss it and never even know it exists.  This isn’t new voodoo, its been around since at least Rails 2.0, perhaps earlier.  If it slid past your radar, this is a bit of magic that’s easy to use and easy to add onto an already working application as an optimization step.  Just don’t tell the DBA.

Hat-tip to the railscasts guys for the migration syntax.  You should also check out their counter cache column screencast for more info.

UPDATE: I made all the changes suggested by Rain in the comments.


Posted
28 August 2009 @ 6pm

Tagged
, , , , ,

Share and Enjoy
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Digg
  • Twitter
  • Reddit
  • MySpace
  • Technorati
  • StumbleUpon
  • Tumblr
  • Slashdot
  • email
  • Print

Comments Off

find_by_param for permalinks

A nice, clear, declarative means of doing something I’ve been writing code to do for years … find_by_param

From the README:


Find_by_param helps you dealing with permalinks and finding objects by our permalink value

class Post < ActiveRecord:Base
  make_permalink :with => :title
end

now you can do Post.find_by_param(...)

If you have a permalink-column find_by_param saves the permalink there and uses that otherwise it just uses the provided
attribute.

I love small and simple plugins.  Thanks to Michael Bumann.  Great job!


Posted
18 August 2009 @ 12pm

Tagged
, , , , ,

Share and Enjoy
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Digg
  • Twitter
  • Reddit
  • MySpace
  • Technorati
  • StumbleUpon
  • Tumblr
  • Slashdot
  • email
  • Print

Comments Off

Handling ajax-y file uploads

I’m working on a site for a client where a user needs to upload a logo for their newly created widget.  The client wants the logo to be visible during the widget creation process (so the user can make sure the logo they’re uploading is correct).  A user can add multiple widgets all at the same time.

I discovered a slightly different strategy for implementing this functionality and as its pretty common, something I’ve done before and I’m kinda pleased by the new strategy, I thought I’d share.

responds_to_parent

A lot of posts exist on the web about how to do file uploads via ajax.  Short answer is, you can’t.  There are lots of work-arounds to make it look like that’s exactly what you’re doing, though.  I’ve been using responds_to_parent for years now.  It’s pretty rad.  Excerpted from its readme:

Adds responds_to_parent to your controller to respond to the parent document of your page.  Make Ajaxy file uploads by posting the form to a hidden iframe, and respond with RJS to the parent window.

Basically what happens is that you create a hidden iframe on the page which is the target of a form submission with a file upload.  responds_to_parent executes the javascript returned to the browser via the form submission (and going into the hidden iframe) in the context of the iframe containing page.  Typically, this bit of javascript is where you update the old logo with the newly uploaded one.

links to other articles w/ explanations

I don’t want to re-cover a lot of previously covered ground, so here’s a collection of links explaining the basics.  The piece I want to concentrate on, is a good way of handling the problem of page flow and separate forms which cannot be embedded, which is beyond the scope of these links.

Incidentally, here’s another solution using jQuery for the hacky part and not responds_to_parent.

two schemes

Ok, so we have our controllers and models and responds_to_parent all setup and ready to go, but now we have a problem.  In every page design I’ve seen that demands this functionality, the file upload–in this case the logo attaching–is done in the middle of a form with all sorts of other values that need to be filled out.  This makes sense from a UI point of view, but isn’t possible in a HTML form sense.  Because you can’t have a form inside another form (what would the submit button be submitting?), you can’t simply put your file upload form where it belongs on the page.

In the past my solution has been to put the file upload form where it belongs page-flow wise and then instead of putting all the other form values inside a form, they’re simply unattached inputs.  Then I create a form at the bottom of the page inside a hidden div which contains all the fields that I want to submit.  I name the visible, unattached inputs by a standard scheme and then I bind to the click event of the submit button such that all of values are copied from the visible, unattached inputs to the invisible real form, which is what gets submitted.

Here’s some example javascript which explains the theory:

CopyValuesForm = Class.create({
    FIELDS = $w('first_name last_name street_address city state zip'),

    initialize: function() {
        this.targetForm = $('target-form');

        $('fake-submit').observe('click',
                                 this.submit.bindAsEventListener(this));
    },

    submit: function(event) {
        event.stop();

        this.FIELDS.each(function(field) {
            this.realField(field).setValue = $F(this.fakeField(field));
        }.bind(this));

        this.targetForm.submit();
    },

    fakeField: function(field) {
        return $('fake_' + field);
    },

    realField: function(field) {
        return $('widgets[' + field ']');
    }
});

This works and isn’t a bad solution.  Its kinda annoying that you have to copy lots and lots of values instead of copying just the file-to-be-uploaded value, but file upload values can’t be copied for security reasons via javascript.  It also stinks because your unattached inputs don’t play nicely with all of Rails built-in form wizardry like hooking in the error message into the fields.  You can overcome all of this, but its with more javascript, some of which has to be customized per-field.

But there’s another solution which obviates the need for copying values and allows you to use built-in Rails form magic to its fullest.

moving the display instead of the form

In this scheme we move the file upload form to the bottom of the page and keep the normal form intact.  We use javascript to change where the file upload form is displayed in the page instead of copying the values.

First the javascript:

var ContainingObject = Class.create({
    removeWidget: function(event) {
        event.stop();
        var widget_container = event.element().up('.widget');
        var widget_id = widget_container.id;

        new Ajax.Request('/containing_objects/1/widgets/2', {
            method: 'delete',
            onSuccess: function(transport) {
                // we no longer contain this widget in the ui, so get rid of it
                this.widgets.unset(widget_id).remove();
                // reposition the remaining forms
                this.widgets.values().invoke('positionUploadForm');
            }.bind(this)
        });
    }
});

var Widget = Class.create({
    initialize: function(container) {
        this.container = container;
        this.dom_id = container.id;
        this.positionUploadForm();
    },

    remove: function() {
        this.uploadForm().remove();
        this.container.remove();
    },

    uploadForm: function() {
        return $('widget-form-' + this.dom_id);
    },

    positionUploadForm: function() {
        var form = this.uploadForm();
        form.absolutize();
        form.clonePosition(this.container.down('.form-goes-here'));
    }
});

There’s of course elided logic there for adding widgets, creating widget objects, etc.  The important pieces to note are that the form is positioned as per the container (.form-goes-here) on initialization and also on removal of any other widget.  It occurs to me writing this, that it would be an excellent application for an event-based setup whereby a widget:remove event is fired from the Widget object (in the remove method) upon its ContainingObject.  All Widgets could observe their ContainingObject for this event.  Anyways … the html/css is pretty rote.  Simply put a div in the normal flow of the form:

<div class=".form-goes-here"></div>

and make sure you specify that its large enough for the form which will be cloned on top of it in your css:

.form-goes-here {
        width: 200px;
        height: 100px;
}

cute work with content_for, yield

You’d want to define the file upload form in the same partial that contains the rest of your form fields (to be shared between the new and edit views) even though you actually want it to spit out into html at the bottom of the page.  That’s exactly what content_for was designed to do.

<% content_for :upload_form do -%>
<div class="logo-form-container">
  <% form_for :logo, :url => logos_path, :html => { :multipart => true, :target => 'upload-frame', :id => "widget-form-#{dom_id(widget)}" } do |f| -%>
  <p>
  <%= f.label :uploaded_data, 'Logo file' %><br/>
  <%= f.file_field :uploaded_data %>
  </p>
  <p>
  <%= f.submit 'Upload image' %>
  </p>
<% end -%>
</div>
<% end -%>

and then in your new/edit.html.erb files:

<%= yield :upload_form %>
<iframe id="upload-frame" name="upload-frame" border="0" width="0" height="0" style="display:none;"></iframe>

I also discovered this somewhat cute way of getting the file upload form into the page even when there’s some ajax to add another form.  The use-case here is that your page uses Ajax to add another widget and bring along all the form fields (including the logo) which need to be submitted.  In the new.js.rjs:

page.insert_html :bottom, "widgets-container", :partial => @widget
page.insert_html :before, "upload-frame", yield(:upload_form)

summing it up

So we know there can’t be embedded forms and we know that we have to do some shenanigans for Ajax looking file uploads. Instead of copying the values from all of the fields in one form into a hidden form, we can position the upload form on the page so it appears to flow normally without actually being embedded. This is simpler and allows us to rely more on the built-in Rails form support and error messages.

Thanks to Mark Catley for the great plugin I’ve used so many times over the years.


authlogic_oauth gotcha

I don’t want to imply by the title this is anyone’s fault but my own.  But since it took me two days to figure out, I thought I’d share.

I’m using the excellent authentication package, authlogic, for my current project.  I want to allow normal registrations through my site and also registrations using Twitter credentials.  In an incremental way, I first implemented normal registrations.  I followed the authlogic documentation and liberally copied & pasted from the authlogic_example source tree.  One quick note here, authlogic is very well documented!

With no problems, registrations on my site worked.  Then I moved on to oauth/Twitter registrations.  Once again I read the documentation and followed the example code.  Once again, it was excellently documented.  This time, though, I fell into a two-day hole of frustration.

This was the error, cause of consternation, and some of the backtrace:

Processing UsersController#create (for 74.98.231.174 at 2009-07-29 15:05:11) [POST]
  Parameters: {"action"=>"create", "controller"=>"users", "oauth_token"=>"NX6WgsvgPiyIJWnHdibE4l23vYnrJo4XztZdzn24mM", "oauth_verifier"=>"S0EUR49GOYRtZ9IFCpXMBHCh4n3eedcnka8sdd7vdM"}
  User Columns (2.3ms)   SHOW FIELDS FROM `users`
  User Load (0.4ms)   SELECT * FROM `users` WHERE (`users`.`persistence_token` = '...') LIMIT 1
  User Load (0.5ms)   SELECT * FROM `users` WHERE (`users`.`oauth_token` = '14934375-oOOgpW5hXiONW6NM4yD9PHlkhAJyHmbVyeLwUz5R4') LIMIT 1
  SQL (0.2ms)   BEGIN
  SQL (0.2ms)   ROLLBACK

OAuth::Unauthorized (401 Unauthorized):
  oauth (0.3.5) [v] lib/oauth/consumer.rb:197:in `token_request'
  oauth (0.3.5) [v] lib/oauth/tokens/request_token.rb:18:in `get_access_token'
  vendor/gems/authlogic-oauth-1.0.7/lib/authlogic_oauth/oauth_process.rb:42:in `generate_access_token'
  vendor/gems/authlogic-oauth-1.0.7/lib/authlogic_oauth/acts_as_authentic.rb:93:in `authenticate_with_oauth'
  vendor/gems/authlogic-oauth-1.0.7/lib/authlogic_oauth/oauth_process.rb:12:in `validate_by_oauth'
  vendor/gems/authlogic-oauth-1.0.7/lib/authlogic_oauth/acts_as_authentic.rb:67:in `save'
  app/controllers/users_controller.rb:20:in `create'
  vendor/gems/authlogic-oauth-1.0.7/lib/oauth_callback_filter.rb:10:in `call'

Unauthorized was quite frustrating as my error because Twitter, to the eye, was certainly authorizing my request.  Also, when the process was tested on the command line via oauth directly, I had no issues.

It turns out the problem was in a before_filter which verified that there was no user in the session when creating a new user (as was suggested by the authlogic_example code for a totally sensible reason).  What I believe was happening as a result of this was that the callback return from Twitter including an oauth_token and oauth_verifier was then being redirected again because of the user in the session whereby another call was attempted of Twitter using the oauth_token that was a param.  Anyways, its not terribly important except to say that it completely broke with an error that didn’t help me decipher the problem.

When I changed the actions for which my before_filter applied, the problem was solved.

class UsersController < ApplicationController
  # authlogic_example: THIS ONE BREAKS OAUTH
#   before_filter :require_no_user, :only => [:new, :create]
  # authlogic_example (oauth branch): THIS ONE WORKS
  before_filter :require_no_user, :only => [:new]

Hopefully my two day of flailing helps someone else out in the same position.  I hate when this happens.

Thanks again to the authors of authlogic and authlogic_oauth for writing some great code and documentation.  I really want to stress that this was my fault, not theirs.  Hopefully this lesson will make it into the documentaiton.


Rails 2.3.1+, Passenger, and Rack 1.0

Just an FYI for those of you running the latest stable Rails.

Rails 2.3.1+ depends on Rack 1.0.  In 2.3.1, it only uses the vendor’d version of Rack 1.0 (which is located under action_controller/vendor).  In 2.3.2, it can use a vendor’d version of Rack 1.0 if the gem isn’t installed in your system.  Rails 2.3.3 depends on the Rack 1.0 gem and no longer contains vendor’d Rack.  I haven’t found any reasons for why this has changed between point releases.

Either way, Rack 1.0 needs for the rack.input to be seekable.  It calls rewind on said input.  Passenger < 2.2.2 passed a UnixSocket as rack.input, which is not seekable.  This will produce the following error in your Rails app:

/!\ FAILSAFE /!\ Thu Jul 23 10:57:14 -0700 2009
Status: 500 Internal Server Error
undefined method `rewind' for #<UNIXSocket:0x2add0f3f45a0>

So, to repeat.  If you’re running Rails in 2.3.1+, you need Rack 1.0, which needs Passenger 2.2.2+.  There have been various efforts by all sorts of software including Facebooker to work around this problem, but those seem to have sometimes caused more issues.

Incidentally, Dreamhost currently has Passenger 2.1.2 installed.  *sigh*

References:

Oh, and I also learned that if there’s a particular commit in Github that you’d like to apply to your own source tree (say, for instance, the patch that removes vendor’d rack 1.0 from Rails which you’d like to reverse), you can simply add .patch onto the end of the URL and you’ll get the relevant patchfile.  Github rocks!

UPDATE: Dreamhost has upgraded their versions of Passenger and Rack, and Rails 2.3.3 is now working.  Thanks Dreamhost!


Posted
16 July 2009 @ 4pm

Tagged
, , ,

Share and Enjoy
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Digg
  • Twitter
  • Reddit
  • MySpace
  • Technorati
  • StumbleUpon
  • Tumblr
  • Slashdot
  • email
  • Print

Comments Off

Rails running in script/console?

If you ever wanted to know if your application is running normally or in console mode, here’s the trick:

def in_console?
  Object.const_defined?(:IRB)
end

This would break if you require’d IRB in your Rails application normally, though I can’t possibly imagine why you would.


Posted
3 July 2009 @ 9am

Tagged
, , , , ,

Share and Enjoy
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Digg
  • Twitter
  • Reddit
  • MySpace
  • Technorati
  • StumbleUpon
  • Tumblr
  • Slashdot
  • email
  • Print

Comments Off

deploy:web:disable and Dreamhost

Following on the heels of Twitter on your maintenance page, the other piece of custom setup I needed for this solution to work was making the site swallow all incoming requests while disabled in order to show the maintenance page.  In the past on sites using Nginx+thin/mongrel the setup has been in the nginx configuration code.  At Dreamhost, where I have my Rails projects (and this site), the setup uses Apache+passenger.  And you don’t have access to the virtual host section of the Apache setup to do the configuration there.

Use .htaccess

The solution was to use .htaccess.  Put it in your public directory.  Check it into source control.  When you deploy your app, the configuration will go with it.

Make a special rule for images, if you want ‘em

My custom maintenace page links to a couple of images (the logo and a background gradient).  The rest of the page I can encapsulate in the html iteself (css and javascript), so there’s no need for special rules about what Apache will choose to server and what it won’t.  But to avoid Apache serving up the maintenance.html page for all your images, you need a special rule.

Below, the complete .htaccess:

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteCond %{REQUEST_URI} !^/images/
RewriteRule ^.*$ /system/maintenance.html [L]

Useful additional links


Twitter on your maintenance page

I recently started a new project.  Because the domain was purchased, one of the first acts of business was a, “Coming Soon,” page.  I decided the maintenance/deploy:web:disable page was the perfect solution.  I could put it up in production and leave it off in staging without special code shenanigans.  It would be re-usable for the future if the site ever needed maintenance.  All-in-all, simple and useful.

How to setup a custom maintenance page

This is totally cribbed from the capistrano default task for deploy:web:disable.  My only change is the location of the maintenance template.

In config/deploy.rb:

namespace :deploy do
  namespace :web do
    desc "Put the `we're working on it' message up using config/templates/maintenance.html.erb"
    task :disable, :roles => :web, :except => { :no_release => true } do
      require 'erb'
      on_rollback { run "rm #{shared_path}/system/maintenance.html" }

      template = File.read(File.join(File.dirname(__FILE__), "templates", "maintenance.html.erb"))
      result = ERB.new(template).result(binding)

      put result, "#{shared_path}/system/maintenance.html", :mode => 0644
    end
  end
end

Simple how-to-include twitter

The interesting twist is putting—instead of stock ,”Coming soon,” or, “We’ll be right back”—a twitter message right into the maintenance page.  The last tweet from the account for the site is automatically included.

The javascript, which doesn’t depend on any outside libraries, for updating a portion of the page with the most recent tweet:

<head>
  <script type="text/javascript">
    function tweetCallback(data) {
        var tweet = data[0];
        if (text = tweet["text"])
            document.getElementById('tweet').innerHTML = ' : ' + text;
    }
  </script>
</head>

At the bottom of the maintenance.html.erb body:

<script type="text/javascript" src="http://twitter.com/statuses/user_timeline/TWITTER_USERNAME_HERE.json?callback=tweetCallback&count=1"></script>

Why its a good idea

I’m really happy with including the last twitter message for the text.  Firstly, the client can change the page whenever they want without contacting me.  Also, it doesn’t involve any application code, which is important since this is also the page the user will see when the site is down.  It promotes the twitter account for the site and at the same time is a totally sensible place to update any possible status messages should they arise.


← Before