Skip to content

Instantly share code, notes, and snippets.

@fcheung

fcheung/Usage Secret

Created December 5, 2013 10:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fcheung/a7db2482d89e7f35133e to your computer and use it in GitHub Desktop.
Save fcheung/a7db2482d89e7f35133e to your computer and use it in GitHub Desktop.
Nested transactions for your specs
class RSpec::Core::ExampleGroup
class Context
def initialize(target)
@_target = target
end
def method_missing name, *args, &block
@_target.send name, *args, &block
end
end
def self.saved_context= value
@saved_context = value
end
def self.saved_context
@saved_context
end
def self.restore_context(target)
if superclass.respond_to? :restore_context
superclass.restore_context(target)
end
if saved_context
saved_context.each do |name, value|
if value[0] < Numeric
target.send :instance_variable_set, '@' + name, value[1]
else
ordering = {}
if value[1].is_a?(Array)
value[1].each_with_index {|x, index| ordering[index]=x}
records = value[0].find(value[1]).index_by(&:id)
target.send :instance_variable_set, '@' + name, value[1].collect {|id| records[id]}
else
target.send :instance_variable_set, '@' + name, value[0].find(value[1])
end
end
end
end
end
def self.common_context(&block)
before(:all) do
context = Context.new(self)
start_transaction_or_savepoint
self.class.saved_context = {}
# a context might depend on an outer common context: restore it to the context object before evaluating the block
self.class.restore_context context
context.instance_eval(&block)
context.instance_values.each do |name, value|
unless name == '_target'
if value.is_a?(Array) && value.all? {|v| v.is_a?(ActiveRecord::Base)} && value.collect {|v| v.class}.uniq.length == 1
self.class.saved_context[name] = [value.first.class, value.collect {|v| v.id}]
elsif value.is_a?(Array) && value.all? {|v| v.is_a?(Numeric)}
self.class.saved_context[name] = [value.first.class, value]
elsif value.is_a?(Numeric)
self.class.saved_context[name] = [value.class, value]
elsif !value.is_a?(ActiveRecord::Base)
raise "Only active record subclasses can be used in common_context, found #{value}"
else
self.class.saved_context[name] = [value.class, value.id]
end
end
end
end
before(:each) do
self.class.restore_context(self)
end
after(:all) do
rollback_transaction_or_savepoint
self.class.saved_context = nil
end
end
end
RSpec.configure do |config|
config.use_transactional_fixtures = false
def start_transaction_or_savepoint
ActiveRecord::Base.connection.transaction_joinable = false
if ActiveRecord::Base.connection.open_transactions == 0
ActiveRecord::Base.connection.begin_db_transaction
else
ActiveRecord::Base.connection.create_savepoint
end
ActiveRecord::Base.connection.increment_open_transactions
end
def rollback_transaction_or_savepoint
ActiveRecord::Base.connection.decrement_open_transactions
if ActiveRecord::Base.connection.open_transactions == 0
ActiveRecord::Base.connection.rollback_db_transaction
else
ActiveRecord::Base.connection.rollback_to_savepoint
end
end
config.around(:each) do |example|
start_transaction_or_savepoint
begin
example.run
ensure
rollback_transaction_or_savepoint
end
end
end
Allows a set of activerecord objects to be re-used across specs
describe 'Foo' do
common_context do
Factory.create(:something)
end
it 'should bar' do
Something.count.should == 1
end
it 'should baz' do
end
end
things saved to a instance variable are preserved:
describe 'Foo' do
common_context do
@something = Factory.create(:something)
end
it 'should bar' do
@something.bar.should be_true
end
end
They are loaded from the database between specs so that unsaved changes are thrwon away
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment