class Fixtures
def self.cache_fixtures(connection, fixtures_map)
def self.cache_fixtures(connection, fixtures_map) cache_for_connection(connection).update(fixtures_map) end
def self.cache_for_connection(connection)
def self.cache_for_connection(connection) @@all_cached_fixtures[connection.object_id] ||= {} @@all_cached_fixtures[connection.object_id] end
def self.cached_fixtures(connection, keys_to_fetch = nil)
def self.cached_fixtures(connection, keys_to_fetch = nil) if keys_to_fetch fixtures = cache_for_connection(connection).values_at(*keys_to_fetch) else fixtures = cache_for_connection(connection).values end fixtures.size > 1 ? fixtures : fixtures.first end
def self.create_fixtures(fixtures_directory, table_names, class_names = {})
def self.create_fixtures(fixtures_directory, table_names, class_names = {}) table_names = [table_names].flatten.map { |n| n.to_s } connection = block_given? ? yield : ActiveRecord::Base.connection table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) } unless table_names_to_fetch.empty? ActiveRecord::Base.silence do connection.disable_referential_integrity do fixtures_map = {} fixtures = table_names_to_fetch.map do |table_name| 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)) end all_loaded_fixtures.update(fixtures_map) connection.transaction(:requires_new => true) do fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } fixtures.each { |fixture| fixture.insert_fixtures } # Cap primary key sequences to max(pk). if connection.respond_to?(:reset_pk_sequence!) table_names.each do |table_name| connection.reset_pk_sequence!(table_name) end end end cache_fixtures(connection, fixtures_map) end end end cached_fixtures(connection, table_names) end
def self.fixture_is_cached?(connection, table_name)
def self.fixture_is_cached?(connection, table_name) cache_for_connection(connection)[table_name] end
def self.identify(label)
Returns a consistent, platform-independent identifier for +label+.
def self.identify(label) Zlib.crc32(label.to_s) % MAX_ID end
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
def self.instantiate_all_loaded_fixtures(object, load_instances = true) all_loaded_fixtures.each do |table_name, fixtures| Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances) end end
def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true) object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures if load_instances ActiveRecord::Base.silence do fixtures.each do |name, fixture| begin object.instance_variable_set "@#{name}", fixture.find rescue FixtureClassNotFound nil end end end end end
def self.reset_cache(connection = nil)
def self.reset_cache(connection = nil) connection ||= ActiveRecord::Base.connection @@all_cached_fixtures[connection.object_id] = {} end
def column_names
def column_names @column_names ||= @connection.columns(@table_name).collect(&:name) end
def csv_file_path
def csv_file_path @fixture_path + ".csv" end
def delete_existing_fixtures
def delete_existing_fixtures @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete' end
def erb_render(fixture_content)
def erb_render(fixture_content) ERB.new(fixture_content).result end
def has_primary_key_column?
def has_primary_key_column? @has_primary_key_column ||= model_class && primary_key_name && model_class.columns.find { |c| c.name == primary_key_name } end
def inheritance_column_name
def inheritance_column_name @inheritance_column_name ||= model_class && model_class.inheritance_column end
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE) @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter @name = table_name # preserve fixture base name @class_name = class_name || (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize) @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}" @table_name = class_name.table_name if class_name.respond_to?(:table_name) @connection = class_name.connection if class_name.respond_to?(:connection) read_fixture_files end
def insert_fixtures
def insert_fixtures now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now now = now.to_s(:db) # allow a standard key to be used for doing defaults in YAML if is_a?(Hash) delete('DEFAULTS') else delete(assoc('DEFAULTS')) end # track any join tables we need to insert later habtm_fixtures = Hash.new do |h, habtm| h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil) end each do |label, fixture| row = fixture.to_hash if model_class && model_class < ActiveRecord::Base # fill in timestamp columns if they aren't specified and the model is set to record_timestamps if model_class.record_timestamps timestamp_column_names.each do |name| row[name] = now unless row.key?(name) end end # interpolate the fixture label row.each do |key, value| row[key] = label if value == "$LABEL" end # generate a primary key if necessary if has_primary_key_column? && !row.include?(primary_key_name) row[primary_key_name] = Fixtures.identify(label) end # If STI is used, find the correct subclass for association reflection reflection_class = if row.include?(inheritance_column_name) row[inheritance_column_name].constantize rescue model_class else model_class end reflection_class.reflect_on_all_associations.each do |association| case association.macro when :belongs_to # Do not replace association name with association foreign key if they are named the same fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s if association.name.to_s != fk_name && value = row.delete(association.name.to_s) if association.options[:polymorphic] if value.sub!(/\s*\(([^\)]*)\)\s*$/, "") target_type = $1 target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s # support polymorphic belongs_to as "label (Type)" row[target_type_name] = target_type end end row[fk_name] = Fixtures.identify(value) end when :has_and_belongs_to_many if (targets = row.delete(association.name.to_s)) targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) join_fixtures = habtm_fixtures[association] targets.each do |target| join_fixtures["#{label}_#{target}"] = Fixture.new( { association.primary_key_name => row[primary_key_name], association.association_foreign_key => Fixtures.identify(target) }, nil, @connection) end end end end end @connection.insert_fixture(fixture, @table_name) end # insert any HABTM join tables we discovered habtm_fixtures.values.each do |fixture| fixture.delete_existing_fixtures fixture.insert_fixtures end end
def model_class
def model_class unless defined?(@model_class) @model_class = if @class_name.nil? || @class_name.is_a?(Class) @class_name else @class_name.constantize rescue nil end end @model_class end
def parse_yaml_string(fixture_content)
def parse_yaml_string(fixture_content) YAML::load(erb_render(fixture_content)) rescue => error raise Fixture::FormatError, "a YAML error occurred 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 #{error.class}: #{error}" end
def primary_key_name
def primary_key_name @primary_key_name ||= model_class && model_class.primary_key end
def read_csv_fixture_files
def read_csv_fixture_files reader = CSV.parse(erb_render(IO.read(csv_file_path))) header = reader.shift i = 0 reader.each do |row| data = {} row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection) end end
def read_fixture_files
def read_fixture_files if File.file?(yaml_file_path) read_yaml_fixture_files elsif File.file?(csv_file_path) read_csv_fixture_files end end
def read_yaml_fixture_files
def read_yaml_fixture_files yaml_string = "" Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path| yaml_string << IO.read(subfixture_path) end yaml_string << IO.read(yaml_file_path) if yaml = parse_yaml_string(yaml_string) # If the file is an ordered map, extract its children. yaml_value = if yaml.respond_to?(:type_id) && yaml.respond_to?(:value) yaml.value else [yaml] end yaml_value.each do |fixture| raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each) fixture.each do |name, data| unless data raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)" end self[name] = Fixture.new(data, model_class, @connection) end end end end
def timestamp_column_names
def timestamp_column_names @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name| column_names.include?(name) end end
def yaml_file_path
def yaml_file_path "#{@fixture_path}.yml" end
def yaml_fixtures_key(path)
def yaml_fixtures_key(path) File.basename(@fixture_path).split(".").first end