Make your controllers filter and block

Posted by duncanbeevers on August 13th, 2007 — Posted in Uncategorized

Often we want to conditionally execute code based on a User’s role.

In Agile Web Development With Rails the before_filter example uses a method called authorize

before_filter :authorize where authorize is implemented like so:

def authorize
  unless User.find_by_id(session[:user_id])
    flash[:notice] = "Please log in"
    redirect_to(:controller => "login", :action => "login")
  end
end

Say our controller contains two actions, :index and :show. And say we’d like to make :show available to the public while :index requires authorization, we can declare the before_filter like so: before_filter :authorize, :only => :index

Now say in the :show action we have one piece of functionality that should only be available to authorized users, but not to anonymous users. Instead of duplicating the authorize logic in the controller action, we can re-use it!

Ugly duplication!

def show
  @book = Book.find( params[:book_id] )
  @price = @book.price
  if User.find_by_id( session[:user_id] )
    @wholesale_price = @book.wholesale_price
  end
end

Let’s make this slightly more descriptive and terrifically flexible.
Start off by renaming the filter method from authorize to authorized_only. The before_filter becomes before_filter :authorized_only, :only => :index

def authorized_only &block
  @active_user ||= User.find_by_id( session[:user_id] )
  if @active_user
    yield block if block_given?
  elsif !block_given?
    flash[:notice] = "Please log in"
    redirect_to(:controller => "login", :action => "login")
  end
end

By passing an optional block to the method, we can use the same authorized_only semantics in the controller show method.

def show
  @book = Book.find( params[:book_id] )
  @price = @book.price
  authorized_only do
    @wholesale_price = @book.wholesale_price
  end
end

I’d actually recommend moving the flash and redirect functionality to a new method called by another before_filter that relies on the state generated by the initial call to authorized_only, but that’s just a point of abstraction, not actual functionality.

This method is simple to extend to various roles, keeping the semantics for each User role consistent, such as admin_only, anonymous_only, owner_only, etc…, all of which clearly communicate the permissions required for access to the indicated functionality. Additionally, adding similar block-evaluating methods to your helpers lets you conditionally include blocks of markup based on a User’s role.

iTerm keyboard niceness

Posted by duncanbeevers on July 20th, 2007 — Posted in Programming


⌥ ← and ⌥ → are used almost universally on the Mac to step the cursor forward and backwards, respectively, one word at a time. Home and End are used likewise, to move the cursor to the beginning or end of a line.

Wanting to use these features in iTerm, I added the following entries to the Global Keyboard Bundle (⌘ ⌥ B)

⌥ ←
  • Key: cursor left
  • Modifier: Option
  • Action: send escape sequence
  • Escape sequence: b
⌥ →
  • Key: cursor right
  • Modifier: Option
  • Action: send escape sequence
  • Escape sequence: f
Home
  • Key: home
  • Modifier: none
  • Action: send hex code
  • Hex code: 01
  • High interception priority
End
  • Key: end
  • Modifier: none
  • Action: send hex code
  • Hex code: 05
  • High interception priority
left one word right one word
beginning of line end of line

link_to_remote goes where?

Posted by duncanbeevers on July 19th, 2007 — Posted in Programming

A list of great things:

These tools make it easy to test the effects of our RJS, but how can we make sure that link_to_remote actually hits the right endpoint?

Well, here’s a stab in the dark, in your test_helper.rb.


def assert_link_to_remote link_text, link_to_remote_url
  assert_select ‘a[onclick*=”new Ajax.Request(\'’ + link_to_remote_url + ‘\’”]’, :text => link_text
end

UFO Pizza

Posted by duncanbeevers on June 1st, 2007 — Posted in Living

The best Gyro’s in Portland are at UFO Pizza. Unfortunately all their contact info on the ‘net seems to be out-of-date.

So I went in to talk to Alkis, and got the straight dope. Here it is, for those who like their gyros Greek.

UFO Pizza
6024 NE Glisan Ave.
Portland OR
Phone: (503) 234 7654
Hours: Monday - Saturday, 11am - 9pm

Seriously.

validates_not_format_of

Posted by duncanbeevers on May 21st, 2007 — Posted in Programming

validates_format_of is great when you want to make sure an attribute conforms to a regular expression. Sometimes though, you need to negate a regular expression match (grep -v.) Here’s a custom validator that wraps that functionality up, as well as some more human-readable permutations such as validates_does_not_begin_with_the_word to keep the intention of the code clearer.

# Helpful validations based on negations of regular expressions.
# validates_not_format_of takes an array of attributes,
#  a regular expression to apply to them,
#  and an optional hash of configuration options.
#
# All other validations take an array of attributes,
#  a word or list of words to act upon,
#  and an optional hash of configuration options.
#
# Examples:
# validates_does_have_the_words :username, :description, %w(chicken yellow coward), :ignore_case => true
# In this case, the model will not be valid if the username or description attributes contain any of the
# restricted words, regardless of case.
#
module ActiveRecord
  module Validations
    module ClassMethods
	
      # Negates a regular expression match against an attribute.
      # Similar to grep -v
      #
      def validates_not_format_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
	
        raise(ArgumentError, \"A regular expression must be supplied as the :with option of the configuration hash\") unless configuration[:with].is_a?(Regexp)
	
        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) if value.to_s =~ configuration[:with]
        end
      end
	
      def validates_does_not_begin_with_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| \"^#{word}\" }
      end
      alias :validates_does_not_begin_with_the_words :validates_does_not_begin_with_the_word
	
      # Validates an attribute does not end with the provided string, or strings.
      # Examples:
      # validates_does_not_end_with_the_word :username, '2000'
      #  or
      # validates_does_not_end_with_the_words :username, %w(xxx ooo 666 McFly)
      #
      def validates_does_not_end_with_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| \"#{word}$\" }
      end
      alias :validates_does_not_end_with_the_words :validates_does_not_end_with_the_word
	
      # Validates an attribute does not contain disallowed words
      # Examples:
      # validates_does_not_have_the_word :username, 'admin'
      #  or
      # validates_does_not_have_the_words :username, %w(admin guest moderator)
      #
      def validates_does_not_have_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| word }
      end
      alias :validates_does_not_have_the_words :validates_does_not_have_the_word
	
      def validates_is_not_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| \"^#{word}$\" }
      end
      alias :validates_is_not_the_words :validates_is_not_the_word
	
      private
      # The block supplied to validates_attributes_not_format_of_block must yield a string
      # that can be converted into a regular expression by Regexp.new
      #
      # The argument supplied to the block is a string that has been regular-expression-quoted,
      # and is safe to use without further escaping.
      #
      # You can pass options for how the regular expression is created in the final options hash.
      # Either use :ignore_case => true
      #  or
      # :regexp_options => Fixnum
      # either of which will be passed as the 2nd argument to Regexp.new, with :regexp_options taking precedence
      # See documentation on Regexp.new for more information on allowable values for :regexp_options
      #
      def validates_attributes_not_format_of_block(attr_names, &block)
        options = attr_names.last.is_a?(Hash) ? attr_names.pop : { }
        word = attr_names.pop
	
        words = word.respond_to?(:each) ? word : [ word ]
        regexp_options = options.delete(:regexp_options) || options.delete(:ignore_case)
	
        restriction = Regexp.new(words.map { |w| \"(#{yield Regexp.quote(w.to_s)})\" }.join('|'), regexp_options)
	
        configuration = {
          # Overridable default configuration
          :message => 'is not valid',
          :on => :save
        }.merge(options || {}).merge(
          # Mandatory default configuration
          :with => restriction
        )
        validates_not_format_of attr_names, configuration
      end
    end
  end
end

ActiveRecord Pagination for Destructive Migrations

Posted by duncanbeevers on December 7th, 2006 — Posted in Programming

When you need to make a one-time change to one of your models, a natural tool to use is Rails Migrations. At a first go, you might try something simple, along these lines:

The Naïve Approach

class UpdateUsersWithoutDefaultAvatars < ActiveRecord::Migration
  def self.up
    options = { :order => "id", :conditions => "avatar IS NULL" }
    User.find(:all).each do |user|
      user.reset_avatar!
    end
  end
	
  def self.down
  end
end

Not bad for a first try, but if we look at ActiveRecord::Base#find_by_sql (to which find calls are eventually delegated) we see the following:

def find_by_sql(sql)
  connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end

The kicker is right there at the call to instantiate where a fully-populated model instance is created. This means that depending on your :conditions, you might have all of those records in-memory simultaneously.

As the number of records returned by your find increases, the situation becomes simply untenable.

Instead, a paged approach can help, but don’t get too eager, this code is still not acceptable for production use.

Hidden Dangers

class UpdateUsersWithoutDefaultAvatars < ActiveRecord::Migration
  def self.up
    options = { :order => "id", :conditions => "avatar IS NULL" }
    per_page = 10
    (0..User.count(options)).step(per_page) do |offset|
    User.find(:all, options.dup.merge({ :limit => per_page, :offset => offset })).each do |user|
      user.reset_avatar!
    end
  end
	
  def self.down
  end
end

The astute reader may note that for each user, call reset_avatar!. Now, you can’t see the internals of this function, but perhaps it’s safe to suspect that it modifies a user’s avatar attribute. Fine, as that’s its intended purpose, but what about our :conditions clause?

For each page of users we fix, our overall count goes down, and our offset skips over records that should have been modified.

A simple solution would be to grab the first pageful of users, perform the operation on them, then request the next first pageful of users, as the ones we just modified no longer fall under our conditions clause.

Should we write this special-case for when the operation being performed on the model is destructive? How can we more elegantly implement this visitor?

In order to squeeze out this kind of super-performance, I chose to extend ActiveRecord::Base. Originally, it was simply out of convenience, but as you’ll see, access to ActiveRecord::Base‘ private instance method construct_finder_sql is key.

Efficiency

module ActiveRecord
  class Base
    def each_by_page per_page, options = {}, &block
      sql = construct_finder_sql(options.dup.merge({ :select => "id" }))
      all_ids = connection.select_all(sql).map { |h| h["id"] }
      at_a_time = 0..(per_page-1)
      begin
        all_ids.slice!(at_a_time).each do |ids|
          find(:all, :conditions => [ "id IN (?)", ids ] ).each &block
        end
      end until all_ids.empty?
    end
  end
end

and in the migration:

class UpdateUsersWithoutDefaultAvatars < ActiveRecord::Migration
  def self.up
    options = { :order => "id", :conditions => "avatar IS NULL" }
    User.each_by_page(10) { user.reset_avatar! }
  end
	
  def self.down
  end
end

First we use our privileged access to construct_finder_sql to build a query that will return only the :ids of the records we want to work with. This will be a large SQL hit, and may require pagination of its own, but the memory footprint is tiny compared to fully instantiating each object, even when your query returns thousands or tens-of-thousands of records.

Results from feeding SQL directly into connection are returned as an array of hashes. Since all we’re really interested in is the id of each row, (the only member of the hash), we perform a simple mapping on each entry.

As an aside, I imagine there’s a simpler way to flatten out this array than what I’ve shown. Let me know if you’ve got a really killer method.

Now that we’ve got our ids, we can chop out manageable pieces, instantiate, and modify until the array of ids to be operated on is empty, all with no risk of records coming back in any way other than we intended.

Update
rcoder notes:

If the update is only affects the User model, ‘User.update_all’ may be sufficient; otherwise, a quick call into ‘ActiveRecord::Base.connection.execute(”…”)’ may be called for.

A good point, when the modification of the model is simple and the conditions apply only to the table you’re working with; however, I feel this paged approach has at least two significant advantages:

  • Records modified by each_by_page are still subject to validations, whereas update_all or pumping SQL directly into Model.connection bypasses these safety checks.
  • Multi-table joins can be trivially incorporated into the conditions, whereas crafting, and re-crafting SQL is slightly more error-prone.

If all your models needed to be updated regularly, it might be worthwhile to look at implementing this a little closer to the steel, but I feel that especially in the case of migrations, the additional clarity and sanity-checking provided by staying within Rails outweigh the performance benefits gained by doing things by hand.

Ruby Advent Calendar

Posted by duncanbeevers on December 1st, 2006 — Posted in Programming

Ruby Inside has just opened up the first treat in their Ruby Advent Calendar and it things are off to a great start. The Amazon S3 services library they demonstrate greatly simplifies the integration of off-site asset storage from within your application, and for those who live in the terminal, it even provides a specialized interactive shell for driving the library. Very cool.

I can’t wait to see what else they have in store.

Setting up Ruby on Rails on Mac OS X

Posted by duncanbeevers on November 10th, 2006 — Posted in Programming

Following the purchase of a new MacBook Core 2 Duo, I needed to get a Rails development environment up-and-running quickly. After experiencing the hassle of setting things up manually, I explored some simpler alternatives and was able to go from out-of-box to a comfortable working environment in under 2 hours, avoid the mire of DarwinPorts, and never insert physical media. So here’s the bundle of software I’ve found essentially indispensable.

Comprehensive List, for the impatient

Up and Running

I imagine that you may already have a basic Mac environment set up for your everyday use, including Instant Messaging, so I’ll skip right to what the developers want. Mac OS X 10.4.8 ships with Ruby 1.8.2 installed, but for Rails development you’ll want at least 1.8.4 (1.8.5 break breakpointer) which is exactly what Locomotive comes with (along with Rails 1.1.6, no edge goodness, but the security vulnerability is patched.)

Go ahead and grab Locomotive and install per usual. You can use Locomotive to bootstrap a new project, or you can import an existing project and launch a terminal from Locomotive in that project’s context, but first, let’s make sure your Locomotive is ready for some real action.

The default bundle included with Locomotive doesn’t include rmagick, which can be notoriously difficult to install under OS X. Go ahead and download the rmagick Locomotive bundle from the Locomotive site and drop it into your Bundles directory, /Applications/Locomotive2/Bundles by default.

Importing an existing project into Locomotive

If you haven’t checked an existing project out of SVN yet, or you aren’t using SVN, first install Martin Ott’s build of SVN 1.4. As of this writing, the Mac build at Metissian is still at 1.3, which may work for your project, but why linger? Once SVN is installed, check your project out. I install something like this.

mkdir ~/Projects
mkdir ~/Projects/my_project
svn co http://svn.myhost.com ~/Projects/my_project

Enter your credentials, and glaze over as your terminal scrolls…

Once checkout is complete, in Locomotive, add an existing project: Applications → Add existing…

Enter the path of your project, ~/Projects/my_project and select the rmagick bundle from the framework dropdown.

Once your application is recognized by Locomotive, you can select and launch a terminal window: Applications → Open Terminal

Making your Locomotive environment persistent

Once the Terminal is open, check out your environment, perhaps try typing ruby -v or rake -T.

At the top of your terminal (or iTerm) window, you should see a line something like source /private/var/tmp/folders.501/TemporaryItems/1DFEAB12-1424-4833-A7E0-26AB557E2034-1783-00001E4A46F9938A.environment.bash

Select and copy this line, and in your terminal type.

mkdir ~/lib
cp /private/var/tmp/folders.501/TemporaryItems/1DFEAB12-1424-4833-A7E0-26AB557E2034-1783-00001E4A46F9938A.environment.bash ~/lib/locomotive.bash

If you’re not interested in switching to your rails application path whenever you spawn a new terminal, modify the new locomotive.bash file. If you’ve got Textmate installed, you can issue the command mate ~/lib/locomotive.bash and remove the last line of the file.

In order to load this file whenever you open a terminal window, you can modify your .bash_profile.

Issue the command mate ~/.bash_profile and add the line source ~/lib/locomotive.bash to the top of the file. Any additional shell customizations can be added to your .bash_profile following the sourcing of the Locomotive script.

Closing

Once Locomotive is installed and persisted, you needn’t actually launch the Locomotive application proper again if you don’t wish. Gems can be installed as usual and will be placed into the Locomotive directory, so wiping Locomotive will keep your system sparkling.

Setting up RadRails 0.7.1 on Windows XP

Posted by duncanbeevers on September 21st, 2006 — Posted in Programming

RadRails released version 0.7.1 (download) on September 14th so I decided to walk through getting it set up with an in-progress project.

First, some basic configuration needs to be done to let RadRails know where a few key binaries are. tshine’s tutorial walks you through this basic setup.
In summary, with some tweaks:

  • Specify ruby interpreter: Window → Preferences… → Ruby → Installed Interpreters → Add →
    • Interpreter Name: Ruby
    • Location c:\ruby\bin\ruby.exe
  • Specify Ri/rdoc paths: Window → Preferences… → Ruby → Ri/rdoc →
    • RDoc path: c:\ruby\bin\rdoc
    • Ri path: c:\ruby\bin\ri
  • Specify rails, rake, and mongrel binaries: Window → Preferences… → Rails → Configuration →
    • Rails path: c:\ruby\bin\rails
    • Rake path: c:\ruby\bin\rake
    • Mongrel path: c:\ruby\bin\mongrel_rails

If you’re a TextMate fan, as many Railers are, you’ll appreciate Corban Brook’s
TextMate accelerator template.

A quick tip: If you’re not already using Subversion, get your Rails project on it. If you are on it, RadRails can quickly associate with your repository, bringing Subversions features seamlessly into your IDE.

If your Subversion repository is only accessible through an SSH tunnel, Martin Woodward’s walk-through provides helpful advice. One note, for my company’s Subversion repository I had to ask RadRails to use JavaSVN instead of JavaHL in order to avoid client out of date errors. If you experience the same issue, you can reach the option from Window → Preferences… → Team → SVN → SVN Interface: JavaSVN (Pure Java)

Once I had the SVN client configured, I was able to import my existing project as a RadRails project;

  • Create new project in workspace: File → New… SVN → Checkout Projects from SVN
  • Configure repository information: Create new repository location → Next
  • Url: svn+ssh://username@svnhost/projectpath/ → Next
  • Select root folder for your application: RAILS_ROOT → Next
  • Select setup method: Check out as project configured using the New Project Wizard → Finish
  • Select project type: Rails → Rails Project → Next
  • Configure project:
    • Project name: Your Project Name
    • check Use default location (note: If your project is already checked-out locally, you specify the local path where it resides, but the project will be re-checked out and any uncommitted local changes will be lost.)
    • uncheck Generate Rails application skeleton
    • uncheck Create a WEBrick server
    • check Create a Mongrel server
    • uncheck Disable table pluralization
    Finish

Though debugging support in RadRails is still experimental, if you’re feeling adventurous you may benefit from Richard Robert’s post on live debugging.

uTorrent Web Interface

Posted by duncanbeevers on September 20th, 2006 — Posted in Uncategorized

My favorite BitTorrent client, µTorrent released a beta version a few days ago which includes an oft-requested web-interface.

If you haven’t already checked it out, you might get a kick out of some its features; bandwidth scheduling, bandwidth limiting (global / per-torrent), and RSS downloading, to name just a few.

And with the new web interface, the features of this lightweight, nimble client are accessible to more people than ever.