<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>activerecord/lib/active_record/dynamic_finder_match.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -51,6 +51,7 @@ require 'active_record/calculations'
 require 'active_record/serialization'
 require 'active_record/attribute_methods'
 require 'active_record/dirty'
+require 'active_record/dynamic_finder_match'
 
 ActiveRecord::Base.class_eval do
   extend ActiveRecord::QueryCache</diff>
      <filename>activerecord/lib/active_record.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1354,8 +1354,8 @@ module ActiveRecord #:nodoc:
       end
 
       def respond_to?(method_id, include_private = false)
-        if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
-          return true if all_attributes_exists?(extract_attribute_names_from_match(match))
+        if match = DynamicFinderMatch.match(method_id)
+          return true if all_attributes_exists?(match.attribute_names)
         end
         super
       end
@@ -1674,88 +1674,65 @@ module ActiveRecord #:nodoc:
         # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
         # attempts to use it do not run through method_missing.
         def method_missing(method_id, *arguments)
-          if match = matches_dynamic_finder?(method_id)
-            finder = determine_finder(match)
-
-            attribute_names = extract_attribute_names_from_match(match)
+          if match = DynamicFinderMatch.match(method_id)
+            attribute_names = match.attribute_names
             super unless all_attributes_exists?(attribute_names)
-
-            self.class_eval %{
-              def self.#{method_id}(*args)
-                options = args.extract_options!
-                attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
-                finder_options = { :conditions =&gt; attributes }
-                validate_find_options(options)
-                set_readonly_option!(options)
-
-                if options[:conditions]
-                  with_scope(:find =&gt; finder_options) do
-                    ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+            if match.finder?
+              finder = match.finder
+              self.class_eval %{
+                def self.#{method_id}(*args)
+                  options = args.extract_options!
+                  attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+                  finder_options = { :conditions =&gt; attributes }
+                  validate_find_options(options)
+                  set_readonly_option!(options)
+
+                  if options[:conditions]
+                    with_scope(:find =&gt; finder_options) do
+                      ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+                    end
+                  else
+                    ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
                   end
-                else
-                  ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
-                end
-              end
-            }, __FILE__, __LINE__
-            send(method_id, *arguments)
-          elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
-            instantiator = determine_instantiator(match)
-            attribute_names = extract_attribute_names_from_match(match)
-            super unless all_attributes_exists?(attribute_names)
-
-            self.class_eval %{
-              def self.#{method_id}(*args)
-                guard_protected_attributes = false
-
-                if args[0].is_a?(Hash)
-                  guard_protected_attributes = true
-                  attributes = args[0].with_indifferent_access
-                  find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
-                else
-                  find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
                 end
+              }, __FILE__, __LINE__
+              send(method_id, *arguments)
+            elsif match.instantiator?
+              instantiator = match.instantiator
+              self.class_eval %{
+                def self.#{method_id}(*args)
+                  guard_protected_attributes = false
+
+                  if args[0].is_a?(Hash)
+                    guard_protected_attributes = true
+                    attributes = args[0].with_indifferent_access
+                    find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
+                  else
+                    find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+                  end
 
-                options = { :conditions =&gt; find_attributes }
-                set_readonly_option!(options)
+                  options = { :conditions =&gt; find_attributes }
+                  set_readonly_option!(options)
 
-                record = find_initial(options)
+                  record = find_initial(options)
 
-                 if record.nil?
-                  record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
-                  #{'yield(record) if block_given?'}
-                  #{'record.save' if instantiator == :create}
-                  record
-                else
-                  record
+                   if record.nil?
+                    record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
+                    #{'yield(record) if block_given?'}
+                    #{'record.save' if instantiator == :create}
+                    record
+                  else
+                    record
+                  end
                 end
-              end
-            }, __FILE__, __LINE__
-            send(method_id, *arguments)
+              }, __FILE__, __LINE__
+              send(method_id, *arguments)
+            end
           else
             super
           end
         end
 
-        def matches_dynamic_finder?(method_id)
-          /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
-        end
-
-        def matches_dynamic_finder_with_initialize_or_create?(method_id)
-          /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
-        end
-
-        def determine_finder(match)
-          match.captures.first == 'all_by' ? :find_every : :find_initial
-        end
-
-        def determine_instantiator(match)
-          match.captures.first == 'initialize' ? :new : :create
-        end
-
-        def extract_attribute_names_from_match(match)
-          match.captures.last.split('_and_')
-        end
-
         def construct_attributes_from_arguments(attribute_names, arguments)
           attributes = {}
           attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }</diff>
      <filename>activerecord/lib/active_record/base.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,6 +12,48 @@ require 'models/customer'
 require 'models/job'
 require 'models/categorization'
 
+class DynamicFinderMatchTest &lt; ActiveRecord::TestCase
+  def test_find_no_match
+    assert_nil ActiveRecord::DynamicFinderMatch.match(&quot;not_a_finder&quot;)
+  end
+
+  def test_find_by
+    match = ActiveRecord::DynamicFinderMatch.match(&quot;find_by_age_and_sex_and_location&quot;)
+    assert_not_nil match
+    assert match.finder?
+    assert_equal :find_initial, match.finder
+    assert_equal %w(age sex location), match.attribute_names
+  end
+
+  def test_find_all_by
+    match = ActiveRecord::DynamicFinderMatch.match(&quot;find_all_by_age_and_sex_and_location&quot;)
+    assert_not_nil match
+    assert match.finder?
+    assert_equal :find_every, match.finder
+    assert_equal %w(age sex location), match.attribute_names
+  end
+
+  def test_find_or_initialize_by
+    match = ActiveRecord::DynamicFinderMatch.match(&quot;find_or_initialize_by_age_and_sex_and_location&quot;)
+    assert_not_nil match
+    assert !match.finder?
+    assert match.instantiator?
+    assert_equal :find_initial, match.finder
+    assert_equal :new, match.instantiator
+    assert_equal %w(age sex location), match.attribute_names
+  end
+
+  def test_find_or_create_by
+    match = ActiveRecord::DynamicFinderMatch.match(&quot;find_or_create_by_age_and_sex_and_location&quot;)
+    assert_not_nil match
+    assert !match.finder?
+    assert match.instantiator?
+    assert_equal :find_initial, match.finder
+    assert_equal :create, match.instantiator
+    assert_equal %w(age sex location), match.attribute_names
+  end
+end
+
 class FinderTest &lt; ActiveRecord::TestCase
   fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
 </diff>
      <filename>activerecord/test/cases/finder_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>3beed9cdb7cf2cf75fb043b72ca99cc23fc11556</id>
    </parent>
  </parents>
  <author>
    <name>Josh Susser</name>
    <email>josh@hasmanythrough.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/143f5fbb21b6dfcaab63d67b44afd922dab9dcf5</url>
  <id>143f5fbb21b6dfcaab63d67b44afd922dab9dcf5</id>
  <committed-date>2008-08-25T23:32:20-07:00</committed-date>
  <authored-date>2008-08-25T19:32:19-07:00</authored-date>
  <message>refactor dynamic finder name matching into its own class

Signed-off-by: Jeremy Kemper &lt;jeremy@bitsweat.net&gt;</message>
  <tree>25551d412c07795edc2782035228f430c0290927</tree>
  <committer>
    <name>Jeremy Kemper</name>
    <email>jeremy@bitsweat.net</email>
  </committer>
</commit>
