Displaying recaptcha inside ModalBox

January 30th, 2009 No Comments »

I just wanted to display reCAPTCHA inside a ModalBox.
This simple task turned out to be quite complicated.
After playing with innerHTML, creating <script> nodes manually and attaching them and other voodoo and black magic and failing to accomplish the desired effect, I settled with displaying the recaptcha inside iframe inside ModalBox.

 Modalbox.show('<iframe src="' + this.href + '?iframe=1"></iframe>', 
  {title: this.title, width: 600});

(The iframe parameter helps to select another layout).

Expire cache item in Rails ActiveSupport::Cache::FileStore

January 30th, 2009 No Comments »

Wouldn’t it be nice to cache some item (e.g. action) for some amount of time, before expiring it? For example, if you’re displaying some page that parts of it change very often, but you don’t want to expire cache every time, since you prefer to serve stale information instead of expiring the page?
The answer is the options[:expires_in] which is supported by the Rails’ ActiveSupport::Cache::MemCacheStore.
The current implementation of ActiveSupport::Cache::FileStore, however, lacks such feature, despite the fact that it’s really easy to implement.

This is the fix accompanied by a unit-test.
cache_fix

And then you can happily add a line like this in your controllers:

caches_action :find, :expires_in => 10.minute

The check, by the way, adds about 7usec to cache read request, which IMHO is negligible.

The Rails core team doesn’t seem to be interested in this patch, though.

I18n support for recaptcha

January 23rd, 2009 No Comments »

There’s a nice plugin which adds helper for the reCAPTCHA. The downside is that it doesn’t support I18n at the moment.
This is a straight forward approach patch that adds this functionality. (I don’t know what will be the fate of I18n in Rails 3, thus the simple check for version).
The error message path then will be:
activerecord.errors.models.model_name.captcha

diff --git a/lib/recaptcha.rb b/lib/recaptcha.rb
index 87c26e9..44413bc 100644
--- a/lib/recaptcha.rb
+++ b/lib/recaptcha.rb
@@ -65,7 +65,11 @@ module Ambethia
             session[:recaptcha_error] = error
             if model = options[:model]
               model.valid?
-              model.errors.add_to_base "Captcha response is incorrect, please try again."
+              if Rails::VERSION::MAJOR >= 2 and Rails::VERSION::MINOR >= 2
+                model.errors.add_to_base I18n.translate("#{model.class.name.underscore}.captcha", :scope => %w(activerecord errors models), :default => "Captcha response is incorrect, please try again.")
+              else
+                model.errors.add_to_base "Captcha response is incorrect, please try again."
+              end
             end
             return false
           else

I wonder if this is the proper way of adding I18n support…
P.S. I reported this on lighthouse.

Caching of complex queries

January 6th, 2009 No Comments »

This trick is great for actions which receive lots of parameters, for example – search controller. Obviously, you wouldn’t want to do that with something that receives a POST, but I omitted the check for it (using :if => Proc.new {|controller| controller.request.get?} , for example), since I think that I know what I’m doing.
So the idea is to map all the parameters to some unique key, and then use that key as an :id for the url_for.

  # This caches the find action by the parameters
  require 'digest/sha1'
  caches_action :find, :expires_in => 10.minute,
    :cache_path => Proc.new { |controller|
        search_key = Digest::SHA1.hexdigest(controller.params.inspect)
        logger.debug "Current search key: #{search_key}"
        controller.url_for(:action => 'find', :id => search_key)
      }

The only minor issue that might arise, is that if you’re using pagination, the page number 1 when reached from the pagination links will have page=1 parameter, but when it’s initially presented, the parameter is absent. Adding such parameter won’t do any good, since the order of keys in hash in undefined. It’s possible, though, to build the key using some other technique, for example by massaging the request string. But since the case is quite rare, I think that any fix will be a waste of CPU cycles.

Better logging in Rails in production mode

December 28th, 2008 3 Comments »

I’m quite surprised, that even of version 2.2.2 of Rails, the logs are written in a way that makes them almost useless, as there’s no time stamp or severity level.
The solution I’ve come up with, is (instead of monkey patching the BufferedLogger) to create a new type of logger which inherits from the BufferedLogger and then change the ‘add’ method in order to produce nicer logs.
This file is lib/audit_logger.rb :

class AuditLogger < ActiveSupport::BufferedLogger
 
  SEVERITIES = Severity.constants.inject([]) {|arr,c| arr[Severity.const_get(c)] = c; arr}
 
  # Redefine the ActiveSupport::BufferedLogger#add
  def add(severity, message = nil, progname = nil, &block)
    return if @level > severity
    message = (message || (block && block.call) || progname).to_s
    # This is the line that was changed:
    message = "#{Time.now.to_formatted_s(:db)} #{SEVERITIES[severity]} #{message.strip}\n"
    buffer << message
    auto_flush
    message
  end
end

Then, in config/environments/production.rb you need to add:

require 'audit_logger'
config.logger = AuditLogger.new(config.log_path)
config.logger.level = AuditLogger::INFO #or other level
# This is what Rails does by default in production mode when using the default logger
config.logger.auto_flushing = false

Ruby ispell gem

November 14th, 2008 2 Comments »

Yesterday I packed my ruby interface to ispell into a gem and published it on Rubyforge.
The rdoc can be found here.
I wonder if anyone still works with ispell besides myself.

String#include? vs. String#match

November 7th, 2008 No Comments »

Interesting phenomenon; for finding whenever some string occurs inside other string, String#match is 5 (!) times faster than String#include? for long strings (50k), and 2 times slower for short strings (tens of bytes).
The slowness can be explained by the time it takes to compile the regexp.
But how can it be so slow for long strings? Going to ask this in ruby.core.

Improved Rails’ delegate method

September 7th, 2008 2 Comments »

The Rails framework includes a simple “delegate” method, which is mixed in the models, and can be used in order to delegate methods to other classes. However it’s limited at least in the following:
1. It doesn’t prevent “train wrecks” – e.g. if the class that the method is delegated to is nil, then there will be an attempt to call this method on the nil class.
2. It doesn’t allow renaming of the methods, e.g. if you have a method called “name” in your class, and a method called “name” in other class you want to delegate to, you have no means of doing this.
The implementation below remedies these shortcomings.

class Module
  def delegate(*methods)
    options = methods.pop
    unless options.is_a?(Hash) && to = options[:to]
      raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
    end
 
    prefix = options[:prefix] || ''
    orig_prefix = prefix
    if prefix != true and !prefix.empty? 
      prefix += '_'
    end
 
    methods.each do |method|
      prefix = to.to_s + '_' if orig_prefix == true
      module_eval(<<-EOS, "(__DELEGATION__)", 1)
        def #{prefix}#{method}(*args, &block)
          return nil unless #{to}
          #{to}.__send__(#{method.inspect}, *args, &block)
        end
      EOS
    end
  end
end

You can use it like this:

delegate :name, :to=> :other, :prefix=>true

and it will delegate a method “name” in “other” class as “other_name” in your class.
Or, alternatively

delegate :name, :to=> :other, :prefix=>'some'

will delegate “some_name” to class “other”, method “name”.

Extensions for acts_as_tree

August 30th, 2008 No Comments »

While the acts_as_tree rails plugin ( http://dev.rubyonrails.com/svn/rails/plugins/acts_as_tree ) provides some basic methods for accessing tree like data structure, I found that it lacks some basic ones, for example – has_children? and has_siblings?
Other than that, it would have been nice to know the tree width and depth (especially if you need to draw it, and need to calculate the scaling.

module ActiveRecord
  module Acts
    module Tree
      module InstanceMethods
        def has_children?
          !self.children.size.zero?
        end
 
        def has_siblings?
          !self.siblings.size.zero?
        end
 
        # Return the tree depth
        def depth
          return 0 unless has_children?
          children.inject(0) {|dep,c| dep > c.depth ? dep : c.depth} + 1
        end
 
        # Return the tree width
        def width
          return 1 unless has_children?
          children.inject(0) {|sum,c| sum + c.width}
        end
      end
    end
  end
end

Maybe if I won’t be lazy, I’ll write some tests and send this for inclusion in the plugin…

C extension for Porter stemmer

August 20th, 2008 No Comments »

When playing with LSI, I noticed that the program runs for too long and uses enormous amounts of memory.
Using a great tool ruby-prof, I found, to my astonishment, that I waste more time in stemming than in SVD.
So I wanted to try to see, if using a compiled C extension will make a difference. So I took the thread-safe porter algorithm from http://tartarus.org/~martin/PorterStemmer/ and wrapped it with swig.
The results were almost in an order of magnitude (10000 rounds for 11 words):

      user     system      total        real
stem :  3.480000   0.250000   3.730000 (  3.719107)
fstem:  0.440000   0.090000   0.530000 (  0.526526)

This I call “performance boost” :)

porter.i (for swig):

%module stemmer
%{
char *stem_word(char *word)
{
  int length, i;
  char *res;
  struct stemmer * z = create_stemmer();
  length  = stem(z, word, strlen(word)-1);
  /* length is the index of last char, add one for size and one for '�' */
  res = (char *)malloc((length+2) * sizeof(char));
  for (i=0; i<=length; i++)
  {
    res[i] = word[i];
  }
  res[length+1] = 0;
  free_stemmer(z);
  return res;
}
%}
%newobject stem_word;
char *stem_word(char *);