history_book 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # HistoryBook [![Build Status](https://secure.travis-ci.org/jtdowney/history_book.png?branch=master)](http://travis-ci.org/jtdowney/history_book)
2
+
3
+ HistoryBook is a ruby implementation of the [event sourcing](http://martinfowler.com/eaaDev/EventSourcing.html) pattern.
4
+
5
+ This library provides an interface for event sourcing over a pluggable back end data store. Currently it supports the [Sequel gem](http://sequel.rubyforge.org/) and its data stores as well as an in-memory data store for testing.
6
+
7
+ ## Usage
8
+
9
+ To run the example below you will need to install (or place in your Gemfile) both the sequel gem and the pg gem.
10
+
11
+ ```ruby
12
+ HistoryBook.configure(:sequel) do |config|
13
+ config.connection_string = 'postgres://localhost/history_book'
14
+ end
15
+
16
+ HistoryBook.open('test_id') do |stream|
17
+ stream << HistoryBook::Event.new(:customer_created, :name => 'Joe Tester', :address => '100 West Washington')
18
+ stream.commit
19
+ end
20
+
21
+ HistoryBook.open('test_id') do |stream|
22
+ stream.events.each do |event|
23
+ puts "Playback event #{event.type} with #{event.data.inspect}"
24
+ end
25
+ end
26
+ ```
27
+
28
+ ## License
29
+
30
+ HistoryBook is released under the [MIT license](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,39 @@
1
+ require 'active_support/core_ext/hash'
2
+ require 'active_support/inflector'
3
+ require 'multi_json'
4
+ require 'thread'
5
+
6
+ require 'history_book/event'
7
+ require 'history_book/stream'
8
+ require 'history_book/version'
9
+
10
+ module HistoryBook
11
+ class << self
12
+ attr_reader :config
13
+ end
14
+
15
+ def self.configure(driver)
16
+ driver_klass = _load_driver(driver)
17
+
18
+ unless @config.instance_of?(driver_klass)
19
+ @config = driver_klass.new
20
+ end
21
+
22
+ if block_given?
23
+ yield @config
24
+ end
25
+ end
26
+
27
+ def self.open(id)
28
+ store = config.create_store
29
+ yield HistoryBook::Stream.new(id, store)
30
+ end
31
+
32
+ def self._load_driver(driver)
33
+ path = "history_book/#{driver}/configuration"
34
+ require path
35
+ path.classify.constantize
36
+ rescue LoadError
37
+ raise "Unable to load #{driver} driver for history_book"
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ module HistoryBook
2
+ class Event
3
+ attr_reader :type, :data
4
+
5
+ def initialize(type, data = {})
6
+ @type = type
7
+ @data = data.with_indifferent_access
8
+ end
9
+
10
+ def ==(other)
11
+ self.type == other.type && self.data == other.data
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'history_book/memory/store'
2
+
3
+ module HistoryBook
4
+ module Memory
5
+ class Configuration
6
+ def create_store
7
+ HistoryBook::Memory::Store.new
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ module HistoryBook
2
+ module Memory
3
+ class Store
4
+ @@events = {}
5
+ @@events_lock = Mutex.new
6
+
7
+ def load_events(id, options = {})
8
+ options = {:from => 1, :to => nil}.merge(options)
9
+ from, to = options.values_at(:from, :to)
10
+
11
+ events = @@events.fetch(id, [])
12
+ _filter_events(events, from, to)
13
+ end
14
+
15
+ def store_events(id, new_events)
16
+ new_events = Array(new_events)
17
+ @@events_lock.synchronize do
18
+ @@events[id] ||= []
19
+ @@events[id].concat(new_events)
20
+ end
21
+ end
22
+
23
+ def _filter_events(events, from, to)
24
+ if to
25
+ events = events.reject.with_index { |e, i| i > to - 1 }
26
+ end
27
+
28
+ events.select.with_index { |e, i| i >= from - 1 }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ require 'sequel'
2
+ require 'history_book/sequel/store'
3
+
4
+ module HistoryBook
5
+ module Sequel
6
+ class Configuration
7
+ attr_accessor :connection_string
8
+
9
+ def create_store
10
+ db = ::Sequel.connect(connection_string)
11
+ HistoryBook::Sequel::Store.new(db)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,82 @@
1
+ module HistoryBook
2
+ module Sequel
3
+ class Store
4
+ def initialize(db)
5
+ @db = db
6
+
7
+ _create_events_table
8
+ end
9
+
10
+ def load_events(id, options = {})
11
+ options = {:from => 1, :to => nil}.merge(options)
12
+ from, to = options.values_at(:from, :to)
13
+
14
+ rows = _load_rows(id, from, to)
15
+ rows.map do |row|
16
+ _transform_row(row)
17
+ end
18
+ end
19
+
20
+ def store_events(id, new_events)
21
+ @db.transaction do
22
+ max_revision = _max_revision(id)
23
+
24
+ new_events = Array(new_events)
25
+ new_events.each do |new_event|
26
+ max_revision += 1
27
+ @db[:events].insert(
28
+ :stream_id => id,
29
+ :revision => max_revision,
30
+ :timestamp => DateTime.now.utc,
31
+ :type => new_event.type.to_s,
32
+ :data => _encode_data(new_event.data)
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ def _create_events_table
39
+ @db.create_table?(:events) do
40
+ primary_key :id
41
+ String :stream_id
42
+ Integer :revision
43
+ DateTime :timestamp
44
+ String :type
45
+ File :data
46
+ index [:stream_id, :revision], :unique => true
47
+ end
48
+ end
49
+
50
+ def _load_rows(id, from, to)
51
+ rows = @db.
52
+ from(:events).
53
+ where(:stream_id => id).
54
+ where('revision >= ?', from)
55
+
56
+ if to
57
+ rows = rows.where('revision <= ?', to)
58
+ end
59
+
60
+ rows
61
+ end
62
+
63
+ def _max_revision(id)
64
+ @db.from(:events).where(:stream_id => id).max(:revision) || 0
65
+ end
66
+
67
+ def _encode_data(data)
68
+ MultiJson.encode(data.to_hash).to_sequel_blob
69
+ end
70
+
71
+ def _decode_data(data)
72
+ MultiJson.decode(data).with_indifferent_access
73
+ end
74
+
75
+ def _transform_row(row)
76
+ type = row[:type].to_sym
77
+ data = _decode_data(row[:data])
78
+ HistoryBook::Event.new(type, data)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ module HistoryBook
2
+ class Stream
3
+ attr_reader :events, :uncommitted_events
4
+
5
+ def initialize(id, store)
6
+ @id = id
7
+ @store = store
8
+ @events = store.load_events(id)
9
+ @uncommitted_events = []
10
+ end
11
+
12
+ def <<(event)
13
+ @uncommitted_events << event
14
+ end
15
+
16
+ def commit
17
+ @store.store_events(@id, @uncommitted_events)
18
+ @events.concat(@uncommitted_events)
19
+ @uncommitted_events = []
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module HistoryBook
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Event do
4
+ describe '==' do
5
+ it 'should be false if nothing is equal' do
6
+ event1 = HistoryBook::Event.new(:foo, :bar => 1)
7
+ event2 = HistoryBook::Event.new(:bar, :foo => 2)
8
+ event1.should_not == event2
9
+ end
10
+
11
+ it 'should be false if only type is the same' do
12
+ event1 = HistoryBook::Event.new(:foo, :bar => 1)
13
+ event2 = HistoryBook::Event.new(:foo, :foo => 2)
14
+ event1.should_not == event2
15
+ end
16
+
17
+ it 'should be false if only data is the same' do
18
+ event1 = HistoryBook::Event.new(:foo, :bar => 1)
19
+ event2 = HistoryBook::Event.new(:bar, :bar => 1)
20
+ event1.should_not == event2
21
+ end
22
+
23
+ it 'should be true if everything is the same' do
24
+ event1 = HistoryBook::Event.new(:foo, :bar => 1)
25
+ event2 = HistoryBook::Event.new(:foo, :bar => 1)
26
+ event1.should == event2
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Memory::Configuration do
4
+ describe 'create_store' do
5
+ it 'should return a memory store' do
6
+ config = HistoryBook::Memory::Configuration.new
7
+ config.create_store.should be_instance_of(HistoryBook::Memory::Store)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Memory::Store do
4
+ before(:each) do
5
+ HistoryBook::Memory::Store.reset!
6
+ @store = HistoryBook::Memory::Store.new
7
+ end
8
+
9
+ it_behaves_like 'a storage driver'
10
+
11
+ describe 'load_events' do
12
+ it 'should return events saved across instances' do
13
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
14
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
15
+
16
+ store1 = HistoryBook::Memory::Store.new
17
+ store1.store_events('test_id', event1)
18
+
19
+ store2 = HistoryBook::Memory::Store.new
20
+ store2.store_events('test_id', event2)
21
+ store2.load_events('test_id').should == [event1, event2]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Sequel::Configuration do
4
+ describe 'create_store' do
5
+ it 'should return a memory store' do
6
+ config = HistoryBook::Sequel::Configuration.new
7
+ config.connection_string = 'sqlite:/'
8
+ config.create_store.should be_instance_of(HistoryBook::Sequel::Store)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Sequel::Store do
4
+ around(:each) do |example|
5
+ @db = Sequel.sqlite(':memory:')
6
+ @store = HistoryBook::Sequel::Store.new(@db)
7
+ @db.transaction do
8
+ example.run
9
+ end
10
+ end
11
+
12
+ it_behaves_like 'a storage driver'
13
+
14
+ describe 'initialize' do
15
+ it 'should create an events table' do
16
+ @db.drop_table?(:events)
17
+ HistoryBook::Sequel::Store.new(@db)
18
+ @db.table_exists?(:events).should be_true
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook::Stream do
4
+ before(:each) do
5
+ HistoryBook::Memory::Store.reset!
6
+ @store = HistoryBook::Memory::Store.new
7
+
8
+ @stream = HistoryBook::Stream.new('test_id', @store)
9
+ end
10
+
11
+ describe '<<' do
12
+ it 'should append uncommitted events' do
13
+ event = HistoryBook::Event.new(:foobar, :foo => 1)
14
+ @stream << event
15
+ @stream.uncommitted_events.should == [event]
16
+ end
17
+ end
18
+
19
+ describe 'commit' do
20
+ it 'should commit all uncommitted events' do
21
+ event = HistoryBook::Event.new(:foobar, :foo => 1)
22
+ @stream << event
23
+ @stream.commit
24
+ @stream.events.should == [event]
25
+ @stream.uncommitted_events.should be_empty
26
+ end
27
+ end
28
+
29
+ describe 'events' do
30
+ it 'should be empty if there are no prior events' do
31
+ @stream.events.should be_empty
32
+ end
33
+
34
+ it 'should be populated with existing events' do
35
+ event = HistoryBook::Event.new(:foobar, :foo => 1)
36
+ @stream << event
37
+ @stream.commit
38
+ @stream = HistoryBook::Stream.new('test_id', @store)
39
+ @stream.events.should == [event]
40
+ end
41
+ end
42
+
43
+ describe 'uncommitted_events' do
44
+ it 'should be empty to start' do
45
+ @stream.uncommitted_events.should be_empty
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe HistoryBook do
4
+ before(:each) do
5
+ HistoryBook.configure(:memory)
6
+ HistoryBook::Memory::Store.reset!
7
+ end
8
+
9
+ describe 'self.configure' do
10
+ it 'should create a driver specific configuration' do
11
+ HistoryBook.config.should be_instance_of(HistoryBook::Memory::Configuration)
12
+ end
13
+
14
+ it 'should yield a configuration object' do
15
+ yielded = false
16
+ HistoryBook.configure(:memory) do |config|
17
+ yielded = true
18
+ config.should == HistoryBook.config
19
+ end
20
+ yielded.should be_true
21
+ end
22
+
23
+ it 'should not overwrite an existing configuration object of the same type' do
24
+ old_config = HistoryBook.config
25
+ HistoryBook.configure(:memory)
26
+ HistoryBook.config.object_id.should == old_config.object_id
27
+ end
28
+
29
+ it 'should overwrite an existing configuration object not of the same type' do
30
+ old_config = HistoryBook.config
31
+ HistoryBook.configure(:sequel)
32
+ HistoryBook.config.object_id.should_not == old_config.object_id
33
+ end
34
+
35
+ it 'should raise an error if driver does not exist' do
36
+ expect do
37
+ HistoryBook.configure(:asdf)
38
+ end.to raise_error("Unable to load asdf driver for history_book")
39
+ end
40
+ end
41
+
42
+ describe 'self.open' do
43
+ it 'should open an event stream for reading and writting' do
44
+ event = HistoryBook::Event.new(:something_happened, :foo => 'bar')
45
+ HistoryBook.open('test_id') do |stream|
46
+ stream << event
47
+ stream.commit
48
+ end
49
+
50
+ HistoryBook.open('test_id') do |stream|
51
+ stream.events.should == [event]
52
+ end
53
+ end
54
+
55
+ it 'should yield an event stream' do
56
+ yielded = false
57
+ HistoryBook.open('test_id') do |stream|
58
+ yielded = true
59
+ stream.should_not be_nil
60
+ end
61
+ yielded.should be_true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,13 @@
1
+ require 'history_book'
2
+ require 'history_book/memory/configuration'
3
+ require 'history_book/sequel/configuration'
4
+
5
+ support_dir = File.expand_path('../support', __FILE__)
6
+ Dir.glob("#{support_dir}/**/*.rb") do |file|
7
+ require file
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.order = 'random'
13
+ end
@@ -0,0 +1,11 @@
1
+ module HistoryBook
2
+ module Memory
3
+ class Store
4
+ def self.reset!
5
+ @@events_lock.synchronize do
6
+ @@events = {}
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ shared_examples 'a storage driver' do
2
+ describe 'load_events' do
3
+ it 'should be empty when no events are stored' do
4
+ @store.load_events('empty_id').should be_empty
5
+ end
6
+
7
+ it 'should return previously stored events' do
8
+ event = HistoryBook::Event.new(:foobar, :foo => 1)
9
+ @store.store_events('test_id', event)
10
+ @store.load_events('test_id').should == [event]
11
+ end
12
+
13
+ it 'should return all events up to a specified sequence' do
14
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
15
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
16
+ @store.store_events('test_id', event1)
17
+ @store.store_events('test_id', event2)
18
+ @store.load_events('test_id', :to => 1).should == [event1]
19
+ end
20
+
21
+ it 'should return all events since a specified sequence' do
22
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
23
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
24
+ @store.store_events('test_id', event1)
25
+ @store.store_events('test_id', event2)
26
+ @store.load_events('test_id', :from => 2).should == [event2]
27
+ end
28
+
29
+ it 'should return all events in a specified range' do
30
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
31
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
32
+ event3 = HistoryBook::Event.new(:foobar, :foo => 3)
33
+ event4 = HistoryBook::Event.new(:foobar, :foo => 4)
34
+ @store.store_events('test_id', [event1, event2, event3, event4])
35
+ @store.load_events('test_id', :from => 2, :to => 3).should == [event2, event3]
36
+ end
37
+ end
38
+
39
+ describe 'store_events' do
40
+ it 'should store events in order' do
41
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
42
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
43
+ @store.store_events('test_id', event1)
44
+ @store.store_events('test_id', event2)
45
+ @store.load_events('test_id').should == [event1, event2]
46
+ end
47
+
48
+ it 'should store an array of events' do
49
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
50
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
51
+ @store.store_events('test_id', [event1, event2])
52
+ @store.load_events('test_id').should == [event1, event2]
53
+ end
54
+
55
+ it 'should store events by id' do
56
+ event1 = HistoryBook::Event.new(:foobar, :foo => 1)
57
+ event2 = HistoryBook::Event.new(:foobar, :foo => 2)
58
+ @store.store_events('test_id1', event1)
59
+ @store.store_events('test_id2', event2)
60
+ @store.load_events('test_id1').should == [event1]
61
+ end
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: history_book
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Downey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '1.0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '1.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.11'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.11'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: sequel
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '3.40'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '3.40'
94
+ - !ruby/object:Gem::Dependency
95
+ name: sqlite3-ruby
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '1.3'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ - !ruby/object:Gem::Dependency
111
+ name: yajl-ruby
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.1'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '1.1'
126
+ description: This library provides an interface for event sourcing over a pluggable
127
+ back end data store. Currently it supports the Sequel gem and its data stores as
128
+ well as an in-memory data store for testing.
129
+ email:
130
+ - jdowney@gmail.com
131
+ executables: []
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - lib/history_book/event.rb
136
+ - lib/history_book/memory/configuration.rb
137
+ - lib/history_book/memory/store.rb
138
+ - lib/history_book/sequel/configuration.rb
139
+ - lib/history_book/sequel/store.rb
140
+ - lib/history_book/stream.rb
141
+ - lib/history_book/version.rb
142
+ - lib/history_book.rb
143
+ - spec/history_book/event_spec.rb
144
+ - spec/history_book/memory/configuration_spec.rb
145
+ - spec/history_book/memory/store_spec.rb
146
+ - spec/history_book/sequel/configuration_spec.rb
147
+ - spec/history_book/sequel/store_spec.rb
148
+ - spec/history_book/stream_spec.rb
149
+ - spec/history_book_spec.rb
150
+ - spec/spec_helper.rb
151
+ - spec/support/resettable_memory_store.rb
152
+ - spec/support/shared_examples_for_stores.rb
153
+ - README.md
154
+ homepage: http://github.com/jtdowney/history_book
155
+ licenses: []
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ none: false
168
+ requirements:
169
+ - - ! '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 1.8.23
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: HistoryBook is a ruby implementation of the event sourcing pattern.
178
+ test_files:
179
+ - spec/history_book/event_spec.rb
180
+ - spec/history_book/memory/configuration_spec.rb
181
+ - spec/history_book/memory/store_spec.rb
182
+ - spec/history_book/sequel/configuration_spec.rb
183
+ - spec/history_book/sequel/store_spec.rb
184
+ - spec/history_book/stream_spec.rb
185
+ - spec/history_book_spec.rb
186
+ - spec/spec_helper.rb
187
+ - spec/support/resettable_memory_store.rb
188
+ - spec/support/shared_examples_for_stores.rb