public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
refactor dynamic finder name matching into its own class

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
joshsusser (author)
Mon Aug 25 19:32:19 -0700 2008
jeremy (committer)
Mon Aug 25 23:32:20 -0700 2008
commit  143f5fbb21b6dfcaab63d67b44afd922dab9dcf5
tree    25551d412c07795edc2782035228f430c0290927
parent  3beed9cdb7cf2cf75fb043b72ca99cc23fc11556
...
51
52
53
 
54
55
56
...
51
52
53
54
55
56
57
0
@@ -51,6 +51,7 @@ require 'active_record/calculations'
0
 require 'active_record/serialization'
0
 require 'active_record/attribute_methods'
0
 require 'active_record/dirty'
0
+require 'active_record/dynamic_finder_match'
0
 
0
 ActiveRecord::Base.class_eval do
0
   extend ActiveRecord::QueryCache
...
1354
1355
1356
1357
1358
 
 
1359
1360
1361
...
1674
1675
1676
1677
1678
1679
1680
 
 
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1717
1718
1719
 
 
1720
1721
 
1722
1723
1724
1725
1726
1727
1728
1729
 
 
 
 
 
 
 
 
1730
1731
1732
1733
 
 
 
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
...
1354
1355
1356
 
 
1357
1358
1359
1360
1361
...
1674
1675
1676
 
 
 
 
1677
1678
1679
 
 
 
 
 
 
 
 
 
 
 
 
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
 
 
1714
1715
1716
 
1717
1718
 
 
 
 
 
 
 
1719
1720
1721
1722
1723
1724
1725
1726
1727
 
 
 
1728
1729
1730
1731
1732
1733
1734
1735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1736
1737
1738
0
@@ -1354,8 +1354,8 @@ module ActiveRecord #:nodoc:
0
       end
0
 
0
       def respond_to?(method_id, include_private = false)
0
-        if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
0
-          return true if all_attributes_exists?(extract_attribute_names_from_match(match))
0
+        if match = DynamicFinderMatch.match(method_id)
0
+          return true if all_attributes_exists?(match.attribute_names)
0
         end
0
         super
0
       end
0
@@ -1674,88 +1674,65 @@ module ActiveRecord #:nodoc:
0
         # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
0
         # attempts to use it do not run through method_missing.
0
         def method_missing(method_id, *arguments)
0
-          if match = matches_dynamic_finder?(method_id)
0
-            finder = determine_finder(match)
0
-
0
-            attribute_names = extract_attribute_names_from_match(match)
0
+          if match = DynamicFinderMatch.match(method_id)
0
+            attribute_names = match.attribute_names
0
             super unless all_attributes_exists?(attribute_names)
0
-
0
-            self.class_eval %{
0
-              def self.#{method_id}(*args)
0
-                options = args.extract_options!
0
-                attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
0
-                finder_options = { :conditions => attributes }
0
-                validate_find_options(options)
0
-                set_readonly_option!(options)
0
-
0
-                if options[:conditions]
0
-                  with_scope(:find => finder_options) do
0
-                    ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
0
+            if match.finder?
0
+              finder = match.finder
0
+              self.class_eval %{
0
+                def self.#{method_id}(*args)
0
+                  options = args.extract_options!
0
+                  attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
0
+                  finder_options = { :conditions => attributes }
0
+                  validate_find_options(options)
0
+                  set_readonly_option!(options)
0
+
0
+                  if options[:conditions]
0
+                    with_scope(:find => finder_options) do
0
+                      ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
0
+                    end
0
+                  else
0
+                    ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
0
                   end
0
-                else
0
-                  ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
0
-                end
0
-              end
0
-            }, __FILE__, __LINE__
0
-            send(method_id, *arguments)
0
-          elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
0
-            instantiator = determine_instantiator(match)
0
-            attribute_names = extract_attribute_names_from_match(match)
0
-            super unless all_attributes_exists?(attribute_names)
0
-
0
-            self.class_eval %{
0
-              def self.#{method_id}(*args)
0
-                guard_protected_attributes = false
0
-
0
-                if args[0].is_a?(Hash)
0
-                  guard_protected_attributes = true
0
-                  attributes = args[0].with_indifferent_access
0
-                  find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
0
-                else
0
-                  find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
0
                 end
0
+              }, __FILE__, __LINE__
0
+              send(method_id, *arguments)
0
+            elsif match.instantiator?
0
+              instantiator = match.instantiator
0
+              self.class_eval %{
0
+                def self.#{method_id}(*args)
0
+                  guard_protected_attributes = false
0
+
0
+                  if args[0].is_a?(Hash)
0
+                    guard_protected_attributes = true
0
+                    attributes = args[0].with_indifferent_access
0
+                    find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
0
+                  else
0
+                    find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
0
+                  end
0
 
0
-                options = { :conditions => find_attributes }
0
-                set_readonly_option!(options)
0
+                  options = { :conditions => find_attributes }
0
+                  set_readonly_option!(options)
0
 
0
-                record = find_initial(options)
0
+                  record = find_initial(options)
0
 
0
-                 if record.nil?
0
-                  record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
0
-                  #{'yield(record) if block_given?'}
0
-                  #{'record.save' if instantiator == :create}
0
-                  record
0
-                else
0
-                  record
0
+                   if record.nil?
0
+                    record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
0
+                    #{'yield(record) if block_given?'}
0
+                    #{'record.save' if instantiator == :create}
0
+                    record
0
+                  else
0
+                    record
0
+                  end
0
                 end
0
-              end
0
-            }, __FILE__, __LINE__
0
-            send(method_id, *arguments)
0
+              }, __FILE__, __LINE__
0
+              send(method_id, *arguments)
0
+            end
0
           else
0
             super
0
           end
0
         end
0
 
0
-        def matches_dynamic_finder?(method_id)
0
-          /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
0
-        end
0
-
0
-        def matches_dynamic_finder_with_initialize_or_create?(method_id)
0
-          /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
0
-        end
0
-
0
-        def determine_finder(match)
0
-          match.captures.first == 'all_by' ? :find_every : :find_initial
0
-        end
0
-
0
-        def determine_instantiator(match)
0
-          match.captures.first == 'initialize' ? :new : :create
0
-        end
0
-
0
-        def extract_attribute_names_from_match(match)
0
-          match.captures.last.split('_and_')
0
-        end
0
-
0
         def construct_attributes_from_arguments(attribute_names, arguments)
0
           attributes = {}
0
           attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
...
12
13
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
16
17
...
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
0
@@ -12,6 +12,48 @@ require 'models/customer'
0
 require 'models/job'
0
 require 'models/categorization'
0
 
0
+class DynamicFinderMatchTest < ActiveRecord::TestCase
0
+  def test_find_no_match
0
+    assert_nil ActiveRecord::DynamicFinderMatch.match("not_a_finder")
0
+  end
0
+
0
+  def test_find_by
0
+    match = ActiveRecord::DynamicFinderMatch.match("find_by_age_and_sex_and_location")
0
+    assert_not_nil match
0
+    assert match.finder?
0
+    assert_equal :find_initial, match.finder
0
+    assert_equal %w(age sex location), match.attribute_names
0
+  end
0
+
0
+  def test_find_all_by
0
+    match = ActiveRecord::DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
0
+    assert_not_nil match
0
+    assert match.finder?
0
+    assert_equal :find_every, match.finder
0
+    assert_equal %w(age sex location), match.attribute_names
0
+  end
0
+
0
+  def test_find_or_initialize_by
0
+    match = ActiveRecord::DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location")
0
+    assert_not_nil match
0
+    assert !match.finder?
0
+    assert match.instantiator?
0
+    assert_equal :find_initial, match.finder
0
+    assert_equal :new, match.instantiator
0
+    assert_equal %w(age sex location), match.attribute_names
0
+  end
0
+
0
+  def test_find_or_create_by
0
+    match = ActiveRecord::DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location")
0
+    assert_not_nil match
0
+    assert !match.finder?
0
+    assert match.instantiator?
0
+    assert_equal :find_initial, match.finder
0
+    assert_equal :create, match.instantiator
0
+    assert_equal %w(age sex location), match.attribute_names
0
+  end
0
+end
0
+
0
 class FinderTest < ActiveRecord::TestCase
0
   fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
0
 

Comments