<commit_before>require 'erb'
require 'yaml'
require 'set'
require 'active_support/dependencies'
require 'active_record/errors'
require "fixation/version"

module Fixation
  class FixtureTable
    attr_reader :filename, :table_name, :connection, :now

    def initialize(filename, table_name, connection, now)
      @filename = filename
      @table_name = table_name
      @connection = connection
      @now = now
    end

    def columns_hash
      @columns_hash ||= connection.columns(table_name).index_by(&:name)
    end

    def content
      template = File.read(filename)
      render_context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding
      ERB.new(template).result(render_context)
    end

    def parsed_rows
      result = YAML.load(content)
      result ||= {} # for completely empty files

      unless (result.is_a?(Hash) || result.is_a?(YAML::Omap)) && result.all? { |name, attributes| name.is_a?(String) && attributes.is_a?(Hash) }
        raise ActiveRecord::Fixture::FormatError, "#{filename} needs to contain a hash of fixtures"
      end

      result.delete('DEFAULTS')
      result
    rescue ArgumentError, Psych::SyntaxError => error
      # we use exactly the same error class and message as ActiveRecord::FixtureSet in case anyone was depending on it
      raise ActiveRecord::Fixture::FormatError, "a YAML error occurred parsing #{filename}. 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}", error.backtrace
    end

    def embellished_rows
      @embellished_rows ||= parsed_rows.each do |name, attributes|
        embellish_fixture(name, attributes, columns_hash)
      end
    end

    def embellish_fixture(name, attributes, columns_hash)
      # populate the primary key column, if not already set
      if columns_hash['id'] && !attributes.has_key?('id')
        attributes['id'] = Fixation.identify(name, columns_hash['id'].type)
      end

      if columns_hash['uuid'] && !attributes.has_key?('uuid')
        attributes['uuid'] = Fixation.identify(name, columns_hash['uuid'].type)
      end

      # substitute $LABEL into all string values
      attributes.each do |column_name, value|
        attributes[column_name] = value.gsub("$LABEL", name) if value.is_a?(String)
      end

      # populate any timestamp columns, if not already set
      %w(created_at updated_at).each do |column_name|
        attributes[column_name] = now if columns_hash[column_name] && !attributes.has_key?(column_name)
      end
      %w(created_at updated_at).each do |column_name|
        attributes[column_name] = now.to_date if columns_hash[column_name] && !attributes.has_key?(column_name)
      end

      # convert any association names into the identity column equivalent
      nonexistant_columns = attributes.keys - columns_hash.keys
      nonexistant_columns.each do |column_name|
        if columns_hash["#{column_name}_id"]
          value = attributes.delete(column_name)

          if columns_hash["#{column_name}_type"] && value.is_a?(String) && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
            # support polymorphic belongs_to as "label (Type)"
            attributes["#{column_name}_type"] = $1
          end

          attributes["#{column_name}_id"] = value ? Fixation.identify(value) : value
        else
          raise ActiveRecord::Fixture::FormatError, "No column named #{column_name} found in table #{table_name}"
        end
      end
    end

    def fixture_ids
      embellished_rows.each_with_object({}) do |(name, attributes), ids|
        ids[name] = attributes['id'] || attributes['uuid']
      end
    end

    def statements
      statements = ["DELETE FROM #{connection.quote_table_name table_name}"]

      unless embellished_rows.empty?
        # first figure out what columns we have to insert into; we're going to need to use the same names for
        # all rows so we can use the multi-line INSERT syntax
        columns_to_include = Set.new
        embellished_rows.each do |name, attributes|
          attributes.each do |column_name, value|
            columns_to_include.add(columns_hash[column_name])
          end
        end

        # now build the INSERT statement
        quoted_column_names = columns_to_include.collect { |column| connection.quote_column_name(column.name) }.join(', ')
        statements <<
          "INSERT INTO #{connection.quote_table_name table_name} (#{quoted_column_names}) VALUES " +
          embellished_rows.collect do |name, attributes|
            '(' + columns_to_include.collect do |column|
              if attributes.has_key?(column.name)
                quote_value(column, attributes[column.name])
              else
                column.default_function || quote_value(column, column.default)
              end
            end.join(', ') + ')'
          end.join(', ')
      end

      statements
    end

    def quote_value(column, value)
      connection.quote(value)
    rescue TypeError
      connection.quote(YAML.dump(value))
    end
  end

  class Fixtures
    def initialize
      @class_names = {}
      @fixture_ids = {}
      @statements = {}

      compile_fixture_files
    end

    def compile_fixture_files(connection = ActiveRecord::Base.connection)
      puts "#{Time.now} building fixtures" if Fixation.trace

      now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
      Fixation.paths.each do |path|
        Dir["#{path}/{**,*}/*.yml"].each do |pathname|
          basename = pathname[path.size + 1..-5]
          compile_fixture_file(pathname, basename.gsub('/', '_'), basename.classify, connection, now) if ::File.file?(pathname)
        end
      end

      puts "#{Time.now} built fixtures for #{@fixture_ids.size} tables" if Fixation.trace
    end

    def compile_fixture_file(filename, table_name, class_name, connection, now)
      fixture_table = FixtureTable.new(filename, table_name, connection, now)
      @fixture_ids[table_name] = fixture_table.fixture_ids
      @statements[table_name] = fixture_table.statements
      @class_names[table_name] = class_name
    end

    def apply_fixtures(connection = ActiveRecord::Base.connection)
      @statements.each do |table_name, table_statements|
        table_statements.each do |statement|
          connection.execute(statement)
        end
      end
    end

    def fixture_methods
      fixture_ids = @fixture_ids
      class_names = @class_names

      methods = Module.new do
        def setup_fixtures(config = ActiveRecord::Base)
          if run_in_transaction?
            @@fixated_fixtures_applied ||= false<commit_msg>use a transaction across all tables while inserting fixtures, to reduce commits<commit_after>require 'erb'
require 'yaml'
require 'set'
require 'active_support/dependencies'
require 'active_record/errors'
require "fixation/version"

module Fixation
  class FixtureTable
    attr_reader :filename, :table_name, :connection, :now

    def initialize(filename, table_name, connection, now)
      @filename = filename
      @table_name = table_name
      @connection = connection
      @now = now
    end

    def columns_hash
      @columns_hash ||= connection.columns(table_name).index_by(&:name)
    end

    def content
      template = File.read(filename)
      render_context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding
      ERB.new(template).result(render_context)
    end

    def parsed_rows
      result = YAML.load(content)
      result ||= {} # for completely empty files

      unless (result.is_a?(Hash) || result.is_a?(YAML::Omap)) && result.all? { |name, attributes| name.is_a?(String) && attributes.is_a?(Hash) }
        raise ActiveRecord::Fixture::FormatError, "#{filename} needs to contain a hash of fixtures"
      end

      result.delete('DEFAULTS')
      result
    rescue ArgumentError, Psych::SyntaxError => error
      # we use exactly the same error class and message as ActiveRecord::FixtureSet in case anyone was depending on it
      raise ActiveRecord::Fixture::FormatError, "a YAML error occurred parsing #{filename}. 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}", error.backtrace
    end

    def embellished_rows
      @embellished_rows ||= parsed_rows.each do |name, attributes|
        embellish_fixture(name, attributes, columns_hash)
      end
    end

    def embellish_fixture(name, attributes, columns_hash)
      # populate the primary key column, if not already set
      if columns_hash['id'] && !attributes.has_key?('id')
        attributes['id'] = Fixation.identify(name, columns_hash['id'].type)
      end

      if columns_hash['uuid'] && !attributes.has_key?('uuid')
        attributes['uuid'] = Fixation.identify(name, columns_hash['uuid'].type)
      end

      # substitute $LABEL into all string values
      attributes.each do |column_name, value|
        attributes[column_name] = value.gsub("$LABEL", name) if value.is_a?(String)
      end

      # populate any timestamp columns, if not already set
      %w(created_at updated_at).each do |column_name|
        attributes[column_name] = now if columns_hash[column_name] && !attributes.has_key?(column_name)
      end
      %w(created_at updated_at).each do |column_name|
        attributes[column_name] = now.to_date if columns_hash[column_name] && !attributes.has_key?(column_name)
      end

      # convert any association names into the identity column equivalent
      nonexistant_columns = attributes.keys - columns_hash.keys
      nonexistant_columns.each do |column_name|
        if columns_hash["#{column_name}_id"]
          value = attributes.delete(column_name)

          if columns_hash["#{column_name}_type"] && value.is_a?(String) && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
            # support polymorphic belongs_to as "label (Type)"
            attributes["#{column_name}_type"] = $1
          end

          attributes["#{column_name}_id"] = value ? Fixation.identify(value) : value
        else
          raise ActiveRecord::Fixture::FormatError, "No column named #{column_name} found in table #{table_name}"
        end
      end
    end

    def fixture_ids
      embellished_rows.each_with_object({}) do |(name, attributes), ids|
        ids[name] = attributes['id'] || attributes['uuid']
      end
    end

    def statements
      statements = ["DELETE FROM #{connection.quote_table_name table_name}"]

      unless embellished_rows.empty?
        # first figure out what columns we have to insert into; we're going to need to use the same names for
        # all rows so we can use the multi-line INSERT syntax
        columns_to_include = Set.new
        embellished_rows.each do |name, attributes|
          attributes.each do |column_name, value|
            columns_to_include.add(columns_hash[column_name])
          end
        end

        # now build the INSERT statement
        quoted_column_names = columns_to_include.collect { |column| connection.quote_column_name(column.name) }.join(', ')
        statements <<
          "INSERT INTO #{connection.quote_table_name table_name} (#{quoted_column_names}) VALUES " +
          embellished_rows.collect do |name, attributes|
            '(' + columns_to_include.collect do |column|
              if attributes.has_key?(column.name)
                quote_value(column, attributes[column.name])
              else
                column.default_function || quote_value(column, column.default)
              end
            end.join(', ') + ')'
          end.join(', ')
      end

      statements
    end

    def quote_value(column, value)
      connection.quote(value)
    rescue TypeError
      connection.quote(YAML.dump(value))
    end
  end

  class Fixtures
    def initialize
      @class_names = {}
      @fixture_ids = {}
      @statements = {}

      compile_fixture_files
    end

    def compile_fixture_files(connection = ActiveRecord::Base.connection)
      puts "#{Time.now} building fixtures" if Fixation.trace

      now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
      Fixation.paths.each do |path|
        Dir["#{path}/{**,*}/*.yml"].each do |pathname|
          basename = pathname[path.size + 1..-5]
          compile_fixture_file(pathname, basename.gsub('/', '_'), basename.classify, connection, now) if ::File.file?(pathname)
        end
      end

      puts "#{Time.now} built fixtures for #{@fixture_ids.size} tables" if Fixation.trace
    end

    def compile_fixture_file(filename, table_name, class_name, connection, now)
      fixture_table = FixtureTable.new(filename, table_name, connection, now)
      @fixture_ids[table_name] = fixture_table.fixture_ids
      @statements[table_name] = fixture_table.statements
      @class_names[table_name] = class_name
    end

    def apply_fixtures(connection = ActiveRecord::Base.connection)
      connection.transaction do
        @statements.each do |table_name, table_statements|
          table_statements.each do |statement|
            connection.execute(statement)
          end
        end
      end
    end

    def fixture_methods
      fixture_ids = @fixture_ids
      class_names = @class_names

      methods = Module.new do
        def setup_fixtures(config = ActiveRecord::Base)
          if run_in_transaction?
            @@fixated_fixtures_applied ||= false
{'fraction_non_alphanumeric': 0.10412979351032449, 'fraction_numerical': 0.0004424778761061947, 'mean_word_length': 2.9838425381903644, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '456366', 'n_tokens_mistral': 3957, 'n_tokens_neox': 3879, 'n_words': 969}