Conditionally caching actions when logged in for Rails 2.1 and 2.0

Posted by face on August 31, 2008

Screen shot of Legaltorrents.com

As some of you know I’ve been working full time for Matson Systems, Inc. building out LegalTorrents. I must apologize I have been neglecting parts of my blog. Fortunately, I’ve been swamped building out cool features for LegalTorrents and Matson wants to contribute back. After this caching article look for a rake task to convert a Rails app from one database platform to another, then a plugin for generating Activity Streams.

As a new community, we have to support tens of thousands of registered users. Yet on any given news day we need to support hundreds of thousands of non registered users.


This can be done using action caching and very modest hardware requirements. Given huge hardware resources, using memcached would solve the issue. However, meeting initial demands can be done using action caching with very modest hardware requirements. We use the built-in rails action caching to disk with a TTL hack from cron. We don’t want our logged-in users subjected to a TTL, as their changes should be instantaneous. We simply don’t cache actions for users who are logged in, and provide cached pages for everyone else. As the number of registered users grows… then we’ll use memcached.


Let’s begin with conditional caching in Rails 2.1 (if 2.0, see below). Conditional action caching is a new feature of the Rails 2.1 API. First off, pre Rails 2.1 the default was disk. In rails 2.1, the default is RAM. Not going to work on limited resources:


# Put this in RAILS_ROOT/config/initializers/something.rb
ActionController::Base.cache_store = :file_store, "#{RAILS_ROOT}/tmp/cache"
Ok, the application controller is a great place to decide if we want to cache:

class ApplicationController < ActionController::Base
  # ...
  protected
  # of course logged_in? needs to be defined...restful_authentication is what I recommend.
  def do_caching?
    !logged_in? && flash.empty?
  end
Now we can use the new Rails 2.1 :if feature to conditionally cache actions:

class TorrentsController < ApplicationController
  # ...
  caches_action :show , :if => Proc.new { |controller|
                          controller.send(:do_caching?) }

The final piece of the puzzle is a TTL. We use find to remove files older than 10 minutes, giving us a 10 minute TTL:

# This cron entry that runs every 10 minutes and removes any files older than 10 minutes named '*.cache'
3,13,23,33,43,53 * * * * find /home/ltdeploy/legaltorrents/tmp/cache -mmin +10 -name '*.cache' -exec rm -f {} \;
And that is it. For those of you not familiar with caches action here is a more complex example for a page that integrates the will_paginate plugin using :cache_path:

class CategoriesController < ApplicationController

  caches_action :show, 
    :if => Proc.new { |controller| controller.send(:do_caching?) }, 
    :cache_path => Proc.new { |c|
      c.params[:page] ?
      "#{c.request.host}.#{c.request.port}/#{c.send(:category_path,c.params[:id])}/page/#{c.params[:page]}" :
      "#{c.request.host}.#{c.request.port}/#{c.send(:category_path,c.params[:id])}/page/1"
    }

End of Story for Rails 2.1



Now, Rails 2.0 doesn’t have :if in caches_action. To work around this we used a simple monkey patch:


class ApplicationController < ActionController::Base
  # ...
  protected
  # Overrides Rails core to do action_cache when not logged in...Only works in Rails 2.0 and maybe earlier
  def perform_caching
    @@perform_caching && !logged_in? && flash.empty?
   end
Then we cache as normal:

class TorrentsController < ApplicationController
  # ...
  caches_action :show


Digg! Delicious! Technorati Blinklist Furl Reddit

Yahoo OpenID has extra security constaints

Posted by face on March 05, 2008

OpenID logo

I have a feeling this will help some of y’all if you are getting the following error:

Sorry! Something is not quite right with the request we received from the website you are trying to use. Please try again in a few minutes. If this error persists, please contact the site administrator for the website you are trying to use. If you are the site administrator, click here to contact us.

I get this error if I try to login on my development environment because localhost:3000 just won’t cut it for Yahoo’s OpenID security policy. If I run from a production URL on port 80, say http://myutil.com/ then signin works (though I haven’t gotten Simple Registration Attribute Exchange working with Yahoo).

From the Yahoo OpenID Developers FAQ:
Yahoo! Security Policies Yahoo! will only support Relying Parties running on webservers with real hostnames (IP addresses are not supported) running on standard ports (Port 80 for HTTP and Port 443 for HTTPS).

Hope this saves ya some time!


Digg! Delicious! Technorati Blinklist Furl Reddit

Prototype translations for Gibberish with Google Language Tools, a mouse click, and 13 lines of code

Posted by face on January 23, 2008

translated pages with google

I wanted to prototype my Gibberish translations before we have an actual translator. I grabbed gibberish_translate and started copying and pasting from Google Language Tools.

After five minutes of this, I thought there has got to be a better way. I googled for an API to the Google tools, and though I found none, I did find a scraping Ruby API called rtranslate. So…

gem install googletranslate
Then I hacked the index method of translations_controller.rb from gibberish_translate. I added the following lines of code

  require 'rtranslate'
  def index
     # ...Mark's entire index method goes here unchanged
    if params[:filter] == "untranslated"
      count=0
      @paginated_keys.each do |key|  
        if ! @translated_messages[key]
          @translated_messages[key] = { 
          :to => Translate.t(@en_messages[key], Language::ENGLISH, session[:translation_locale] ),
          :from => @en_messages[key]
          }
        end
        break if (count += 1) == per_page
        sleep 1 # Let's be nice to google
      end
    end
  end
 # end of index from gibberish_translate's translations_controller.rb

And now Google does the work for me with the click of a mouse!

Note I did make some other changes to Mark’s code. There was a bug in translations_controller.rb in that it lost your current local when saving changes. To fix this I changed the set_translation_locale to use the session of there is no paramater:


  def set_translation_locale
    session[:translation_locale] = params[:translation_locale] if params[:translation_locale]
    session[:translation_locale] = Gibberish.languages.first if Gibberish.languages if ! session[:translation_locale]
  end

I also made some changes to gibberish_translate’s extractor.rb to handle Gibberish strings with default keys ("foo"[] is a valid Gibberish way of saying "foo"[:foo]):


    def message_pattern(start_token, end_token)
      /#{start_token}((?:[^#{end_token}](?:\\#{end_token})?)+)#{end_token}\[:*([a-z_]*)[,\]]/m
    end

    def add_messages(contents, start_token, end_token)
      contents.scan(message_pattern(start_token, end_token)).each do |text, key|
        key = text.tr('[  ]', '_').downcase if ( key == '' )
        add_message(key, remove_quotes(text, end_token))
      end
    end

The final tweaks I made was to make the find system call more portable (no -regex on OpenBSD) and also have it search for strings in my gibberish_rails plugin:


    def files_with_messages
      `find #{dirs_to_search.join(" ")} -type f '(' -name '*rb' -or -name '*.ml' ')'`.split.map(&:chomp)
    end

    def dirs_to_search
      %w(app config lib vendor/plugins/gibberish_rails).map { |dir| "#{RAILS_ROOT}/#{dir}" }
    end

Peace!

Portions of the above code Copyright© 2007 Peter Marklund


Digg! Delicious! Technorati Blinklist Furl Reddit

gibberish_rails: a Ruby-On-Rails plugin to translate Rails with Gibberish

Posted by face on January 22, 2008

Translated Rails Registration


With migrating from Rails 1 to Rails 2, I have tried to simplify. When I wanted to prototype a multilingual Rails application I was very intrigued by Gibberish and it’s simplicity.

As all Gibberish does is translate strings and all this plugin attempts to do is translate srings in Rails. This plugin is in a very early prototype stage but I expect it to be useful none the less.

If you want full localization of dates, numbers, the world etc. check out some of the other more mature localization plugins.

If you are trying to localize your Rails strings with Gibberish, then this plugin is for you.

When I set out I didn’t even expect to make a plugin, just write some simple ruby in my project. However, it turns out there is a reason for the bloat in localization plugins…rails was never designed to be localized and has some quirks that lead to the necessity of overriding large core rails methods. The rails core team is obviously aware the problem and are working on a solution with ticket 9726. I’m hoping Rails ticket 9726 will make it to edge and then I’ll be able to simplify this plugin.

Without further adieu, I give you gibberish_rails.

Here is a link to the RDoc.

Quickie instructions (includes install for Gibberish).

./script/plugin install svn://errtheblog.com/svn/plugins/gibberish
./script/plugin install http://svn.myutil.com/projects/plugins/gibberish_rails/

Please read the README in it’s entirety before using.

Now you must translate your strings. I recommend using gibberish_translate. My next article will be on automatic prototyping translations with gibberish_translate and Google Language Tools.


Digg! Delicious! Technorati Blinklist Furl Reddit

OpenID-2.0.2 with Rails-2.0.2

Posted by face on December 29, 2007

OpenID makes sense. Dr. Nick’s multi-OpenIDs per user example app makes even more sense.

In the middle of integrating it into my project, gem-1.0.1 came out and broke ruby-openid-1.1.4. Dr. Nick’s great example no longer worked!

A little digging and I found Dr. Nick’s example uses the standard open_id_authentication. That has a patch to work on ruby-openid-2.0.2 and rails 2 which can be found here.

So in a nutshell, I grabbed openidauth_multiopenid-0.3.2 from Dr. Nick, removed a bunch of stuff from vendor plugins. Updated Rakefile, config/boot.rb, and config/environment.rb for rails 2.0.2. Patched vendor/plugins/open_id_authentication for ruby-openid-2.0.2. Regenerated db/migration/002_add_open_id_authentication_tables.rb. And installed ruby-openid-2.0.2 as a system gem.

As a little code is worth more than a thousand words, here is Dr. Nick’s example application fully ported to rails 2.0.2 in ZIP and TAR.gz.

For my port of Dr. Nick’s example above to work, you will need rails-2.0.2 and ruby-openid-2.0.2 installed as a gems.

Security Update: January 4th, 2007 I noticed the example adds edit, update, and destroy to users_controller.rb using params[:id] thus allowing any logged in user to edit, update, and destroy any user of the system. To fix, simply change the first line of edit, update, and destroy to use the current logged in user (i.e. @user = User.find(self.current_user.id)).

Another Update:February 27th, 2007 One of my clients noticed the user_openids_controller’s index method finds all openids for all users if you surf to user_openids URL. To fix, change the find in user_openids_controller.index to be @user_openids = UserOpenid.find_all_by_user_id(@user.id). I think it’s time I put this example under SVN and apply these security upates…

It should look something like this under rails 2.0.2:

References:

http://drnicwilliams.com/2007/07/26/sample-app-rails-multiple-openids-per-user/
http://dev.rubyonrails.org/ticket/10604
http://openidenabled.com/ruby-openid/
http://svn.rubyonrails.org/rails/plugins/open_id_authentication/
http://openid.net/


Digg! Delicious! Technorati Blinklist Furl Reddit

Flex 2 SDK on OpenBSD 4.1

Posted by face on October 15, 2007

Java is a prerequisite. I tried building a simple mxml file with kaffe on OpenBSD, but kaffe failed with a CDATA error for flex 2 and then simply dumped core flex 3 beta. So before we start, lets build and install JDK for OpenBSD from source. Once you have built jdk-1.5, install it and ensure $JAVA_HOME is set and $JAVA_HOME/bin is on your path.

If you don’t already have wget and unzip installed, we can install them now:

sudo csh
setenv PKG_PATH ftp://ftp2.usa.openbsd.org/pub/OpenBSD/4.1/packages/i386
pkg_add wget unzip

The flex sdk is free and comes in a multi-platform binary format. Adobe forgot to put it in a directory, so lets make one for it now and then grab and unzip the sdk:

sudo mkdir /usr/local/flex2sdk-2.01
sudo ln -s /usr/local/flex2sdk-2.01 /usr/local/flex
cd /tmp
wget http://download.macromedia.com/pub/flex/sdk/flex2_sdk_hf1.zip
cd /usr/local/flex
sudo unzip /tmp/flex2_sdk_hf1.zip
rm -f  /tmp/flex2_sdk_hf1.zip
sudo chmod +x /usr/local/flex/bin/mxml

That should do it. Lets try it out. Place the following in a file called hello.mxml:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="center" verticalAlign="center">
  <mx:Panel title="Yo">
    <mx:Text htmlText="Hello &lt;b&gt;new&lt;/b&gt; World!" />
  </mx:Panel>
</mx:Application>

And now compile it:

/usr/local/flex/bin/mxmlc hello.mxml

And you should end up with hello.swf:


Digg! Delicious! Technorati Blinklist Furl Reddit

How to create and add favorite icons to your website (favicon.ico)

Posted by face on October 13, 2007

1. Introduction

You may have noticed that firefox as well as IE 5 and higher have support for icons associated with URLs. These icons are displayed in the address bar of firefox as well as along side bookmarks/favorites in firefox as well as IE.

If you don't have a /favicon.ico installed on your site you will something similar to the following errors in your apache error_log file:


[error] [client X.X.X.X] File does not exist: /htdocs/favicon.ico

Using png2ico is an intuitive way to create a single favicon.ico file containing the web standard 16x16 and 32x32 resolutions. This Guide focuses on using png2ico along with The GIMP.

The GIMP 2.X supports "Save As" .ico natively. I could not find an easy way to edit and place both resolution icons in one file. So if you only want a single 16x16 favicon.ico, just use GIMP2. However, If you want two resolutions in your .ico...continue following this guide.


Digg! Delicious! Technorati Blinklist Furl Reddit