replay 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .idea/*
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@replay
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in replay.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "ruby-debug19", :platforms => :ruby_19
8
+ gem "minitest"
9
+ gem "mocha"
10
+ end
data/README ADDED
@@ -0,0 +1,27 @@
1
+ class Domain::Model
2
+ include Replay::Domain
3
+
4
+ def twitter_update
5
+ #do stuff
6
+ signal_event "twitter_updated", status
7
+ self.save
8
+ end
9
+
10
+ apply "twitter_updated" do |status|
11
+ model.twitter_status = status
12
+ end
13
+
14
+ end
15
+
16
+
17
+ #usage
18
+ model = Model.find(params[:model_id])
19
+ model.twitter_update
20
+
21
+ #listener
22
+ class TweetReport
23
+ include Replay::Projector
24
+ listen :twitter_updated do |model_id, status|
25
+ #update read model/report/whatever...
26
+ end
27
+ end
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,14 @@
1
+ require "replay/version"
2
+ require 'active_support/concern'
3
+ require 'replay/unknown_event_error'
4
+ require 'replay/test_storage'
5
+ require 'replay/configuration'
6
+ require 'replay/event'
7
+ require 'replay/event_store'
8
+ require 'replay/active_record_event_store'
9
+ require 'replay/domain'
10
+ require 'replay/projector'
11
+
12
+ module Replay
13
+
14
+ end
@@ -0,0 +1,32 @@
1
+ if defined?(ActiveRecord)
2
+ module Replay
3
+ class ActiveRecordEvent < ::ActiveRecord::Base
4
+ set_table_name "replay_events"
5
+ serialize :arguments
6
+ end
7
+
8
+ #needs model id
9
+ class ActiveRecordEventLogEntry < ::ActiveRecord::Base
10
+ set_table_name "replay_event_log_entries"
11
+ serialize :data
12
+ end
13
+
14
+ class ActiveRecordEventStore
15
+ def store(event, model_id, *args)
16
+ ar_event = Replay::ActiveRecordEvent.new
17
+ ar_event.event = event
18
+ ar_event.model_id = model_id
19
+ ar_event.arguments = args
20
+ ar_event.save
21
+ end
22
+
23
+ def log_event(event, model_id, *args)
24
+ log_entry = Replay::ActiveRecordEventLogEntry.new
25
+ log_entry.event = event
26
+ log_entry.model_id = model_id
27
+ log_entry.data = args
28
+ log_entry.save
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module Replay
2
+ class Configuration
3
+ attr_accessor :storage
4
+
5
+ def storage=(stores)
6
+ stores = [stores] unless stores.is_a?(Array)
7
+ @storage = stores
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ module Replay::Domain
2
+ extend ActiveSupport::Concern
3
+ include Replay
4
+
5
+ included do
6
+ self.extend ClassMethods
7
+ self.event_blocks = {}
8
+ end
9
+
10
+ def signal_event(event, *args)
11
+ EventStore.handle_event(event, self.id, *args)
12
+ apply_event(event, *args)
13
+ end
14
+
15
+ def apply_event(event, *args)
16
+ event_block = event_blocks[event.to_sym]
17
+ raise UnknownEventError.new("#{event} is not a known event on class #{self.class.name}") unless event_block
18
+ EventStore.log_event(event, self.id, *args)
19
+ self.instance_exec(*args, &event_block)
20
+ end
21
+
22
+ def event_blocks
23
+ self.class.event_blocks
24
+ end
25
+
26
+ module ClassMethods
27
+ def apply(event, &block)
28
+ event_blocks[event.to_sym] = block
29
+ end
30
+
31
+ attr_accessor :event_blocks
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ module Replay
2
+ class Event
3
+ attr_accessor :event_type, :attributes
4
+
5
+ def initialize(event_type, *data)
6
+ @event_type = event_type
7
+ if data.first.kind_of? Hash
8
+ @attributes = HashWithIndifferentAccess.new data.first.dup
9
+ else
10
+ @attributes = HashWithIndifferentAccess.new({data.first => data.last})
11
+ end
12
+ end
13
+
14
+ def method_missing(method, *args)
15
+ method_root = method.to_s.gsub(/=$/, "")
16
+ if @attributes && @attributes.has_key?(method_root)
17
+ if method.to_s[/=$/]
18
+ @attributes[method_root] = args.first
19
+ else
20
+ @attributes[method]
21
+ end
22
+ else
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ module Replay
2
+ class EventStore
3
+ include Replay
4
+
5
+ class << self
6
+ attr_accessor :configuration
7
+ attr_accessor :listeners
8
+ attr_accessor :test_mode
9
+ attr_accessor :event_stream
10
+ end
11
+ self.listeners = {}
12
+ self.configuration = Replay::Configuration.new
13
+ self.event_stream = []
14
+
15
+ def self.configure(&block)
16
+ block.call(self.configuration)
17
+ end
18
+
19
+ def self.storage
20
+ configuration.storage
21
+ end
22
+
23
+ def self.add_listener(event, new_listener)
24
+ self.listeners[event] = [] unless self.listeners[event]
25
+ self.listeners[event] << new_listener
26
+ end
27
+
28
+ def self.clear_listeners
29
+ listeners = {}
30
+ end
31
+
32
+ def self.log_event(event, model_id, *args)
33
+ if configuration.storage
34
+ configuration.storage.each do |event_store_adapter|
35
+ event_store_adapter.log_event(event, model_id, *args)
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.handle_event(event, model_id, *args)
41
+ self.event_stream << Event.new(event, args.last) if self.test_mode
42
+
43
+ if configuration.storage
44
+ configuration.storage.each do |event_store_adapter|
45
+ event_store_adapter.store(event, model_id, *args)
46
+ end
47
+ end
48
+ if self.listeners[event]
49
+ self.listeners[event].each do |listener|
50
+ listener.handle_event(event, model_id, *args)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module Replay::Projector
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ self.extend(ClassMethods)
6
+ self.listening_blocks = {}
7
+ end
8
+
9
+ module ClassMethods
10
+ def listen(event, &block)
11
+ listening_blocks[event] = [] unless listening_blocks[event]
12
+ listening_blocks[event] << block
13
+ Replay::EventStore.add_listener(event, self)
14
+ end
15
+
16
+ private
17
+ attr_accessor :listening_blocks
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ module Replay
2
+ class TestStorage
3
+ # To change this template use File | Settings | File Templates.
4
+ def store(event, id, *args)
5
+ true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,2 @@
1
+ class UnknownEventError < RuntimeError
2
+ end
@@ -0,0 +1,3 @@
1
+ module Replay
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "replay/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "replay"
7
+ s.version = Replay::VERSION
8
+ s.authors = ["karmajunkie"]
9
+ s.email = ["keith.gaddis@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Replay supports event-sourced data models.}
12
+ s.description = %q{Replay supports event-sourced data models.}
13
+
14
+ s.rubyforge_project = "replay"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "activerecord"
23
+ s.add_development_dependency "sqlite3-ruby"
24
+ # s.add_runtime_dependency "rest-client"
25
+ s.add_runtime_dependency "activesupport"
26
+ end
@@ -0,0 +1,6 @@
1
+ $:<<"lib"
2
+ require 'active_record'
3
+ require 'minitest/autorun'
4
+ require 'replay'
5
+ require 'ruby-debug'
6
+ require 'mocha'
Binary file
@@ -0,0 +1,24 @@
1
+ require './test/spec_helper'
2
+
3
+ ::ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => "test/test_events.sqlite3"
6
+ )
7
+ ::ActiveRecord::Base.connection.execute("create table if not exists replay_events(id primary key, event, model_id, arguments, created_at, updated_at)")
8
+
9
+ describe Replay::ActiveRecordEventStore do
10
+ before do
11
+ @store = Replay::ActiveRecordEventStore.new
12
+ end
13
+ def action
14
+ @store.store(:foo_event, 123, 'bar')
15
+ end
16
+ describe "#store" do
17
+ it "should create an event in the database" do
18
+ count = Replay::ActiveRecordEvent.count
19
+ action
20
+ (Replay::ActiveRecordEvent.count - count).must_equal 1
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,53 @@
1
+ require './test/spec_helper'
2
+
3
+ describe Replay do
4
+ class ReplayTestClass
5
+ include Replay::Domain
6
+
7
+ attr_accessor :bar_value
8
+
9
+ def id
10
+ 1
11
+ end
12
+
13
+ def foo
14
+ signal_event :foo_happened, "bar"
15
+ end
16
+
17
+ def bad_foo
18
+ signal_event :no_foo_lovey
19
+ end
20
+
21
+ apply :foo_happened do |what|
22
+ self.bar_value = what
23
+ end
24
+ end
25
+
26
+ before do
27
+ @something = ReplayTestClass.new
28
+ end
29
+ describe "behavior with signalling" do
30
+ it "raises an error for unknown events" do
31
+ lambda{@something.bad_foo}.must_raise UnknownEventError
32
+ end
33
+
34
+ it "sends the event to the EventStore" do
35
+ Replay::EventStore.expects(:handle_event).with(:foo_happened, 1, 'bar')
36
+ @something.foo
37
+ end
38
+ end
39
+
40
+ describe "rebuilding" do
41
+
42
+ end
43
+
44
+ describe "applying an event" do
45
+ it "changes state" do
46
+ @something.foo
47
+ @something.bar_value.must_equal "bar"
48
+ end
49
+ end
50
+
51
+
52
+ end
53
+
@@ -0,0 +1,13 @@
1
+ require './test/spec_helper'
2
+
3
+ describe Replay::Event do
4
+ before do
5
+ @event = Replay::Event.new("shit_happened", :foo => :bar)
6
+ end
7
+
8
+ describe "method_missing" do
9
+ it "fakes the reader" do
10
+ @event.foo.must_equal :bar
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ require './test/spec_helper'
2
+
3
+ describe Replay::EventStore do
4
+ before do
5
+ Replay::EventStore.configure do |config|
6
+ config.storage = Replay::TestStorage.new
7
+ end
8
+ end
9
+ after do
10
+ Replay::EventStore.clear_listeners
11
+ end
12
+ describe "#handle_event" do
13
+ def action
14
+ Replay::EventStore.handle_event(:foo_event, 123, {:bar => "fly"})
15
+ end
16
+ it "stores the event" do
17
+ Replay::TestStorage.any_instance.expects(:store).with(:foo_event, 123, {:bar => "fly"} )
18
+ action
19
+ end
20
+ it "notifies listeners" do
21
+ listener = mock("listener") do
22
+ expects(:handle_event).with(:foo_event, 123, {:bar => 'fly'})
23
+ end
24
+ Replay::EventStore.add_listener(:foo_event, listener)
25
+ action
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ require './test/spec_helper'
2
+
3
+ describe Replay::Projector do
4
+ after do
5
+ Replay::EventStore.clear_listeners
6
+ end
7
+
8
+ describe "#listen" do
9
+ it "adds the class as a listener to the EventStore" do
10
+ class ProjectorExample
11
+ Replay::EventStore.expects(:add_listener).with(:foo_happened, self)
12
+
13
+ include Replay::Projector
14
+ listen :foo_happened do |model_id, arg|
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: replay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - karmajunkie
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sqlite3-ruby
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Replay supports event-sourced data models.
63
+ email:
64
+ - keith.gaddis@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rvmrc
71
+ - Gemfile
72
+ - README
73
+ - Rakefile
74
+ - lib/replay.rb
75
+ - lib/replay/active_record_event_store.rb
76
+ - lib/replay/configuration.rb
77
+ - lib/replay/domain.rb
78
+ - lib/replay/event.rb
79
+ - lib/replay/event_store.rb
80
+ - lib/replay/projector.rb
81
+ - lib/replay/test_storage.rb
82
+ - lib/replay/unknown_event_error.rb
83
+ - lib/replay/version.rb
84
+ - replay.gemspec
85
+ - test/spec_helper.rb
86
+ - test/test_events.sqlite3
87
+ - test/unit/active_record_event_store_spec.rb
88
+ - test/unit/domain_spec.rb
89
+ - test/unit/event_spec.rb
90
+ - test/unit/event_store_spec.rb
91
+ - test/unit/projector_spec.rb
92
+ homepage: ''
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project: replay
112
+ rubygems_version: 1.8.24
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Replay supports event-sourced data models.
116
+ test_files:
117
+ - test/spec_helper.rb
118
+ - test/test_events.sqlite3
119
+ - test/unit/active_record_event_store_spec.rb
120
+ - test/unit/domain_spec.rb
121
+ - test/unit/event_spec.rb
122
+ - test/unit/event_store_spec.rb
123
+ - test/unit/projector_spec.rb