Nick Kallen noted that the behavior of has_many_with_args can be accomplished using has_finder.

Too true!

Imagine a system in which Users send Whispers to one another. We would like to see Whispers scoped so that:

  • A User viewing their own Whispers should see all Whispers they have received

  • A User viewing another User’s Whispers should see only Whispers they themselves sent

With the clean chainable syntax provided by has_finder, this is straight-forward. We return associations directly (notice, no find calls) and isolate declaration of the scoping the behavior to the class affected by it.

class Whisper < ActiveRecord::Base
  has_finder :from, lambda { |sender| { :conditions => [ 'sender_id = ?', sender.id ] } }
end
 
class User < ActiveRecord::Base
  has_many :received_whispers, :foreign_key => 'recipient_id'
 
  def whispers_viewable_by user
    return whispers if self == user
    whispers.from(user)
  end
end

How expressive is that? Business rules are cleanly captured in the models and our associations stay so limber!

Thanks to Pat Maddox for turning me on to has_finder, and Nick Kallen for writing it.

class Fixnum
  def clip_to range
    r = range.first < range.last ? range : (range.last)..(range.first)
    return [ [ r.min, self ].max, r.max ].min
  end
end

Updatehas_finder accomplishes everything has_many_with_args does in an elegant and extensible fashion. As it’s slated for inclusion in Rails 2.1, I thought I’d describe how to accomplish the scoped whispers example presented in this post using has_finder.

Read the solution here.

Rails Associations are powerful tools, but sometimes they can be a bit over-reaching.

Let’s examine a hypothetical has_many association and how it would traditionally be managed.

class User
  has_many :sent_whispers, :class_name => 'Whisper', :foreign_key => 'sender_id'
  has_many :received_whispers, :class_name => 'Whisper', :foreign_key => 'recipient_id'
end
 
class Whisper
  belongs_to :sender, :class_name => 'User'
  belongs_to :recipient, :class_name => 'User'
end

Now, if you’d like to scope down received whispers to just those sent by a specific user, you’d ordinarily probably use something along these lines.

def whispers_from sender
  with_scope( :find => { :conditions => [ 'sender_id = ?', sender.id ] } ) do
    received_whispers.find(:all)
  end
end

Unfortunately, this is pretty limiting. For example, any association extensions defined on :received_whispers are unavailable when accessing whispers_from. We also lose out on all the association calculation methods and can no longer use << to insert new elements directly into the association.

We could get the type of focused behavior we wanted by adding a conditions clause to the association.

has_many :whispers_from,
  :class_name => 'Whisper',
  :foreign_key => 'recipient_id',
  :conditions => [ 'sender_id = ?', sender.id ]

This won’t work either! sender isn’t defined in the class scope as the association is being created, so the conditions clause is at a loss. But all is not lost! A poorly-documented feature of the association methods is the lazy evaluation of conditions.

has_many :whispers_from,
  :class_name => 'Whisper',
  :foreign_key => 'recipient_id',
  :conditions => 'sender_id = #{sender.id}'

Notice that the conditions clause is singly-quoted. When the conditions clause is evaulated, the sender method will be called on the User. Better, but still not much use to us, as the User doesn’t have a sender method. Well, what if sender were an ordinary attr_reader on the User? If we could get @sender set prior to the evaluation of the association conditions, we’d be set!

Okay, we can do this!
I whipped up has_many_with_args (suggestions for a better name?) which allows you to create an association with teeth!

class User
  has_many_with_args :whispers_from, :sender,
    :class_name => 'Whisper',
    :foreign_key => 'recipient_id',
    :conditions => 'sender_id = #{sender.id}'
end

And that’s it! Now you can use all the regular has_many association methods, calculation methods, and your own association extensions. Boss!

alice = User.find(1)
bob = User.find(2)
alice.whispers_from(bob).count # SELECT COUNT(*) FROM users WHERE recipient_id=1 AND sender_id=2

It’s all packaged up as a plugin for you. Just install using piston:

piston import http://hasmanywithargs.googlecode.com/svn/trunk/ vendor/plugins/has_many_with_args

For the brave and curious, have a look at the source to see how the attr_readers are set up and initialized when the association is accessed.

Displaying ads on a site can be a little tricky. Many ad networks simply provide you with a snippet of javascript that must be embedded directly into a page where an ad is to be displayed. These often use document.write to generate more script tags which in turn may generate the actual ad content.

Regardless, sourcing external javascript files in this way can lead to choppy page loads as progressive rendering halts, waiting for this external content to be downloaded and evaluated. Yahoo!’s YSlow grades a page down for this, specifically for violation of Rule 6: Put Scripts at the Bottom.

With scripts, progressive rendering is blocked for all content below the script. Moving scripts as low in the page as possible means there’s more content above the script that is rendered sooner.

In some situations it’s not easy to move scripts to the bottom. If, for example, the script uses document.write to insert part of the page’s content, it can’t be moved lower in the page.

This was causing us serious pains as our ad-provider sometimes responded slowly, and sometimes responded not-at-all. In the spirit of graceful degradation, I crafted this solution for Rails.

Instead of generating the ad markup where the ad was supposed to appear, I first generate a placeholder DOM node, and using a content_for block, tuck the actual markup away. In the application’s layout template (application.rhtml), the last 3 lines are like so.

<%= yield :support_footer %>
  </body>
</html>

Generating the support_footer is straight-forward:

module DeferredContentInsertionHelper
 
  def deferred_insertion to_insert, options = {}
    content = case to_insert
    when String
      to_insert
    when Hash
      render_to_string(to_insert)
    end
 
    container = "di_#{content.hash}"
    defer_container = "defer_#{container}"
 
    content_for :support_footer do
      [ content_tag('div', content, :id => defer_container),
        javascript_tag("$('#{container}').parentNode.replaceChild($('#{defer_container}'), $('#{container}'));")
      ].join
    end
 
    # Insert a placeholder that the real content will replace once its loaded
    content_tag('span', '', :id => container)
  end
 
end

Now, when I want to generate an ad in the layout, instead of issuing:

<%= render :partial => 'ads/banner' %>

I can issue a deferred insertion and get a much more responsive page:

<%= deferred_insertion :partial => 'ads/banner' %>

Note how content generated in the support_footer is initially hidden, and is shown after it has been moved into place.

We were experiencing issues with ads being initially hidden and then moved into place. By leaving them visible, they render properly. The page layout changing as elements are added and removed can be overcome with appropriate css sizing.

I recently posted on determining whether two arrays contain the same elements irrespective of ordering.

The approach supplied works for some basic Ruby types, but failed for complex types such as arrays of instances of custom classes.

class Foo
  attr_accessor :i
 
  def initialize new_i
    self.i = new_i
  end
 
  def == another_foo
    self.i == another_foo.i
  end
 
end
 
foo1 = Foo.new(1)
foo2 = Foo.new(1)
 
[ foo1 ].same_elements?( [ foo2 ] ) # Expected true, got false

The culprit lay in Ruby’s Hash equality comparison method. In the original implementation of same_elements? a hash was built for each array. This hash’s keys were the unique elements of the array and its values were the counts of the number of occurrences of that key within the parent array.

Unfortunately, this led to the final comparison:

foo1 == foo2 # true
{ foo1 => 1 } == { foo2 => 1 } # false

To overcome this, we had to compare keys and values individually instead of with the built-in hash comparison.

class Array
  # Ask an Array whether it shares the same elements with another Array, irrespective of order
  # Options
  # :allow_duplicates
  #   If set to true arrays with the same elements, but differing numbers of those elements
  #   are treated as the same.
  #   Examples
  #     [ :a ].same_elements?( [ :a, :a ] ) => false
  #     [ :a ].same_elements?( [ :a, :a ], :allow_duplicates => true) => true
  def same_elements? another_array, options = {}
    raise ArgumentError, "#{another_array.inspect} was expected to be an Array" unless another_array.kind_of?(Array)
    s = self
    a = another_array
    if options[:allow_duplicates]
      s = s.uniq
      a = a.uniq
    end
 
    return element_counts(s) == element_counts(a)
  end
 
  private
    def element_counts obj
      result = []
      obj.uniq.map { |e|
        [ e, obj.inject(0) { |i, e2| i + (e == e2 ? 1 : 0 ) } ]
      }.each { |p| result << p.first; result << p.last }
 
      HashEqualityChecker.new(Hash[ *result ])
    end
end
 
class HashEqualityChecker < Hash
  def initialize new_hash
    self.replace new_hash
  end
 
  def == another_hash_equality_checker
    return false unless another_hash_equality_checker.size == size
 
    another_hash_equality_checker.inject(true) do |still_same, kv_pair|
      ak, av = kv_pair
 
      match = select do |k, v|
        k == ak && v == av
      end
 
      found_match = !match.empty?
      still_same && found_match
    end
  end
 
end

Now instead of using a default Hash for the element counts, we use the new HashEqualityChecker, which performs a == comparison on each pair of elements in the underlying Hash.

So you forgot to add a uniqueness constraint to a table in your db and over time, race conditions have wreaked havoc on your validates_uniqueness_of validations.

Time to clean out those duplicates!

module DuplicateFinder
 
  def find_duplicate_sets_on_keys *uniqueness_keys
    uniqueness_list = uniqueness_keys.join(', ')
    find(:all, :select => "#{uniqueness_list}, COUNT(#{primay_key}) AS total", :group => "#{uniqueness_list} HAVING total > 1")
  end
 
  def find_duplicates_for_set duplicate_set, options = {}
    uniqueness_keys = duplicate_set.attributes.reject do |attr_name, attr_value| attr_name == 'total' end.keys
    condition_values = uniqueness_keys.map do |uniqueness_key| duplicate_set.send uniqueness_key end
    uniqueness_condition = [ uniqueness_keys.map do |uniqueness_key| "#{uniqueness_key} = ?" end.join(' AND ') ] + condition_values
 
    with_scope :find => { :conditions => uniqueness_condition } do
      find(:all, options)
    end
  end
 
end
 
class ActiveRecord::Base
 
  class << self
    include DuplicateFinder
  end
 
end

Now you find and operate on duplicates within your migration like so:

require 'duplicate_finder'
 
class AddIndexForFavoriteColor < ActiveRecord::Migration
  class FavoriteColor < ActiveRecord::Base
    def self.destroy_duplicates!
      find_duplicate_sets_on_keys(:user_id, :color_id).each do |duplicate_set|
        dups = find_duplicates_for_set(duplicate_set)
 
        # All but the last
        dups[0..-2].each do |duplicate|
          duplicate.destroy
        end
 
      end
    end
  end
 
  def self.up
    FavoriteColor.destroy_duplicates!
    add_index :favorite_colors, [:user_id, :color_id], :unique => true
  end
 
  def self.down
    remove_index :favorite_colors, :column => [:user_id, :color_id]
  end
end

I split the find_duplicate_sets_on_keys and find_duplicates_for_set up to minimize the number of records instantiated at any given time.

Last month, Thoughtbot’s Matt Jankowski wrote up a post about How to not accidentally send thousands of ‘beta invites’.

The method outlined is elegant and simple. It also requires modifying the recipients of all of your ActionMailer models.

We wanted something that would allow our QA team to safely test email-sending features all over the site, and without a lot of management overhead.

module ActionMailer
  class Part
    def to_mail_with_capture defaults
      self.body += defaults.captures
      to_mail_without_capture defaults
    end
    alias_method_chain :to_mail, :capture
  end
 
  class Base
 
    cattr_writer :captured_email_recipient_address
 
    def captured_email_recipient_address
      @@captured_email_recipient_address
    end
 
    def captures
      # Add information about captured recipients to the footer of the email body
      captured_addresses = @captures.map do |method, addresses|
 
        if addresses.kind_of?(Array)
          captured_address = [
            "<ul>",
            addresses.map { |a| "<li>#{a}</li>" },
            "</ul>"
          ].join
        else
          captured_address = addresses
        end
 
        [
          "<dl>",
            "<dt>", method, "</dt>",
            "<dd>", captured_address, "</dd>",
          "</dl>"
        ].join
      end
 
      captured_addresses_report = [
        "<div><p>Intended recipients</p>",
        captured_addresses,
        "</div>"
      ].join
 
      captured_addresses_report
    end
 
    def captured method, list
      @captures ||= {}
      @captures[method] = list
      self.recipients_without_capture = captured_email_recipient_address
    end
 
    def recipients_with_capture= list
      captured :recipients, list
    end
    def cc_with_capture= list
      captured :cc, list
    end
    def bcc_with_capture= list
      captured :bcc, list
    end
 
    # Defer to the overridden setters if setting the list of recipients
    # Otherwise, return the unaliased list
    def recipients_with_capture list = nil
      list ? self.recipients_with_capture= list : recipients_without_capture
    end
    def cc_with_capture list = nil
      list ? self.cc_with_capture= list : cc_without_capture
    end
    def bcc_with_capture list = nil
      list ? self.bcc_with_capture= list : bcc_without_capture
    end
 
    alias_method_chain :cc=, :capture
    alias_method_chain :bcc=, :capture
    alias_method_chain :recipients=, :capture
    alias_method_chain :cc, :capture
    alias_method_chain :bcc, :capture
    alias_method_chain :recipients, :capture
 
  end
end

Requiring this file will cause all ActionMailer deliveries to be sent to one address, which you can set by using:

ActionMailer::Base.captured_email_recipient_address = "testing@yourdomain.com"

Additionally, a div will be appended to the email body indicating who its intended recipients were, and how the email would have been sent to them; recipient, cc, or bcc. Perfect for QA!

We simply set up the testing address as a mailing list in Google Docs, so we don’t have to manage who gets these emails from within the application, and can instead use Google’s tools.

Remember to set in your deploy recipe whether or not emails should be captured, as you wouldn’t want to accidentally capture all the email being sent out from your live application!

UPDATE: A more comprehensive method of comparing arrays is covered in this updated article.

When you don’t care what order items in an array are in, don’t pretend you do.

Say you’ve got a list of expected results, and some rows returned by the database:

expected_words = [ 'some', 'known', 'items' ]
found_words = Words.find( :all, :conditions => [ 'name IN (?)', expected_words ] ).map { |w| w.name }

Instead of asserting:

assert_equal expected_words, found_words

Consider:

assert_same_elements expected_words, found_words

To achieve this, I follow the pattern presented by Jay Fields in Convention Is Important, extending a core object and placing the extension in central location. In my case, this location is lib/core_extensions/array/same_elements.rb

class Array
  # Ask an Array whether it shares the same elements with another Array, irrespective of order
  # Options
  # :allow_duplicates
  #   If set to true arrays with the same elements, but differing numbers of those elements
  #   are treated as the same.
  #   Examples
  #     [ :a ].same_elements?( [ :a, :a ] ) => false
  #     [ :a ].same_elements?( [ :a, :a ], :allow_duplicates => true) => true
  def same_elements? another_array, options = {}
    raise ArgumentError, "#{another_array.inspect} was expected to be an Array" unless another_array.kind_of?(Array)
    s = self
    a = another_array
    if options[:allow_duplicates]
      s = s.uniq
      a = a.uniq
    end
 
    return element_counts(s) == element_counts(a)
  end
 
  private
    def element_counts obj
      result = []
      obj.uniq.map { |e|
        [ e, obj.inject(0) { |i, e2| i + (e == e2 ? 1 : 0 ) } ]
      }.each { |p| result << p.first; result << p.last }
      Hash[ *result ]
    end
end

This extensions allows us to ask an array about its similarity to another array.

The most interesting part of this method is the element_counts, which returns a hash whose keys are the unique elements of the array and whose values are the number of instances of each unique value in the original array.

In order to be able to use this in your tests, simply add this small helper method to test/test_helper.rb

# Assert two enumerables have the same elements, irrespective of order
def assert_same_elements enum1, enum2, *args
  message = args.last.kind_of?(String) ? args.pop : "Expected Arrays to have same elements"
  options = args.last.kind_of?(Hash) ? args.pop : {}
  assert_block(build_message(message, "<?> expected to have the same elements as \n<?>.\n", enum2, enum1)) { enum1.same_elements?(enum2, options) }
end

Bonus: Tests

require File.dirname(__FILE__) + '/../unit_test_helper'
 
class ArraySameElementsTest < Test::Unit::TestCase
  def test_same_elements_with_identical_arrays
    assert_equal true, [ :a ].same_elements?( [ :a ] )
  end
 
  def test_same_elements_with_no_shared_items
    assert_equal false, [ :a ].same_elements?( [ :b ] )
  end
 
  def test_same_elements_with_different_ordering
    assert_equal true, [ :a, :b ].same_elements?( [ :b, :a ] )
  end
 
  def test_same_elements_should_not_allow_duplicates
    assert_equal false, [ :a ].same_elements?( [ :a, :a ] ), 'Should not allow duplicates in other array.'
    assert_equal false, [ :a, :a ].same_elements?( [ :a ] ), 'Should not allow duplicates in self.'
    assert_equal false, [ :a, :b, :b ].same_elements?( [ :a, :a, :b ] ), 'Should not allow different duplicates in self and other.'
    assert_equal true, [ :a, :b, :b ].same_elements?( [ :a, :b, :b ] ), 'Should allow duplicates present in both self and other.'
  end
 
  def test_same_elements_should_allow_duplicates_if_allow_duplicates
    assert_equal true, [ :a ].same_elements?( [ :a, :a ], :allow_duplicates => true )
    assert_equal true, [ :a, :a ].same_elements?( [ :a ], :allow_duplicates => true )
    assert_equal true, [ :a, :b, :b ].same_elements?( [ :a, :a, :b ], :allow_duplicates => true )
  end
 
  def test_same_elements_should_not_allow_different_elements_if_allow_duplicates
    assert_equal false, [ :a, :b ].same_elements?( [ :a, :a, :b, :c ], :allow_duplicates => true )
  end
 
  def test_same_elements_should_raise_argument_error_if_provided_argument_is_not_an_array
    assert_raise_with_error_message ArgumentError, :error_message => / was expected to be an Array$/ do
      [ ].same_elements? nil
    end
  end
end

Sometimes I want to verify the value within a constant is handled in a certain way, independently of what the actual value in the constant is defined as.

So I change it, temporarily.

ApplicationConfig.override_constant(:RECORD_ANALYTICS, false) do
  puts "For the duration of this block, RECORD_ANALYTICS is #{ApplicationConfig::RECORD_ANALYTICS}"
end

# Override the value of a Module Constant for the duration of a block.
class Module
  def override_constant constant_name, new_value
    raise ArgumentError, 'No block provided to establish a context in which the constant is overridden' unless block_given?
    # Cannot override unless constant is defined in Module
    raise NameError, "#{self} does not define #{constant_name}" unless const_defined?(constant_name)
 
    # Store original value to replace it at the end
    original_value = const_get(constant_name)
 
    # Remove original definition
    remove_const constant_name
 
    # Insert alternative definition
    const_set constant_name, new_value
 
    begin
      yield
 
    ensure
      # Remove alternative definition
      remove_const constant_name
 
      # Restore original definition
      const_set constant_name, original_value
    end
  end
end

class OverrideConstantTest < Test::Unit::TestCase
  module TestModule
    DEFINED_CONSTANT = true
  end
 
  def test_should_raise_argument_error_when_block_is_not_provided
    assert_raise_with_error_message ArgumentError, :error_message => 'No block provided to establish a context in which the constant is overridden' do
      TestModule.override_constant :DEFINED_CONSTANT, true
    end
  end
 
  def test_should_raise_error_when_module_does_not_define_constant
    assert_raise_with_error_message NameError, :error_message => 'OverrideConstantTest::TestModule does not define NO_SUCH_CONSTANT' do
      TestModule.override_constant :NO_SUCH_CONSTANT, true do
      end
    end
  end
 
  def test_should_alter_value_of_constant_for_duration_of_block
    assert_equal true, TestModule::DEFINED_CONSTANT
 
    TestModule.override_constant :DEFINED_CONSTANT, false do
      assert_equal false, TestModule::DEFINED_CONSTANT
    end
 
    assert_equal true, TestModule::DEFINED_CONSTANT
  end
 
end

Verifying errors are raised is handy, but are you sure you’re capturing the right one?

Check the message!

# assert_raise allows you to check for the type of an error but not to check the error messages contents
# This is a wrapper to assert_raise that adds this functionality.
#
# Use with the traditional assert_raise syntax
# http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit/Assertions.html
#
# To specify the error message, provide a string or regular expression to the :error_message option
#
# Example
# assert_raise_with_error_message ArgumentError, :error_message => /is not refuelable$/ do
#   outfitter.refuel(Asteriod.new)
# end
#
class Test::Unit::TestCase
  def assert_raise_with_error_message *args, &block
    options = args.last.kind_of?(Hash) ? args.pop : {}
    exception = assert_raise *args, &block
    expected_message = options[:error_message]
    case expected_message
    when String
      assert_equal expected_message, exception.message
    when Regexp
      assert_match expected_message, exception.message
    end
  end
end