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

Changeset 2714

Show
Ignore:
Timestamp:
10/23/05 19:02:38 (3 years ago)
Author:
bitsweat
Message:

r2718@asus: jeremy | 2005-10-23 14:45:30 -0700
Ticket 2404 - fixture delete order
r2719@asus: jeremy | 2005-10-23 15:01:13 -0700
Keep closer tabs on dirty, loaded, and declared fixtures. Closes #2404.
r2720@asus: jeremy | 2005-10-23 16:09:19 -0700
ensure table names are strings. set dirty = dirty union loaded rather than dirty = loaded.

Files:

Legend:

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

    r2713 r2714  
    11*1.12.1* (October 19th, 2005) 
     2 
     3* Keep closer tabs on dirty, loaded, and declared fixtures.  #2404 [ryand-ruby@zenspider.com] 
    24 
    35* Map Active Record time to SQL TIME.  #2575, #2576 [Robby Russell <robby@planetargon.com>] 
  • trunk/activerecord/lib/active_record/fixtures.rb

    r2650 r2714  
    22require 'yaml' 
    33require 'csv' 
     4require 'set' 
    45 
    56module YAML #:nodoc: 
     
    123124# 
    124125# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger 
    125 # the testing environment to automatically load the appropriate fixtures into the database before each test.   
     126# the testing environment to automatically load the appropriate fixtures into the database before each test. 
    126127# To ensure consistent data, the environment deletes the fixtures before running the load. 
    127128# 
     
    149150# 
    150151#   - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance: 
    151 #       self.use_instantiated_fixtures = :no_instances  
     152#       self.use_instantiated_fixtures = :no_instances 
    152153# 
    153154# Even if auto-instantiated fixtures are disabled, you can still access them 
     
    181182# = Transactional fixtures 
    182183# 
    183 # TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.  
     184# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. 
    184185# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused. 
    185186# 
     
    187188#     self.use_transactional_fixtures = true 
    188189#     self.use_instantiated_fixtures = false 
    189 #    
     190# 
    190191#     fixtures :foos 
    191 #    
     192# 
    192193#     def test_godzilla 
    193194#       assert !Foo.find(:all).empty? 
     
    195196#       assert Foo.find(:all).empty? 
    196197#     end 
    197 #    
     198# 
    198199#     def test_godzilla_aftermath 
    199200#       assert !Foo.find(:all).empty? 
    200201#     end 
    201202#   end 
    202 #    
    203 # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,  
     203# 
     204# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, 
    204205# 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. 
    205206# 
    206 # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide  
     207# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide 
    207208# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+) 
    208209# 
    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.  
     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. 
    214215#      Use InnoDB, MaxDB, or NDB instead. 
    215216class Fixtures < YAML::Omap 
     
    231232    ActiveRecord::Base.logger.level = old_logger_level 
    232233  end 
    233    
     234 
    234235  def self.instantiate_all_loaded_fixtures(object, load_instances=true) 
    235     all_loaded_fixtures.each do |table_name, fixtures| 
    236       Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances) 
    237     end 
    238   end 
    239    
     236    all_loaded_fixtures.each do |fixtures| 
     237      Fixtures.instantiate_fixtures(object, fixtures.table_name, fixtures, load_instances) 
     238    end 
     239  end 
     240 
    240241  cattr_accessor :all_loaded_fixtures 
    241   self.all_loaded_fixtures = {} 
     242  self.all_loaded_fixtures = [] 
    242243 
    243244  def self.create_fixtures(fixtures_directory, *table_names) 
     245    table_names = table_names.flatten 
    244246    connection = block_given? ? yield : ActiveRecord::Base.connection 
    245     old_logger_level = ActiveRecord::Base.logger.level 
    246  
    247     begin 
    248       ActiveRecord::Base.logger.level = Logger::ERROR 
    249  
    250       fixtures_map = {} 
    251       fixtures = table_names.flatten.map do |table_name| 
    252         fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 
    253       end                
    254       all_loaded_fixtures.merge! fixtures_map   
    255        
    256        
     247 
     248    ActiveRecord::Base.logger.silence do 
     249      fixtures = table_names.map do |table_name| 
     250        Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 
     251      end 
     252 
    257253      connection.transaction do 
    258         fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 
    259         fixtures.each { |fixture| fixture.insert_fixtures } 
    260       end 
    261  
    262       # Cap primary key sequences to max(pk). 
    263       if connection.respond_to?(:reset_pk_sequence!) 
    264         table_names.flatten.each do |table_name| 
    265           connection.reset_pk_sequence!(table_name) 
    266         end 
    267       end 
    268  
     254        table_names.reverse.each do |table_name| 
     255          connection.delete "DELETE FROM #{table_name}" 
     256        end 
     257 
     258        fixtures.each { |f| f.insert_fixtures } 
     259 
     260        if connection.respond_to?(:reset_pk_sequence!) 
     261          table_names.flatten.each do |table_name| 
     262            connection.reset_pk_sequence!(table_name) 
     263          end 
     264        end 
     265      end 
     266 
     267      all_loaded_fixtures.concat fixtures 
    269268      return fixtures.size > 1 ? fixtures : fixtures.first 
    270     ensure 
    271       ActiveRecord::Base.logger.level = old_logger_level 
     269    end 
     270  end 
     271 
     272  def self.delete_fixtures(table_names) 
     273    ActiveRecord::Base.silence do 
     274      connection = block_given? ? yield : ActiveRecord::Base.connection 
     275 
     276      connection.transaction do 
     277        table_names.reverse.each do |table_name| 
     278          connection.delete "DELETE FROM #{table_name}" 
     279        end 
     280      end 
    272281    end 
    273282  end 
     
    424433    class TestCase #:nodoc: 
    425434      cattr_accessor :fixture_path 
     435      cattr_accessor :dirty_fixture_table_names 
     436      cattr_accessor :loaded_fixture_table_names 
    426437      class_inheritable_accessor :fixture_table_names 
    427438      class_inheritable_accessor :use_transactional_fixtures 
     
    429440      class_inheritable_accessor :pre_loaded_fixtures 
    430441 
     442      self.dirty_fixture_table_names = [] 
     443      self.loaded_fixture_table_names = [] 
    431444      self.fixture_table_names = [] 
    432445      self.use_transactional_fixtures = false 
     
    434447      self.pre_loaded_fixtures = false 
    435448 
    436       @@already_loaded_fixtures = {} 
    437  
    438       def self.fixtures(*table_names) 
    439         table_names = table_names.flatten 
    440         self.fixture_table_names |= table_names 
    441         require_fixture_classes(table_names) 
    442         setup_fixture_accessors(table_names) 
    443       end 
    444  
    445       def self.require_fixture_classes(table_names=nil) 
    446         (table_names || fixture_table_names).each do |table_name|  
    447           begin 
    448             require Inflector.singularize(table_name.to_s) 
    449           rescue LoadError 
    450             # Let's hope the developer has included it himself 
    451           end 
    452         end 
    453       end 
    454  
    455       def self.setup_fixture_accessors(table_names=nil) 
    456         (table_names || fixture_table_names).each do |table_name| 
    457           table_name = table_name.to_s.tr('.','_') 
    458           define_method(table_name) do |fixture, *optionals| 
    459             force_reload = optionals.shift 
    460             @fixture_cache[table_name] ||= Hash.new 
    461             @fixture_cache[table_name][fixture] = nil if force_reload 
    462             @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find 
    463           end 
    464         end 
    465       end 
    466  
    467       def self.uses_transaction(*methods) 
    468         @uses_transaction ||= [] 
    469         @uses_transaction.concat methods.map { |m| m.to_s } 
    470       end 
    471  
    472       def self.uses_transaction?(method) 
    473         @uses_transaction && @uses_transaction.include?(method.to_s) 
     449      class << self 
     450        def fixtures(*table_names) 
     451          table_names = table_names.flatten.map { |n| n.to_s } 
     452          self.fixture_table_names |= table_names 
     453          self.dirty_fixture_table_names |= table_names 
     454 
     455          require_fixture_classes(table_names) 
     456          setup_fixture_accessors(table_names) 
     457        end 
     458 
     459        def require_fixture_classes(table_names=nil) 
     460          (table_names || fixture_table_names).each do |table_name| 
     461            begin 
     462              require table_name.to_s.singularize 
     463            rescue LoadError 
     464              # Let's hope the developer has included it himself 
     465            end 
     466          end 
     467        end 
     468 
     469        def setup_fixture_accessors(table_names = nil) 
     470          (table_names || fixture_table_names).each do |table_name| 
     471            table_name = table_name.to_s.tr('.', '_') 
     472            class_eval <<-end_eval, __FILE__, __LINE__ 
     473              def #{table_name}(fixture, force_reload = false) 
     474                fixture = fixture.to_s 
     475                @fixture_cache ||= Hash.new { |h,k| h[k] = Hash.new } 
     476                if force_reload or @fixture_cache['#{table_name}'][fixture].nil? 
     477                  @fixture_cache['#{table_name}'][fixture] = @loaded_fixtures['#{table_name}'][fixture].find 
     478                end 
     479                @fixture_cache['#{table_name}'][fixture] 
     480              end 
     481            end_eval 
     482          end 
     483        end 
     484 
     485        def uses_transaction(*methods) 
     486          @uses_transaction ||= Set.new 
     487          @uses_transaction += methods.flatten.map { |m| m.to_s } 
     488        end 
     489 
     490        def uses_transaction?(*methods) 
     491          @uses_transaction && methods.flatten.all? { |m| @uses_transaction.include?(m.to_s) } 
     492        end 
    474493      end 
    475494 
    476495      def use_transactional_fixtures? 
    477         use_transactional_fixtures && 
    478           !self.class.uses_transaction?(method_name) 
     496        use_transactional_fixtures && !self.class.uses_transaction?(method_name) 
    479497      end 
    480498 
    481499      def setup_with_fixtures 
    482500        if pre_loaded_fixtures && !use_transactional_fixtures 
    483           raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'  
    484         end 
    485  
    486         @fixture_cache = Hash.new 
     501          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' 
     502        end 
    487503 
    488504        # Load fixtures once and begin transaction. 
    489505        if use_transactional_fixtures? 
    490           if @@already_loaded_fixtures[self.class] 
    491             @loaded_fixtures = @@already_loaded_fixtures[self.class] 
    492           else 
    493             load_fixtures 
    494             @@already_loaded_fixtures[self.class] = @loaded_fixtures 
    495           end 
     506          reload_fixtures! unless @loaded_fixtures and dirty_fixture_table_names.empty? 
    496507          ActiveRecord::Base.lock_mutex 
    497508          ActiveRecord::Base.connection.begin_db_transaction 
     
    499510        # Load fixtures for every test. 
    500511        else 
    501           @@already_loaded_fixtures[self.class] = nil 
    502           load_fixtures 
     512          reload_fixtures! 
     513          self.dirty_fixture_table_names |= loaded_fixture_table_names 
    503514        end 
    504515 
     
    514525          ActiveRecord::Base.connection.rollback_db_transaction 
    515526          ActiveRecord::Base.unlock_mutex 
     527        end 
     528        unless dirty_fixture_table_names.empty? 
     529          Fixtures.delete_fixtures(dirty_fixture_table_names) 
     530          dirty_fixture_table_names.clear 
    516531        end 
    517532      end 
     
    541556 
    542557      private 
    543         def load_fixtures 
    544           @loaded_fixtures = {} 
    545           fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names) 
    546           unless fixtures.nil? 
    547             if fixtures.instance_of?(Fixtures) 
    548               @loaded_fixtures[fixtures.table_name] = fixtures 
    549             else 
    550               fixtures.each { |f| @loaded_fixtures[f.table_name] = f } 
    551             end 
     558        def reload_fixtures! 
     559          # Clear dirty fixtures and loaded fixtures which were not declared 
     560          # for this test case. 
     561          wipe = dirty_fixture_table_names + loaded_fixture_table_names - fixture_table_names 
     562          Fixtures.delete_fixtures(wipe) unless wipe.empty? 
     563          dirty_fixture_table_names.clear 
     564          loaded_fixture_table_names.clear 
     565 
     566          case fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names) 
     567            when Fixtures 
     568              loaded_fixture_table_names.push fixtures.table_name.to_s 
     569              @loaded_fixtures = { fixtures.table_name => fixtures } 
     570            when Enumerable 
     571              @loaded_fixtures = {} 
     572              loaded_fixture_table_names.concat fixtures.map { |f| 
     573                @loaded_fixtures[f.table_name] = f 
     574                f.table_name.to_s 
     575              } 
    552576          end 
    553577        end 
     
    560584            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? 
    561585            unless @@required_fixture_classes 
    562               self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys 
     586              names = Fixtures.all_loaded_fixtures.map { |fixtures| fixtures.table_name } 
     587              self.class.require_fixture_classes names 
    563588              @@required_fixture_classes = true 
    564589            end 
  • trunk/activerecord/test/column_alias_test.rb

    r1457 r2714  
    33 
    44class TestColumnAlias < Test::Unit::TestCase 
     5  fixtures :topics 
     6 
     7  QUERY = if 'OCI' == ActiveRecord::Base.connection.adapter_name 
     8            'SELECT id AS pk FROM topics WHERE ROWNUM < 2' 
     9          else 
     10            'SELECT id AS pk FROM topics' 
     11          end 
    512 
    613  def test_column_alias 
    7     topic = Topic.find(1) 
    8     if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter 
    9       if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter) 
    10         records = topic.connection.select_all("SELECT id AS pk FROM topics WHERE ROWNUM < 2") 
    11         assert_equal("pk", records[0].keys[0]) 
    12       end 
    13     else 
    14       records = topic.connection.select_all("SELECT id AS pk FROM topics") 
    15       assert_equal("pk", records[0].keys[0]) 
    16     end 
     14    records = Topic.connection.select_all(QUERY) 
     15    assert_equal 'pk', records[0].keys[0] 
    1716  end 
    18    
    1917end 
  • trunk/activerecord/test/conditions_scoping_test.rb

    r2510 r2714  
    7070 
    7171class HasAndBelongsToManyScopingTest< Test::Unit::TestCase 
    72   fixtures :posts, :categories 
     72  fixtures :posts, :categories, :categories_posts 
    7373 
    7474  def setup 
     
    8686    assert_equal 2, @welcome.categories.find_all_by_type('Category').size 
    8787  end 
    88  
    8988end 
    9089 
  • trunk/activerecord/test/finder_test.rb

    r2705 r2714  
    77 
    88class FinderTest < Test::Unit::TestCase 
    9   fixtures :companies, :topics, :entrants, :developers, :posts 
     9  fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts 
    1010 
    1111  def test_find 
  • trunk/activerecord/test/fixtures_test.rb

    r2681 r2714  
    8787    assert_nil(secondRow["author_email_address"])         
    8888  ensure 
     89    Fixtures.all_loaded_fixtures.delete(topics) 
    8990    ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil 
    9091  end 
     
    260261 
    261262  def test_fixture_table_names 
    262     assert_equal([:topics, :developers, :accounts], fixture_table_names) 
     263    assert_equal %w(topics developers accounts), fixture_table_names 
    263264  end 
    264265end 
     
    270271 
    271272  def test_fixture_table_names 
    272     assert_equal([:topics, :developers, :accounts], fixture_table_names) 
     273    assert_equal %w(topics developers accounts), fixture_table_names 
    273274  end 
    274275end 
     
    290291  end 
    291292end 
     293 
     294 
     295class 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 
     302end 
     303 
     304class 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 
     311end 
     312 
     313class 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 
     321end 
     322 
     323class 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 
     331end