Created
March 1, 2010 12:55
-
-
Save kristianmandrup/318350 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Proposal for a Simulated::Transactions API, "A Simulated Transaction Log and Rollback API" | |
# The example shows how this common API could be used for Mongo DB and Mongoid, both transactionless persistence stores? | |
# This API could be used in testing frameworks such as rspec, test-unit, cucumber and the like | |
# in order to ensure running a test doesn't have side effects, even in the context of a transactionless persistence store | |
class DocA | |
include MongoMapper::Document | |
# include specific Simulated::Transactions model | |
# note, the testing framework should be responsible for adding this for any model | |
include Simulated::Transactions::MongoMapper | |
key :title, String | |
end | |
module MongoMapper | |
class Document | |
def self.add_simulated_transactions | |
include Simulated::Transactions::MongoMapper | |
end | |
end | |
end | |
# testing framework on initialization (instances of ActiveModel ?) | |
all_model_classes.each do |model_class| | |
model_class.add_simulated_transactions | |
end | |
class DocB | |
include Mongoid::Document | |
include Simulated::Transactions::Mongoid | |
field :first_name | |
end | |
# each transactionless persistence store has its own module below Simulated::Transactions | |
module Simulated | |
module Transactions | |
module MongoMapper | |
include SimulatedTransactionModel | |
# this module must then ensure versioning capability is added to each model where it is included | |
include ::MongoMapper::Versioned | |
end | |
end | |
end | |
module Simulated | |
module Transactions | |
module Mongoid | |
include SimulatedTransactionModel | |
include ::Mongoid::Versioned | |
end | |
end | |
end | |
module Simulated | |
module TransactionModel | |
def self.simulated_transaction_model? | |
true | |
end | |
def self.included(module_name) | |
# take advantage of naming convention ! | |
define_method :transaction_index do | |
# module_name in these example is either Mongoid or MongoMapper :) | |
"Simulated::#{module_name}TransactionIndex".constantize.instance | |
end | |
end | |
# ensures this call_back handler is added to the method stack for after_save call_back | |
after_save :add_simulated_transaction_log | |
def add_simulated_transaction_log | |
index = transaction_index | |
index[self.object_id] ? index[self.object_id]++ : index[self.object_id] = 1 | |
end | |
end | |
end | |
# each transactionless persistence store has its TransactionIndex subclass inheriting from Simulated::TransactionIndex | |
module Simulated | |
class MongoMapperTransactionIndex < TransactionIndex | |
def initialize | |
super | |
end | |
def self.instance | |
@instance ||= new | |
end | |
# call specific "previous" method of versioning module | |
def self.previous(transaction_obj) | |
transaction_obj.previous | |
end | |
end | |
end | |
module Simulated | |
class MongoidTransactionIndex < TransactionIndex | |
def initialize | |
super | |
end | |
def self.instance | |
@instance ||= new | |
end | |
# specific "previous" logic for its own versioning module | |
def self.previous(transaction_obj) | |
# is there a built-in ? | |
last_version = self.class.first(:conditions => { :_id => id, :version => transaction_obj.version -1 }) | |
last_version ? last_version : nil | |
end | |
end | |
end | |
module Simulated | |
# The transaction log | |
class TransactionIndex | |
attr_writer :trans_index | |
def initialize | |
@index ||= {} | |
end | |
def after_save | |
index[self.object_id] ? index[self.object_id][:count]++ : index[self.object_id] = {:obj => self, :count => 1} | |
end | |
def self.rollback | |
index.each do |trans| | |
trans[:count].times do | |
# go to previous version, if no previous version it must be initial version, so then delete the record? | |
trans[:obj].delete if !previous(trans[:obj]) | |
end | |
end | |
end | |
end | |
end | |
class Tester | |
def my_test | |
doc = Doc.create(:title=>"v1") # # => after_save {27 => {:obj => <obj:27>, :count => 1 }} | |
doc.title = 'hello' | |
Doc.save # => after_save {27 => {:obj => <obj:27>, :count => 1 }} | |
doc.title = 'goodbye' | |
Doc.save # => after_save {27 => {:obj => <obj:27>, :count => 2 }} | |
end | |
end | |
# Perform tests in class using Mongo DB with 'Mongo Mapper' and 'versioned' gems, do rollback at end using Simulated Transactions! | |
perform(Tester, :my_test) | |
def perform(clazz, method) | |
begin | |
clazz.send method | |
ensure | |
if class.respond_to? simulated_transaction_model? | |
TransactionIndex.rollback | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment