Ruby lambda

I stumbled upon a problem with one of my scripts: how do you split a yielded block between two different callers while conforming to the DRY principe? The answer is lambda method.

I have a rather big database with multiple interconnected tables. I needed a script that parses this database and outputs a result for some query. Obvious answer is to use ActiveRecord for this. So I did. But the problem arose when I added pagination so that my script does not explode in memory. I needed at the same time:

  • limit the memory usage with pagination
  • preserve the option to limit query with SQL LIMIT command, which pagination ignores.

Lambda to the rescue

Fortunately I was aware of the ruby’s lambda method, though this would be probably the very first time I actually needed it. The answer is to create a lambda object and give it to the yielding method instead of a block:

# just a simple processor for a ActiveRecord record
proc_book = lambda{|book|
  # here we process each record
  puts book.name
}

conds = ["books.name is like ?", "ruby"]

# Tables to be eager-loaded
include_tables = [:author, {:reviews => :author}]

# if limit is given and it is within reasonable size, use it
if limit && limit > 0 && limit < 5000
   Book.find(:all, :conditions => conds ,:limit => limit,
      :include => include_tables).each(&proc_book)
else
  # Ignore limit and hook up will_paginate
  WillPaginate::enable_activerecord
  Book.paginated_each(:conditions => conds,
      :include => include_tables, &proc_book)
end

This script is of course a simple proof of concept, but it serves the point and is derived from a real world usage. Note also the referenced conds and include_tables variables, to be even more DRY.

3 thoughts on “Ruby lambda

  1. I think this is an example of bad programming, regardless of whether it’s a proof-of-concept or not. You have an if/else statement where both execution branches perform a kind of paginated query, only using different methods.

    Lambdas have their uses, but not to DRY up code that was overcomplicated in the first place.

    Like

    • I can see your point, but the foremost reason for this post was to show how the same yielded block can be used in multiple places (for people who missed it in the Pickaxe).

      The actual code is such because my existing code provides an option to limit the number of rows returned and I could not change that. But at the same time iterating over all records blew out of memory very quickly so that pagination was needed.
      Yes, I admit that I was in a bit of hurry and did not read will_paginate API docs thoroughly, but to my testing it simply ignored the :limit and I could not find an alternative to limit the total result set while using the paginate call.

      And as pagination is a wrapper layer above core find, I do not see a problem in dropping that layer when not needed while using lambda to keep it DRY.

      You are welcome to explain where I went wrong.

      Like

  2. It can be used for dynamic ActiveRecord callbacks meaning that you can pass the proc ref to the model instance to be processed on any callback. In general it can look like this.

    proc_after_save = lambda{|book|
    puts book.name
    }

    @book = Book.find(1)
    @book.set_callback(proc_after_save)

    class Book << ActiveRecord::Base
    @extra_callbacks = []
    before_save :run_callback

    def run_callback
    @extra_callbacks.each do |p|
    p.call
    end
    end

    def set_callback(proc_ref)
    @extra_callbacks << proc_ref
    end

    end

    But in general, it looks way too Perlish

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s