Skip to content

Instantly share code, notes, and snippets.

@x9sim9
Forked from toby-1-kenobi/fixture_parser.rb
Last active November 14, 2019 11:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save x9sim9/78405f13b698b87ab7b234ea793399ca to your computer and use it in GitHub Desktop.
Save x9sim9/78405f13b698b87ab7b234ea793399ca to your computer and use it in GitHub Desktop.
Parse rails fixtures to seed the database without destroying or duplicating data

Seeds Yaml

Fixtures within Seeds, completely seperate from test

This simple class is designed to populate a database from yml files in the exact same way that fixtures work with additional support for namespacing

db/seeds.rb ++ require R.root.join("lib", "seeds_yaml").to_s ++ SeedsYaml.new

db/seeds/

  • all yaml files used in production, test, and development
  • mymodel.yml

db/seeds/development/

  • all yaml files used in development
  • mymodel.yml

db/seeds/test/

  • all yaml files used in test
  • mymodel.yml

db/seeds/production/

  • all yaml files used in production
  • mymodel.yml

Note: filename should match modelname exactly

Namespacing

For namespaced models Namespace::MyModel

namespace--model.yml

Future

I would like to put this into a ruby gem at some point, but no experience with gems, any volunteers?

class SeedsYaml
BASE = R.root.join("db").to_s
SEEDS = R.root.join("db", "seeds").to_s
DEVELOPMENT = R.root.join("db", "seeds", "development").to_s
TEST = R.root.join("db", "seeds", "test").to_s
PRODUCTION = R.root.join("db", "seeds", "production").to_s
require 'yaml'
def initialize
@options = {}
build_options(SEEDS)
case Rails.env
when "development"
build_options(DEVELOPMENT)
when "test"
build_options(TEST)
when "production"
build_options(PRODUCTION)
end
parse_fixtures @options
end
def build_options(directory)
Dir::foreach(directory) do |file|
file = directory + "/" + file
next if File.directory?(file) || File.extname(file) != ".yml"
model_name = File.basename(file, ".yml").camelcase
@options[file] = { model_name: model_name, key_field: 'id' }
end
end
protected
def parse_fixtures(files_options_hash)
# load the data from the files
fixtures = Hash.new
files_options_hash.each_key do |filename|
fixtures[filename] = YAML.load(ERB.new(File.read(filename)).result)
end
# before updating collect all the objects by names given in fixtures
all_objects = Hash.new
# and remember which need to be updated with their hash
to_update = Hash.new
# for each fixture file
fixtures.each do |filename, fixtures_hash|
model_classname = files_options_hash[filename][:model_name].classify.split("--").select { |w| w.capitalize! || w }.join("::")
model_class = model_classname.constantize
key_field = files_options_hash[filename][:key_field]
#for each fixture within the file
fixtures_hash.each do |fixture, values_hash|
if files_options_hash[filename][:update?]
# if we doing updates get or initialise it and apply the values then save
model_instance = model_class.find_or_initialize_by(key_field => values_hash[key_field])
all_objects[fixture] = model_instance
to_update[fixture] = values_hash
else
# if we're not updating check if it's there and add it if it's not
model_instance = model_class.find_by(key_field => values_hash[key_field])
if !model_instance
model_instance = model_class.new
to_update[fixture] = values_hash
end
all_objects[fixture] = model_instance
end
end
end
# now we've collected all the models we can update the ones that need updating
to_update.each do |fixture_name, values_hash|
update_model_instance(all_objects[fixture_name], values_hash, all_objects.except(fixture_name))
end
end
# Update a model instance with values from a fixture.
# Some values may refer to other fixtures
def update_model_instance(model_instance, values_hash, all_fixture_instances)
values_hash.each do |field, value|
# if the field is an array we need to assign with 'push' instead of '='
# if namespaced model, alter key with table prefix
namespace = model_instance.class.module_parent
if (namespace.respond_to?("table_name_prefix"))
table_prefix = namespace.table_name_prefix
if model_instance.attributes.key?(table_prefix + field + "_id")
field_target = table_prefix + field
else
field_target = field
end
else
field_target = field
end
if model_instance.send(field_target).respond_to?("push")
model_instance.send(field_target).clear
push = true
end
# it may be a comma seperated list of fixtures so process it
values_array = replace_strings_with_fixtures(value, all_fixture_instances)
values_array.each do |value|
if push
model_instance.send(field_target).push(value)
else
field_type = model_instance.type_for_attribute(field_target)
# Find Referenced Model Record by id, when integer passed as value
if field_type.type == nil and value.is_a?(Numeric) # is reference and using numeric reference
model_instance.class.reflect_on_all_associations(:belongs_to).each do |belongs_to|
if (belongs_to.name.to_s == field_target)
value = belongs_to.klass.find(value)
end
end
end
model_instance.send(field_target + '=', value)
end
end
end
model_instance.save!
end
def replace_strings_with_fixtures(value, all_fixture_instances)
if value.respond_to?("split")
# it might be a comma sperated list of fixtures
fixtures_array = value.split(',').map(&:strip)
fixtures_array.each_with_index do |fixture_name, index|
if obj = all_fixture_instances[fixture_name]
fixtures_array[index] = obj
else
# could not find one fixture
# it must just be a string after all
return [value]
end
end
return fixtures_array
end
# it's not a string, return it in it's own array
return [value]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment