active_projection 0.5.2 → 0.5.3
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 +4 -4
- data/MIT-LICENSE +19 -19
- data/README.md +7 -7
- data/app/models/active_projection/cached_projection_repository.rb +4 -4
- data/app/models/active_projection/projection.rb +5 -5
- data/app/models/active_projection/projection_repository.rb +45 -45
- data/db/migrate/01_create_projections.rb +10 -10
- data/lib/active_projection.rb +17 -17
- data/lib/active_projection/autoload.rb +11 -9
- data/lib/active_projection/event_client.rb +171 -171
- data/lib/active_projection/projection_type.rb +91 -90
- data/lib/active_projection/projection_type_registry.rb +26 -28
- data/lib/active_projection/railtie.rb +8 -8
- data/lib/active_projection/server.rb +65 -66
- data/lib/active_projection/version.rb +1 -1
- data/spec/factories/event_factory.rb +8 -8
- data/spec/lib/projection_type_registry_spec.rb +19 -19
- data/spec/lib/projecton_type_spec.rb +87 -87
- data/spec/models/projection_repository_spec.rb +30 -30
- data/spec/support/active_record.rb +40 -38
- metadata +4 -4
@@ -1,90 +1,91 @@
|
|
1
|
-
module ActiveProjection
|
2
|
-
module ProjectionType
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
class WrongArgumentsCountError < StandardError
|
5
|
-
end
|
6
|
-
included do
|
7
|
-
ProjectionTypeRegistry
|
8
|
-
end
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
self.handlers = Hash.new do |hash, key|
|
12
|
-
hash[key] = []
|
13
|
-
end
|
14
|
-
self.class.public_instance_methods(false).each do |method_name|
|
15
|
-
method = self.class.instance_method method_name
|
16
|
-
|
17
|
-
event_type = ProjectionType.method_name_to_event_type method_name
|
18
|
-
handlers[event_type] << [method_name, method.arity]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def evaluate(headers)
|
23
|
-
unless solid?
|
24
|
-
LOGGER.error "[#{self.class.name}] is broken"
|
25
|
-
return false
|
26
|
-
end
|
27
|
-
last_id = fetch_last_id
|
28
|
-
event_id = headers[:id]
|
29
|
-
case
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def invoke(event, headers)
|
43
|
-
event_id = headers[:id]
|
44
|
-
event_type = event.class.name.to_sym
|
45
|
-
handlers[event_type].each do |method, arity|
|
46
|
-
begin
|
47
|
-
if 1 == arity
|
48
|
-
|
49
|
-
else
|
50
|
-
|
51
|
-
end
|
52
|
-
rescue
|
53
|
-
LOGGER.error "[#{self.class.name}]: error processing #{event_type}[#{event_id}]\n#{e.message}\n#{e.backtrace}"
|
54
|
-
|
55
|
-
raise
|
56
|
-
end
|
57
|
-
end
|
58
|
-
update_last_id event_id
|
59
|
-
LOGGER.debug "[#{self.class.name}]: successfully processed #{event_type}[#{event_id}]"
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
1
|
+
module ActiveProjection
|
2
|
+
module ProjectionType
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
class WrongArgumentsCountError < StandardError
|
5
|
+
end
|
6
|
+
included do
|
7
|
+
ProjectionTypeRegistry.register(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
self.handlers = Hash.new do |hash, key|
|
12
|
+
hash[key] = []
|
13
|
+
end
|
14
|
+
self.class.public_instance_methods(false).each do |method_name|
|
15
|
+
method = self.class.instance_method method_name
|
16
|
+
fail WrongArgumentsCountError if 2 < method.arity || method.arity < 1
|
17
|
+
event_type = ProjectionType.method_name_to_event_type method_name
|
18
|
+
handlers[event_type] << [method_name, method.arity]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate(headers)
|
23
|
+
unless solid?
|
24
|
+
LOGGER.error "[#{self.class.name}] is broken"
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
last_id = fetch_last_id
|
28
|
+
event_id = headers[:id]
|
29
|
+
case
|
30
|
+
when last_id + 1 == event_id
|
31
|
+
true
|
32
|
+
when last_id >= event_id
|
33
|
+
LOGGER.debug "[#{self.class.name}]: event #{event_id} already processed"
|
34
|
+
false
|
35
|
+
when last_id < event_id
|
36
|
+
mark_broken
|
37
|
+
LOGGER.error "[#{self.class.name}]: #{event_id - last_id} events are missing"
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def invoke(event, headers)
|
43
|
+
event_id = headers[:id]
|
44
|
+
event_type = event.class.name.to_sym
|
45
|
+
handlers[event_type].each do |method, arity|
|
46
|
+
begin
|
47
|
+
if 1 == arity
|
48
|
+
send method, event
|
49
|
+
else
|
50
|
+
send method, event, headers
|
51
|
+
end
|
52
|
+
rescue => e
|
53
|
+
LOGGER.error "[#{self.class.name}]: error processing #{event_type}[#{event_id}]\n#{e.message}\n#{e.backtrace}"
|
54
|
+
mark_broken
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
end
|
58
|
+
update_last_id event_id
|
59
|
+
LOGGER.debug "[#{self.class.name}]: successfully processed #{event_type}[#{event_id}]"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_accessor :handlers
|
65
|
+
attr_writer :projection_id
|
66
|
+
|
67
|
+
def self.method_name_to_event_type(method_name)
|
68
|
+
method_name.to_s.gsub('__', '/').camelcase.to_sym
|
69
|
+
end
|
70
|
+
|
71
|
+
def projection_id
|
72
|
+
@projection_id ||= ProjectionRepository.ensure_exists(self.class.name).id
|
73
|
+
end
|
74
|
+
|
75
|
+
def solid?
|
76
|
+
ProjectionRepository.solid? projection_id
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_last_id
|
80
|
+
ProjectionRepository.last_id projection_id
|
81
|
+
end
|
82
|
+
|
83
|
+
def mark_broken
|
84
|
+
ProjectionRepository.mark_broken projection_id
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_last_id(id)
|
88
|
+
ProjectionRepository.set_last_id projection_id, id
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,28 +1,26 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
|
3
|
-
module ActiveProjection
|
4
|
-
class ProjectionTypeRegistry
|
5
|
-
include Singleton
|
6
|
-
|
7
|
-
# register a new projection class
|
8
|
-
#
|
9
|
-
# The best way to create a new projection is using the ProjectionType module
|
10
|
-
# This module automatically registers each class
|
11
|
-
def self.register(projection)
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
# @return an enumerable with all projections
|
16
|
-
def self.projections
|
17
|
-
instance.projections.each
|
18
|
-
end
|
19
|
-
|
20
|
-
def projections
|
21
|
-
@projections ||= self.class.registry.freeze.map { |projection_class| projection_class.new }.freeze
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module ActiveProjection
|
4
|
+
class ProjectionTypeRegistry
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
# register a new projection class
|
8
|
+
#
|
9
|
+
# The best way to create a new projection is using the ProjectionType module
|
10
|
+
# This module automatically registers each class
|
11
|
+
def self.register(projection)
|
12
|
+
registry << projection
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return an enumerable with all projections
|
16
|
+
def self.projections
|
17
|
+
instance.projections.each
|
18
|
+
end
|
19
|
+
|
20
|
+
def projections
|
21
|
+
@projections ||= self.class.registry.freeze.map { |projection_class| projection_class.new }.freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
cattr_accessor(:registry) { [] }
|
25
|
+
end
|
26
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'active_projection'
|
2
|
-
require 'rails'
|
3
|
-
|
4
|
-
module ActiveProjection
|
5
|
-
class Railtie < Rails::Railtie # :nodoc:
|
6
|
-
config.eager_load_namespaces << ActiveProjection
|
7
|
-
end
|
8
|
-
end
|
1
|
+
require 'active_projection'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ActiveProjection
|
5
|
+
class Railtie < Rails::Railtie # :nodoc:
|
6
|
+
config.eager_load_namespaces << ActiveProjection
|
7
|
+
end
|
8
|
+
end
|
@@ -1,66 +1,65 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
|
3
|
-
module ActiveProjection
|
4
|
-
class Server
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
cattr_accessor :
|
27
|
-
cattr_accessor :
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
attr_writer :
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
options
|
61
|
-
options
|
62
|
-
options
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module ActiveProjection
|
4
|
+
class Server
|
5
|
+
def self.run(options = nil)
|
6
|
+
new(options).run
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(new_options = nil)
|
10
|
+
self.options = new_options.deep_symbolize_keys! unless new_options.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
EventClient.start options
|
15
|
+
end
|
16
|
+
|
17
|
+
def env
|
18
|
+
@env = ENV['PROJECTION_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
19
|
+
end
|
20
|
+
|
21
|
+
def options
|
22
|
+
@options ||= parse_options(ARGV)
|
23
|
+
end
|
24
|
+
|
25
|
+
cattr_accessor :base_path
|
26
|
+
cattr_accessor :config_file
|
27
|
+
cattr_accessor :rails_config_file
|
28
|
+
|
29
|
+
def config_file
|
30
|
+
self.class.config_file ||= File.expand_path('config/disco.yml', base_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def rails_config_file
|
34
|
+
self.class.rails_config_file ||= File.expand_path('config/database.yml', base_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_writer :options
|
40
|
+
attr_writer :domain
|
41
|
+
|
42
|
+
def default_options
|
43
|
+
{
|
44
|
+
projection_database: {
|
45
|
+
adapter: 'sqlite3',
|
46
|
+
database: File.expand_path('db/production.sqlite3', base_path),
|
47
|
+
},
|
48
|
+
event_connection: {
|
49
|
+
scheme: 'amqp',
|
50
|
+
userinfo: nil,
|
51
|
+
host: '127.0.0.1',
|
52
|
+
port: 9797,
|
53
|
+
},
|
54
|
+
event_exchange: 'events',
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_options(_args)
|
59
|
+
options = default_options
|
60
|
+
options.merge! YAML.load_file(config_file)[env].deep_symbolize_keys! unless config_file.blank?
|
61
|
+
options[:projection_database] = YAML.load_file(rails_config_file)[env].deep_symbolize_keys! unless rails_config_file.blank?
|
62
|
+
options
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'factory_girl'
|
2
|
-
|
3
|
-
FactoryGirl.define do
|
4
|
-
factory :projection, class: ActiveProjection::Projection do
|
5
|
-
last_id 4
|
6
|
-
solid true
|
7
|
-
end
|
8
|
-
end
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
factory :projection, class: ActiveProjection::Projection do
|
5
|
+
last_id 4
|
6
|
+
solid true
|
7
|
+
end
|
8
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe ActiveProjection::ProjectionTypeRegistry do
|
4
|
-
class TestEvent
|
5
|
-
end
|
6
|
-
class TestProjection
|
7
|
-
end
|
8
|
-
before :all do
|
9
|
-
ActiveProjection::ProjectionTypeRegistry.register(TestProjection)
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'initializes' do
|
13
|
-
ActiveProjection::ProjectionTypeRegistry.instance.
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'report projections' do
|
17
|
-
ActiveProjection::ProjectionTypeRegistry.projections.count.
|
18
|
-
end
|
19
|
-
end
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe ActiveProjection::ProjectionTypeRegistry do
|
4
|
+
class TestEvent
|
5
|
+
end
|
6
|
+
class TestProjection
|
7
|
+
end
|
8
|
+
before :all do
|
9
|
+
ActiveProjection::ProjectionTypeRegistry.register(TestProjection)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'initializes' do
|
13
|
+
expect(ActiveProjection::ProjectionTypeRegistry.instance).to be
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'report projections' do
|
17
|
+
expect(ActiveProjection::ProjectionTypeRegistry.projections.count).to eq(1)
|
18
|
+
end
|
19
|
+
end
|