happenings 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ddc6f910e11bfdc46fc02009443d8d946930daa7
4
+ data.tar.gz: afdf5f4d4c91fee91648729afac51447a305e157
5
+ SHA512:
6
+ metadata.gz: 6abb8f430a2b00daea4a13802c62207a42baf14f2835b9345be1b19f804f0d75723c63cf63eb83bf2b0302a2a630c14f656807d04f88a2a56b0cc8cc41d4cb5f
7
+ data.tar.gz: 99ecdb60c6c6d5a644149baf0465076ac15cdfc4d6138d2e00144b56da6547698f5d4c9ca616fc888a8cd02e47e0f68a55d32de02f162bac7aa73d8ab7abd56f
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in happenings.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Desmond Bowe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # Happenings
2
+
3
+ A light framework for building and publishing domain events.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'happenings'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install happenings
18
+
19
+ ## Basic Usage
20
+
21
+ Start by creating a Plain Old Ruby Object for your domain event and including the `Happening::Event` module.
22
+ You'll want to declare an initialize method that sets up any needed variables. Then, implement
23
+ a `#strategy` method and add your business logic there. This method will be called when your
24
+ Happening Event is run.
25
+
26
+ ```
27
+ class ResetPasswordEvent
28
+ include Happenings::Event
29
+
30
+ def initialize user, new_password, new_password_confirmation
31
+ @user = user
32
+ @new_password = new_password
33
+ @new_password_confirmation = new_password_confirmation
34
+ end
35
+
36
+ def strategy
37
+ if @new_password == @new_password_confirmation
38
+ @user.reset_password! @new_password
39
+ success! message: 'Password reset successfully'
40
+ else
41
+ failure! message: 'Password must match confirmation'
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
47
+ Run the event using the `#run` method as follows:
48
+
49
+ ```
50
+ event = ResetPasswordEvent.new(user, 'secret_password', 'secret_password')
51
+ if event.run!
52
+ # it worked, do something
53
+ flash[:notice] = event.message
54
+ else
55
+ # event failed for some reason
56
+ flash[:error] = event.message
57
+ end
58
+ ```
59
+
60
+ `#run!` will return Boolean `true` or `false` depending on the outcome of your strategy.
61
+ `#strategy` must return with `#success!` or `#failure!` or a `Happenings::OutcomeError` will
62
+ be raised.
63
+
64
+ ## Success, Failure
65
+ `#success!` and `#failure!` will set a `succeeded?` attribute and set optional keys for
66
+ `message` and `reason` attributes. `message` is meant for human-readable messages,
67
+ such as "Password reset failed", whereas `reason` is designed for machine-sortable
68
+ filtering, such as "confirmation\_mismatch". A `duration` attribute is also recorded.
69
+
70
+
71
+ ## Publishing
72
+ Happenings makes it easy to disseminate your business events to interested parties, such as
73
+ your analytics system, cache counters, or background workers. Happenings will swallow the
74
+ events by default, but it's recommended that you set a publisher in the configuration (see below).
75
+ This publisher must respond to a `publish` method that accepts two arguments: the
76
+ payload and a hash of additional info. This arrangement is geared towards a message broker like
77
+ RabbitMQ, but you can certainly write your own wrapper for another messaging bus like Redis.
78
+
79
+ Publishing happens automatically when `#run!` is called, regardless of the strategy outcome. The following methods are important:
80
+
81
+ `payload`: The main package of the event. defaults to `{}`, but should
82
+ be overridden in your event to include useful info such as the user id, changed attributes, etc.
83
+
84
+ `routing_key`: The routable description of the event. Defaults to `#{app_name}.#{event_name}.#{outcome}`, where outcome is either 'success' or 'failure'.
85
+
86
+ `event_name`: A machine-filterable version of the event. Defaults to the underscored class name.
87
+
88
+ Here's an expanded version of our Reset Password example above that includes publishing features:
89
+
90
+ ```
91
+ class MyEventPublisher
92
+ require 'bunny'
93
+
94
+ def initialize
95
+ @rabbitmq = Bunny.new
96
+ @rabbitmq.start
97
+ @rabbitmq_channel = @rabbitmq.create_channel
98
+ @events_exchange = @rabbitmq_channel.topic 'events', durable: true
99
+ end
100
+
101
+ def publish message, properties
102
+ @events_exchange.publish JSON.dump(message), properties
103
+ end
104
+ end
105
+
106
+
107
+ Happenings.configure do |config|
108
+ config.publisher = MyEventPublisher.new
109
+ config.app_name = 'my_app'
110
+ end
111
+
112
+ class ResetPasswordEvent
113
+ include Happenings::Event
114
+
115
+ attr_reader :user, :new_password, :new_password_confirmation
116
+
117
+ def initialize user, new_password, new_password_confirmation
118
+ @user = user
119
+ @new_password = new_password
120
+ @new_password_confirmation = new_password_confirmation
121
+ end
122
+
123
+ def strategy
124
+ ensure_passwords_match and
125
+ reset_user_password
126
+ end
127
+
128
+ def payload
129
+ { user: { id: user.id } }
130
+ end
131
+
132
+
133
+ private
134
+
135
+ def reset_user_password
136
+ user.reset_password! new_password
137
+ success! message: 'Password reset successfully'
138
+ end
139
+
140
+ def ensure_passwords_match
141
+ new_password == new_password_confirmation or
142
+ failure! message: 'Password must match confirmation'
143
+ end
144
+ end
145
+ ```
146
+
147
+ If the event is successful, `MyEventPublisher#publish` will receive the following parameters:
148
+ ```
149
+ message.inspect # => { user: { id: 2 },
150
+ event: 'reset_password_event',
151
+ reason: nil,
152
+ message: 'Password reset successfully',
153
+ duration: '0.0015',
154
+ succeeded: true }
155
+
156
+ properties.inspect # => { message_id: <SecureRandom.uuid>,
157
+ routing_key: 'my_app.reset_password_event.success',
158
+ timestamp: <Time.now.to_i> }
159
+ ```
160
+
161
+
162
+ ## Configuration
163
+ You can change the Happenings configuration by passing a block to the `.configure` method.
164
+ If you're using Happenings with Rails, a good place for this setup is in an
165
+ initializer such as `config/initializers/happenings.rb`:
166
+
167
+ ```
168
+ Happenings.configure do |config|
169
+ config.logger = your_logger
170
+ config.publisher = your_publisher
171
+ config.app_name = 'my awesome app'
172
+ config.socks = 'black socks'
173
+ end
174
+ ```
175
+
176
+ ## Requirements
177
+
178
+ * ActiveSupport (>= 2.3)
179
+
180
+ ## Contributing
181
+
182
+ 1. Fork it ( http://github.com/desmondmonster/happenings/fork )
183
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
184
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
185
+ 4. Push to the branch (`git push origin my-new-feature`)
186
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'happenings/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "happenings"
8
+ spec.version = Happenings::VERSION
9
+ spec.authors = ["Desmond Bowe"]
10
+ spec.email = ["desmondbowe@gmail.com"]
11
+ spec.summary = %q{Event-Driven Domain Scaffold}
12
+ spec.description = %q{For use in applications where business domain events are first-class citizens}
13
+ spec.homepage = "https://github.com/desmondmonster/happenings"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activesupport', '>= 2.3'
22
+ spec.add_development_dependency 'bundler', '~> 1.5'
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ end
@@ -0,0 +1,42 @@
1
+ module Happenings
2
+ class Config
3
+ require 'logger'
4
+
5
+ def initialize
6
+ set_default_attributes
7
+ end
8
+
9
+ def set_default_attributes
10
+ self.logger = default_logger
11
+ self.publisher = default_publisher
12
+ self.app_name = nil
13
+ end
14
+
15
+ def method_missing method, *args
16
+ if method =~ /=$/
17
+ attribute = method.to_s.sub '=', ''
18
+ self.class.instance_eval { attr_accessor attribute }
19
+ self.send method, args.first
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+
26
+ private
27
+
28
+ def default_logger
29
+ Logger.new $stdout
30
+ end
31
+
32
+ def default_publisher
33
+ NullPublisher
34
+ end
35
+
36
+ class NullPublisher
37
+ def self.publish payload, options
38
+ true
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,107 @@
1
+ module Happenings
2
+
3
+ class OutcomeError < StandardError; end
4
+
5
+ module Event
6
+
7
+ require 'active_support/inflector'
8
+
9
+ attr_reader :duration, :message, :reason, :succeeded
10
+
11
+ def run!
12
+ time do
13
+ strategy
14
+ end
15
+
16
+ raise OutcomeError.new 'no outcome specified' if no_outcome_specified?
17
+
18
+ publish
19
+
20
+ succeeded?
21
+ end
22
+
23
+ def strategy
24
+ success!
25
+ end
26
+
27
+ def success! options = {}
28
+ result true, options
29
+ end
30
+
31
+ def failure! options = {}
32
+ result false, options
33
+ end
34
+
35
+ def succeeded?
36
+ succeeded
37
+ end
38
+
39
+ def payload
40
+ {}
41
+ end
42
+
43
+ def routing_key
44
+ [routing_key_prefixes, event_name, outcome].compact.join '.'
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def result succeeded, options
51
+ @succeeded = succeeded
52
+ @message = options[:message]
53
+ @reason = options[:reason]
54
+ end
55
+
56
+ # overload in subclass.
57
+ def routing_key_prefixes
58
+
59
+ end
60
+
61
+ def app_name
62
+ warn "#app_name is deprecated"
63
+ Happenings.config.app_name && Happenings.config.app_name.gsub(' ', '').underscore
64
+ end
65
+
66
+ def event_name
67
+ self.class.to_s.split('::').last.underscore
68
+ end
69
+
70
+ def publish
71
+ # TODO: change to #reverse_merge because that puts the main payload first
72
+ Happenings.config.publisher.publish additional_info.merge(payload), properties
73
+ end
74
+
75
+ def properties
76
+ { message_id: SecureRandom.uuid,
77
+ routing_key: routing_key,
78
+ timestamp: Time.now.to_i }
79
+ end
80
+
81
+ def additional_info
82
+ { event: event_name,
83
+ reason: reason,
84
+ message: message,
85
+ succeeded: succeeded,
86
+ duration: formatted_duration }
87
+ end
88
+
89
+ def outcome
90
+ succeeded? ? 'success' : 'failure'
91
+ end
92
+
93
+ def no_outcome_specified?
94
+ succeeded.nil?
95
+ end
96
+
97
+ def formatted_duration
98
+ "%.6f" % @duration
99
+ end
100
+
101
+ def time
102
+ initial_time = Time.now.to_f
103
+ yield
104
+ @duration = Time.now.to_f - initial_time
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module Happenings
2
+ VERSION = "0.1.0"
3
+ end
data/lib/happenings.rb ADDED
@@ -0,0 +1,16 @@
1
+
2
+ require 'happenings/version'
3
+ require_relative 'happenings/config'
4
+ require_relative 'happenings/event'
5
+
6
+
7
+ module Happenings
8
+
9
+ def self.configure
10
+ yield config if block_given?
11
+ end
12
+
13
+ def self.config
14
+ @@config ||= Config.new
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'config' do
4
+
5
+ context 'with default settings' do
6
+ it 'uses the library defaults' do
7
+ expect(Happenings.config.logger).to be_a Logger
8
+ end
9
+ end
10
+
11
+ context 'when settings are specified' do
12
+ it 'uses the new settings' do
13
+ require 'logger'
14
+
15
+ class FooLogger < Logger; end
16
+
17
+ Happenings.configure do |config|
18
+ config.logger = FooLogger.new $stdout
19
+ end
20
+
21
+ expect(Happenings.config.logger).to be_a FooLogger
22
+ end
23
+ end
24
+
25
+ context 'when random settings are specified' do
26
+ it 'stores them in the config' do
27
+ Happenings.configure {|c| c.socks = 'pants'}
28
+ expect(Happenings.config.socks).to eq 'pants'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'a happening event' do
4
+
5
+ let(:event) { Event.new }
6
+
7
+ describe '#run!' do
8
+ context 'when no result action is called' do
9
+ before { class Event; def strategy; end; end }
10
+
11
+ it 'raises an exception' do
12
+ expect {event.run!}.to raise_error Happenings::OutcomeError
13
+ end
14
+ end
15
+
16
+ context 'when the strategy is successful' do
17
+ before do
18
+ class Event
19
+ def strategy
20
+ success! message: 'it worked'
21
+ end
22
+ end
23
+ end
24
+
25
+ it 'returns true' do
26
+ expect(event.run!).to be
27
+ expect(event).to be_succeeded
28
+ end
29
+
30
+ it 'records the duration of the run' do
31
+ event.run!
32
+ expect(event.duration).to be > 0.0
33
+ end
34
+
35
+ it 'sets the message' do
36
+ event.run!
37
+ expect(event.message).to eq 'it worked'
38
+ end
39
+
40
+ it 'publishes the event' do
41
+ Happenings.config.publisher.should_receive :publish
42
+ event.run!
43
+ end
44
+ end
45
+
46
+ context 'when the strategy is unsuccessful' do
47
+ before do
48
+ class Event
49
+ def strategy
50
+ failure! message: 'it did not work'
51
+ end
52
+ end
53
+ end
54
+
55
+ it 'returns false' do
56
+ expect(event.run!).not_to be
57
+ expect(event).not_to be_succeeded
58
+ end
59
+
60
+ it 'records the duration of the run' do
61
+ event.run!
62
+ expect(event.duration).to be > 0.0
63
+ end
64
+
65
+ it 'sets the message' do
66
+ event.run!
67
+ expect(event.message).to eq 'it did not work'
68
+ end
69
+
70
+ it 'publishes the event' do
71
+ Happenings.config.publisher.should_receive :publish
72
+ event.run!
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'event publishing' do
78
+ let(:user) { User.new }
79
+ let(:password) { 'password' }
80
+ let(:payload) do
81
+ { user: { id: 2 },
82
+ event: 'reset_password_event',
83
+ reason: nil,
84
+ message: message,
85
+ succeeded: succeeded }
86
+ end
87
+ let(:properties) { { routing_key: "reset_password_event.#{outcome}" } }
88
+
89
+ context 'when the strategy is successful' do
90
+ let(:confirmation) { password }
91
+ let(:succeeded) { true }
92
+ let(:message) { 'Password reset successfully' }
93
+ let(:outcome) { 'success' }
94
+
95
+ it 'publishes the event' do
96
+ Happenings.config.publisher.should_receive(:publish)
97
+ .with(hash_including(:duration, payload), hash_including(:message_id, :timestamp, properties))
98
+
99
+ ResetPasswordEvent.new(user, password, confirmation).run!
100
+ end
101
+ end
102
+
103
+ context 'when the strategy is unsuccessful' do
104
+ let(:confirmation) { 'not the password' }
105
+ let(:succeeded) { false }
106
+ let(:message) { 'Password must match confirmation' }
107
+ let(:outcome) { 'failure' }
108
+
109
+ it 'publishes the event' do
110
+ Happenings.config.publisher.should_receive(:publish)
111
+ .with(hash_including(:duration, payload), hash_including(:message_id, :timestamp, properties))
112
+
113
+ ResetPasswordEvent.new(user, password, confirmation).run!
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+
2
+ require 'bundler/setup'
3
+ Bundler.require(:default, :development)
4
+
5
+
6
+ # This file was generated by the `rspec --init` command. Conventionally, all
7
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
8
+ # Require this file using `require "spec_helper"` to ensure that it is only
9
+ # loaded once.
10
+ #
11
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
12
+
13
+ Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f}
14
+
15
+ RSpec.configure do |config|
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+ end
@@ -0,0 +1,7 @@
1
+ class Event
2
+ include Happenings::Event
3
+
4
+ def strategy
5
+ end
6
+
7
+ end
@@ -0,0 +1,35 @@
1
+ class ResetPasswordEvent
2
+
3
+ include Happenings::Event
4
+
5
+ attr_reader :user, :new_password, :new_password_confirmation
6
+
7
+ def initialize user, new_password, new_password_confirmation
8
+ @user = user
9
+ @new_password = new_password
10
+ @new_password_confirmation = new_password_confirmation
11
+ end
12
+
13
+ def strategy
14
+ ensure_passwords_match and
15
+ reset_user_password
16
+ end
17
+
18
+ def payload
19
+ { user: { id: user.id } }
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def reset_user_password
26
+ user.reset_password! new_password
27
+ success! message: 'Password reset successfully'
28
+ end
29
+
30
+ def ensure_passwords_match
31
+ new_password == new_password_confirmation or
32
+ failure! message: 'Password must match confirmation'
33
+ end
34
+ end
35
+
@@ -0,0 +1,10 @@
1
+ class User
2
+
3
+ def id
4
+ 2
5
+ end
6
+
7
+ def reset_password! password
8
+ true
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: happenings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Desmond Bowe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: For use in applications where business domain events are first-class
84
+ citizens
85
+ email:
86
+ - desmondbowe@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - happenings.gemspec
98
+ - lib/happenings.rb
99
+ - lib/happenings/config.rb
100
+ - lib/happenings/event.rb
101
+ - lib/happenings/version.rb
102
+ - spec/happenings/configuration_spec.rb
103
+ - spec/happenings/event_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/support/event.rb
106
+ - spec/support/reset_password_event.rb
107
+ - spec/support/user.rb
108
+ homepage: https://github.com/desmondmonster/happenings
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.0.3
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Event-Driven Domain Scaffold
132
+ test_files:
133
+ - spec/happenings/configuration_spec.rb
134
+ - spec/happenings/event_spec.rb
135
+ - spec/spec_helper.rb
136
+ - spec/support/event.rb
137
+ - spec/support/reset_password_event.rb
138
+ - spec/support/user.rb