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

Ticket #9981: foxy_fixtures.diff

File foxy_fixtures.diff, 20.1 kB (added by jbarnette, 1 year ago)
  • activerecord/test/fixtures_test.rb

    old new  
    77require 'fixtures/joke' 
    88require 'fixtures/course' 
    99require 'fixtures/category' 
     10require 'fixtures/parrot' 
     11require 'fixtures/pirate' 
     12require 'fixtures/treasure' 
    1013 
    1114class FixturesTest < Test::Unit::TestCase 
    1215  self.use_instantiated_fixtures = true 
     
    446449    assert_equal 'Welcome to the weblog', posts(:welcome).title 
    447450  end 
    448451end 
     452 
     453class FoxyFixturesTest < Test::Unit::TestCase 
     454  fixtures :parrots, :parrots_pirates, :pirates, :treasures 
     455     
     456  def test_identifies_strings 
     457    assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo")) 
     458    assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO")) 
     459  end 
     460   
     461  def test_identifies_symbols 
     462    assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo)) 
     463  end 
     464   
     465  TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) 
     466   
     467  def test_populates_timestamp_columns 
     468    TIMESTAMP_COLUMNS.each do |property| 
     469      assert_not_nil(parrots(:george).send(property), "should set #{property}") 
     470    end 
     471  end 
     472   
     473  def test_populates_all_columns_with_the_same_time 
     474    last = nil 
     475     
     476    TIMESTAMP_COLUMNS.each do |property| 
     477      current = parrots(:george).send(property) 
     478      last ||= current 
     479       
     480      assert_equal(last, current) 
     481      last = current 
     482    end 
     483  end 
     484   
     485  def test_only_populates_columns_that_exist 
     486    assert_not_nil(pirates(:blackbeard).created_on) 
     487    assert_not_nil(pirates(:blackbeard).updated_on) 
     488  end 
     489   
     490  def test_preserves_existing_fixture_data 
     491    assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) 
     492    assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) 
     493  end 
     494   
     495  def test_generates_unique_ids 
     496    assert_not_nil(parrots(:george).id) 
     497    assert_not_equal(parrots(:george).id, parrots(:louis).id) 
     498  end 
     499   
     500  def test_resolves_belongs_to_symbols 
     501    assert_equal(parrots(:george), pirates(:blackbeard).parrot) 
     502  end 
     503 
     504  def test_supports_join_tables 
     505    assert(pirates(:blackbeard).parrots.include?(parrots(:george))) 
     506    assert(pirates(:blackbeard).parrots.include?(parrots(:louis))) 
     507    assert(parrots(:george).pirates.include?(pirates(:blackbeard))) 
     508  end 
     509   
     510  def test_supports_inline_habtm 
     511    assert(parrots(:george).treasures.include?(treasures(:diamond))) 
     512    assert(parrots(:george).treasures.include?(treasures(:sapphire))) 
     513    assert(!parrots(:george).treasures.include?(treasures(:ruby))) 
     514  end 
     515   
     516  def test_supports_yaml_arrays 
     517    assert(parrots(:louis).treasures.include?(treasures(:diamond))) 
     518    assert(parrots(:louis).treasures.include?(treasures(:sapphire))) 
     519  end 
     520   
     521  def test_strips_DEFAULTS_key 
     522    assert_raise(StandardError) { parrots(:DEFAULTS) } 
     523     
     524    # this lets us do YAML defaults and not have an extra fixture entry 
     525    %w(sapphire ruby).each { |t| assert(parrots(:davey).treasures.include?(treasures(t))) } 
     526  end 
     527   
     528  def test_supports_label_interpolation 
     529    assert_equal("frederick", parrots(:frederick).name) 
     530  end 
     531end 
  • activerecord/test/fixtures/treasure.rb

    old new  
     1class Treasure < ActiveRecord::Base 
     2  has_and_belongs_to_many :parrots 
     3end 
  • activerecord/test/fixtures/treasures.yml

    old new  
     1diamond: 
     2  name: $LABEL 
     3 
     4sapphire: 
     5  name: $LABEL 
     6 
     7ruby: 
     8  name: $LABEL 
  • activerecord/test/fixtures/pirate.rb

    old new  
     1class Pirate < ActiveRecord::Base 
     2  belongs_to :parrot 
     3  has_and_belongs_to_many :parrots 
     4end 
  • activerecord/test/fixtures/pirates.yml

    old new  
     1blackbeard: 
     2  catchphrase: "Yar." 
     3  parrot: george 
     4 
     5redbeard: 
     6  catchphrase: "Avast!" 
     7  parrot: louis 
     8  created_on: <%= 2.weeks.ago.to_s(:db) %> 
     9  updated_on: <%= 2.weeks.ago.to_s(:db) %> 
  • activerecord/test/fixtures/parrots.yml

    old new  
     1george: 
     2  name: "Curious George" 
     3  treasures: diamond, sapphire 
     4 
     5louis: 
     6  name: "King Louis" 
     7  treasures: [diamond, sapphire] 
     8 
     9frederick: 
     10  name: $LABEL 
     11 
     12DEFAULTS: &DEFAULTS 
     13  treasures: sapphire, ruby 
     14 
     15davey: 
     16  <<: *DEFAULTS 
  • activerecord/test/fixtures/db_definitions/schema.rb

    old new  
    295295    t.column :city, :string, :null => false 
    296296    t.column :type, :string 
    297297  end 
     298   
     299  create_table :parrots, :force => true do |t| 
     300    t.column :name, :string 
     301    t.column :created_at, :datetime 
     302    t.column :created_on, :datetime 
     303    t.column :updated_at, :datetime 
     304    t.column :updated_on, :datetime 
     305  end 
     306   
     307  create_table :pirates, :force => true do |t| 
     308    t.column :catchphrase, :string 
     309    t.column :parrot_id, :integer 
     310    t.column :created_on, :datetime 
     311    t.column :updated_on, :datetime 
     312  end 
     313   
     314  create_table :parrots_pirates, :id => false, :force => true do |t| 
     315    t.column :parrot_id, :integer 
     316    t.column :pirate_id, :integer 
     317  end 
     318   
     319  create_table :treasures, :force => true do |t| 
     320    t.column :name, :string 
     321  end 
     322   
     323  create_table :parrots_treasures, :id => false, :force => true do |t| 
     324    t.column :parrot_id, :integer 
     325    t.column :treasure_id, :integer 
     326  end 
    298327end 
  • activerecord/test/fixtures/parrots_pirates.yml

    old new  
     1george_blackbeard: 
     2  parrot_id: <%= Fixtures.identify(:george) %> 
     3  pirate_id: <%= Fixtures.identify(:blackbeard) %> 
     4 
     5louis_blackbeard: 
     6  parrot_id: <%= Fixtures.identify(:louis) %> 
     7  pirate_id: <%= Fixtures.identify(:blackbeard) %> 
  • activerecord/test/fixtures/parrot.rb

    old new  
     1class Parrot < ActiveRecord::Base 
     2  has_and_belongs_to_many :pirates 
     3  has_and_belongs_to_many :treasures 
     4end 
  • activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

    old new  
    7070        name 
    7171      end 
    7272 
     73      # REFERENTIAL INTEGRITY ==================================== 
     74 
     75      # Override to turn off referential integrity while executing +&block+ 
     76      def disable_referential_integrity(&block) 
     77        yield 
     78      end           
     79 
    7380      # CONNECTION MANAGEMENT ==================================== 
    7481 
    7582      # Is this connection active and ready to perform queries? 
  • activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

    old new  
    364364        end 
    365365      end 
    366366 
     367      # REFERENTIAL INTEGRITY ==================================== 
    367368 
     369      def disable_referential_integrity(&block) #:nodoc: 
     370        execute(tables.collect { |name| "ALTER TABLE #{name} DISABLE TRIGGER ALL" }.join(";")) 
     371       
     372        begin 
     373          yield 
     374        ensure 
     375          execute(tables.collect { |name| "ALTER TABLE #{name} ENABLE TRIGGER ALL" }.join(";")) 
     376        end 
     377      end           
     378 
    368379      # DATABASE STATEMENTS ====================================== 
    369380 
    370381      # Executes a SELECT query and returns an array of rows. Each row is an 
  • activerecord/lib/active_record/connection_adapters/mysql_adapter.rb

    old new  
    224224        "0" 
    225225      end 
    226226 
     227      # REFERENTIAL INTEGRITY ==================================== 
    227228 
     229      def disable_referential_integrity(&block) #:nodoc: 
     230        old = select_value("SELECT @@FOREIGN_KEY_CHECKS") 
     231 
     232        begin 
     233          update("SET FOREIGN_KEY_CHECKS = 0") 
     234          yield 
     235        ensure 
     236          update("SET FOREIGN_KEY_CHECKS = #{old}")               
     237        end 
     238      end           
     239 
    228240      # CONNECTION MANAGEMENT ==================================== 
    229241 
    230242      def active? 
  • activerecord/lib/active_record/fixtures.rb

    old new  
    215215#      the results of your transaction until Active Record supports nested transactions or savepoints (in progress.) 
    216216#   2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. 
    217217#      Use InnoDB, MaxDB, or NDB instead. 
     218# 
     219# = Advanced YAML Fixtures 
     220#  
     221# YAML fixtures that don't specify an ID get some extra features: 
     222# 
     223# * Stable, autogenerated ID's 
     224# * Label references for associations (belongs_to, has_one, has_many) 
     225# * HABTM associations as inline lists 
     226# * Autofilled timestamp columns 
     227# * Fixture label interpolation 
     228# * Support for YAML defaults 
     229#  
     230# == Stable, autogenerated ID's 
     231#  
     232# Here, have a monkey fixture: 
     233#  
     234#   george: 
     235#     id: 1 
     236#     name: George the Monkey 
     237#  
     238#   reginald: 
     239#     id: 2 
     240#     name: Reginald the Pirate 
     241#  
     242# Each of these fixtures has two unique identifiers: one for the database 
     243# and one for the humans. Why don't we generate the primary key instead? 
     244# Hashing each fixture's label yields a consistent ID: 
     245#  
     246#   george: # generated id: 503576764 
     247#     name: George the Monkey 
     248#  
     249#   reginald: # generated id: 324201669 
     250#     name: Reginald the Pirate 
     251#      
     252# ActiveRecord looks at the fixture's model class, discovers the correct 
     253# primary key, and generates it right before inserting the fixture 
     254# into the database. 
     255#  
     256# The generated ID for a given label is constant, so we can discover 
     257# any fixture's ID without loading anything, as long as we know the label. 
     258#  
     259# == Label references for associations (belongs_to, has_one, has_many) 
     260#  
     261# Specifying foreign keys in fixtures can be very fragile, not to 
     262# mention difficult to read. Since ActiveRecord can figure out the ID of 
     263# and fixture from its label, you can specify FK's by label instead of ID. 
     264#  
     265# === belongs_to 
     266#  
     267# Let's break out some more monkeys and pirates. 
     268#  
     269#   ### in pirates.yml 
     270#    
     271#   reginald: 
     272#     id: 1 
     273#     name: Reginald the Pirate 
     274#     monkey_id: 1 
     275#    
     276#   ### in monkeys.yml 
     277#    
     278#   george: 
     279#     id: 1 
     280#     name: George the Monkey 
     281#     pirate_id: 1 
     282#  
     283# Add a few more monkeys and pirates and break this into multiple files, 
     284# and it gets pretty hard to keep track of what's going on. Let's 
     285# use labels instead of ID's: 
     286#  
     287#   ### in pirates.yml 
     288#  
     289#   reginald: 
     290#     name: Reginald the Pirate 
     291#     monkey: george 
     292#  
     293#   ### in monkeys.yml 
     294#  
     295#   george: 
     296#     name: George the Monkey 
     297#     pirate: reginald 
     298#  
     299# Pow! All is made clear. ActiveRecord reflects on the fixture's model class, 
     300# finds all the +belongs_to+ associations, and allows you to specify 
     301# a target *label* for the *association* (monkey: george) rather than 
     302# a target *id* for the *FK* (monkey_id: 1). 
     303#  
     304# === has_and_belongs_to_many 
     305#  
     306# Time to give our monkey some fruit. 
     307#  
     308#   ### in monkeys.yml 
     309#    
     310#   george: 
     311#     id: 1 
     312#     name: George the Monkey 
     313#     pirate_id: 1 
     314#  
     315#   ### in fruits.yml 
     316#    
     317#   apple: 
     318#     id: 1 
     319#     name: apple 
     320#  
     321#   orange: 
     322#     id: 2 
     323#     name: orange 
     324#  
     325#   grape: 
     326#     id: 3 
     327#     name: grape 
     328#    
     329#   ### in fruits_monkeys.yml 
     330#    
     331#   apple_george: 
     332#     fruit_id: 1 
     333#     monkey_id: 1 
     334#  
     335#   orange_george: 
     336#     fruit_id: 2 
     337#     monkey_id: 1 
     338#  
     339#   grape_george: 
     340#     fruit_id: 3 
     341#     monkey_id: 1 
     342#  
     343# Let's make the HABTM fixture go away. 
     344#  
     345#   ### in monkeys.yml 
     346#  
     347#   george: 
     348#     name: George the Monkey 
     349#     pirate: reginald 
     350#     fruits: apple, orange, grape 
     351#  
     352#   ### in fruits.yml 
     353#  
     354#   apple: 
     355#     name: apple 
     356#  
     357#   orange: 
     358#     name: orange 
     359#  
     360#   grape: 
     361#     name: grape 
     362#  
     363# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits 
     364# on George's fixture, but we could've just as easily specified a list 
     365# of monkeys on each fruit. As with +belongs_to+, ActiveRecord reflects on 
     366# the fixture's model class and discovers the +has_and_belongs_to_many+ 
     367# associations. 
     368#  
     369# == Autofilled timestamp columns 
     370#  
     371# If your table/model specifies any of ActiveRecord's 
     372# standard timestamp columns (created_at, created_on, updated_at, updated_on), 
     373# they will automatically be set to Time.now. 
     374#  
     375# If you've set specific values, they'll be left alone. 
     376#  
     377# == Fixture label interpolation 
     378# 
     379# The label of the current fixture is always available as a column value: 
     380#  
     381#   geeksomnia: 
     382#     name: Geeksomnia's Account 
     383#     subdomain: $LABEL 
     384#  
     385# Also, sometimes (like when porting older join table fixtures) you'll need 
     386# to be able to get ahold of the identifier for a given label. ERB 
     387# to the rescue: 
     388#  
     389#   george_reginald: 
     390#     monkey_id: <%= Fixtures.identify(:reginald) %> 
     391#     pirate_id: <%= Fixtures.identify(:george) %> 
     392# 
     393# == Support for YAML defaults 
     394#  
     395# You probably already know how to use YAML to set and reuse defaults in 
     396# your +database.yml+ file,. You can use the same technique in your fixtures: 
     397#  
     398#   DEFAULTS: &DEFAULTS 
     399#     created_on: <%= 3.weeks.ago.to_s(:db) %> 
     400#      
     401#   first: 
     402#     name: Smurf 
     403#     <<: *DEFAULTS 
     404#  
     405#   second: 
     406#     name: Fraggle 
     407#     <<: *DEFAULTS 
     408#  
     409# Any fixture labeled "DEFAULTS" is safely ignored. 
     410 
    218411class Fixtures < YAML::Omap 
    219412  DEFAULT_FILTER_RE = /\.ya?ml$/ 
    220413 
     
    279472 
    280473    unless table_names_to_fetch.empty? 
    281474      ActiveRecord::Base.silence do 
    282         fixtures_map = {} 
     475        connection.disable_referential_integrity do 
     476          fixtures_map = {} 
    283477 
    284         fixtures = table_names_to_fetch.map do |table_name| 
    285           fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s)) 
    286         end 
     478          fixtures = table_names_to_fetch.map do |table_name| 
     479            fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s)) 
     480          end 
    287481 
    288         all_loaded_fixtures.update(fixtures_map) 
     482          all_loaded_fixtures.update(fixtures_map) 
    289483 
    290         connection.transaction(Thread.current['open_transactions'].to_i == 0) do 
    291           fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 
    292           fixtures.each { |fixture| fixture.insert_fixtures } 
     484          connection.transaction(Thread.current['open_transactions'].to_i == 0) do 
     485            fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } 
     486            fixtures.each { |fixture| fixture.insert_fixtures } 
    293487 
    294           # Cap primary key sequences to max(pk). 
    295           if connection.respond_to?(:reset_pk_sequence!) 
    296             table_names.each do |table_name| 
    297               connection.reset_pk_sequence!(table_name) 
     488            # Cap primary key sequences to max(pk). 
     489            if connection.respond_to?(:reset_pk_sequence!) 
     490              table_names.each do |table_name| 
     491                connection.reset_pk_sequence!(table_name) 
     492              end 
    298493            end 
    299494          end 
     495 
     496          cache_fixtures(connection, fixtures) 
    300497        end 
    301  
    302         cache_fixtures(connection, fixtures) 
    303498      end 
    304499    end 
    305500    cached_fixtures(connection, table_names) 
    306501  end 
     502   
     503  # Returns a consistent identifier for +label+. This will always 
     504  # be a positive integer, and will always be the same for a given 
     505  # label, assuming the same OS, platform, and version of Ruby. 
     506  def self.identify(label) 
     507    label.to_s.hash.abs 
     508  end 
    307509 
    308510  attr_reader :table_name 
    309511 
     
    322524  end 
    323525 
    324526  def insert_fixtures 
    325     values.each do |fixture| 
     527    now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now 
     528    now = now.to_s(:db) 
     529     
     530    # allow a standard key to be used for doing defaults in YAML 
     531    delete(assoc("DEFAULTS")) 
     532     
     533    # track any join tables we need to insert later 
     534    habtm_fixtures = Hash.new do |h, habtm| 
     535      h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil) 
     536    end 
     537     
     538    each do |label, fixture| 
     539      row = fixture.to_hash 
     540       
     541      if model_class && model_class < ActiveRecord::Base && !row[primary_key_name] 
     542        # fill in timestamp columns if they aren't specified 
     543        timestamp_column_names.each do |name| 
     544          row[name] = now unless row.key?(name) 
     545        end 
     546         
     547        # interpolate the fixture label 
     548        row.each do |key, value| 
     549          row[key] = label if value == "$LABEL" 
     550        end 
     551         
     552        # generate a primary key 
     553        row[primary_key_name] = Fixtures.identify(label) 
     554         
     555        model_class.reflect_on_all_associations.each do |association| 
     556          case association.macro 
     557          when :belongs_to 
     558            if value = row.delete(association.name.to_s) 
     559              fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s 
     560              row[fk_name] = Fixtures.identify(value) 
     561            end 
     562          when :has_and_belongs_to_many 
     563            if (targets = row.delete(association.name.to_s)) 
     564              targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) 
     565              join_fixtures = habtm_fixtures[association] 
     566 
     567              targets.each do |target| 
     568                join_fixtures["#{label}_#{target}"] = Fixture.new( 
     569                  { association.primary_key_name => Fixtures.identify(label), 
     570                    association.association_foreign_key => Fixtures.identify(target) }, nil) 
     571              end 
     572            end 
     573          end 
     574        end 
     575      end 
     576       
    326577      @connection.insert_fixture(fixture, @table_name) 
    327578    end 
     579     
     580    # insert any HABTM join tables we discovered 
     581    habtm_fixtures.values.each do |fixture| 
     582      fixture.delete_existing_fixtures 
     583      fixture.insert_fixtures 
     584    end 
    328585  end 
    329586 
    330587  private 
     588    class HabtmFixtures < ::Fixtures #:nodoc: 
     589      def read_fixture_files; end 
     590    end 
     591 
     592    def model_class 
     593      @model_class ||= @class_name.is_a?(Class) ? 
     594        @class_name : @class_name.constantize rescue nil 
     595    end 
     596   
     597    def primary_key_name 
     598      @primary_key_name ||= model_class && model_class.primary_key 
     599    end 
     600 
     601    def timestamp_column_names 
     602      @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name| 
     603        column_names.include?(name) 
     604      end 
     605    end 
     606 
     607    def column_names 
     608      @column_names ||= @connection.columns(@table_name).collect(&:name) 
     609    end 
     610   
    331611    def read_fixture_files 
    332612      if File.file?(yaml_file_path) 
    333613        read_yaml_fixture_files