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

Changeset 2730

Show
Ignore:
Timestamp:
10/25/05 18:14:09 (3 years ago)
Author:
bitsweat
Message:

r2736@asus: jeremy | 2005-10-24 17:08:12 -0700
Test for eager associations with limits should not assume that records are ordered by id.
r2737@asus: jeremy | 2005-10-24 19:06:09 -0700
Fail fast if invalid primary key column.
r2746@asus: jeremy | 2005-10-25 15:37:28 -0700
Begin rollback of fixture delete order. Its solves a problem for 1% of users who already have a workaround while severely slowing down the other 99%.
r2747@asus: jeremy | 2005-10-25 16:03:01 -0700
Rollback the rest.
r2748@asus: jeremy | 2005-10-25 16:06:26 -0700
Re-add fixtures declaration to conditions scoping test
r2749@asus: jeremy | 2005-10-25 16:09:03 -0700
Re-add fixtures declaration to finder test
r2750@asus: jeremy | 2005-10-25 16:13:50 -0700
Don't assume keyboards table is empty

Files:

Legend:

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

    r2717 r2730  
    11*SVN* 
    2  
    3 * Keep closer tabs on dirty, loaded, and declared fixtures.  #2404 [ryand-ruby@zenspider.com] 
    42 
    53* Map Active Record time to SQL TIME.  #2575, #2576 [Robby Russell <robby@planetargon.com>] 
  • trunk/activerecord/lib/active_record/base.rb

    r2718 r2730  
    11141114        attr_name = self.class.primary_key 
    11151115        column    = column_for_attribute(attr_name) 
     1116        raise ActiveRecordError, "No such primary key column #{attr_name} for table #{table_name}" if column.nil? 
    11161117        define_read_method(:id, attr_name, column) if self.class.generate_read_methods 
    11171118        (value = @attributes[attr_name]) && column.type_cast(value) 
  • trunk/activerecord/lib/active_record/fixtures.rb

    r2719 r2730  
    22require 'yaml' 
    33require 'csv' 
    4 require 'set' 
    54 
    65module YAML #:nodoc: 
     
    124123# 
    125124# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger 
    126 # the testing environment to automatically load the appropriate fixtures into the database before each test. 
     125# the testing environment to automatically load the appropriate fixtures into the database before each test.   
    127126# To ensure consistent data, the environment deletes the fixtures before running the load. 
    128127# 
     
    150149# 
    151150#   - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance: 
    152 #       self.use_instantiated_fixtures = :no_instances 
     151#       self.use_instantiated_fixtures = :no_instances  
    153152# 
    154153# Even if auto-instantiated fixtures are disabled, you can still access them 
     
    182181# = Transactional fixtures 
    183182# 
    184 # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. 
     183# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.  
    185184# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused. 
    186185# 
     
    188187#     self.use_transactional_fixtures = true 
    189188#     self.use_instantiated_fixtures = false 
    190 # 
     189#    
    191190#     fixtures :foos 
    192 # 
     191#    
    193192#     def test_godzilla 
    194193#       assert !Foo.find(:all).empty? 
     
    196195#       assert Foo.find(:all).empty? 
    197196#     end 
    198 # 
     197#    
    199198#     def test_godzilla_aftermath 
    200199#       assert !Foo.find(:all).empty? 
    201200#     end 
    202201#   end 
    203 # 
    204 # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, 
     202#    
     203# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,  
    205204# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes. 
    206205# 
    207 # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide 
     206# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide  
    208207# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+) 
    209208# 
    210 # When *not* to use transactional fixtures: 
    211 #   1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit, 
    212 #      particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify 
    213 #      the results of your transaction until Active Record supports nested transactions or savepoints (in progress.) 
    214 #   2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. 
     209# When *not* to use transactional fixtures:  
     210#   1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,  
     211#      particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify  
     212#      the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)  
     213#   2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.  
    215214#      Use InnoDB, MaxDB, or NDB instead. 
    216215class Fixtures < YAML::Omap 
     
    231230 
    232231  def self.instantiate_all_loaded_fixtures(object, load_instances=true) 
    233     all_loaded_fixtures.each do |fixtures| 
    234       Fixtures.instantiate_fixtures(object, fixtures.table_name, fixtures, load_instances) 
    235     end 
    236   end 
    237  
     232    all_loaded_fixtures.each do |table_name, fixtures| 
     233      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances) 
     234    end 
     235  end 
     236   
    238237  cattr_accessor :all_loaded_fixtures 
    239   self.all_loaded_fixtures = [] 
     238  self.all_loaded_fixtures = {} 
    240239 
    241240  def self.create_fixtures(fixtures_directory, *table_names) 
    242     table_names = table_names.flatten 
     241    table_names = table_names.flatten.map { |n| n.to_s } 
    243242    connection = block_given? ? yield : ActiveRecord::Base.connection 
    244243 
    245244    ActiveRecord::Base.logger.silence do 
     245      fixtures_map = {} 
    246246      fixtures = table_names.map do |table_name| 
    247         Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 
    248       end 
     247        fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 
     248      end                
     249      all_loaded_fixtures.merge! fixtures_map   
    249250 
    250251      connection.transaction do 
    251         table_names.reverse.each do |table_name| 
    252           connection.delete "DELETE FROM #{table_name}" 
    253         end 
    254  
    255         fixtures.each { |f| f.insert_fixtures } 
    256  
     252        fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 
     253        fixtures.each { |fixture| fixture.insert_fixtures } 
     254 
     255        # Cap primary key sequences to max(pk). 
    257256        if connection.respond_to?(:reset_pk_sequence!) 
    258           table_names.flatten.each do |table_name| 
     257          table_names.each do |table_name| 
    259258            connection.reset_pk_sequence!(table_name) 
    260259          end 
     
    262261      end 
    263262 
    264       all_loaded_fixtures.concat fixtures 
    265263      return fixtures.size > 1 ? fixtures : fixtures.first 
    266     end 
    267   end 
    268  
    269   def self.delete_fixtures(table_names) 
    270     ActiveRecord::Base.silence do 
    271       connection = block_given? ? yield : ActiveRecord::Base.connection 
    272  
    273       connection.transaction do 
    274         table_names.reverse.each do |table_name| 
    275           connection.delete "DELETE FROM #{table_name}" 
    276         end 
    277       end 
    278264    end 
    279265  end 
     
    430416    class TestCase #:nodoc: 
    431417      cattr_accessor :fixture_path 
    432       cattr_accessor :dirty_fixture_table_names 
    433       cattr_accessor :loaded_fixture_table_names 
    434418      class_inheritable_accessor :fixture_table_names 
    435419      class_inheritable_accessor :use_transactional_fixtures 
     
    437421      class_inheritable_accessor :pre_loaded_fixtures 
    438422 
    439       self.dirty_fixture_table_names = [] 
    440       self.loaded_fixture_table_names = [] 
    441423      self.fixture_table_names = [] 
    442424      self.use_transactional_fixtures = false 
     
    444426      self.pre_loaded_fixtures = false 
    445427 
    446       class << self 
    447         def fixtures(*table_names) 
    448           table_names = table_names.flatten.map { |n| n.to_s } 
    449           self.fixture_table_names |= table_names 
    450           self.dirty_fixture_table_names |= table_names 
    451  
    452           require_fixture_classes(table_names) 
    453           setup_fixture_accessors(table_names) 
    454         end 
    455  
    456         def require_fixture_classes(table_names=nil) 
    457           (table_names || fixture_table_names).each do |table_name| 
    458             begin 
    459               require table_name.to_s.singularize 
    460             rescue LoadError 
    461               # Let's hope the developer has included it himself 
    462             end 
    463           end 
    464         end 
    465  
    466         def setup_fixture_accessors(table_names = nil) 
    467           (table_names || fixture_table_names).each do |table_name| 
    468             table_name = table_name.to_s.tr('.', '_') 
    469             class_eval <<-end_eval, __FILE__, __LINE__ 
    470               def #{table_name}(fixture, force_reload = false) 
    471                 fixture = fixture.to_s 
    472                 @fixture_cache ||= Hash.new { |h,k| h[k] = Hash.new } 
    473                 if force_reload or @fixture_cache['#{table_name}'][fixture].nil? 
    474                   @fixture_cache['#{table_name}'][fixture] = @loaded_fixtures['#{table_name}'][fixture].find 
    475                 end 
    476                 @fixture_cache['#{table_name}'][fixture] 
    477               end 
    478             end_eval 
    479           end 
    480         end 
    481  
    482         def uses_transaction(*methods) 
    483           @uses_transaction ||= Set.new 
    484           @uses_transaction += methods.flatten.map { |m| m.to_s } 
    485         end 
    486  
    487         def uses_transaction?(*methods) 
    488           @uses_transaction && methods.flatten.all? { |m| @uses_transaction.include?(m.to_s) } 
    489         end 
     428      @@already_loaded_fixtures = {} 
     429 
     430      def self.fixtures(*table_names) 
     431        table_names = table_names.flatten.map { |n| n.to_s } 
     432        self.fixture_table_names |= table_names 
     433        require_fixture_classes(table_names) 
     434        setup_fixture_accessors(table_names) 
     435      end 
     436 
     437      def self.require_fixture_classes(table_names=nil) 
     438        (table_names || fixture_table_names).each do |table_name|  
     439          begin 
     440            require Inflector.singularize(table_name.to_s) 
     441          rescue LoadError 
     442            # Let's hope the developer has included it himself 
     443          end 
     444        end 
     445      end 
     446 
     447      def self.setup_fixture_accessors(table_names=nil) 
     448        (table_names || fixture_table_names).each do |table_name| 
     449          table_name = table_name.to_s.tr('.','_') 
     450          define_method(table_name) do |fixture, *optionals| 
     451            force_reload = optionals.shift 
     452            @fixture_cache[table_name] ||= Hash.new 
     453            @fixture_cache[table_name][fixture] = nil if force_reload 
     454            @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find 
     455          end 
     456        end 
     457      end 
     458 
     459      def self.uses_transaction(*methods) 
     460        @uses_transaction ||= [] 
     461        @uses_transaction.concat methods.map { |m| m.to_s } 
     462      end 
     463 
     464      def self.uses_transaction?(method) 
     465        @uses_transaction && @uses_transaction.include?(method.to_s) 
    490466      end 
    491467 
    492468      def use_transactional_fixtures? 
    493         use_transactional_fixtures && !self.class.uses_transaction?(method_name) 
     469        use_transactional_fixtures && 
     470          !self.class.uses_transaction?(method_name) 
    494471      end 
    495472 
    496473      def setup_with_fixtures 
    497474        if pre_loaded_fixtures && !use_transactional_fixtures 
    498           raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' 
    499         end 
     475          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'  
     476        end 
     477 
     478        @fixture_cache = Hash.new 
    500479 
    501480        # Load fixtures once and begin transaction. 
    502481        if use_transactional_fixtures? 
    503           reload_fixtures! unless @loaded_fixtures and dirty_fixture_table_names.empty? 
     482          if @@already_loaded_fixtures[self.class] 
     483            @loaded_fixtures = @@already_loaded_fixtures[self.class] 
     484          else 
     485            load_fixtures 
     486            @@already_loaded_fixtures[self.class] = @loaded_fixtures 
     487          end 
    504488          ActiveRecord::Base.lock_mutex 
    505489          ActiveRecord::Base.connection.begin_db_transaction 
     
    507491        # Load fixtures for every test. 
    508492        else 
    509           reload_fixtures! 
    510           self.dirty_fixture_table_names |= loaded_fixture_table_names 
     493          @@already_loaded_fixtures[self.class] = nil 
     494          load_fixtures 
    511495        end 
    512496 
     
    522506          ActiveRecord::Base.connection.rollback_db_transaction 
    523507          ActiveRecord::Base.unlock_mutex 
    524         end 
    525         unless dirty_fixture_table_names.empty? 
    526           Fixtures.delete_fixtures(dirty_fixture_table_names) 
    527           dirty_fixture_table_names.clear 
    528508        end 
    529509      end 
     
    553533 
    554534      private 
    555         def reload_fixtures! 
    556           # Clear dirty fixtures and loaded fixtures which were not declared 
    557           # for this test case. 
    558           wipe = dirty_fixture_table_names | loaded_fixture_table_names - fixture_table_names 
    559           Fixtures.delete_fixtures(wipe) unless wipe.empty? 
    560           dirty_fixture_table_names.clear 
    561           loaded_fixture_table_names.clear 
    562  
    563           case fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names) 
    564             when Fixtures 
    565               loaded_fixture_table_names.push fixtures.table_name.to_s 
    566               @loaded_fixtures = { fixtures.table_name => fixtures } 
    567             when Enumerable 
    568               @loaded_fixtures = {} 
    569               loaded_fixture_table_names.concat fixtures.map { |f| 
    570                 @loaded_fixtures[f.table_name] = f 
    571                 f.table_name.to_s 
    572               } 
     535        def load_fixtures 
     536          @loaded_fixtures = {} 
     537          fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names) 
     538          unless fixtures.nil? 
     539            if fixtures.instance_of?(Fixtures) 
     540              @loaded_fixtures[fixtures.table_name] = fixtures 
    573541            else 
    574               @loaded_fixtures = {} 
     542              fixtures.each { |f| @loaded_fixtures[f.table_name] = f } 
     543            end 
    575544          end 
    576545        end 
     
    583552            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? 
    584553            unless @@required_fixture_classes 
    585               names = Fixtures.all_loaded_fixtures.map { |fixtures| fixtures.table_name } 
    586               self.class.require_fixture_classes names 
     554              self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys 
    587555              @@required_fixture_classes = true 
    588556            end 
  • trunk/activerecord/test/associations_go_eager_test.rb

    r2675 r2730  
    9797   
    9898  def test_eager_with_has_many_and_limit 
    99     posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2) 
     99    posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) 
    100100    assert_equal 2, posts.size 
    101101    assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size } 
  • trunk/activerecord/test/conditions_scoping_test.rb

    r2714 r2730  
    8686    assert_equal 2, @welcome.categories.find_all_by_type('Category').size 
    8787  end 
     88 
    8889end 
    8990 
  • trunk/activerecord/test/fixtures_test.rb

    r2719 r2730  
    8787    assert_nil(secondRow["author_email_address"])         
    8888  ensure 
    89     Fixtures.all_loaded_fixtures.delete(topics) 
    9089    ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil 
    9190  end 
     
    291290  end 
    292291end 
    293  
    294  
    295 class FixtureCleanup1Test < Test::Unit::TestCase 
    296   fixtures :topics 
    297  
    298   def test_isolation 
    299     assert_equal 0, Developer.count 
    300     assert_equal 2, Topic.count 
    301   end 
    302 end 
    303  
    304 class FixtureCleanup2Test < Test::Unit::TestCase 
    305   fixtures :developers 
    306  
    307   def test_isolation 
    308     assert_equal 0, Topic.count 
    309     assert_equal 10, Developer.count 
    310   end 
    311 end 
    312  
    313 class FixtureCleanup3Test < FixtureCleanup2Test 
    314   self.use_transactional_fixtures = false 
    315  
    316   def test_dirty_fixture_table_names 
    317     assert_equal %w(developers), dirty_fixture_table_names 
    318     assert_equal %w(developers), loaded_fixture_table_names 
    319     assert_equal %w(developers), fixture_table_names 
    320   end 
    321 end 
    322  
    323 class FixtureCleanup4Test < FixtureCleanup2Test 
    324   self.use_transactional_fixtures = true 
    325  
    326   def test_dirty_fixture_table_names 
    327     assert_equal [], dirty_fixture_table_names 
    328     assert_equal %w(developers), loaded_fixture_table_names 
    329     assert_equal %w(developers), fixture_table_names 
    330   end 
    331 end 
    332  
    333 class FixtureCleanup5Test < FixtureCleanup3Test 
    334   self.use_instantiated_fixtures = false 
    335  
    336   def test_dirty_fixture_table_names 
    337     assert_equal %w(developers), dirty_fixture_table_names 
    338     assert_equal %w(developers), loaded_fixture_table_names 
    339     assert_equal %w(developers), fixture_table_names 
    340   end 
    341 end 
    342  
    343 class FixtureCleanup6Test < FixtureCleanup4Test 
    344   self.use_instantiated_fixtures = true 
    345  
    346   def test_dirty_fixture_table_names 
    347     assert_equal [], dirty_fixture_table_names 
    348     assert_equal %w(developers), loaded_fixture_table_names 
    349     assert_equal %w(developers), fixture_table_names 
    350   end 
    351 end 
    352  
    353 class FixtureCleanup7Test < Test::Unit::TestCase 
    354   self.use_transactional_fixtures = false 
    355   self.use_instantiated_fixtures = true 
    356  
    357   def test_dirty_fixture_table_names 
    358     assert_equal [], dirty_fixture_table_names 
    359     assert_equal [], loaded_fixture_table_names 
    360     assert_equal [], fixture_table_names 
    361   end 
    362  
    363   def test_isolation 
    364     assert_equal 0, Topic.count 
    365     assert_equal 0, Developer.count 
    366   end 
    367 end 
    368  
    369 class FixtureCleanup8Test < FixtureCleanup7Test 
    370   self.use_transactional_fixtures = true 
    371   self.use_instantiated_fixtures = true 
    372 end 
  • trunk/activerecord/test/pk_test.rb

    r2718 r2730  
    2525 
    2626  def test_customized_primary_key_auto_assigns_on_save 
     27    Keyboard.delete_all 
    2728    keyboard = Keyboard.new(:name => 'HHKB') 
    2829    assert_nothing_raised { keyboard.save! }