I wanted to have an easy way of performing case insensitive searches using dynamic finders without manually constructing the query, so if I called a finder with the _ci suffix, e.g. Customer.find_by_name_ci(‘Big Kahuna Burgers’), it would result in a query with lower(name) = lower(‘Big Kahuna Burgers’) in the where clause.
I could have created a new class that inherited from ActiveRecord::Base and overridden _methodmissing there, but then I and other developers would have to remember to derive all my models from this new class instead of ActiveRecord::Base. Instead I chose to extend ActiveRecord::Base itself.
This is the most obvious way of doing it - just reopen the class and add the functionality in. It’s ok if you’re adding something small but can get unwieldy as you add more.
class ActiveRecord::Base
# the new method has to be a class method
# note that it refers to the default implementation as
# method_missing_without_case_insensitive_finders
def self.method_missing_with_case_insensitive_finders(method_called, *args, &block)
match = method_called.to_s.match(/^find_by_(\w+)_ci$/)
if match && respond_to?("find_by_#{match[1]}")
find(:all, :conditions => ["lower(#{match[1]}) = lower(?)", *args[0]])
else
method_missing_without_case_insensitive_finders(method_called, *args, &block)
end
end
# method_missing is a class method, and this is how class methods are aliased
class << self
alias_method_chain(:method_missing, :case_insensitive_finders)
end
end
In theory, it’s also possible to put the new functionality into a separate module to make things a bit cleaner. You can do it using either extend or include. However, I couldn’t make this work in production mode (but I’m including the code for the sake of completeness below).
module CaseInsensitiveFinders
def method_missing_with_case_insensitive_finders(method_called, *args, &block)
match = method_called.to_s.match(/^find_by_(\w+)_ci$/)
if match && respond_to?("find_by_#{match[1]}")
find(:all, :conditions => ["lower(#{match[1]}) = lower(?)", *args[0]])
else
method_missing_without_case_insensitive_finders(method_called, *args, &block)
end
end
end
The only trick is that you need to add your new method to ActiveRecord::Base as a class method, so you have to use extend instead of include:
class ActiveRecord::Base
extend CaseInsensitiveFinders
class << self
alias_method_chain(:method_missing, :case_insensitive_finders)
end
end
It’s also possible to move _alias_methodchain into your module, in which case you add just one line to ActiveRecord::Base:
class ActiveRecord::Base
include CaseInsensitiveFinders
end
To make this work, in your module you have to override self.included which gets called when the module is included. You also need to put your method in a sub-module, and call base.extend(ClassMethods). Also note the use of _base.classeval.
module CaseInsensitiveFinders
module ClassMethods
def method_missing_with_case_insensitive_finders(method_called, *args, &block)
match = method_called.to_s.match(/^find_by_(\w+)_ci$/)
if match && respond_to?("find_by_#{match[1]}")
find(:all, :conditions => ["lower(#{match[1]}) = lower(?)", *args[0]])
else
method_missing_without_case_insensitive_finders(method_called, *args, &block)
end
end
end
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method_chain(:method_missing, :case_insensitive_finders)
end
end
end
end
I put this code into _activerecordextensions.rb in the models folder, but it can go anywhere so long as it gets automatically loaded by Rails before you access your models.
Works on: Rails 2.3.5