synapse-core 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/lib/synapse/command.rb +2 -0
  2. data/lib/synapse/command/callbacks/future.rb +50 -0
  3. data/lib/synapse/command/command_callback.rb +0 -47
  4. data/lib/synapse/command/message.rb +1 -1
  5. data/lib/synapse/common.rb +1 -0
  6. data/lib/synapse/common/concurrency/executor.rb +13 -0
  7. data/lib/synapse/common/message.rb +9 -2
  8. data/lib/synapse/common/message_builder.rb +5 -1
  9. data/lib/synapse/configuration.rb +1 -0
  10. data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +2 -8
  11. data/lib/synapse/configuration/component/event_sourcing.rb +3 -3
  12. data/lib/synapse/configuration/component/event_sourcing/repository.rb +19 -2
  13. data/lib/synapse/configuration/component/event_sourcing/{aggregate_snapshot_taker.rb → snapshot/aggregate_taker.rb} +4 -4
  14. data/lib/synapse/configuration/component/event_sourcing/{interval_snapshot_policy.rb → snapshot/interval_policy.rb} +0 -0
  15. data/lib/synapse/configuration/component/mixin/thread_pool.rb +26 -0
  16. data/lib/synapse/domain/message.rb +0 -24
  17. data/lib/synapse/domain/message_builder.rb +0 -9
  18. data/lib/synapse/event_sourcing.rb +1 -1
  19. data/lib/synapse/event_sourcing/aggregate_root.rb +2 -1
  20. data/lib/synapse/event_sourcing/caching.rb +66 -0
  21. data/lib/synapse/event_sourcing/repository.rb +22 -15
  22. data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +8 -9
  23. data/lib/synapse/event_sourcing/snapshot/policy.rb +1 -0
  24. data/lib/synapse/event_sourcing/snapshot/taker.rb +31 -2
  25. data/lib/synapse/repository/repository.rb +18 -4
  26. data/lib/synapse/repository/simple_repository.rb +8 -17
  27. data/lib/synapse/serialization/converter.rb +2 -2
  28. data/lib/synapse/serialization/converter/identity.rb +2 -2
  29. data/lib/synapse/serialization/converter/json.rb +3 -3
  30. data/lib/synapse/serialization/converter/ox.rb +3 -3
  31. data/lib/synapse/serialization/message/metadata.rb +2 -2
  32. data/lib/synapse/serialization/message/serialization_aware.rb +2 -2
  33. data/lib/synapse/serialization/message/serialized_message.rb +7 -24
  34. data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
  35. data/lib/synapse/uow.rb +0 -1
  36. data/lib/synapse/uow/nesting.rb +2 -2
  37. data/lib/synapse/uow/uow.rb +12 -13
  38. data/lib/synapse/version.rb +1 -1
  39. data/test/configuration/component/event_sourcing/repository_test.rb +24 -1
  40. data/test/event_sourcing/caching_test.rb +118 -0
  41. data/test/event_sourcing/repository_test.rb +2 -1
  42. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +46 -16
  43. data/test/repository/locking_test.rb +1 -10
  44. data/test/serialization/serializer/attribute_test.rb +51 -0
  45. data/test/uow/uow_test.rb +9 -12
  46. metadata +12 -9
  47. data/lib/synapse/event_sourcing/storage_listener.rb +0 -34
  48. data/lib/synapse/uow/storage_listener.rb +0 -14
  49. data/test/event_sourcing/storage_listener_test.rb +0 -81
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = '0.5.3'
2
+ VERSION = '0.5.4'
3
3
  end
@@ -44,7 +44,7 @@ module Synapse
44
44
  Object.new
45
45
  end
46
46
 
47
- @builder.aggregate_snapshot_taker
47
+ @builder.snapshot_taker
48
48
  @builder.interval_snapshot_policy
49
49
 
50
50
  @builder.factory :conflict_resolver do
@@ -66,6 +66,29 @@ module Synapse
66
66
  assert_same snapshot_taker, repository.snapshot_taker
67
67
  end
68
68
 
69
+ should 'build caching repository if cache is set' do
70
+ @builder.simple_event_bus
71
+ @builder.factory :event_store do
72
+ Object.new
73
+ end
74
+
75
+ @builder.factory :cache do
76
+ Object.new
77
+ end
78
+
79
+ @builder.es_repository :account_repository do
80
+ use_aggregate_type Object
81
+ use_cache :cache
82
+ end
83
+
84
+ repository = @container.resolve :account_repository
85
+
86
+ cache = @container.resolve :cache
87
+
88
+ assert_instance_of EventSourcing::CachingEventSourcingRepository, repository
89
+ assert_same cache, repository.cache
90
+ end
91
+
69
92
  end
70
93
  end
71
94
  end
@@ -0,0 +1,118 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module EventSourcing
5
+ class CachingEventSourcingRepositoryTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @unit_provider = UnitOfWork::UnitOfWorkProvider.new
9
+ @unit = UnitOfWork::UnitOfWork.new @unit_provider
10
+ @unit.start
11
+ @factory = GenericAggregateFactory.new StubAggregate
12
+ @event_store = Object.new
13
+ @lock_manager = Repository::NullLockManager.new
14
+ @cache = Object.new
15
+
16
+ @repository = CachingEventSourcingRepository.new @factory, @event_store, @lock_manager
17
+ @repository.cache = @cache
18
+ @repository.event_bus = EventBus::SimpleEventBus.new
19
+ @repository.unit_provider = @unit_provider
20
+ end
21
+
22
+ should 'load from cache before hitting the event store' do
23
+ aggregate_id = SecureRandom.uuid
24
+ aggregate = StubAggregate.new aggregate_id
25
+
26
+ mock(@cache).fetch(aggregate_id) do
27
+ aggregate
28
+ end
29
+
30
+ assert_same aggregate, @repository.load(aggregate_id)
31
+ end
32
+
33
+ should 'raise an exception if the aggregate loaded from the cache is marked for deletion' do
34
+ aggregate_id = SecureRandom.uuid
35
+ aggregate = StubAggregate.new aggregate_id
36
+ aggregate.delete_me
37
+
38
+ mock(@cache).fetch(aggregate_id) do
39
+ aggregate
40
+ end
41
+
42
+ assert_raise AggregateDeletedError do
43
+ @repository.load aggregate_id
44
+ end
45
+ end
46
+
47
+ should 'load from the event store if cache miss' do
48
+ type_identifier = @factory.type_identifier
49
+ aggregate_id = SecureRandom.uuid
50
+
51
+ mock(@cache).fetch(aggregate_id)
52
+ mock(@event_store).read_events(type_identifier, aggregate_id) do
53
+ raise EventStore::StreamNotFoundError.new(type_identifier, aggregate_id)
54
+ end
55
+
56
+ assert_raise Repository::AggregateNotFoundError do
57
+ @repository.load aggregate_id
58
+ end
59
+ end
60
+
61
+ should 'clear the cache if the unit of work is rolled back' do
62
+ aggregate_id = SecureRandom.uuid
63
+ aggregate = StubAggregate.new aggregate_id
64
+
65
+ mock(@cache).fetch(aggregate_id) do
66
+ aggregate
67
+ end
68
+
69
+ mock(@cache).delete(aggregate_id)
70
+
71
+ @repository.load aggregate_id
72
+ @unit.rollback
73
+ end
74
+
75
+ should 'delete aggregate from cache when aggregate is deleted' do
76
+ type_identifier = @factory.type_identifier
77
+ aggregate_id = SecureRandom.uuid
78
+ aggregate = StubAggregate.new aggregate_id
79
+
80
+ mock(@cache).fetch(aggregate_id).ordered do
81
+ aggregate
82
+ end
83
+
84
+ mock(@event_store).append_events(type_identifier, anything).ordered
85
+
86
+ mock(@cache).write(aggregate_id, aggregate).ordered
87
+
88
+ loaded_aggregate = @repository.load aggregate_id
89
+ loaded_aggregate.delete_me
90
+
91
+ @unit.commit
92
+ end
93
+
94
+ should 'delete aggregate from cache when commit goes wrong' do
95
+ type_identifier = @factory.type_identifier
96
+ aggregate_id = SecureRandom.uuid
97
+ aggregate = StubAggregate.new aggregate_id
98
+
99
+ mock(@cache).fetch(aggregate_id).ordered do
100
+ aggregate
101
+ end
102
+
103
+ mock(@event_store).append_events(type_identifier, anything).ordered do
104
+ raise
105
+ end
106
+
107
+ mock(@cache).delete(aggregate_id).ordered
108
+
109
+ @repository.load aggregate_id
110
+
111
+ assert_raise RuntimeError do
112
+ @unit.commit
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -90,7 +90,8 @@ module Synapse
90
90
 
91
91
  should 'register a snapshot listener if a policy and taker are set' do
92
92
  @repository.snapshot_policy = IntervalSnapshotPolicy.new 30
93
- @repository.snapshot_taker = AggregateSnapshotTaker.new @event_store
93
+ @repository.snapshot_taker = AggregateSnapshotTaker.new
94
+ @repository.snapshot_taker.event_store = @event_store
94
95
 
95
96
  mock(@unit).register_listener(anything)
96
97
  mock(@unit).register_listener(is_a(SnapshotUnitOfWorkListener))
@@ -5,33 +5,63 @@ module Synapse
5
5
  module EventSourcing
6
6
 
7
7
  class AggregateSnapshotTakerTest < Test::Unit::TestCase
8
+ def setup
9
+ @event_store = Object.new
10
+ @aggregate_factory = GenericAggregateFactory.new StubAggregate
11
+
12
+ @snapshot_taker = AggregateSnapshotTaker.new
13
+ @snapshot_taker.event_store = @event_store
14
+ @snapshot_taker.register_factory @aggregate_factory
15
+ end
16
+
8
17
  should 'store a snapshot by serializing the aggregate itself' do
9
- event_store = Object.new
10
- aggregate_factory = GenericAggregateFactory.new StubAggregate
18
+ type_identifier = @aggregate_factory.type_identifier
19
+ id = SecureRandom.uuid
11
20
 
12
- type_identifier = aggregate_factory.type_identifier
13
- id = 123
21
+ events = Array.new
22
+ events.push create_domain_event StubCreatedEvent.new(id), 0, id
23
+ events.push create_domain_event StubChangedEvent.new, 1, id
24
+ events.push create_domain_event StubChangedEvent.new, 2, id
14
25
 
15
- event = Domain::DomainEventMessage.build do |builder|
16
- builder.payload = StubCreatedEvent.new id
17
- builder.sequence_number = 0
18
- builder.aggregate_id = id
19
- end
20
- stream = Domain::SimpleDomainEventStream.new event
26
+ stream = Domain::SimpleDomainEventStream.new events
21
27
 
22
- mock(event_store).read_events(type_identifier, id) do
28
+ mock(@event_store).read_events(type_identifier, id) do
23
29
  stream
24
30
  end
25
31
 
26
- mock(event_store).append_snapshot_event(type_identifier, anything) do |_, snapshot|
32
+ mock(@event_store).append_snapshot_event(type_identifier, anything) do |_, snapshot|
27
33
  assert_equal StubAggregate, snapshot.payload_type
28
- assert_equal 0, snapshot.sequence_number
34
+ assert_equal 2, snapshot.sequence_number
29
35
  assert_equal id, snapshot.aggregate_id
30
36
  end
31
37
 
32
- snapshot_taker = AggregateSnapshotTaker.new event_store
33
- snapshot_taker.register_factory aggregate_factory
34
- snapshot_taker.schedule_snapshot type_identifier, id
38
+ @snapshot_taker.schedule_snapshot type_identifier, id
39
+ end
40
+
41
+ should 'not store a snapshot if it replaces only one event' do
42
+ type_identifier = @aggregate_factory.type_identifier
43
+ id = SecureRandom.uuid
44
+
45
+ events = Array.new
46
+ events.push create_domain_event StubCreatedEvent.new(id), 0, id
47
+
48
+ stream = Domain::SimpleDomainEventStream.new events
49
+
50
+ mock(@event_store).read_events(type_identifier, id) do
51
+ stream
52
+ end
53
+
54
+ @snapshot_taker.schedule_snapshot type_identifier, id
55
+ end
56
+
57
+ private
58
+
59
+ def create_domain_event(payload, sequence_number, aggregate_id)
60
+ Domain::DomainEventMessage.build do |builder|
61
+ builder.payload = payload
62
+ builder.sequence_number = sequence_number
63
+ builder.aggregate_id = aggregate_id
64
+ end
35
65
  end
36
66
  end
37
67
 
@@ -6,16 +6,13 @@ module Synapse
6
6
  def setup
7
7
  @event_bus = Object.new
8
8
  @lock_manager = Object.new
9
- @storage_listener = Object.new
10
9
  @unit_provider = UnitOfWork::UnitOfWorkProvider.new
11
10
 
12
11
  @unit = UnitOfWork::UnitOfWork.new @unit_provider
13
12
  @unit.start
14
13
 
15
- # I herd you like dependencies
16
14
  @repository = TestRepository.new @lock_manager
17
15
  @repository.event_bus = @event_bus
18
- @repository.storage_listener = @storage_listener
19
16
  @repository.unit_provider = @unit_provider
20
17
  end
21
18
 
@@ -25,8 +22,6 @@ module Synapse
25
22
 
26
23
  aggregate = TestAggregateRoot.new 123, nil
27
24
 
28
- mock(@storage_listener).store(aggregate)
29
-
30
25
  @repository.add aggregate
31
26
  @unit.commit
32
27
  end
@@ -75,7 +70,7 @@ module Synapse
75
70
  end
76
71
 
77
72
  class TestRepository < LockingRepository
78
- attr_accessor :aggregate, :storage_listener
73
+ attr_accessor :aggregate
79
74
 
80
75
  protected
81
76
 
@@ -92,10 +87,6 @@ module Synapse
92
87
  def aggregate_type
93
88
  TestAggregateRoot
94
89
  end
95
-
96
- def storage_listener
97
- @storage_listener
98
- end
99
90
  end
100
91
  end
101
92
  end
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module Serialization
5
+ class AttributeSerializerTest < Test::Unit::TestCase
6
+
7
+ should 'support serialization and deserialization of a hash' do
8
+ converter_factory = ConverterFactory.new
9
+ serializer = AttributeSerializer.new converter_factory
10
+
11
+ content = {
12
+ foo: 0
13
+ }
14
+
15
+ serialized_object = serializer.serialize(content, Hash)
16
+ assert_same content, serialized_object.content
17
+ assert_same content, serializer.deserialize(serialized_object)
18
+
19
+ assert serializer.can_serialize_to? Hash
20
+ end
21
+
22
+ should 'support serialization and deserialization of a compatible object' do
23
+ converter_factory = ConverterFactory.new
24
+ serializer = AttributeSerializer.new converter_factory
25
+
26
+ content = SomeAttributeEvent.new 0
27
+
28
+ attributes = {
29
+ foo: 0
30
+ }
31
+
32
+ serialized_object = serializer.serialize(content, Hash)
33
+ assert_equal attributes, serialized_object.content
34
+
35
+ deserialized = serializer.deserialize serialized_object
36
+ assert_equal attributes, deserialized.attributes
37
+ end
38
+
39
+ end
40
+
41
+ class SomeAttributeEvent
42
+ attr_accessor :attributes
43
+
44
+ def initialize(some_value)
45
+ @attributes = {
46
+ foo: some_value
47
+ }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -37,12 +37,12 @@ module Synapse
37
37
  aggregate_d = TestAggregateB.new 3
38
38
 
39
39
  event_bus = Object.new
40
- storage_listener = Object.new
40
+ storage_callback = lambda {}
41
41
 
42
- assert_same aggregate_a, @uow.register_aggregate(aggregate_a, event_bus, storage_listener)
43
- assert_same aggregate_b, @uow.register_aggregate(aggregate_b, event_bus, storage_listener)
44
- assert_same aggregate_c, @uow.register_aggregate(aggregate_c, event_bus, storage_listener)
45
- assert_same aggregate_c, @uow.register_aggregate(aggregate_d, event_bus, storage_listener)
42
+ assert_same aggregate_a, @uow.register_aggregate(aggregate_a, event_bus, &storage_callback)
43
+ assert_same aggregate_b, @uow.register_aggregate(aggregate_b, event_bus, &storage_callback)
44
+ assert_same aggregate_c, @uow.register_aggregate(aggregate_c, event_bus, &storage_callback)
45
+ assert_same aggregate_c, @uow.register_aggregate(aggregate_d, event_bus, &storage_callback)
46
46
  end
47
47
 
48
48
  should 'interact with a transaction manager on commit' do
@@ -157,7 +157,7 @@ module Synapse
157
157
  end
158
158
  end
159
159
 
160
- should 'rollback if an aggregate storage listener raises an exception' do
160
+ should 'rollback if an aggregate storage callback raises an exception' do
161
161
  aggregate_root = Object.new
162
162
  mock(aggregate_root).add_registration_listener
163
163
  mock(aggregate_root).id
@@ -165,11 +165,6 @@ module Synapse
165
165
  event_bus = Object.new
166
166
  cause = TestError.new
167
167
 
168
- storage_listener = Object.new
169
- mock(storage_listener).store(aggregate_root) {
170
- raise cause
171
- }
172
-
173
168
  listener = UnitOfWorkListener.new
174
169
  mock(listener).on_prepare_commit(@uow, anything, anything)
175
170
  mock(listener).on_rollback(@uow, cause)
@@ -178,7 +173,9 @@ module Synapse
178
173
 
179
174
  @uow.start
180
175
  @uow.register_listener listener
181
- @uow.register_aggregate aggregate_root, event_bus, storage_listener
176
+ @uow.register_aggregate aggregate_root, event_bus do |aggregate|
177
+ raise cause
178
+ end
182
179
 
183
180
  assert_raises TestError do
184
181
  @uow.commit
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synapse-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-03 00:00:00.000000000 Z
12
+ date: 2013-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -86,12 +86,12 @@ files:
86
86
  - lib/synapse/uow/transaction_manager.rb
87
87
  - lib/synapse/uow/factory.rb
88
88
  - lib/synapse/uow/uow.rb
89
- - lib/synapse/uow/storage_listener.rb
90
89
  - lib/synapse/uow/listener_collection.rb
91
90
  - lib/synapse/uow/listener.rb
92
91
  - lib/synapse/uow/nesting.rb
93
92
  - lib/synapse/command/rollback_policy.rb
94
93
  - lib/synapse/command/interceptors/serialization.rb
94
+ - lib/synapse/command/callbacks/future.rb
95
95
  - lib/synapse/command/gateway/interval_retry_scheduler.rb
96
96
  - lib/synapse/command/gateway/retrying_callback.rb
97
97
  - lib/synapse/command/gateway/retry_scheduler.rb
@@ -119,13 +119,13 @@ files:
119
119
  - lib/synapse/repository/repository.rb
120
120
  - lib/synapse/auditing.rb
121
121
  - lib/synapse/event_sourcing/aggregate_factory.rb
122
+ - lib/synapse/event_sourcing/caching.rb
122
123
  - lib/synapse/event_sourcing/stream_decorator.rb
123
124
  - lib/synapse/event_sourcing/entity.rb
124
125
  - lib/synapse/event_sourcing/snapshot/aggregate_taker.rb
125
126
  - lib/synapse/event_sourcing/snapshot/policy.rb
126
127
  - lib/synapse/event_sourcing/snapshot/unit_listener.rb
127
128
  - lib/synapse/event_sourcing/snapshot/taker.rb
128
- - lib/synapse/event_sourcing/storage_listener.rb
129
129
  - lib/synapse/event_sourcing/member.rb
130
130
  - lib/synapse/event_sourcing/repository.rb
131
131
  - lib/synapse/event_sourcing/aggregate_root.rb
@@ -148,9 +148,9 @@ files:
148
148
  - lib/synapse/configuration/component/command_bus/gateway.rb
149
149
  - lib/synapse/configuration/component/repository/simple_repository.rb
150
150
  - lib/synapse/configuration/component/repository/locking_repository.rb
151
- - lib/synapse/configuration/component/event_sourcing/aggregate_snapshot_taker.rb
151
+ - lib/synapse/configuration/component/event_sourcing/snapshot/aggregate_taker.rb
152
+ - lib/synapse/configuration/component/event_sourcing/snapshot/interval_policy.rb
152
153
  - lib/synapse/configuration/component/event_sourcing/repository.rb
153
- - lib/synapse/configuration/component/event_sourcing/interval_snapshot_policy.rb
154
154
  - lib/synapse/configuration/component/upcasting/upcaster_chain.rb
155
155
  - lib/synapse/configuration/component/process_manager.rb
156
156
  - lib/synapse/configuration/component/process_manager/mapping_process_manager.rb
@@ -159,6 +159,7 @@ files:
159
159
  - lib/synapse/configuration/component/upcasting.rb
160
160
  - lib/synapse/configuration/component/serialization.rb
161
161
  - lib/synapse/configuration/component/uow.rb
162
+ - lib/synapse/configuration/component/mixin/thread_pool.rb
162
163
  - lib/synapse/configuration/component/repository.rb
163
164
  - lib/synapse/configuration/component/event_bus/simple_event_bus.rb
164
165
  - lib/synapse/configuration/component/serialization/converter_factory.rb
@@ -203,6 +204,7 @@ files:
203
204
  - lib/synapse/common/identifier.rb
204
205
  - lib/synapse/common/duplication.rb
205
206
  - lib/synapse/common/concurrency/public_lock.rb
207
+ - lib/synapse/common/concurrency/executor.rb
206
208
  - lib/synapse/common/concurrency/identifier_lock.rb
207
209
  - lib/synapse/common/message.rb
208
210
  - lib/synapse/common/errors.rb
@@ -266,11 +268,11 @@ files:
266
268
  - test/repository/optimistic_test.rb
267
269
  - test/repository/locking_test.rb
268
270
  - test/test_helper.rb
269
- - test/event_sourcing/storage_listener_test.rb
270
271
  - test/event_sourcing/repository_test.rb
271
272
  - test/event_sourcing/aggregate_root_test.rb
272
273
  - test/event_sourcing/snapshot/interval_policy_test.rb
273
274
  - test/event_sourcing/snapshot/aggregate_taker_test.rb
275
+ - test/event_sourcing/caching_test.rb
274
276
  - test/event_sourcing/entity_test.rb
275
277
  - test/event_sourcing/fixtures.rb
276
278
  - test/event_sourcing/aggregate_factory_test.rb
@@ -333,6 +335,7 @@ files:
333
335
  - test/serialization/lazy_object_test.rb
334
336
  - test/serialization/serializer/oj_test.rb
335
337
  - test/serialization/serializer/ox_test.rb
338
+ - test/serialization/serializer/attribute_test.rb
336
339
  - test/serialization/serializer/marshal_test.rb
337
340
  - test/serialization/serialized_object_test.rb
338
341
  - test/serialization/revision_resolver_test.rb
@@ -353,7 +356,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
353
356
  version: '0'
354
357
  segments:
355
358
  - 0
356
- hash: -4187832660438552395
359
+ hash: 293343002242626074
357
360
  required_rubygems_version: !ruby/object:Gem::Requirement
358
361
  none: false
359
362
  requirements:
@@ -362,7 +365,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
362
365
  version: '0'
363
366
  segments:
364
367
  - 0
365
- hash: -4187832660438552395
368
+ hash: 293343002242626074
366
369
  requirements: []
367
370
  rubyforge_project:
368
371
  rubygems_version: 1.8.25