SQL Logging: Gemified and Now for Rails 3

Of the handful of plug-ins I’ve written for Rails over the years, the one I install on new projects almost with thinking is sql_logging. I wrote that plug-in almost three years ago and it continues to work on Rails 2.3 apps today.

That isn’t true on Rails 3, though, so over the last few days I’ve taken the time to reevaluate the plug-in and figure out how to do the same work in Rails 3. The result is the new sql-logging gem. The source is hosted on GitHub and the gem itself is available, like any other, on rubygems.org.

Read More

Couldn’t find ‘rspec’ generator?

If you’ve moved on to Rails 3 and RSpec 2, but have older projects that are still on Rails 2.3 and RSpec 1.3.x, you may notice that script/generate no longer shows the RSpec generators in them, even if you’ve frozen the correct version of rspec and rspec-rails into vendor/gems.

I’m not sure of the proper place to report this bug, but a quickie workaround is to uninstall the newer rspec-rails gem so that 1.3.x is the newest in gem list.

Bundler Simplified

Following up on my previous post about adapting pre-Bundler workflows to current best practices, Yehuda Katz posted his suggestions on how to approach Bundler in terms of what you used to do. It’s a good read.

One thing that caught me up was the deployment suggestion to require 'bundler/capistrano' in deploy.rb. If you installed Rails 3.0 soon after it was released, you may still have one of the Bundler 1.0 release candidates. I had RC3, and it didn’t include the Capistrano file, which led to a load error. Update your gem, and all will be well.

vendor/rails in the Age of Bundler

I’ve only followed the development of Rails 3.0 from a distance, mainly taking note of the major features and goals for the project and mostly ignoring all the little details that go into it. Once the 3.0 release candidate arrived, I started my next internal project with it and have been slowly coming up to speed on those little details. One that’s stymied me for a while is Bundler. Read More

Ruby, Threads and RubyCocoa

If you’re a Rails developer working on Mac OS X 10.6, you may have seen this message:

Ruby threads cannot be used in RubyCocoa without patches to the Ruby interpreter

It is caused by a plug-in or gem requiring osx/cocoa, frequently attachment_fu. AttachmentFu can use CoreImage as an image processor instead of calling out to an image manipulation library such as ImageMagick or GD2.

The warning is harmless, but it can be very noisy. To disable it, you can simply remove CoreImage from AttachmentFu’s list of image processors by creating a new file in config/initializers. Its contents should be this one line:

Technoweenie::AttachmentFu.default_processors.delete('CoreImage')

Image resize operations will now use one of the other image processors, which you can install from MacPorts. I never deploy to a Mac OS X server for production, and I prefer to run as much of the same code as possible in development, so this isn’t a problem for me.

Remember HTTP Password in Mobile Safari

In iPhone OS 3.0, Apple allowed Mobile Safari to save usernames and passwords in web forms. Unfortunately, Safari does not offer to do the same thing for HTTP Basic and Digest authentication. I’ve become fond of using HTTP authentication because it is very easy to set up, either in your Apache virtual host configuration or within a Rails application. There are many times that a full-fledged user database is unnecessary for a simple administration back-end.

There is a work-around, though it does mean storing your user and password in plaintext in your device’s bookmarks. HTTP allows you to supply authentication credentials as part of the URL, in the form http://username:password@example.com/.

Use content_for to Put Markup In Its Place

Here is a useful trick for ensuring that you keep your partial templates well organized without sacrificing page-load times or duplicating your layouts unnecessarily.

You can use content_for to capture some markup, but have it emitted into the page from somewhere else.

Two places this is immediately useful: adding additional tags inside <head> and placing in-line Javascript near </body>, while keeping the code itself right next to the DOM elements it works upon.

content_for works by capturing whatever appears inside the block and storing it for later use. You emit whatever is stored using yield. What’s more, content_for doesn’t clobber the previous captured text if you use it more than once with the same key.

Say you have a fancy Javascript control that replaces a standard <select> element in forms. You can do this in your partials:

<select ...>
  <option>...</option>
  <option>...</option>
  ...
</select>
<% content_for :body_close do %>
  <script type="text/javascript">
    // code that does something with that <select>
  </script>
<% end >

And then in your layout, just before the </body>:

<%= yield :body_close %>

Because most browsers will run in-line Javascript upon encountering it, this would delay the execution of the code until all of the page content was loaded, making your page appear to load faster.

What’s more, if that fancy Javascript control also requires a CSS file, but you don’t want to require the browser to fetch it on all of the other pages that don’t need it, you can conditionally add it to <head> by defining another content_for key that accumulates additional markup to go there:

<% content_for :head do %>
  <%= stylesheet_link_tag 'fancy_control' %>
<% end %>

And in your layout:

<head>
  ...
  <%= yield :head %>
</head>

This is also useful for keeping per-page markup like <title> and meta tags in the template and out of your controller.

IE + Rails Javascript Caching Gotcha

Rails supports a simple method of asset bundling with javascript_include_tag by way of the :cache option:

<%= javascript_include_tag 'first', 'second', 'third', :cache => true %>

There’s a gotcha hiding here that you may not find until you deploy to production and visit your site in IE.

Admittedly, this is a bug, but if you happen to have a second javascript_include_tag with the :cache argument in the same page, IE will choke on lots of the script, telling you “object doesn’t support this property or method,” among other things.

The problem is that Rails can’t resolve multiple tags using the same value for :cache, and it emits two <script> tags referencing the same Javascript file. IE gets confused by the second one (perhaps loading it twice?). Every other browser I’ve used handles it fine.

The solution: either don’t use :cache more than once or make sure you use explicit, unique bundle names instead of just true.

On the Pain of Developing for Facebook

I’m in the last stages of building a site that integrates with Facebook. It includes a Facebook Connect component and a separate Facebook iframe application. In the hopes that some of my experience will help someone else out there, I will share some of my suffering and how I worked through it.

This is a Rails 2.3 application, using the Facebooker gem, version 1.0.54.

Safari, iframes, and cookies.

Chances are high you’ve found this page via search, and that you are desperate to find a solution to allow your Rails sessions to carry over from page #1 of your iframe application to page #2 and beyond. Will Henderson has a solution when your Rails application uses the ActiveRecord session store, but new Rails applications use the cookie store. There is no session to retrieve from the database; passing a row ID with links doesn’t help.

I am fortunate that my iframe application has a splash screen, with only one useful link on it, to start the “real” application. I append every fb_sig parameter from the current request to the link. Once the user clicks that link, the iframe no longer falls under Safari’s 3rd party cookie policy, and so the next page can set cookies. Because all of the fb_sig parameters are there, Facebooker gives precedence to them, recreating the Facebook session without consulting cookies (which either don’t exist or are stale).

<%= link_to 'Start the app', params.merge(:action => :app) %>

Now, your app doesn’t always get these. When the application is first installed, you won’t get the fb_sig parameters. In this case, I force a redirect:

unless params[:fb_sig_ss]
  redirect_to "http://#{Facebooker.canvas_server_base}/#{Facebooker.facebooker_config['canvas_page_name']}/"
  return
end

However…

Facebooker’s Rack middleware trashes your params.

Facebooker includes a piece of Rack middleware that automatically validates the signature on fb_sig parameters, and aborts the request if the signature isn’t valid. This works great on the initial request, but fails on the second request, with exactly the same parameters.

The culprit is near the bottom of Facebook#call, in rack/facebook.rb, where it calls convert_parameters!. This method is trying to be helpful, turning things like Unix time_t into Ruby Time instances. If you are trying to pass these parameters along in a link, Time.to_s is not a Unix time_t, and so the signature validation will fail, and you’re screwed.

Pre-Rails 2.3, there is no Rack middleware, and signature validation happens in another part of the Facebooker gem, which does not trash the original params. It does the same conversion, but on a copy. As near as I can tell, calling convert_parameters! is unnecessary, and commenting it out solves my problem. When Safari loads page #2, Facebooker creates its session, which can be stored in the Rails session, using the cookie store.

Reconstituting a Facebook session in Flash.

The bulk of this iframe application is actually written in Flash. Every Facebook application has an App ID (essentially a public key) and a secret key. The secret key must never be allowed outside your organization or people can fake API requests from your application. Embedding your secret key in a Flash SWF is therefore not an option, because a SWF can be decompiled. Facebook’s solution to this is the session secret, passed as fb_sig_ss.

According to Adobe’s ActionScript 3.0 Client Library for Facebook API reference, one can use the FacebookSessionUtil class to recreate a session using only the public key and the session secret. This is not true. I have to pass in all of the fb_sig parameters to Flash in order to make it work. However you start your Flash content, FlashVars must include:

<%= params.collect { |k,v| "#{k}=#{v}" if k.starts_with?("fb_sig")}.compact.join("&") %>

A refreshed page lags a “logged-in” state change until refreshed a second time.

When a user logs out of Facebook and refreshes your Facebook Connect web site, it doesn’t reflect the user’s “logged-in” state change until another refresh. You can automate this and have the page refresh itself when necessary:

<% init_fb_connect 'XFBML', :app_settings => '{"reloadIfSessionStateChanged":true}' do %>
  FB.ensureInit(function() {
    FB.Connect.get_status().waitUntilReady(function(status) {
      switch (status) {
        case FB.ConnectState.connected:
          $$('.fbConnectLoggedIn').invoke('show');
          $$('.fbConnectLoggedOut').invoke('hide');
          break;
        case FB.ConnectState.appNotAuthorized:
        case FB.ConnectState.userNotLoggedIn:
          $$('.fbConnectLoggedIn').invoke('hide');
          $$('.fbConnectLoggedOut').invoke('show');
          break;
      }
    });
  });
<% end %>

There are actually two parts to this trick. First, :app_settings => '{"reloadIfSessionStateChanged":true}' triggers the automatic refresh.

Second, if you have some common part of your layout that switches on a user’s Facebook status, this precludes your use of page caching. For an otherwise static page, this is a real sacrifice. To work around this, I include both sets of mark-up, classed with either “fbConnectLoggedIn” or “fbConnectLoggedOut”, and invoke Prototype’s show() or hide() on them as part of the Facebook Connect initialization. As long as you are careful to use XFBML tags that don’t require actual values from a Facebook session (e.g. <fb:name uid="loggedinuser" ...>), this works very well.