Skip to content

Instantly share code, notes, and snippets.

@stellard
Created January 3, 2015 20:24
Show Gist options
  • Save stellard/c87d1007590b13256f38 to your computer and use it in GitHub Desktop.
Save stellard/c87d1007590b13256f38 to your computer and use it in GitHub Desktop.
KujiFixtureLoader "name to be changed"
#The locations and naming of these files is by convention spec/fixtures/fixture_loader/*_fixture.rb
let(:bin_group) { Fabricate(:bin_group) }
let(:external_bin) { Fabricate(:bin_type, bin_group: bin_group, :is_external => true) }
let(:internal_bin1) { Fabricate(:bin_type, bin_group: bin_group, :is_external => false) }
let(:internal_bin2) { Fabricate(:bin_type, bin_group: bin_group, :is_external => false) }
let(:postpaid_bag) { Fabricate(:bin_type, bin_group: bin_group, :is_bag => true, :charged_on_delivery => false) }
let(:prepaid_bag) { Fabricate(:bin_type, bin_group: bin_group, :is_bag => true, :charged_on_delivery => true) }
let(:service_fee1) { Fabricate(:service_fee) }
let(:service_fee2) { Fabricate(:service_fee) }
describe 'example' do
#-Loads the example_fixture.rb into the database in a before all
#-Adds let blocks for everything in the fixture
#-Users database cleaner to set transactions as the cleaning strategy
#-Resets back to the loaded fixtures on each run
load_fixtures :example
it "should have access to the let blocks" do
bin_group.should_not be_nil
postpaid_bag.should_not be_nil
end
end
module KujiRspecFixtureLoader
module ExampleMethods
def dump_all_fixtures
KujiFixture.all.each do |fixture|
fixture.refresh_fixtures if fixture.expired?
end
end
end
module ExampleGroupMethods
def load_fixtures fixture
unless respond_to?(:metadata) && metadata[:broken]
before(:all) do
KujiFixture[fixture].refresh_fixtures if KujiFixture[fixture].expired?
KujiFixture[fixture].load_fixtures
end
after(:all) do
DatabaseCleaner[:active_record].clean_with(:truncation)
end
KujiFixture[fixture].object_store.methods.each do |name|
let(name) { KujiFixture[fixture].object_store[name] }
end
end
end
end
def self.included(mod)
mod.extend ExampleGroupMethods
mod.__send__ :include, ExampleMethods
end
end
class KujiFixture
attr_accessor :name
def initialize name
`mkdir -p #{input_dir}`
`mkdir -p #{output_dir}`
@name = name
end
class << self
def directory
@directory ||= Hash[*Dir['spec/fixtures/fixture_loader/input/**/*.rb'].map { |d| File.basename(d)[/(.+)_fixture\.rb\z/, 1].to_sym }.map { |name| [name, KujiFixture.new(name)] }.flatten]
end
def all
directory.values
end
def [] name
directory[name]
end
end
def refresh_fixtures
puts "Dumping Fixture #{name}"
run_and_dump_fixtures
end
def load_fixtures
port = db_config[:port]
`psql -d #{db_config[:database]} -f #{fixture_output_file_path} -U #{db_config[:username]}#{" -p #{port}" unless port.blank?} -h #{db_config[:host]} > /dev/null`
end
def expired?
#return true
#debugger
last_schema_digest != RuntimeStats.latest_schema_digest ||
last_run.nil? || last_modified > last_run ||
!(File.exists?(fixture_output_file_path) && File.exists?(object_map_file_path))
end
def object_map
begin
@object_map ||= YAML.load_file(object_map_file_path)
rescue Errno::ENOENT
run_and_dump_fixtures
retry
end
end
class StoreProxy
attr_reader :methods
def initialize object_map
object_map.each_pair do |name, hash|
if hash[:type] == "db"
instance_eval "def #{name}; #{hash[:class]}.find('#{hash[:id]}'); end"
elsif hash[:type] == "serialized"
instance_eval "def #{name}; YAML::load('#{hash[:data]}'); end"
elsif hash[:type] == "money"
instance_eval "def #{name}; Money.new(#{hash[:fractional]}, '#{hash[:currency]}'); end"
else
raise "Unknow object type: #{hash[:type]} in Fixture #{fixture}"
end
end
@methods = object_map.keys
end
def [] name
send(name) if respond_to?(name)
rescue => e
#debugger
raise e
end
end
def object_store
@object_store ||= StoreProxy.new(object_map)
end
private
def dump_fixtures
port = db_config[:port]
`pg_dump --disable-triggers -T "schema_migrations" -T "audit.logged_actions" -U #{db_config[:username]}#{" -p #{port}" unless port.blank?} -h #{db_config[:host]} -a #{db_config[:database]} > #{fixture_output_file_path}`
end
def run_and_dump_fixtures
clean_db
#debugger
puts "Running fixture file: #{fixture_input_file_path}"
#debugger
eval_proxy.run
#debugger
dump_fixtures
#debugger
dump_object_map
store_last_run
#debugger
clean_db
end
def clean_db
DatabaseCleaner[:active_record].clean_with(:truncation)
#PgAudit::LoggedAction.delete_all
end
def last_modified
File.mtime(fixture_input_file_path)
end
def db_config
User.connection_config
end
def eval_proxy
@eval_proxy ||= EvalProxy.new fixture_input_file_path
end
def dump_object_map
puts "Warning: Cannot dump object hash since it is empty" if eval_proxy.object_hash.blank?
new_object_map = {}
eval_proxy.object_hash.each_pair do |key, value|
if value.class.respond_to?(:find) && value.respond_to?(:id)
new_object_map[key] = { :id => value.id, :class => value.class.to_s, :type => "db" }
elsif value.is_a?(Money)
new_object_map[key] = { :fractional => value.fractional, :currency => value.currency.to_s, :type => "money" }
else
new_object_map[key] = { :data => YAML::dump(value), :type => "serialized" }
end
end
File.open(object_map_file_path, 'w+') {|f| f.write(new_object_map.to_yaml) }
@object_map = new_object_map
end
def fixture_output_file_path
output_dir("#{name}_fixture.sql").to_s
end
def object_map_file_path
output_dir("#{name}_object_map.yml").to_s
end
def fixture_input_file_path
input_dir("#{name}_fixture.rb").to_s
end
def store_last_run
RuntimeStats.store(name, Time.now)
end
def last_run
RuntimeStats.last_modified name
end
def last_schema_digest
RuntimeStats.schema_digest name
end
def output_dir name = ''
Rails.root.join('spec/fixtures/fixture_loader/output', name)
end
def input_dir name = ''
Rails.root.join('spec/fixtures/fixture_loader/input', name)
end
class EvalProxy
attr_reader :file_path
def initialize file_path
@file_path = file_path
end
def let name, &block
raise "Already defined let(:#{name}) for fixture '#{file_path}'" unless proc_hash[name].nil?
(proc_hash[name] = block).tap { define_method_for(name) }
end
alias :let! :let
def run
eval(::File.open(file_path).read, binding, file_path)
eval_procs
end
def object_hash
@object_hash ||= {}
end
private
def define_method_for name
instance_eval %|
def #{name}
get(:#{name})
end
|
end
def get name
return object_hash[name] if object_hash[name]
if proc_hash[name]
object_hash[name] = proc_hash[name].call
else
raise "fixture not found with name #{name}"
end
end
def eval_procs
proc_hash.keys.each { |name| get name }
end
def proc_hash
@proc_hash ||= {}
end
end
class RuntimeStats
class << self
def store name, time
(data(name)[:last_modified] = time).tap do
data(name)[:schema_digest] = latest_schema_digest
persist
end
end
def schema_digest name
data(name)[:schema_digest]
end
def last_modified name
data(name)[:last_modified]
end
def file_path
Rails.root.join('spec/fixtures/fixture_loader/', "runtime_stats.yml").to_s
end
def latest_schema_digest
Digest::MD5.hexdigest(ActiveRecord::SchemaMigration.pluck(:version).sort.join)
end
def persist
File.open(file_path, 'w+') {|f| f.write(@data.to_yaml) }
end
def data name
begin
@data ||= YAML.load_file(file_path)
raise("#{file_path} is not a valid yaml file, you can delete it and it will be regenerated") unless @data
name.blank? ? @data : @data[name] ||= {}
rescue Errno::ENOENT
write_blank_file
retry
end
end
private
def write_blank_file
File.open(file_path, 'w+') {|f| f.write({}.to_yaml) }
end
end
end
end
RSpec.configure do |config|
config.include KujiRspecFixtureLoader
end
module RSpec
module Core
class ExampleGroup
include KujiRspecFixtureLoader
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment