module ApplicationSeeds

A library for managing a standardized set of seed data for applications in a non-production environment.

See README.md for API documentation.

Constants

MAX_ID
VERSION

Public Class Methods

config() click to toggle source

Fetch the configuration.

# File lib/application_seeds.rb, line 32
def config
  @_config ||= { :id_type => :integer }
end
config=(config) click to toggle source

Specify any configuration, such as the type of ids to generate (:integer or :uuid).

# File lib/application_seeds.rb, line 24
def config=(config)
  warn "WARNING!  Calling ApplicationSeeds.config= after dataset has been set (ApplicationSeeds.dataset=) may not produce expected results." unless @dataset.nil?
  @_config = config
end
config_value(key) click to toggle source

Fetch data from the _config.yml files.

# File lib/application_seeds.rb, line 39
def config_value(key)
  config_values[key.to_s]
end
create_object!(clazz, id, attributes, options={}) click to toggle source

This call will create a new instance of the specified class, with the specified id and attributes.

# File lib/application_seeds.rb, line 104
def create_object!(clazz, id, attributes, options={})
  validate = options[:validate].nil? ? true : options[:validate]

  x = clazz.new
  x.attributes = attributes.reject { |k,v| !x.respond_to?("#{k}=") }
  x.id = id
  x.save!(:validate => validate)
  x
end
data_directory() click to toggle source

Fetch the name of the directory where the application seed data is loaded from, if it was set using data_diretory=.

# File lib/application_seeds.rb, line 78
def data_directory
  @data_directory
end
data_directory=(directory) click to toggle source

Specify the name of the directory that contains the application seed data.

# File lib/application_seeds.rb, line 66
def data_directory=(directory)
  if Dir.exist?(directory)
    @data_directory = directory
  else
    raise "ERROR: The #{directory} directory does not appear to contain application seed data"
  end
end
data_gem_name() click to toggle source

Fetch the name of the directory where the application seed data is loaded from. Defaults to "applicadtion_seed_data" if it was not set using data_gem_name=.

# File lib/application_seeds.rb, line 59
def data_gem_name
  @data_gem_name || "application_seed_data"
end
data_gem_name=(gem_name) click to toggle source

Specify the name of the gem that contains the application seed data.

# File lib/application_seeds.rb, line 46
def data_gem_name=(gem_name)
  spec = Gem::Specification.find_by_name(gem_name)
  if Dir.exist?(File.join(spec.gem_dir, "lib", "seeds"))
    @data_gem_name = gem_name
  else
    raise "ERROR: The #{gem_name} gem does not appear to contain application seed data"
  end
end
dataset=(dataset) click to toggle source

Specify the name of the dataset to use. An exception will be raised if the dataset could not be found.

# File lib/application_seeds.rb, line 86
def dataset=(dataset)
  clear_cached_data
  @dataset = dataset

  if dataset.nil? || dataset.strip.empty? || dataset_path(dataset).nil?
    datasets = Dir[File.join(seed_data_path, "**", "*")].select { |x| File.directory?(x) }.map { |x| File.basename(x) }.join(', ')

    error_message =  "\nERROR: A valid dataset is required!\n"
    error_message << "Usage: bundle exec rake application_seeds:load[your_data_set]\n\n"
    error_message << "Available datasets: #{datasets}\n\n"
    raise error_message
  end
end
defer_referential_integrity_checks() { || ... } click to toggle source

Defer the enforcement of foreign key constraints while the block is being executed.

# File lib/application_seeds.rb, line 151
def defer_referential_integrity_checks
  Database.without_foreign_keys do
    yield
  end
end
label_for_id(seed_type, id) click to toggle source

Fetch the label for the associated seed type and ID.

# File lib/application_seeds.rb, line 160
def label_for_id(seed_type, id)
  x = seed_labels[seed_type.to_s].select { |label, ids| ids[:integer] == id || ids[:uuid] == id }
  x.keys.first.to_sym if x && x.keys.first
end
reset_sequence_numbers() click to toggle source

This method will reset the sequence numbers on id columns for all tables in the database with an id column. If you are having issues where you are unable to insert new data into the databse after your dataset has been imported, then this should correct them.

# File lib/application_seeds.rb, line 131
def reset_sequence_numbers
  result = Database.connection.exec("SELECT table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema')")
  table_names = result.map { |row| row.values_at('table_name')[0] }

  table_names_with_id_column = table_names.select do |table_name|
    result = Database.connection.exec("SELECT column_name FROM information_schema.columns WHERE table_name = '#{table_name}';")
    column_names = result.map { |row| row.values_at('column_name')[0] }
    column_names.include?('id')
  end

  table_names_with_id_column.each do |table_name|
    result = Database.connection.exec("SELECT pg_get_serial_sequence('#{table_name}', 'id');")
    sequence_name = result.getvalue(0, 0)
    Database.connection.exec("SELECT setval('#{sequence_name}', (select MAX(id) from #{table_name}));")
  end
end
seed_data_exists?(type) click to toggle source

Returns true if the specified data file exists in this dataset, false if it does not.

Examples:

ApplicationSeeds.seed_data_exists?(:campaigns)
# File lib/application_seeds.rb, line 121
def seed_data_exists?(type)
  !processed_seed_data[type.to_s].nil?
end

Private Class Methods

clear_cached_data() click to toggle source
# File lib/application_seeds.rb, line 383
def clear_cached_data
  @seed_labels = nil
  @processed_seed_data = nil
  @raw_seed_data = nil
  @seed_data_files = nil
  @config_values = nil
end
config_values() click to toggle source
# File lib/application_seeds.rb, line 209
def config_values
  return @config_values unless @config_values.nil?

  @config_values = {}
  path = dataset_path(@dataset)
  while (seed_data_path != path) do
    config_file = Dir[File.join(path, "_config.yml")].first
    values = config_file.nil? ? {} : YAML.load(ERB.new(File.read(config_file)).result)
    @config_values = values.merge(@config_values)
    path.sub!(/\/[^\/]+$/, "")
  end
  @config_values
end
dataset_path(dataset) click to toggle source
# File lib/application_seeds.rb, line 167
def dataset_path(dataset)
  Dir[File.join(seed_data_path, "**", "*")].select { |x| File.directory?(x) && File.basename(x) == dataset }.first
end
fetch(type) { |attributes| ... } click to toggle source
# File lib/application_seeds.rb, line 337
def fetch(type, &block)
  result = {}
  processed_seed_data[type].each do |label, attrs|
    attributes = attrs.clone
    id = seed_labels[type][label][id_type(type)]
    if !block_given? || (block_given? && yield(attributes) == true)
      result[id] = Attributes.new(attributes)
    end
  end
  result
end
fetch_with_id(type, id) click to toggle source
# File lib/application_seeds.rb, line 349
def fetch_with_id(type, id)
  data = nil
  seed_labels[type].each do |label, ids|
    if ids.values.map(&:to_s).include?(id.to_s)
      data = processed_seed_data[type][label]
      data['id'] = seed_labels[type][label][id_type(type)]
      break
    end
  end
  raise "No seed data could be found for '#{type}' with id #{id}" if data.nil?
  Attributes.new(data)
end
fetch_with_label(type, label) click to toggle source
# File lib/application_seeds.rb, line 362
def fetch_with_label(type, label)
  data = processed_seed_data[type][label]
  raise "No seed data could be found for '#{type}' with label #{label}" if data.nil?
  data['id'] = seed_labels[type][label][id_type(type)]
  Attributes.new(data)
end
generate_ids(id) click to toggle source
# File lib/application_seeds.rb, line 375
def generate_ids(id)
  { :integer => id, :uuid => "00000000-0000-0000-0000-%012d" % id }
end
generate_unique_ids(seed_type, label) click to toggle source
# File lib/application_seeds.rb, line 370
def generate_unique_ids(seed_type, label)
  checksum = Zlib.crc32(seed_type + label) % MAX_ID
  generate_ids(checksum)
end
id_type(type) click to toggle source
# File lib/application_seeds.rb, line 379
def id_type(type)
  self.config["#{type}_id_type".to_sym] || self.config[:id_type]
end
method_missing(method, *args) click to toggle source
# File lib/application_seeds.rb, line 314
def method_missing(method, *args)
  self.send(:seed_data, method, args.shift)
end
processed_seed_data() click to toggle source
# File lib/application_seeds.rb, line 243
def processed_seed_data
  return @processed_seed_data unless @processed_seed_data.nil?

  @processed_seed_data = {}
  seed_data_files.each do |seed_file|
    basename = File.basename(seed_file, ".yml")
    data = raw_seed_data[seed_file]
    if data
      data.each do |label, attributes|
        data[label] = replace_labels_with_ids(attributes)
      end

      if processed_seed_data[basename].nil?
        processed_seed_data[basename] = data
      else
        processed_seed_data[basename] = data.merge(processed_seed_data[basename])
      end
    end
  end
  @processed_seed_data
end
raw_seed_data() click to toggle source
# File lib/application_seeds.rb, line 196
def raw_seed_data
  return @raw_seed_data unless @raw_seed_data.nil?

  @raw_seed_data = {}
  seed_data_files.each do |seed_file|
    data = YAML.load(ERB.new(File.read(seed_file)).result)
    if data
      @raw_seed_data[seed_file] = data
    end
  end
  @raw_seed_data
end
replace_array_of_labels(type, value) click to toggle source
# File lib/application_seeds.rb, line 295
def replace_array_of_labels(type, value)
  # Handle the case where seed data type cannot be determined by the
  # name of the attribute -- employer_ids: [ma_and_pa, super_corp] (companies)
  if value =~ /\((.*)\)/
    type = $1
    value = value.sub(/\((.*)\)/, "").strip
    value =~ /^\[(.*)\]$/
    value = $1.split(',').map(&:strip)
  end

  if seed_labels[type]
    value = value.map do |v|
      label_ids = seed_labels[type][v.to_s]
      (label_ids && label_ids[id_type(type)]) || v
    end
  end
  value
end
replace_labels_with_ids(attributes) click to toggle source
# File lib/application_seeds.rb, line 265
def replace_labels_with_ids(attributes)
  new_attributes = {}
  attributes.each do |key, value|
    new_attributes[key] = value
    if key =~ /^(.*)_id$/ || key =~ /^(.*)_uuid$/
      new_attributes[key] = replace_single_label($1.pluralize, value)
    end

    if key =~ /^(.*)_ids$/ || key =~ /^(.*)_uuids$/
      new_attributes[key] = replace_array_of_labels($1.pluralize, value)
    end
  end
  new_attributes
end
replace_single_label(type, value) click to toggle source
# File lib/application_seeds.rb, line 280
def replace_single_label(type, value)
  # Handle the case where seed data type cannot be determined by the
  # name of the attribute -- employer_id: ma_and_pa (companies)
  if value =~ /\((.*)\)/
    type = $1
    value = value.sub(/\((.*)\)/, "").strip
  end

  if seed_labels[type]
    label_ids = seed_labels[type][value.to_s]
    value = label_ids[id_type(type)] if label_ids
  end
  value
end
seed_data(type, options) click to toggle source
# File lib/application_seeds.rb, line 318
def seed_data(type, options)
  type = type.to_s
  raise "No seed data file could be found for '#{type}'" if processed_seed_data[type].nil?

  if options.nil?
    fetch(type)
  elsif options.is_a?(Fixnum) || options.is_a?(String)
    fetch_with_id(type, options)
  elsif options.is_a?(Symbol)
    fetch_with_label(type, options.to_s)
  elsif options.is_a? Hash
    fetch(type) do |attributes|
      options.stringify_keys!
      options = replace_labels_with_ids(options)
      (options.to_a - attributes.to_a).empty?
    end
  end
end
seed_data_files() click to toggle source
# File lib/application_seeds.rb, line 183
def seed_data_files
  return @seed_data_files unless @seed_data_files.nil?

  @seed_data_files = []
  path = dataset_path(@dataset)
  while (seed_data_path != path) do
    files = Dir[File.join(path, "*.yml")].reject { |file| file =~ /\/_config.yml$/ }
    @seed_data_files.concat(files)
    path.sub!(/\/[^\/]+$/, "")
  end
  @seed_data_files
end
seed_data_path() click to toggle source
# File lib/application_seeds.rb, line 171
def seed_data_path
  return @seed_data_path unless @seed_data_path.nil?

  if data_directory
    @seed_data_path = data_directory
  else
    spec = Gem::Specification.find_by_name(data_gem_name)
    @seed_data_path = File.join(spec.gem_dir, "lib", "seeds")
  end
  @seed_data_path
end
seed_labels() click to toggle source
# File lib/application_seeds.rb, line 223
def seed_labels
  return @seed_labels unless @seed_labels.nil?

  @seed_labels = {}
  seed_data_files.each do |seed_file|
    seed_type = File.basename(seed_file, ".yml")
    @seed_labels[seed_type] ||= {}

    data = raw_seed_data[seed_file]
    if data
      data.each do |label, attributes|
        specified_id = attributes['id']
        ids = specified_id.nil? ? generate_unique_ids(seed_type, label) : generate_ids(specified_id)
        @seed_labels[seed_type][label] = ids
      end
    end
  end
  @seed_labels
end