sandthorn 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10d8ea5557695337009c236ea93deaf0eef47d13
4
- data.tar.gz: d9f2920cb7b31df316859f51ed76864a360b6ccd
3
+ metadata.gz: 406c440420a9d7f88fe93583721b54b2f5d21248
4
+ data.tar.gz: 9f9158b4be2dd23d95e952c9a9649a17a2b6b4c5
5
5
  SHA512:
6
- metadata.gz: bf97fec50cc1c40ff231a1907a4e7e6a134338766329e7eadb69220047b06a3307a18fa1c703952ac497477d78a10480cc80504735d8239102fda77b7f1c895c
7
- data.tar.gz: dd51c3758eb294f6f766f7b5fa5d7fc5fe31facdf76bc0eeaa95d44e5c55aae0d898cce785653cbda7104e733d7e201f46c50da430edba179cea9c2ab6c4bda7
6
+ metadata.gz: cc57b92951bd52e571e2428c33fa81b297bfe6a7a488569fd54cf116f822b5dee4549377adb003344004e4f7868a0aa76086b938cf210b6a45dd378373303a7c
7
+ data.tar.gz: 2843b99f89f0b2261b0b554f2812dc198b6d21084cf0890a3d2a0b1540bdb1145c49bcf3a5ffeb78a5f07ce1998201a6a21b3e2a24b5da9a43bb0a440fcf8ab8
data/Gemfile.lock CHANGED
@@ -16,45 +16,51 @@ GEM
16
16
  term-ansicolor
17
17
  thor
18
18
  diff-lcs (1.2.5)
19
- docile (1.1.3)
20
- gem-release (0.7.1)
19
+ docile (1.1.5)
20
+ gem-release (0.7.3)
21
21
  method_source (0.8.2)
22
- mime-types (2.2)
23
- multi_json (1.9.2)
22
+ mime-types (2.3)
23
+ multi_json (1.10.1)
24
+ netrc (0.7.7)
24
25
  pg (0.17.1)
25
- pry (0.9.12.6)
26
- coderay (~> 1.0)
27
- method_source (~> 0.8)
26
+ pry (0.10.0)
27
+ coderay (~> 1.1.0)
28
+ method_source (~> 0.8.1)
28
29
  slop (~> 3.4)
29
30
  pry-doc (0.6.0)
30
31
  pry (~> 0.9)
31
32
  yard (~> 0.8)
32
- rake (10.2.2)
33
- rest-client (1.6.7)
34
- mime-types (>= 1.16)
35
- rspec (2.14.1)
36
- rspec-core (~> 2.14.0)
37
- rspec-expectations (~> 2.14.0)
38
- rspec-mocks (~> 2.14.0)
39
- rspec-core (2.14.8)
40
- rspec-expectations (2.14.5)
41
- diff-lcs (>= 1.1.3, < 2.0)
42
- rspec-mocks (2.14.6)
33
+ rake (10.3.2)
34
+ rest-client (1.7.2)
35
+ mime-types (>= 1.16, < 3.0)
36
+ netrc (~> 0.7)
37
+ rspec (3.0.0)
38
+ rspec-core (~> 3.0.0)
39
+ rspec-expectations (~> 3.0.0)
40
+ rspec-mocks (~> 3.0.0)
41
+ rspec-core (3.0.3)
42
+ rspec-support (~> 3.0.0)
43
+ rspec-expectations (3.0.3)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.0.0)
46
+ rspec-mocks (3.0.3)
47
+ rspec-support (~> 3.0.0)
48
+ rspec-support (3.0.3)
43
49
  sandthorn_driver_sequel (1.1.0)
44
50
  pg
45
51
  sequel
46
- sequel (4.9.0)
47
- simplecov (0.8.2)
52
+ sequel (4.13.0)
53
+ simplecov (0.9.0)
48
54
  docile (~> 1.1.0)
49
55
  multi_json
50
56
  simplecov-html (~> 0.8.0)
51
57
  simplecov-html (0.8.0)
52
- slop (3.5.0)
58
+ slop (3.6.0)
53
59
  sqlite3 (1.3.9)
54
60
  term-ansicolor (1.3.0)
55
61
  tins (~> 1.0)
56
62
  thor (0.19.1)
57
- tins (1.1.0)
63
+ tins (1.3.0)
58
64
  yard (0.8.7.4)
59
65
 
60
66
  PLATFORMS
data/README.md CHANGED
@@ -4,16 +4,32 @@
4
4
  [![Gem Version](https://badge.fury.io/rb/sandthorn.png)](http://badge.fury.io/rb/sandthorn)
5
5
 
6
6
  # Sandthorn Event Sourcing
7
- A ruby framework for saving an object's state as a series of events, and tracking non state changing events.
7
+ A ruby library for saving an object's state as a series of events.
8
8
 
9
- ## What is Event Sourcing
9
+ ## What is Event Sourcing?
10
10
 
11
11
  "Capture all changes to an application state as a sequence of events."
12
12
  [Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html)
13
13
 
14
- ## The short story
14
+ ## When do I need event sourcing?
15
15
 
16
- Think of it as an object database where you not only what the new value of the attribute is but when and why it changed.
16
+ If the history of how an object came to be is important a well known technique is to generate a separate history log. The log is generated in parallel with the object and all actions to the object needs to be stored to the log by a separate method call. With event sourcing the history log is now integrated with the object and generated based on the actions that are made on the object, the log is now the fact that the object is built upon.
17
+
18
+
19
+ ## Why Sandthorn?
20
+
21
+ If you have been following [Uncle Bob](http://blog.8thlight.com/uncle-bob/2014/05/11/FrameworkBound.html) you know what he thinks of the "Rails way" and how we get bound to the Rails framework. We have created Sandthorn to decouple our models from Active Record and restore them to what they should be, i.e., Plain Old Ruby Objects (PORO) with a twist of Sandthorn magic.
22
+
23
+ Check out examples of Sandthorn:
24
+
25
+ * [Examples](https://github.com/Sandthorn/sandthorn_examples) including a product shop and TicTacToe game.
26
+ * Live [demo](http://demo.sandthorn.org) comparing Active Record and Sandthorn.
27
+
28
+ ## How do I use Sandthorn?
29
+
30
+
31
+
32
+ Think of it as an object database where you store not only what the new value of an attribute is, but also when and why it changed.
17
33
  _Example:_
18
34
 
19
35
  ```ruby
@@ -65,12 +81,9 @@ ship.save
65
81
 
66
82
  new_ship = Ship.find ship.id
67
83
  puts new_ship.name
68
-
69
- # For more info look at the specs.
70
-
71
84
  ```
72
85
 
73
- ## Installation
86
+ # Installation
74
87
 
75
88
  Add this line to your application's Gemfile:
76
89
 
@@ -84,18 +97,144 @@ Or install it yourself as:
84
97
 
85
98
  $ gem install sandthorn
86
99
 
87
- ## Usage
100
+ # Configuring Sandthorn
101
+
102
+ Sandthorn relies on a driver is specific to the data storage that you are using. This means Sandthorn can be used with any data storage given that a driver exists.
103
+
104
+ To setup a driver you need to add it to your project's Gemfile and configure it in your application code.
105
+
106
+ gem 'sandthorn_driver_sequel'
107
+
108
+ The driver is configured when your application launches. Here's an example of how to do it using the Sequel driver and a sqlite3 database.
109
+
110
+ ```ruby
111
+ url = "sqlite://spec/db/sequel_driver.sqlite3"
112
+ driver = SandthornDriverSequel.driver_from_url(url: url)
113
+ catch_all_config = [ { driver: driver } ]
114
+ Sandthorn.configuration = catch_all_config
115
+ ```
116
+
117
+ First we specify the path to the sqlite3 database in the `url` variable. Secondly, the specific driver is instantiated with the `url`. Hence, the driver could be instantiated using a different configuration, for example, an address to a Postgres database. Finally, `Sandthorn.configure` accepts a keyword list with options. The only option which is required is `driver`.
118
+
119
+ The first time you use the Sequel driver it is necessary to install the database schema.
120
+
121
+ ```ruby
122
+ url = "sqlite://spec/db/sequel_driver.sqlite3"
123
+ SandthornDriverSequel::Migration.new url: url
124
+ SandthornDriverSequel.migrate_db url: url
125
+ ```
126
+
127
+ Optionally, when using Sandthorn in your tests you can configure it in a `spec_helper.rb` which is then required by your test suites [example](https://github.com/Sandthorn/sandthorn_examples/blob/master/sandthorn_tictactoe/spec/spec_helper.rb#L20-L30). Note that the Sequel driver accepts a special parameter to empty the database between each test.
128
+
129
+ The Sequel driver is the only production-ready driver to date.
130
+
131
+ # Usage
132
+
133
+ Any object that should have event sourcing capability must include the methods provided by `Sandthorn::AggregateRoot`. These make it possible to `commit` events and `save` changes to an aggregate. Use the `include` directive as follows:
134
+
135
+ ```ruby
136
+ require 'sandthorn'
137
+
138
+ class Board
139
+ include Sandthorn::AggregateRoot
140
+ end
141
+ ```
142
+
143
+ All objects that include `Sandthorn::AggregateRoot` is provided with an `aggregate_id` which is a [UUID](http://en.wikipedia.org/wiki/Universally_unique_identifier).
144
+
145
+ ### `Sandthorn::AggregateRoot.commit`
146
+
147
+ It is required that an event is commited to the aggregate to be stored as an event. `commit` extracts the object's delta and locally caches the state changes that has been applied to the aggregate. Commonly, commit is called when an event is applied. In [CQRS](http://martinfowler.com/bliki/CQRS.html), events are named using past tense.
148
+
149
+ ```ruby
150
+ def mark player, pos_x, pos_y
151
+ # change some state
152
+ marked
153
+ end
154
+
155
+ def marked
156
+ commit
157
+ end
158
+ ```
159
+
160
+ `commit` determines the state changes by monitoring the object's readable fields.
161
+
162
+ ### `Sandthorn::AggregateRoot.save`
163
+
164
+ Once one or more commits have been applied to an aggregate it should be saved. This means all commited events will be persisted by the specific Sandthorn driver. `save` is called by the owning object.
165
+
166
+ ```ruby
167
+ board = Board.new
168
+ board.mark :o, 0, 1
169
+ board.save
170
+ ```
171
+
172
+ ### `Sandthorn::AggregateRoot.all`
173
+
174
+ It is possible to retrieve an array with all instances of a specific aggregate.
175
+
176
+ ```ruby
177
+ Board.all
178
+ ```
179
+
180
+ Since it return's an `Array` you can, for example, filter on an aggregate's fields
181
+
182
+ ```ruby
183
+ Board.all.select { |board| board.active == true }
184
+ ```
185
+
186
+ ### `Sandthorn::AggregateRoot.find`
187
+
188
+ Using `find` it is possible to retrieve a specific aggregate using it's id.
189
+
190
+ ```ruby
191
+ uuid = '550e8400-e29b-41d4-a716-446655440000'
192
+ board = Board.find(uuid)
193
+ board.aggregate_id == uuid
194
+ ```
195
+
196
+ If no aggregate with the specifid id is found, a `Sandthorn::Errors::AggregateNotFound` exception is raised.
197
+
198
+
199
+ ### `Sandthorn::AggregateRoot.aggregate_trace`
200
+
201
+ Using `aggregate_trace` one can store meta data with events. The data is not aggregate specific, for example, one can store who executed a specific command on the aggregate.
202
+
203
+ ```ruby
204
+ board.aggregate_trace {player: "Fred"} do |aggregate|
205
+ aggregate.mark :o, 0, 1
206
+ aggregate.save
207
+ end
208
+ ```
209
+
210
+ `aggregate_trace` can also be specified on a class.
211
+
212
+ ````ruby
213
+ Board.aggregate_trace {ip: :127.0.0.1} do
214
+ board = Board.new
215
+ board.mark :o , 0, 1
216
+ board.save
217
+ end
218
+ ```
219
+
220
+ In this case, the resulting events from the commands `new` and `mark` will have the trace `{ip: :127.0.0.1}` attached to them.
221
+
222
+
223
+ # Development
224
+
225
+ Run tests: `rake`
226
+
227
+ Run benchmark tests: `rake benchmark`
88
228
 
89
- TODO: Write usage instructions here
229
+ Load a console: `rake console`
90
230
 
91
- ## Development
231
+ # Contributing
92
232
 
93
- run:
94
- `rake console`
233
+ We're happy to accept pull requests that makes the code cleaner or more idiomatic, the documentation more understandable, or improves the testsuite. Even considering opening an issue for what's troubling you or writing a blog post about how you used Sandthorn is worth a lot too!
95
234
 
96
- ## Contributing
235
+ In general, the contribution process for code works like this.
97
236
 
98
- 1. Fork it
237
+ 1. Fork this repo
99
238
  2. Create your feature branch (`git checkout -b my-new-feature`)
100
239
  3. Commit your changes (`git commit -am 'Add some feature'`)
101
240
  4. Push to the branch (`git push origin my-new-feature`)
data/lib/sandthorn.rb CHANGED
@@ -58,14 +58,31 @@ module Sandthorn
58
58
  def get_events aggregate_types: [], take: 0, after_sequence_number: 0
59
59
  drivers = drivers_for_aggregate_types type_names: aggregate_types
60
60
  raise Sandthorn::Errors::Error.new "Cannot get events from multiple contexts simultaneously, only one single context can be handled at a time." unless drivers.length == 1
61
- driver = drivers.first
61
+ driver = drivers.first
62
62
  events = driver.get_events aggregate_types: aggregate_types, take: take, after_sequence_number: after_sequence_number
63
63
  events.each do |event|
64
64
  event[:event_args] = deserialize event[:event_data]
65
65
  event.delete(:event_data)
66
- end
66
+ end
67
67
  events
68
68
  end
69
+
70
+ def obsolete_snapshots type_names: [], min_event_distance: 0
71
+ drivers = drivers_for_aggregate_types type_names: type_names
72
+ obsolete = drivers.flat_map { |driver| driver.obsolete_snapshots(class_names: type_names, max_event_distance: min_event_distance) }
73
+ yielder = []
74
+ obsolete.each do |single_obsolete|
75
+ type = Kernel.const_get single_obsolete[:aggregate_type]
76
+ aggregate = type.aggregate_find single_obsolete[:aggregate_id]
77
+ if block_given?
78
+ yield aggregate
79
+ else
80
+ yielder << aggregate
81
+ end
82
+ end
83
+ yielder unless block_given?
84
+ end
85
+
69
86
  private
70
87
  def driver_for class_name, &block
71
88
  driver = identify_driver_from_class class_name
@@ -73,8 +90,8 @@ module Sandthorn
73
90
  driver
74
91
  end
75
92
  def identify_driver_from_class class_name
76
- matches = configuration.select do |conf|
77
- r = Regexp.new "^#{conf[:aggregate_pattern]}"
93
+ matches = configuration.select do |conf|
94
+ r = Regexp.new "^#{conf[:aggregate_pattern]}"
78
95
  pattern = class_name.to_s
79
96
  conf[:aggregate_pattern].nil? || r.match(pattern)
80
97
  end
@@ -7,6 +7,7 @@ module Sandthorn
7
7
  attr_reader :aggregate_current_event_version
8
8
  attr_reader :aggregate_originating_version
9
9
  attr_reader :aggregate_stored_serialized_object
10
+ attr_reader :aggregate_trace_information
10
11
 
11
12
  alias :id :aggregate_id
12
13
 
@@ -22,45 +23,65 @@ module Sandthorn
22
23
  event[:event_data] = Sandthorn.serialize event[:event_args]
23
24
  event[:event_args] = nil #Not send extra data over the wire
24
25
  end
26
+
25
27
  unless aggregate_events.empty?
26
- Sandthorn.save_events( aggregate_events, aggregate_originating_version, aggregate_id, self.class.name)
28
+ Sandthorn.save_events(
29
+ aggregate_events,
30
+ aggregate_originating_version,
31
+ aggregate_id,
32
+ self.class.name
33
+ )
34
+
27
35
  @aggregate_events = []
28
36
  @aggregate_originating_version = @aggregate_current_event_version
29
37
  end
38
+
30
39
  self
31
40
  end
32
41
 
42
+ def aggregate_trace args
43
+ @aggregate_trace_information = args
44
+ yield self if block_given?
45
+ @aggregate_trace_information = nil
46
+ end
47
+
33
48
  def commit *args
34
49
  aggregate_attribute_deltas = get_delta
35
-
50
+
36
51
  unless aggregate_attribute_deltas.empty?
37
52
  method_name = caller_locations(1,1)[0].label.gsub("block in ", "")
38
53
  increase_current_aggregate_version!
39
- data = {:method_name => method_name, :method_args => args, :attribute_deltas => aggregate_attribute_deltas}
40
- data.merge!({trace: @aggregate_trace_information}) unless @aggregate_trace_information.nil? || @aggregate_trace_information.empty?
41
- @aggregate_events << ({:aggregate_version => @aggregate_current_event_version, :event_name => method_name, :event_args => data})
54
+
55
+ data = {
56
+ method_name: method_name,
57
+ method_args: args,
58
+ attribute_deltas: aggregate_attribute_deltas
59
+ }
60
+ trace_information = @aggregate_trace_information
61
+ unless trace_information.nil? || trace_information.empty?
62
+ data.merge!({ trace: trace_information })
63
+ end
64
+
65
+ @aggregate_events << ({
66
+ aggregate_version: @aggregate_current_event_version,
67
+ event_name: method_name,
68
+ event_args: data
69
+ })
42
70
  end
71
+
43
72
  self
44
73
  end
45
74
 
46
75
  alias :record_event :commit
47
-
48
- # def aggregate_trace args
49
- # @aggregate_trace_information = args
50
- # yield self
51
- # @aggregate_trace_information = nil
52
- # end
53
76
 
54
77
  module ClassMethods
55
78
 
56
- # @@aggregate_trace_information = nil
57
- # def aggregate_trace args
58
- # @@aggregate_trace_information = args
59
- # @aggregate_trace_information = args
60
- # yield self
61
- # @@aggregate_trace_information = nil
62
- # @aggregate_trace_information = nil
63
- # end
79
+ @@aggregate_trace_information = nil
80
+ def aggregate_trace args
81
+ @@aggregate_trace_information = args
82
+ yield self
83
+ @@aggregate_trace_information = nil
84
+ end
64
85
 
65
86
  def all
66
87
  aggregate_id_list = Sandthorn.get_aggregate_list_by_typename(self.name)
@@ -75,34 +96,41 @@ module Sandthorn
75
96
  def aggregate_find aggregate_id
76
97
  class_name = self.respond_to?(:name) ? self.name : self.class # to be able to extend a string for example.
77
98
  events = Sandthorn.get_aggregate(aggregate_id, class_name)
78
- raise Sandthorn::Errors::AggregateNotFound unless events and !events.empty?
79
99
 
80
- transformed_events = events.map { |e| e.merge(event_args: Sandthorn.deserialize(e[:event_data])) }
100
+ unless events and !events.empty?
101
+ raise Sandthorn::Errors::AggregateNotFound
102
+ end
103
+
104
+ transformed_events = events.map do |e|
105
+ e.merge(event_args: Sandthorn.deserialize(e[:event_data]))
106
+ end
107
+
81
108
  aggregate_build transformed_events
82
109
  end
83
110
 
84
111
  def new *args
85
- aggregate = super
86
- aggregate.aggregate_base_initialize
87
- aggr = aggregate
88
-
89
- #aggregate.aggregate_trace @@aggregate_trace_information do |aggr|
90
- aggregate.aggregate_initialize
91
- aggregate.send :set_aggregate_id, Sandthorn.generate_aggregate_id
92
- aggregate.send :commit, *args
93
- aggregate
94
- #end
112
+ super.tap do |aggregate|
113
+ aggregate.aggregate_trace @@aggregate_trace_information do |aggr|
114
+ aggr.aggregate_base_initialize
115
+ aggr.aggregate_initialize
116
+ aggr.send :set_aggregate_id, Sandthorn.generate_aggregate_id
117
+ aggr.send :commit, *args
118
+ return aggr
119
+ end
120
+ end
95
121
  end
96
122
 
97
123
  def aggregate_build events
98
124
  current_aggregate_version = 0
125
+
99
126
  if first_event_snapshot?(events)
100
- aggregate = start_build_from_snapshot events
127
+ aggregate = start_build_from_snapshot events
101
128
  current_aggregate_version = aggregate.aggregate_originating_version
102
129
  events.shift
103
130
  else
104
131
  aggregate = start_build_from_new events
105
132
  end
133
+
106
134
  attributes = build_instance_vars_from_events events
107
135
  current_aggregate_version = events.last[:aggregate_version] unless events.empty?
108
136
  aggregate.send :clear_aggregate_events
@@ -112,9 +140,11 @@ module Sandthorn
112
140
  aggregate.send :set_instance_variables!, attributes
113
141
  aggregate
114
142
  end
143
+
115
144
  private
145
+
116
146
  def build_instance_vars_from_events events
117
- events.inject({}) do |instance_vars, event |
147
+ events.each_with_object({}) do |event, instance_vars|
118
148
  event_args = event[:event_args]
119
149
  event_name = event[:event_name]
120
150
  attribute_deltas = event_args[:attribute_deltas]
@@ -124,22 +154,26 @@ module Sandthorn
124
154
  end
125
155
  instance_vars.merge! deltas
126
156
  end
127
- instance_vars
128
157
  end
129
158
  end
159
+
130
160
  def first_event_snapshot? events
131
161
  events.first[:event_name].to_sym == :aggregate_set_from_snapshot
132
162
  end
163
+
133
164
  def start_build_from_snapshot events
134
165
  snapshot = events.first[:event_args][0]
135
166
  end
167
+
136
168
  def start_build_from_new events
137
169
  new_args = events.first[:event_args][:method_args]
170
+
138
171
  if new_args.nil?
139
172
  aggregate = new
140
173
  else
141
- aggregate = new *new_args
174
+ aggregate = new(*new_args)
142
175
  end
176
+
143
177
  aggregate.send :aggregate_clear_current_event_version!
144
178
  aggregate
145
179
  end
@@ -154,7 +188,12 @@ module Sandthorn
154
188
  end
155
189
 
156
190
  def extract_relevant_aggregate_instance_variables
157
- instance_variables.select { |i| i.to_s=="@aggregate_id" || !i.to_s.start_with?("@aggregate_") }
191
+ instance_variables.select do |variable|
192
+ equals_aggregate_id = variable.to_s == "@aggregate_id"
193
+ does_not_contain_aggregate = !variable.to_s.start_with?("@aggregate_")
194
+
195
+ equals_aggregate_id || does_not_contain_aggregate
196
+ end
158
197
  end
159
198
 
160
199
  def set_orginating_aggregate_version! aggregate_version
@@ -13,13 +13,17 @@ module Sandthorn
13
13
  def set_instance_variables! attribute
14
14
  super attribute
15
15
  init_vars = extract_relevant_aggregate_instance_variables
16
- init_vars.each {|attribute_name| @aggregate_stored_instance_variables[attribute_name] = ::Marshal.dump(instance_variable_get(attribute_name)) }
16
+
17
+ init_vars.each do |attribute_name|
18
+ @aggregate_stored_instance_variables[attribute_name] =
19
+ ::Marshal.dump(instance_variable_get(attribute_name))
20
+ end
17
21
  end
18
22
 
19
23
  def get_delta
20
24
  deltas = extract_relevant_aggregate_instance_variables
21
- deltas.each { |d| delta_attribute(d)}
22
-
25
+ deltas.each { |d| delta_attribute(d) }
26
+
23
27
  result = @aggregate_attribute_deltas
24
28
  clear_aggregate_deltas
25
29
  result
@@ -30,6 +34,7 @@ module Sandthorn
30
34
  def delta_attribute attribute_name
31
35
  old_dump = @aggregate_stored_instance_variables[attribute_name]
32
36
  new_dump = ::Marshal.dump(instance_variable_get(attribute_name))
37
+
33
38
  unless old_dump == new_dump
34
39
  store_attribute_deltas attribute_name, new_dump, old_dump
35
40
  store_aggregate_instance_variable attribute_name, new_dump
@@ -39,7 +44,12 @@ module Sandthorn
39
44
  def store_attribute_deltas attribute_name, new_dump, old_dump
40
45
  new_value_to_store = ::Marshal.load(new_dump)
41
46
  old_value_to_store = old_dump ? ::Marshal.load(old_dump) : nil
42
- @aggregate_attribute_deltas << { :attribute_name => attribute_name.to_s.delete("@"), :old_value => old_value_to_store, :new_value => new_value_to_store}
47
+
48
+ @aggregate_attribute_deltas << {
49
+ attribute_name: attribute_name.to_s.delete("@"),
50
+ old_value: old_value_to_store,
51
+ new_value: new_value_to_store
52
+ }
43
53
  end
44
54
 
45
55
  def store_aggregate_instance_variable attribute_name, new_dump
@@ -51,4 +61,4 @@ module Sandthorn
51
61
  end
52
62
  end
53
63
  end
54
- end
64
+ end
@@ -9,17 +9,26 @@ module Sandthorn
9
9
  end
10
10
 
11
11
  def aggregate_snapshot!
12
- raise Errors::SnapshotError.new "Can't take snapshot on object with unsaved events" if @aggregate_events.count > 0
12
+ if @aggregate_events.count > 0
13
+ raise Errors::SnapshotError,
14
+ "Can't take snapshot on object with unsaved events"
15
+ end
16
+
13
17
  @aggregate_snapshot = {
14
- :event_name => "aggregate_set_from_snapshot",
15
- :event_args => [self],
16
- :aggregate_version => @aggregate_current_event_version
18
+ event_name: "aggregate_set_from_snapshot",
19
+ event_args: [self],
20
+ aggregate_version: @aggregate_current_event_version
17
21
  }
18
22
  end
19
23
 
20
24
  def save_snapshot
21
- raise Errors::SnapshotError.new "No snapshot has been created!" unless aggregate_snapshot
22
- @aggregate_snapshot[:event_data] = Sandthorn.serialize aggregate_snapshot[:event_args]
25
+ unless aggregate_snapshot
26
+ raise Errors::SnapshotError, "No snapshot has been created!"
27
+ end
28
+
29
+ @aggregate_snapshot[:event_data] = Sandthorn
30
+ .serialize aggregate_snapshot[:event_args]
31
+
23
32
  @aggregate_snapshot[:event_args] = nil
24
33
  Sandthorn.save_snapshot aggregate_snapshot, aggregate_id, self.class.name
25
34
  @aggregate_snapshot = nil
@@ -2,51 +2,83 @@ module Sandthorn
2
2
  module EventInspector
3
3
  def has_unsaved_event? event_name, options = {}
4
4
  unsaved = events_with_trace_info
5
+
5
6
  if self.aggregate_events.empty?
6
7
  unsaved = []
7
8
  else
8
- unsaved.reject! { |e| e[:aggregate_version] < self.aggregate_events.first[:aggregate_version] }
9
+ unsaved.reject! do |e|
10
+ e[:aggregate_version] < self
11
+ .aggregate_events.first[:aggregate_version]
12
+ end
9
13
  end
14
+
10
15
  matching_events = unsaved.select { |e| e[:event_name] == event_name }
11
16
  event_exists = matching_events.length > 0
12
17
  trace = has_trace? matching_events, options.fetch(:trace, {})
13
18
 
14
- return event_exists && trace
19
+ !!(event_exists && trace)
15
20
  end
21
+
16
22
  def has_saved_event? event_name, options = {}
17
23
  saved = events_with_trace_info
18
- saved.reject! { |e| e[:aggregate_version] >= self.aggregate_events.first[:aggregate_version] } unless self.aggregate_events.empty?
24
+
25
+ unless self.aggregate_events.empty?
26
+ saved.reject! do |e|
27
+ e[:aggregate_version] >= self
28
+ .aggregate_events.first[:aggregate_version]
29
+ end
30
+ end
31
+
19
32
  matching_events = saved.select { |e| e[:event_name] == event_name }
20
33
  event_exists = matching_events.length > 0
21
34
  trace = has_trace? matching_events, options.fetch(:trace, {})
22
35
 
23
- return event_exists && trace
36
+ !!(event_exists && trace)
24
37
  end
38
+
25
39
  def has_event? event_name, options = {}
26
- matching_events = events_with_trace_info.select { |e| e[:event_name] == event_name }
40
+ matching_events = events_with_trace_info
41
+ .select { |e| e[:event_name] == event_name }
42
+
27
43
  event_exists = matching_events.length > 0
28
44
  trace = has_trace? matching_events, options.fetch(:trace, {})
29
- return event_exists && trace
45
+ !!(event_exists && trace)
30
46
  end
47
+
31
48
  def events_with_trace_info
32
49
  saved = Sandthorn.get_aggregate_events self.aggregate_id, self.class
33
50
  unsaved = self.aggregate_events
34
- all = saved.concat(unsaved).sort { |a, b| a[:aggregate_version] <=> b[:aggregate_version] }
51
+ all = saved
52
+ .concat(unsaved)
53
+ .sort { |a, b| a[:aggregate_version] <=> b[:aggregate_version] }
54
+
35
55
  extracted = all.collect do |e|
36
56
  if e[:event_args].nil? && !e[:event_data].nil?
37
57
  data = Sandthorn.deserialize e[:event_data]
38
58
  else
39
59
  data = e[:event_args]
40
60
  end
41
- trace = data[:trace] unless data.nil? || !data.is_a?(Hash)
42
- {aggregate_version: e[:aggregate_version], event_name: e[:event_name].to_sym, trace: trace }
61
+
62
+ unless data.nil? || !data.is_a?(Hash)
63
+ trace = data[:trace]
64
+ end
65
+
66
+ {
67
+ aggregate_version: e[:aggregate_version],
68
+ event_name: e[:event_name].to_sym,
69
+ trace: trace
70
+ }
43
71
  end
44
- return extracted
72
+
73
+ extracted
45
74
  end
75
+
46
76
  private
77
+
47
78
  def get_unsaved_events event_name
48
79
  self.aggregate_events.select { |e| e[:event_name] == event_name.to_s }
49
80
  end
81
+
50
82
  def get_saved_events event_name
51
83
  saved_events = Sandthorn.get_aggregate_events self.aggregate_id, self.class
52
84
  saved_events.select { |e| e[:event_name] == event_name.to_s }
@@ -60,4 +92,4 @@ module Sandthorn
60
92
  true
61
93
  end
62
94
  end
63
- end
95
+ end
@@ -1,3 +1,3 @@
1
1
  module Sandthorn
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ class AnAggregate
4
+ include Sandthorn::AggregateRoot
5
+ attr_accessor :test_block_count
6
+ def initialize
7
+ @test_block_count = false
8
+ end
9
+ def touch; touched; end
10
+ def touched; commit; end
11
+ end
12
+
13
+ describe Sandthorn do
14
+ before(:each) {
15
+ @aggregate = AnAggregate.new
16
+ @aggregate.touch
17
+ @aggregate.save
18
+ }
19
+
20
+ context "when doing snapshots" do
21
+ it "retrieves a list of obsolete snapshots" do
22
+ obsolete_aggregates = Sandthorn.obsolete_snapshots type_names: [AnAggregate], min_event_distance: 0
23
+ expect(obsolete_aggregates).to_not be_empty
24
+ end
25
+
26
+ it "accepts a block that is applied to each aggregate" do
27
+ obsolete_aggregates = []
28
+ Sandthorn.obsolete_snapshots type_names: [AnAggregate], min_event_distance: 0 do |aggr|
29
+ aggr.test_block_count = true
30
+ obsolete_aggregates << aggr
31
+ end
32
+ expect(obsolete_aggregates.all? { |aggregate| aggregate.test_block_count == true }).to be_truthy
33
+ end
34
+
35
+ it "only retrieves aggregates older than min_event_distance" do
36
+ obsolete_aggregates = Sandthorn.obsolete_snapshots type_names: [AnAggregate], min_event_distance: 10
37
+ expect(obsolete_aggregates).to be_empty
38
+ end
39
+ end
40
+ end
data/spec/tracing_spec.rb CHANGED
@@ -1,121 +1,107 @@
1
- # require 'spec_helper'
2
- # require 'sandthorn/event_inspector'
3
- # require 'sandthorn/aggregate_root_dirty_hashy'
1
+ require 'spec_helper'
2
+ require 'sandthorn/event_inspector'
3
+ require 'sandthorn/aggregate_root_snapshot'
4
4
 
5
- # class UsualSuspect
6
- # include Sandthorn::AggregateRoot::DirtyHashy
5
+ class UsualSuspect
6
+ include Sandthorn::AggregateRoot
7
7
 
8
- # def initialize full_name
9
- # @full_name = full_name
10
- # @charges = []
11
- # end
8
+ def initialize full_name
9
+ @full_name = full_name
10
+ @charges = []
11
+ end
12
12
 
13
- # def charge_suspect_of_crime! crime_name
14
- # suspect_was_charged crime_name
15
- # end
13
+ def charge_suspect_of_crime! crime_name
14
+ suspect_was_charged crime_name
15
+ end
16
16
 
17
- # private
18
- # def suspect_was_charged crime_name
19
- # @charges << crime_name
20
- # record_event crime_name
21
- # end
22
- # end
17
+ private
18
+ def suspect_was_charged crime_name
19
+ @charges << crime_name
20
+ record_event crime_name
21
+ end
22
+ end
23
23
 
24
- # class Simple
25
- # end
26
- # module Go
27
- # def go
28
- # @foo = "bar"
29
- # record_event
30
- # end
31
- # end
24
+ class Simple
25
+ include Sandthorn::AggregateRoot
26
+ end
27
+ module Go
28
+ def go
29
+ @foo = "bar"
30
+ record_event
31
+ end
32
+ end
32
33
 
33
- # describe "using a traced change" do
34
- # context "when extending an instance with aggregate_root" do
35
- # it "should record tracing if specified" do
36
- # simple = Simple.new
37
- # simple.extend Sandthorn::EventInspector
38
- # simple.extend Sandthorn::AggregateRoot::DirtyHashy
39
- # simple.extend Sandthorn::AggregateRootSnapshot
34
+ describe "using a traced change" do
35
+ context "when extending an instance with aggregate_root" do
36
+ it "should record tracing if specified" do
37
+ simple = Simple.new
38
+ simple.extend Sandthorn::EventInspector
39
+ simple.extend Sandthorn::AggregateRootSnapshot
40
40
 
41
- # simple.extend Go
42
- # simple.aggregate_trace "123" do |traced|
43
- # traced.go
44
- # end
45
- # simple.events_with_trace_info.last[:trace].should eql("123")
46
- # end
47
- # end
48
- # context "when not tracing" do
49
- # it "should not have any trace event info at all on new" do
50
- # suspect = UsualSuspect.new "Ronny"
51
- # event = suspect.aggregate_events.first
52
- # event[:trace].should be_nil
53
- # end
54
- # it "should not have any trace event info at all on regular event" do
55
- # suspect = UsualSuspect.new "Ronny"
56
- # event = suspect.aggregate_events.first
57
- # event[:trace].should be_nil
58
- # end
59
- # end
60
- # context "when changing aggregate in a traced context" do
61
- # let(:suspect) {UsualSuspect.new("Conny").extend Sandthorn::EventInspector}
62
- # it "should record modififier in the event" do
63
- # suspect.aggregate_trace "Lars Krantz" do |s|
64
- # s.charge_suspect_of_crime! "Theft"
65
- # end
66
- # event = suspect.events_with_trace_info.last
67
- # event[:trace].should eql "Lars Krantz"
68
- # end
41
+ simple.extend Go
42
+ simple.aggregate_trace "123" do |traced|
43
+ traced.go
44
+ end
45
+ simple.events_with_trace_info.last[:trace].should eql("123")
46
+ end
47
+ end
48
+ context "when not tracing" do
49
+ it "should not have any trace event info at all on new" do
50
+ suspect = UsualSuspect.new "Ronny"
51
+ event = suspect.aggregate_events.first
52
+ event[:trace].should be_nil
53
+ end
54
+ it "should not have any trace event info at all on regular event" do
55
+ suspect = UsualSuspect.new "Ronny"
56
+ event = suspect.aggregate_events.first
57
+ event[:trace].should be_nil
58
+ end
59
+ end
60
+ context "when changing aggregate in a traced context" do
61
+ let(:suspect) {UsualSuspect.new("Conny").extend Sandthorn::EventInspector}
62
+ it "should record modififier in the event" do
63
+ suspect.aggregate_trace "Ture Sventon" do |s|
64
+ s.charge_suspect_of_crime! "Theft"
65
+ end
66
+ event = suspect.events_with_trace_info.last
67
+ event[:trace].should eql "Ture Sventon"
68
+ end
69
69
 
70
- # it "should record optional other tracing information" do
71
- # trace_info = {ip: "127.0.0.1", client: "Mozilla"}
72
- # suspect.aggregate_trace trace_info do |s|
73
- # s.charge_suspect_of_crime! "Murder"
74
- # end
75
- # event = suspect.events_with_trace_info.last
76
- # event[:trace].should eql trace_info
77
- # end
78
- # end
79
- # context "when initializing a new aggregate in a traced context" do
80
- # it "should record modifier in the new event" do
81
- # UsualSuspect.aggregate_trace "Lars Krantz" do
82
- # suspect = UsualSuspect.new("Sonny").extend Sandthorn::EventInspector
83
- # event = suspect.events_with_trace_info.first
84
- # event[:trace].should eql "Lars Krantz"
85
- # end
86
- # end
87
- # it "should record tracing for all events in the trace block" do
88
- # trace_info = {gender: :unknown, occupation: :master}
89
- # UsualSuspect.aggregate_trace trace_info do
90
- # suspect = UsualSuspect.new("Sonny").extend Sandthorn::EventInspector
91
- # suspect.charge_suspect_of_crime! "Hit and run"
92
- # event = suspect.events_with_trace_info.last
93
- # event[:trace].should eql trace_info
94
- # end
95
- # end
96
- # it "should record tracing for all events in the trace block" do
97
- # trace_info = {user_aggregate_id: "foo-bar-x", gender: :unknown, occupation: :master}
98
- # UsualSuspect.aggregate_trace trace_info do
99
- # suspect = UsualSuspect.new("Conny").extend Sandthorn::EventInspector
100
- # suspect.charge_suspect_of_crime! "Desception"
101
- # event = suspect.events_with_trace_info.last
102
- # event[:trace].should eql trace_info
103
- # end
104
- # end
105
- # it "should only record info within block" do
106
- # fork do
107
- # UsualSuspect.aggregate_trace "Lars Krantz" do
108
- # suspect = UsualSuspect.new("Sonny").extend Sandthorn::EventInspector
109
- # event = suspect.events_with_trace_info.first
110
- # event[:trace].should eql "Lars Krantz"
111
- # sleep 1
112
- # end
113
- # end
114
- # sleep 0.5
115
- # s2 = UsualSuspect.new("Ronny").extend Sandthorn::EventInspector
116
- # event = s2.events_with_trace_info.first
117
- # event[:trace].should be_nil
118
- # end
119
- # end
70
+ it "should record optional other tracing information" do
71
+ trace_info = {ip: "127.0.0.1", client: "Mozilla"}
72
+ suspect.aggregate_trace trace_info do |s|
73
+ s.charge_suspect_of_crime! "Murder"
74
+ end
75
+ event = suspect.events_with_trace_info.last
76
+ event[:trace].should eql trace_info
77
+ end
78
+ end
79
+ context "when initializing a new aggregate in a traced context" do
80
+ it "should record modifier in the new event" do
81
+ UsualSuspect.aggregate_trace "Ture Sventon" do
82
+ suspect = UsualSuspect.new("Sonny").extend Sandthorn::EventInspector
83
+ event = suspect.events_with_trace_info.first
84
+ event[:trace].should eql "Ture Sventon"
85
+ end
86
+ end
87
+ it "should record tracing for all events in the trace block" do
88
+ trace_info = {gender: :unknown, occupation: :master}
89
+ UsualSuspect.aggregate_trace trace_info do
90
+ suspect = UsualSuspect.new("Sonny").extend Sandthorn::EventInspector
91
+ suspect.charge_suspect_of_crime! "Hit and run"
92
+ event = suspect.events_with_trace_info.last
93
+ event[:trace].should eql trace_info
94
+ end
95
+ end
96
+ it "should record tracing for all events in the trace block" do
97
+ trace_info = {user_aggregate_id: "foo-bar-x", gender: :unknown, occupation: :master}
98
+ UsualSuspect.aggregate_trace trace_info do
99
+ suspect = UsualSuspect.new("Conny").extend Sandthorn::EventInspector
100
+ suspect.charge_suspect_of_crime! "Desception"
101
+ event = suspect.events_with_trace_info.last
102
+ event[:trace].should eql trace_info
103
+ end
104
+ end
105
+ end
120
106
 
121
- # end
107
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sandthorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Krantz
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-04-10 00:00:00.000000000 Z
12
+ date: 2014-10-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -204,6 +204,7 @@ files:
204
204
  - spec/different_driver_spec.rb
205
205
  - spec/event_inspector_spec.rb
206
206
  - spec/get_events_spec.rb
207
+ - spec/sandthorn_spec.rb
207
208
  - spec/spec_helper.rb
208
209
  - spec/tracing_spec.rb
209
210
  homepage: ''
@@ -240,6 +241,7 @@ test_files:
240
241
  - spec/different_driver_spec.rb
241
242
  - spec/event_inspector_spec.rb
242
243
  - spec/get_events_spec.rb
244
+ - spec/sandthorn_spec.rb
243
245
  - spec/spec_helper.rb
244
246
  - spec/tracing_spec.rb
245
247
  has_rdoc: