0

A model Country has a attribute code which is automatically converted to lowercase by a before_save callback. Is it possible to force this behaviour on "magic" methods without rewriting large chunks of ActiveRecord::Base?

class Country < ActiveRecord::Base
  attr_accessible :code
  validates :code, :presence => true
  validates_uniqueness_of :code, :case_sensitive => false

  before_save do |country|
    country.code.downcase! unless country.code.nil?
  end
end

RSpec

describe Country do
  describe 'data normalization'
    before :each do
      @country = FactoryGirl.create(:country, :code => 'DE')
    end

    # passes
    it 'should normalize the code to lowercase on insert' do
      @country.code.should eq 'de'
    end

    # fails
    it 'should be agnostic to uppercase finds' do
      country = Country.find_by_code('DE')
      country.should_not be_nil
    end 

    # fails
    it 'should be agnostic to uppercase finds_or_creates' do
      country = Country.find_or_create_by_code('DE')
      country.id.should_not be_nil # ActiveRecord Bug?
    end
end
wintersolutions
  • 5,173
  • 5
  • 30
  • 51

1 Answers1

0

This is what I came up with, altough I really hate that approach (as mentioned in the question). An easy alternative would be to set the column, table or whole database up to ignore case (but this is db dependendt).

class Country < ActiveRecord::Base
  attr_accessible :code
  validates :code, :presence => true
  validates_uniqueness_of :code, :case_sensitive => false

  before_save do |country|
    country.code.downcase! unless country.code.nil?
  end

  class ActiveRecord::Base
    def self.method_missing_with_code_finders(method_id, *arguments, &block)
      if match = (ActiveRecord::DynamicFinderMatch.match(method_id) || ActiveRecord::DynamicScopeMatch.match(method_id))
        attribute_names = match.attribute_names
        if code_index = attribute_names.find_index('code')
          arguments[code_index].downcase!
        end
      end
      method_missing_without_code_finders(method_id, *arguments, &block)
    end

    class << self
      alias_method_chain(:method_missing, :code_finders)
    end
  end
end
wintersolutions
  • 5,173
  • 5
  • 30
  • 51