Ticket #1911: more_flexible_fixture_architecture-2.patch
| File more_flexible_fixture_architecture-2.patch, 22.4 kB (added by duane.johnson@gmail.com, 3 years ago) |
|---|
-
test/fixtures_test.rb
old new 68 68 end 69 69 end 70 70 71 def test_fixtures_not_found 72 assert_raise(Fixture::FixtureError) { 73 Fixtures.new(nil, File.join(File.dirname(__FILE__), 'fixtures'), FixtureGroup.new("bad_extension")) 74 } 75 end 76 71 77 def test_deprecated_yaml_extension 72 78 assert_raise(Fixture::FormatError) { 73 Fixtures.new(nil, 'bad_extension', File.join(File.dirname(__FILE__), 'fixtures'))79 Fixtures.new(nil, File.join(File.dirname(__FILE__), 'fixtures', 'bad_fixtures'), FixtureGroup.new("deprecated")) 74 80 } 75 81 end 76 82 … … 101 107 end 102 108 103 109 def test_empty_yaml_fixture 104 assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/yml/accounts")110 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("accounts")) 105 111 end 106 112 107 113 def test_empty_yaml_fixture_with_a_comment_in_it 108 assert_not_nil Fixtures.new( Account.connection, "companies", File.dirname(__FILE__) + "/fixtures/naked/yml/companies")114 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("companies")) 109 115 end 110 116 111 117 def test_dirty_dirty_yaml_file 112 118 assert_raises(Fixture::FormatError) do 113 Fixtures.new( Account.connection, "courses", File.dirname(__FILE__) + "/fixtures/naked/yml/courses")119 Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/yml/", FixtureGroup.new("courses")) 114 120 end 115 121 end 116 122 117 123 def test_empty_csv_fixtures 118 assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/csv/accounts")124 assert_not_nil Fixtures.new( Account.connection, File.dirname(__FILE__) + "/fixtures/naked/csv/", FixtureGroup.new("accounts")) 119 125 end 120 126 end 121 127 … … 212 218 213 219 end 214 220 221 class FixturesGroupTest < Test::Unit::TestCase 222 def test_get_table_name_from_model_class 223 fg = FixtureGroup.new(:filedoesnotexist, :class_name => "DeveloperWithAggregate") 224 assert_equal fg.table_name, 'developers' 225 assert_equal fg.class_name, 'DeveloperWithAggregate' 226 end 227 228 def test_assume_correct_class_name_from_table_name 229 fg = FixtureGroup.new(:developers, :table_name => "tests") 230 assert_equal fg.table_name, 'tests' 231 assert_equal fg.class_name, 'Test' 232 end 215 233 234 def test_file_name_is_a_string 235 fg = FixtureGroup.new(:developers) 236 assert String === fg.file_name 237 end 238 239 def test_fixture_group_name 240 fg = FixtureGroup.new(:developers) 241 assert Symbol === fg.group_name 242 assert_equal fg.group_name, :developers 243 244 fg = FixtureGroup.new(:developers, :group_name => "other") 245 assert Symbol === fg.group_name 246 assert_equal fg.group_name, :other 247 end 248 end 216 249 250 class MultipleFixturesForTheSameTableTest < Test::Unit::TestCase 251 self.use_instantiated_fixtures = true 252 fixture :topics, :class_name => "Topic" 253 fixture :topics2, :class_name => "Topic" 254 255 def test_loaded_fixtures 256 assert_equal @topics.size, 2 257 assert_equal @topics2.size, 2 258 union = Topic.find(:all) 259 assert_equal union.size, 4 260 end 261 end 217 262 263 class MixSingularAndPluralFixturesTest < Test::Unit::TestCase 264 self.use_instantiated_fixtures = true 265 fixtures :topics, :developers, :accounts 266 fixture :topics2, :table_name => "topics" 267 268 def test_loaded_fixtures 269 assert_equal @topics.size, 2 270 assert_equal @topics2.size, 2 271 union = Topic.find(:all) 272 assert_equal union.size, 4 273 end 274 end 218 275 219 276 class AlternateFileFixturesTest < Test::Unit::TestCase 277 self.use_instantiated_fixtures = true 278 fixture :topics2, :class_name => "Topic" 279 280 def test_alternate_fixture_file_was_loaded 281 assert_equal @topics2.size, 2 282 assert_nil @first 283 assert_nil @second 284 assert_equal @third.id, 3 285 assert_equal @fourth.id, 4 286 topics = Topic.find(:all) 287 assert_equal @topics2.size, 2 288 end 289 end -
test/fixtures/topics2.yml
old new 1 third: 2 id: 3 3 title: The Third Topic 4 author_name: Duane 5 author_email_address: david@loudthinking.com 6 written_on: 2003-07-16t15:28:00.00+01:00 7 last_read: 2004-04-15 8 bonus_time: 2005-01-30t15:28:00.00+01:00 9 content: Have a nice day 10 approved: 0 11 replies_count: 0 12 13 fourth: 14 id: 4 15 title: The Fourth topic 16 author_name: John 17 written_on: 2003-07-15t15:28:00.00+01:00 18 content: Have a nice day 19 approved: 1 20 replies_count: 2 21 parent_id: 1 -
lib/active_record/fixtures.rb
old new 2 2 require 'yaml' 3 3 require 'csv' 4 4 5 # A FixtureGroup is a set of fixtures identified by a name. Normally, this is the name of the 6 # corresponding table in the database. For example, when you declare the use of fixtures in a 7 # TestUnit class, like so: 8 # fixtures :users 9 # you are creating a FixtureGroup whose name is 'users', and whose defaults are set such that the 10 # +class_name+, +file_name+ and +table_name+ are guessed from the FixtureGroup's name. 11 class FixtureGroup 12 attr_accessor :table_name, :class_name, :connection 13 attr_reader :group_name, :file_name 14 15 def initialize(file_name, optional_names = {}) 16 self.file_name = file_name 17 self.group_name = optional_names[:group_name] || file_name 18 if optional_names[:table_name] 19 self.table_name = optional_names[:table_name] 20 self.class_name = optional_names[:class_name] || Inflector.classify(@table_name.to_s.gsub('.','_')) 21 elsif optional_names[:class_name] 22 self.class_name = optional_names[:class_name] 23 if Object.const_defined?(@class_name) 24 model_class = Object.const_get(@class_name) 25 self.table_name = model_class.table_name 26 end 27 end 28 29 # In case either :table_name or :class_name was not set: 30 self.table_name ||= @group_name.to_s 31 self.class_name ||= Inflector.classify(@table_name.to_s.gsub('.','_')) 32 end 33 34 def file_name=(name) 35 @file_name = name.to_s 36 end 37 38 def group_name=(name) 39 @group_name = name.to_sym 40 end 41 42 def class_file_name 43 Inflector.underscore(@class_name) 44 end 45 46 # Instantiate an array of FixtureGroup objects from an array of strings (table_names) 47 def self.array_from_names(names) 48 names.collect { |n| FixtureGroup.new(n) } 49 end 50 51 def hash 52 @group_name.hash 53 end 54 55 def eql?(other) 56 @group_name.eql? other.group_name 57 end 58 end 59 5 60 # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours: 6 61 # 7 62 # 1. YAML fixtures … … 194 249 class Fixtures < Hash 195 250 DEFAULT_FILTER_RE = /\.ya?ml$/ 196 251 197 def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)198 old_logger_level = ActiveRecord::Base.logger.level199 ActiveRecord::Base.logger.level = Logger::ERROR200 201 object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures202 if load_instances203 fixtures.each do |name, fixture|204 if model = fixture.find205 object.instance_variable_set "@#{name}", model206 end207 end208 end209 210 ActiveRecord::Base.logger.level = old_logger_level211 end212 213 def self.instantiate_all_loaded_fixtures(object, load_instances=true)214 all_loaded_fixtures.each do |table_name, fixtures|215 Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)216 end217 end218 219 252 cattr_accessor :all_loaded_fixtures 220 253 self.all_loaded_fixtures = {} 221 254 222 def self.create_fixtures(fixtures_directory, *table_names) 223 connection = block_given? ? yield : ActiveRecord::Base.connection 224 old_logger_level = ActiveRecord::Base.logger.level 255 attr_accessor :connection, :fixtures_directory, :file_filter 256 attr_accessor :fixture_group 225 257 226 begin 227 ActiveRecord::Base.logger.level = Logger::ERROR 228 229 fixtures_map = {} 230 fixtures = table_names.flatten.map do |table_name| 231 fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s)) 232 end 233 all_loaded_fixtures.merge! fixtures_map 234 235 connection.transaction do 236 fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 237 fixtures.each { |fixture| fixture.insert_fixtures } 238 end 239 240 reset_sequences(connection, table_names) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) 241 242 return fixtures.size > 1 ? fixtures : fixtures.first 243 ensure 244 ActiveRecord::Base.logger.level = old_logger_level 245 end 246 end 247 248 # Work around for PostgreSQL to have new fixtures created from id 1 and running. 249 def self.reset_sequences(connection, table_names) 250 table_names.flatten.each do |table| 251 table_class = Inflector.classify(table.to_s) 252 if Object.const_defined?(table_class) 253 pk = eval("#{table_class}::primary_key") 254 if pk == 'id' 255 connection.execute( 256 "SELECT setval('#{table.to_s}_id_seq', (SELECT MAX(id) FROM #{table.to_s}), true)", 257 'Setting Sequence' 258 ) 259 end 260 end 261 end 262 end 263 264 attr_reader :table_name 265 266 def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE) 267 @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter 268 @class_name = Inflector.classify(@table_name) 269 258 def initialize(connection, fixtures_directory, fixture_group, file_filter = DEFAULT_FILTER_RE) 259 @connection, @fixtures_directory = connection, fixtures_directory 260 @fixture_group = fixture_group 261 @file_filter = file_filter 270 262 read_fixture_files 271 263 end 272 264 273 265 def delete_existing_fixtures 274 @connection.delete "DELETE FROM #{@ table_name}", 'Fixture Delete'266 @connection.delete "DELETE FROM #{@fixture_group.table_name}", 'Fixture Delete' 275 267 end 276 268 277 269 def insert_fixtures 278 270 values.each do |fixture| 279 @connection.execute "INSERT INTO #{@ table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'271 @connection.execute "INSERT INTO #{@fixture_group.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' 280 272 end 281 273 end 282 274 283 275 private 284 276 def read_fixture_files 285 277 if File.file?(yaml_file_path) 286 # YAML fixtures 287 begin 288 yaml = YAML::load(erb_render(IO.read(yaml_file_path))) 289 yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml 290 rescue Exception=>boom 291 raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}" 292 end 278 read_yaml_fixture_files 293 279 elsif File.file?(csv_file_path) 294 # CSV fixtures 295 reader = CSV::Reader.create(erb_render(IO.read(csv_file_path))) 296 header = reader.shift 297 i = 0 298 reader.each do |row| 299 data = {} 300 row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } 301 self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name) 302 end 280 read_csv_fixture_files 303 281 elsif File.file?(deprecated_yaml_file_path) 304 282 raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}" 283 elsif File.directory?(File.join(@fixtures_directory, @fixture_group.file_name)) 284 read_standard_fixture_files 305 285 else 306 # Standard fixtures 307 Dir.entries(@fixture_path).each do |file| 308 path = File.join(@fixture_path, file) 309 if File.file?(path) and file !~ @file_filter 310 self[file] = Fixture.new(path, @class_name) 311 end 286 raise Fixture::FixtureError, "Couldn't find a yaml, csv or standard file to load at #{@fixtures_directory}." 287 end 288 end 289 290 def read_yaml_fixture_files 291 # YAML fixtures 292 begin 293 yaml = YAML::load(erb_render(IO.read(yaml_file_path))) 294 yaml.each { |name, data| self[name] = Fixture.new(data, @fixture_group.class_name) } if yaml 295 rescue Exception=>boom 296 raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}" 297 end 298 end 299 300 def read_csv_fixture_files 301 # CSV fixtures 302 reader = CSV::Reader.create(erb_render(IO.read(csv_file_path))) 303 header = reader.shift 304 i = 0 305 reader.each do |row| 306 data = {} 307 row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } 308 self["#{fixture_group.class_file_name}_#{i+=1}"]= Fixture.new(data, @fixture_group.class_name) 309 end 310 end 311 312 def read_standard_fixture_files 313 # Standard fixtures 314 path = File.join(@fixtures_directory, @fixture_group.file_name) 315 Dir.entries(path).each do |file| 316 path = File.join(@fixtures_directory, file) 317 if File.file?(path) and file !~ @file_filter 318 self[file] = Fixture.new(path, @fixture_group.class_name) 312 319 end 313 320 end 314 321 end 315 322 316 323 def yaml_file_path 317 "#{@fixture_path}.yml"324 fixture_path_with_extension ".yml" 318 325 end 319 326 320 327 def deprecated_yaml_file_path 321 "#{@fixture_path}.yaml"328 fixture_path_with_extension ".yaml" 322 329 end 323 330 324 331 def csv_file_path 325 @fixture_path +".csv"332 fixture_path_with_extension ".csv" 326 333 end 327 334 328 def yaml_fixtures_key(path)329 File. basename(@fixture_path).split(".").first330 end 335 def fixture_path_with_extension(ext) 336 File.join(@fixtures_directory, @fixture_group.file_name + ext) 337 end 331 338 332 339 def erb_render(fixture_content) 333 340 ERB.new(fixture_content).result 334 341 end 342 343 #def yaml_fixtures_key(path) 344 # File.basename(@fixture_path).split(".").first 345 #end 346 347 public 348 class << self 349 def instantiate_fixtures(object, fixture_group_name, fixtures, load_instances=true) 350 old_logger_level = ActiveRecord::Base.logger.level 351 ActiveRecord::Base.logger.level = Logger::ERROR 352 353 # table_name.to_s.gsub('.','_') replaced by 'fixture_group_name' 354 object.instance_variable_set "@#{fixture_group_name}", fixtures 355 if load_instances 356 fixtures.each do |name, fixture| 357 if model = fixture.find 358 object.instance_variable_set "@#{name}", model 359 end 360 end 361 end 362 363 ActiveRecord::Base.logger.level = old_logger_level 364 end 365 366 def instantiate_all_loaded_fixtures(object, load_instances=true) 367 all_loaded_fixtures.each do |fixture_group_name, fixtures| 368 Fixtures.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances) 369 end 370 end 371 372 373 def create_fixtures(fixtures_directory, *fixture_groups) 374 connection = block_given? ? yield : ActiveRecord::Base.connection 375 old_logger_level = ActiveRecord::Base.logger.level 376 fixture_groups.flatten! 377 378 # Backwards compatibility: Allow an array of table names to be passed in, but just use them 379 # to create an array of FixtureGroup objects 380 if not fixture_groups.empty? and fixture_groups.first.is_a?(String) 381 fixture_groups = FixtureGroup.array_from_names(fixture_groups) 382 end 383 384 begin 385 ActiveRecord::Base.logger.level = Logger::ERROR 386 387 fixtures_map = {} 388 fixtures = fixture_groups.map do |group| 389 fixtures_map[group.group_name] = Fixtures.new(connection, fixtures_directory, group) 390 end 391 # Make sure all refs to all_loaded_fixtures use group_name as hash index, not table_name 392 all_loaded_fixtures.merge! fixtures_map 393 394 connection.transaction do 395 fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 396 fixtures.each { |fixture| fixture.insert_fixtures } 397 end 398 399 reset_sequences(connection, fixture_groups) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) 400 401 return fixtures.size > 1 ? fixtures : fixtures.first 402 ensure 403 ActiveRecord::Base.logger.level = old_logger_level 404 end 405 end 406 407 # Work around for PostgreSQL to have new fixtures created from id 1 and running. 408 def reset_sequences(connection, fixture_groups) 409 fixture_groups.flatten.each do |group| 410 if Object.const_defined?(group.class_name) 411 pk = eval("#{group.class_name}::primary_key") 412 if pk == 'id' 413 connection.execute( 414 "SELECT setval('#{group.table_name}_id_seq', (SELECT MAX(id) FROM #{group.table_name}), true)", 415 'Setting Sequence' 416 ) 417 end 418 end 419 end 420 end 421 end 335 422 end 336 423 337 424 class Fixture #:nodoc: … … 399 486 class TestCase #:nodoc: 400 487 include ClassInheritableAttributes 401 488 402 cattr_accessor :fixture _path403 class_inheritable_accessor :fixture_ table_names489 cattr_accessor :fixtures_directory 490 class_inheritable_accessor :fixture_groups 404 491 class_inheritable_accessor :use_transactional_fixtures 405 492 class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances 406 493 class_inheritable_accessor :pre_loaded_fixtures 407 494 408 self.fixture_ table_names = []495 self.fixture_groups = [] 409 496 self.use_transactional_fixtures = false 410 497 self.use_instantiated_fixtures = true 411 498 self.pre_loaded_fixtures = false 412 499 413 500 @@already_loaded_fixtures = {} 414 501 415 def self.fixtures(*table_names) 416 table_names = table_names.flatten 417 self.fixture_table_names |= table_names 418 require_fixture_classes(table_names) 419 setup_fixture_accessors(table_names) 502 # Backwards compatibility 503 def self.fixture_path=(path); self.fixtures_directory = path; end 504 def self.fixture_path; self.fixtures_directory; end 505 # It would be more appropriate to call this 'fixture_group_names', but it's for back compat. 506 def fixture_table_names; fixture_groups.collect { |g| g.group_name }; end 507 508 def self.fixture(file_name, options = {}) 509 self.fixture_groups |= [FixtureGroup.new(file_name, options)] 510 require_fixture_classes 511 setup_fixture_accessors 420 512 end 421 513 422 def self.require_fixture_classes(table_names=nil) 423 (table_names || fixture_table_names).each do |table_name| 514 def self.fixtures(*file_names) 515 self.fixture_groups |= FixtureGroup.array_from_names(file_names.flatten) 516 require_fixture_classes 517 setup_fixture_accessors 518 end 519 520 def self.require_fixture_classes(override_fixture_groups=nil) 521 (override_fixture_groups || fixture_groups).each do |group| 424 522 begin 425 require Inflector.singularize(table_name.to_s)523 require group.class_file_name 426 524 rescue LoadError 427 525 # Let's hope the developer has included it himself 428 526 end 429 527 end 430 528 end 431 529 432 def self.setup_fixture_accessors( table_names=nil)433 ( table_names || fixture_table_names).each do |table_name|434 table_name = table_name.to_s.tr('.','_')435 define_method( table_name) do |fixture, *optionals|530 def self.setup_fixture_accessors(override_fixture_groups=nil) 531 (override_fixture_groups || fixture_groups).each do |group| 532 # table_name = table_name.to_s.tr('.','_') 533 define_method(group.group_name) do |fixture, *optionals| 436 534 force_reload = optionals.shift 437 @fixture_cache[ table_name] ||= Hash.new438 @fixture_cache[ table_name][fixture] = nil if force_reload439 @fixture_cache[ table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find535 @fixture_cache[group.group_name] ||= Hash.new 536 @fixture_cache[group.group_name][fixture] = nil if force_reload 537 @fixture_cache[group.group_name][fixture] ||= @loaded_fixtures[group.group_name][fixture.to_s].find 440 538 end 441 539 end 442 540 end … … 519 617 private 520 618 def load_fixtures 521 619 @loaded_fixtures = {} 522 fixtures = Fixtures.create_fixtures(fixture _path, fixture_table_names)620 fixtures = Fixtures.create_fixtures(fixtures_directory, fixture_groups) 523 621 unless fixtures.nil? 524 622 if fixtures.instance_of?(Fixtures) 525 @loaded_fixtures[fixtures. table_name] = fixtures623 @loaded_fixtures[fixtures.fixture_group.group_name] = fixtures 526 624 else 527 fixtures.each { |f| @loaded_fixtures[f. table_name] = f }625 fixtures.each { |f| @loaded_fixtures[f.fixture_group.group_name] = f } 528 626 end 529 627 end 530 628 end … … 536 634 if pre_loaded_fixtures 537 635 raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? 538 636 unless @@required_fixture_classes 539 self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys 637 groups = Fixtures.all_loaded_fixtures.values.collect { |f| f.group_name } 638 self.class.require_fixture_classes groups 540 639 @@required_fixture_classes = true 541 640 end 542 641 Fixtures.instantiate_all_loaded_fixtures(self, load_instances?) 543 642 else 544 643 raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? 545 @loaded_fixtures.each do | table_name, fixtures|546 Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)644 @loaded_fixtures.each do |fixture_group_name, fixtures| 645 Fixtures.instantiate_fixtures(self, fixture_group_name, fixtures, load_instances?) 547 646 end 548 647 end 549 648 end