Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7510

Show
Ignore:
Timestamp:
09/18/07 10:04:11 (2 years ago)
Author:
xal
Message:

Define dynamic finders as real methods after first usage. Close #9317

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r7509 r7510  
    11*SVN* 
     2 
     3* Define dynamic finders as real methods after first usage. [bscofield] 
    24 
    35* Deprecation: remove deprecated threaded_connections methods. Use allow_concurrency instead.  [Jeremy Kemper] 
  • trunk/activerecord/lib/active_record/base.rb

    r7509 r7510  
    12211221        # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)  
    12221222        # or find_or_create_by_user_and_password(user, password). 
     1223        # 
     1224        # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future 
     1225        # attempts to use it do not run through method_missing. 
    12231226        def method_missing(method_id, *arguments) 
    12241227          if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s) 
     
    12281231            super unless all_attributes_exists?(attribute_names) 
    12291232 
    1230             attributes = construct_attributes_from_arguments(attribute_names, arguments) 
    1231  
    1232             case extra_options = arguments[attribute_names.size] 
    1233               when nil 
    1234                 options = { :conditions => attributes } 
     1233            self.class_eval %{ 
     1234              def self.#{method_id}(*args)  
     1235                options = args.last.is_a?(Hash) ? args.pop : {} 
     1236                attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) 
     1237                finder_options = { :conditions => attributes } 
     1238                validate_find_options(options) 
    12351239                set_readonly_option!(options) 
    1236                 ActiveSupport::Deprecation.silence { send(finder, options) } 
    1237  
    1238               when Hash 
    1239                 finder_options = extra_options.merge(:conditions => attributes) 
    1240                 validate_find_options(finder_options) 
    1241                 set_readonly_option!(finder_options) 
    1242  
    1243                 if extra_options[:conditions] 
    1244                   with_scope(:find => { :conditions => extra_options[:conditions] }) do 
    1245                     ActiveSupport::Deprecation.silence { send(finder, finder_options) } 
     1240 
     1241                if options[:conditions] 
     1242                  with_scope(:find => finder_options) do 
     1243                    ActiveSupport::Deprecation.silence { send(:#{finder}, options) } 
    12461244                  end 
    12471245                else 
    1248                   ActiveSupport::Deprecation.silence { send(finder, finder_options) } 
     1246                  ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) } 
    12491247                end 
    1250  
    1251               else 
    1252                 raise ArgumentError, "Unrecognized arguments for #{method_id}: #{extra_options.inspect}" 
    1253             end 
     1248              end 
     1249            } 
     1250            send(method_id, *arguments) 
    12541251          elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s) 
    12551252            instantiator = determine_instantiator(match) 
     
    12571254            super unless all_attributes_exists?(attribute_names) 
    12581255 
    1259             if arguments[0].is_a?(Hash) 
    1260               attributes = arguments[0].with_indifferent_access 
    1261               find_attributes = attributes.slice(*attribute_names) 
    1262             else 
    1263               find_attributes = attributes = construct_attributes_from_arguments(attribute_names, arguments) 
    1264             end 
    1265             options = { :conditions => find_attributes } 
    1266             set_readonly_option!(options) 
    1267  
    1268             find_initial(options) || send(instantiator, attributes) 
     1256            self.class_eval %{ 
     1257              def self.#{method_id}(*args)  
     1258                if args[0].is_a?(Hash) 
     1259                  attributes = args[0].with_indifferent_access 
     1260                  find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}]) 
     1261                else 
     1262                  find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) 
     1263                end 
     1264                 
     1265                options = { :conditions => find_attributes } 
     1266                set_readonly_option!(options) 
     1267 
     1268                find_initial(options) || send(:#{instantiator}, attributes) 
     1269              end 
     1270            } 
     1271            send(method_id, *arguments) 
    12691272          else 
    12701273            super 
  • trunk/activerecord/test/finder_test.rb

    r7497 r7510  
    325325    assert_nil Topic.find_by_title("The First Topic!") 
    326326  end 
     327   
     328  def test_find_by_one_attribute_caches_dynamic_finder 
     329    # ensure this test can run independently of order 
     330    class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title) 
     331    assert !Topic.respond_to?(:find_by_title) 
     332    t = Topic.find_by_title("The First Topic") 
     333    assert Topic.respond_to?(:find_by_title) 
     334  end 
     335 
     336  def test_dynamic_finder_returns_same_results_after_caching 
     337    # ensure this test can run independently of order 
     338    class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.respond_to?(:find_by_title) 
     339    t = Topic.find_by_title("The First Topic") 
     340    assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached 
     341  end 
    327342 
    328343  def test_find_by_one_attribute_with_order_option 
     
    333348  def test_find_by_one_attribute_with_conditions 
    334349    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) 
     350  end 
     351 
     352  def test_dynamic_finder_on_one_attribute_with_conditions_caches_method 
     353    # ensure this test can run independently of order 
     354    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit) 
     355    assert !Account.respond_to?(:find_by_credit_limit) 
     356    a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) 
     357    assert Account.respond_to?(:find_by_credit_limit) 
     358  end 
     359 
     360  def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching 
     361    # ensure this test can run independently of order 
     362    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit) 
     363    a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) 
     364    assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached 
    335365  end 
    336366 
     
    441471  end 
    442472 
     473  def test_dynamic_find_or_initialize_from_one_attribute_caches_method 
     474    class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.respond_to?(:find_or_initialize_by_name)     
     475    assert !Company.respond_to?(:find_or_initialize_by_name) 
     476    sig38 = Company.find_or_initialize_by_name("38signals") 
     477    assert Company.respond_to?(:find_or_initialize_by_name) 
     478  end 
     479 
    443480  def test_find_or_initialize_from_two_attributes 
    444481    another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") 
     
    463500    assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" } 
    464501    assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" } 
     502  end 
     503 
     504  def test_dynamic_finder_with_invalid_params 
     505    assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } 
    465506  end 
    466507