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 +30 -0
- data/lib/history_book.rb +39 -0
- data/lib/history_book/event.rb +14 -0
- data/lib/history_book/memory/configuration.rb +11 -0
- data/lib/history_book/memory/store.rb +32 -0
- data/lib/history_book/sequel/configuration.rb +15 -0
- data/lib/history_book/sequel/store.rb +82 -0
- data/lib/history_book/stream.rb +22 -0
- data/lib/history_book/version.rb +3 -0
- data/spec/history_book/event_spec.rb +29 -0
- data/spec/history_book/memory/configuration_spec.rb +10 -0
- data/spec/history_book/memory/store_spec.rb +24 -0
- data/spec/history_book/sequel/configuration_spec.rb +11 -0
- data/spec/history_book/sequel/store_spec.rb +21 -0
- data/spec/history_book/stream_spec.rb +48 -0
- data/spec/history_book_spec.rb +64 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/resettable_memory_store.rb +11 -0
- data/spec/support/shared_examples_for_stores.rb +63 -0
- metadata +188 -0
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).
|
data/lib/history_book.rb
ADDED
@@ -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,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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|