bcdd-result 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -2
  3. data/CHANGELOG.md +99 -16
  4. data/README.md +618 -247
  5. data/Rakefile +1 -1
  6. data/Steepfile +4 -4
  7. data/examples/multiple_listeners/Rakefile +55 -0
  8. data/examples/multiple_listeners/app/models/account/member.rb +10 -0
  9. data/examples/multiple_listeners/app/models/account/owner_creation.rb +62 -0
  10. data/examples/multiple_listeners/app/models/account.rb +11 -0
  11. data/examples/multiple_listeners/app/models/user/creation.rb +67 -0
  12. data/examples/multiple_listeners/app/models/user/token/creation.rb +51 -0
  13. data/examples/multiple_listeners/app/models/user/token.rb +7 -0
  14. data/examples/multiple_listeners/app/models/user.rb +15 -0
  15. data/examples/multiple_listeners/config/boot.rb +16 -0
  16. data/examples/multiple_listeners/config/initializers/bcdd.rb +9 -0
  17. data/examples/multiple_listeners/config.rb +27 -0
  18. data/examples/multiple_listeners/db/setup.rb +60 -0
  19. data/examples/multiple_listeners/lib/bcdd/result/event_logs_record.rb +27 -0
  20. data/examples/multiple_listeners/lib/bcdd/result/rollback_on_failure.rb +15 -0
  21. data/examples/multiple_listeners/lib/event_logs_listener/stdout.rb +60 -0
  22. data/examples/multiple_listeners/lib/runtime_breaker.rb +11 -0
  23. data/examples/service_objects/Rakefile +36 -0
  24. data/examples/service_objects/app/models/account/member.rb +10 -0
  25. data/examples/service_objects/app/models/account.rb +11 -0
  26. data/examples/service_objects/app/models/user/token.rb +7 -0
  27. data/examples/service_objects/app/models/user.rb +15 -0
  28. data/examples/service_objects/app/services/account/owner_creation.rb +47 -0
  29. data/examples/service_objects/app/services/application_service.rb +79 -0
  30. data/examples/service_objects/app/services/user/creation.rb +56 -0
  31. data/examples/service_objects/app/services/user/token/creation.rb +37 -0
  32. data/examples/service_objects/config/boot.rb +17 -0
  33. data/examples/service_objects/config/initializers/bcdd.rb +9 -0
  34. data/examples/service_objects/config.rb +20 -0
  35. data/examples/service_objects/db/setup.rb +49 -0
  36. data/examples/single_listener/Rakefile +92 -0
  37. data/examples/single_listener/app/models/account/member.rb +10 -0
  38. data/examples/single_listener/app/models/account/owner_creation.rb +62 -0
  39. data/examples/single_listener/app/models/account.rb +11 -0
  40. data/examples/single_listener/app/models/user/creation.rb +67 -0
  41. data/examples/single_listener/app/models/user/token/creation.rb +51 -0
  42. data/examples/single_listener/app/models/user/token.rb +7 -0
  43. data/examples/single_listener/app/models/user.rb +15 -0
  44. data/examples/single_listener/config/boot.rb +16 -0
  45. data/examples/single_listener/config/initializers/bcdd.rb +9 -0
  46. data/examples/single_listener/config.rb +23 -0
  47. data/examples/single_listener/db/setup.rb +49 -0
  48. data/examples/single_listener/lib/bcdd/result/rollback_on_failure.rb +15 -0
  49. data/examples/single_listener/lib/runtime_breaker.rb +11 -0
  50. data/examples/single_listener/lib/single_event_logs_listener.rb +117 -0
  51. data/lib/bcdd/{result/context → context}/callable_and_then.rb +5 -4
  52. data/lib/bcdd/{result/context → context}/expectations/mixin.rb +3 -3
  53. data/lib/bcdd/{result/context → context}/expectations.rb +2 -2
  54. data/lib/bcdd/context/failure.rb +9 -0
  55. data/lib/bcdd/{result/context → context}/mixin.rb +4 -4
  56. data/lib/bcdd/context/success.rb +37 -0
  57. data/lib/bcdd/context.rb +91 -0
  58. data/lib/bcdd/failure.rb +23 -0
  59. data/lib/bcdd/result/_self.rb +198 -0
  60. data/lib/bcdd/result/callable_and_then/caller.rb +1 -1
  61. data/lib/bcdd/result/config/switchers/addons.rb +2 -2
  62. data/lib/bcdd/result/config/switchers/constant_aliases.rb +1 -3
  63. data/lib/bcdd/result/config/switchers/features.rb +5 -5
  64. data/lib/bcdd/result/config/switchers/pattern_matching.rb +1 -1
  65. data/lib/bcdd/result/config.rb +9 -2
  66. data/lib/bcdd/result/contract/for_types.rb +1 -1
  67. data/lib/bcdd/result/contract/for_types_and_values.rb +2 -0
  68. data/lib/bcdd/result/contract/type_checker.rb +4 -0
  69. data/lib/bcdd/result/event_logs/config.rb +28 -0
  70. data/lib/bcdd/result/event_logs/listener.rb +51 -0
  71. data/lib/bcdd/result/event_logs/listeners.rb +87 -0
  72. data/lib/bcdd/result/event_logs/tracking/disabled.rb +15 -0
  73. data/lib/bcdd/result/event_logs/tracking/enabled.rb +161 -0
  74. data/lib/bcdd/result/event_logs/tracking.rb +26 -0
  75. data/lib/bcdd/result/{transitions → event_logs}/tree.rb +46 -4
  76. data/lib/bcdd/result/event_logs.rb +27 -0
  77. data/lib/bcdd/result/expectations/mixin.rb +2 -2
  78. data/lib/bcdd/result/failure.rb +1 -3
  79. data/lib/bcdd/result/ignored_types.rb +14 -0
  80. data/lib/bcdd/result/mixin.rb +2 -2
  81. data/lib/bcdd/result/success.rb +1 -3
  82. data/lib/bcdd/result/version.rb +1 -1
  83. data/lib/bcdd/result.rb +25 -191
  84. data/lib/bcdd/success.rb +23 -0
  85. data/sig/bcdd/context.rbs +175 -0
  86. data/sig/bcdd/failure.rbs +13 -0
  87. data/sig/bcdd/result/config.rbs +1 -2
  88. data/sig/bcdd/result/context.rbs +2 -165
  89. data/sig/bcdd/result/contract.rbs +1 -0
  90. data/sig/bcdd/result/event_logs.rbs +189 -0
  91. data/sig/bcdd/result/ignored_types.rbs +9 -0
  92. data/sig/bcdd/result.rbs +14 -32
  93. data/sig/bcdd/success.rbs +13 -0
  94. metadata +75 -22
  95. data/lib/bcdd/result/context/failure.rb +0 -9
  96. data/lib/bcdd/result/context/success.rb +0 -19
  97. data/lib/bcdd/result/context.rb +0 -93
  98. data/lib/bcdd/result/failure/methods.rb +0 -21
  99. data/lib/bcdd/result/success/methods.rb +0 -21
  100. data/lib/bcdd/result/transitions/tracking/disabled.rb +0 -27
  101. data/lib/bcdd/result/transitions/tracking/enabled.rb +0 -100
  102. data/lib/bcdd/result/transitions/tracking.rb +0 -20
  103. data/lib/bcdd/result/transitions.rb +0 -28
  104. data/sig/bcdd/result/transitions.rbs +0 -100
@@ -5,9 +5,9 @@ class BCDD::Result
5
5
  module Addons
6
6
  AFFECTS = %w[
7
7
  BCDD::Result.mixin
8
- BCDD::Result::Context.mixin
8
+ BCDD::Context.mixin
9
9
  BCDD::Result::Expectations.mixin
10
- BCDD::Result::Context::Expectations.mixin
10
+ BCDD::Context::Expectations.mixin
11
11
  ].freeze
12
12
 
13
13
  OPTIONS = {
@@ -4,9 +4,7 @@ class BCDD::Result
4
4
  class Config
5
5
  module ConstantAliases
6
6
  MAPPING = {
7
- 'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result },
8
- 'Context' => { target: ::Object, name: :Context, value: ::BCDD::Result::Context },
9
- 'BCDD::Context' => { target: ::BCDD, name: :Context, value: ::BCDD::Result::Context }
7
+ 'Result' => { target: ::Object, name: :Result, value: ::BCDD::Result }
10
8
  }.transform_values!(&:freeze).freeze
11
9
 
12
10
  OPTIONS = MAPPING.to_h do |option_name, mapping|
@@ -6,20 +6,20 @@ class BCDD::Result
6
6
  OPTIONS = {
7
7
  expectations: {
8
8
  default: true,
9
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
9
+ affects: %w[BCDD::Result::Expectations BCDD::Context::Expectations]
10
10
  },
11
- transitions: {
11
+ event_logs: {
12
12
  default: true,
13
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
13
+ affects: %w[BCDD::Result BCDD::Context BCDD::Result::Expectations BCDD::Context::Expectations]
14
14
  },
15
15
  and_then!: {
16
16
  default: false,
17
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
17
+ affects: %w[BCDD::Result BCDD::Context BCDD::Result::Expectations BCDD::Context::Expectations]
18
18
  }
19
19
  }.transform_values!(&:freeze).freeze
20
20
 
21
21
  Listener = ->(option_name, _bool) do
22
- Thread.current[Transitions::THREAD_VAR_NAME] = nil if option_name == :transitions
22
+ Thread.current[EventLogs::THREAD_VAR_NAME] = nil if option_name == :event_logs
23
23
  end
24
24
 
25
25
  def self.switcher
@@ -6,7 +6,7 @@ class BCDD::Result
6
6
  OPTIONS = {
7
7
  nil_as_valid_value_checking: {
8
8
  default: false,
9
- affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
9
+ affects: %w[BCDD::Result::Expectations BCDD::Context::Expectations]
10
10
  }
11
11
  }.transform_values!(&:freeze).freeze
12
12
 
@@ -9,8 +9,6 @@ require_relative 'config/switchers/pattern_matching'
9
9
 
10
10
  class BCDD::Result
11
11
  class Config
12
- include Singleton
13
-
14
12
  attr_reader :addon, :feature, :constant_alias, :pattern_matching
15
13
 
16
14
  def initialize
@@ -21,6 +19,10 @@ class BCDD::Result
21
19
  @and_then_ = CallableAndThen::Config.new
22
20
  end
23
21
 
22
+ def event_logs
23
+ EventLogs::Config.instance
24
+ end
25
+
24
26
  def and_then!
25
27
  @and_then_
26
28
  end
@@ -31,6 +33,7 @@ class BCDD::Result
31
33
  constant_alias.freeze
32
34
  pattern_matching.freeze
33
35
  and_then!.freeze
36
+ event_logs.freeze
34
37
 
35
38
  super
36
39
  end
@@ -53,5 +56,9 @@ class BCDD::Result
53
56
  "options=#{options.keys.sort.inspect} " \
54
57
  "and_then!=#{and_then!.options.inspect}>"
55
58
  end
59
+
60
+ @instance = new
61
+
62
+ singleton_class.send(:attr_reader, :instance)
56
63
  end
57
64
  end
@@ -11,7 +11,7 @@ class BCDD::Result
11
11
  end
12
12
 
13
13
  def type?(type)
14
- allowed_types.member?(type)
14
+ IgnoredTypes.include?(type) || allowed_types.member?(type)
15
15
  end
16
16
 
17
17
  def type!(type)
@@ -30,6 +30,8 @@ class BCDD::Result
30
30
  def type_and_value!(data)
31
31
  type, value = data.type, data.value
32
32
 
33
+ return value if IgnoredTypes.include?(type)
34
+
33
35
  value_checking = @types_and_values[type!(type)]
34
36
 
35
37
  checking_result = value_checking === value
@@ -10,6 +10,10 @@ module BCDD::Result::Contract
10
10
  @expectations = expectations
11
11
  end
12
12
 
13
+ def allow!(type)
14
+ expectations.type!(type)
15
+ end
16
+
13
17
  def allow?(types)
14
18
  validate(types, expected: expectations, allow_empty: false)
15
19
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::EventLogs
4
+ class Config
5
+ attr_reader :listener, :trace_id
6
+
7
+ def initialize
8
+ @trace_id = -> {}
9
+ @listener = Listener::Null.new
10
+ end
11
+
12
+ def listener=(arg)
13
+ Listener.kind?(arg) or raise ::ArgumentError, "#{arg.inspect} must be a #{Listener}"
14
+
15
+ @listener = arg
16
+ end
17
+
18
+ def trace_id=(arg)
19
+ raise ::ArgumentError, 'must be a lambda with arity 0' unless arg.is_a?(::Proc) && arg.lambda? && arg.arity.zero?
20
+
21
+ @trace_id = arg
22
+ end
23
+
24
+ @instance = new
25
+
26
+ singleton_class.send(:attr_reader, :instance)
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::EventLogs
4
+ module Listener
5
+ module ClassMethods
6
+ def around_event_logs?
7
+ false
8
+ end
9
+
10
+ def around_and_then?
11
+ false
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def self.extended(base)
20
+ base.extend(ClassMethods)
21
+ end
22
+
23
+ def self.kind?(arg)
24
+ (arg.is_a?(::Class) && arg < self) || (arg.is_a?(::Module) && arg.is_a?(self)) || arg.is_a?(Listeners::Chain)
25
+ end
26
+
27
+ def on_start(scope:); end
28
+
29
+ def around_event_logs(scope:)
30
+ yield
31
+ end
32
+
33
+ def around_and_then(scope:, and_then:)
34
+ yield
35
+ end
36
+
37
+ def on_record(record:); end
38
+
39
+ def on_finish(event_logs:); end
40
+
41
+ def before_interruption(exception:, event_logs:); end
42
+ end
43
+
44
+ module Listener::Null
45
+ extend Listener
46
+
47
+ def self.new
48
+ self
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::EventLogs
4
+ class Listeners
5
+ class Chain
6
+ include Listener
7
+
8
+ attr_reader :listeners
9
+
10
+ def initialize(list)
11
+ if list.empty? || list.any? { !Listener.kind?(_1) }
12
+ raise ArgumentError, "listeners must be a list of #{Listener}"
13
+ end
14
+
15
+ around_and_then = list.select(&:around_and_then?)
16
+ around_event_logs = list.select(&:around_event_logs?)
17
+
18
+ raise ArgumentError, 'only one listener can have around_and_then? == true' if around_and_then.size > 1
19
+ raise ArgumentError, 'only one listener can have around_event_logs? == true' if around_event_logs.size > 1
20
+
21
+ @listeners = { list: list, around_and_then: around_and_then[0], around_event_logs: around_event_logs[0] }
22
+ end
23
+
24
+ def new
25
+ list, around_and_then, around_event_logs = listeners[:list], nil, nil
26
+
27
+ instances = list.map do |item|
28
+ instance = item.new
29
+ around_and_then = instance if listener?(:around_and_then, instance)
30
+ around_event_logs = instance if listener?(:around_event_logs, instance)
31
+
32
+ instance
33
+ end
34
+
35
+ list.one? ? list[0].new : Listeners.send(:new, instances, around_and_then, around_event_logs)
36
+ end
37
+
38
+ private
39
+
40
+ def listener?(name, obj)
41
+ listener = listeners[name]
42
+
43
+ !listener.nil? && (obj.is_a?(listener) || obj == listener)
44
+ end
45
+ end
46
+
47
+ private_class_method :new
48
+
49
+ def self.[](*listeners)
50
+ Chain.new(listeners)
51
+ end
52
+
53
+ attr_reader :listeners, :around_and_then_listener, :around_event_logs_listener
54
+
55
+ private :listeners, :around_and_then_listener, :around_event_logs_listener
56
+
57
+ def initialize(listeners, around_and_then_listener, around_event_logs_listener)
58
+ @listeners = listeners
59
+ @around_and_then_listener = around_and_then_listener || Listener::Null
60
+ @around_event_logs_listener = around_event_logs_listener || Listener::Null
61
+ end
62
+
63
+ def on_start(scope:)
64
+ listeners.each { _1.on_start(scope: scope) }
65
+ end
66
+
67
+ def around_event_logs(scope:, &block)
68
+ around_event_logs_listener.around_event_logs(scope: scope, &block)
69
+ end
70
+
71
+ def around_and_then(scope:, and_then:, &block)
72
+ around_and_then_listener.around_and_then(scope: scope, and_then: and_then, &block)
73
+ end
74
+
75
+ def on_record(record:)
76
+ listeners.each { _1.on_record(record: record) }
77
+ end
78
+
79
+ def on_finish(event_logs:)
80
+ listeners.each { _1.on_finish(event_logs: event_logs) }
81
+ end
82
+
83
+ def before_interruption(exception:, event_logs:)
84
+ listeners.each { _1.before_interruption(exception: exception, event_logs: event_logs) }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::EventLogs
4
+ module Tracking::Disabled
5
+ def self.exec(_name, _desc)
6
+ EnsureResult[yield]
7
+ end
8
+
9
+ def self.record(result); end
10
+
11
+ def self.record_and_then(_type, _data)
12
+ yield
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDD::Result::EventLogs
4
+ class Tracking::Enabled
5
+ attr_accessor :tree, :records, :root_started_at, :listener
6
+
7
+ private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=, :listener, :listener=
8
+
9
+ def exec(name, desc)
10
+ event_log_node, scope = start(name, desc)
11
+
12
+ result = nil
13
+
14
+ listener.around_event_logs(scope: scope) do
15
+ result = EnsureResult[yield]
16
+ end
17
+
18
+ tree.move_to_root! if event_log_node.root?
19
+
20
+ finish(result)
21
+
22
+ result
23
+ rescue ::Exception => e
24
+ err!(e, event_log_node)
25
+ end
26
+
27
+ def err!(exception, event_log_node)
28
+ if event_log_node.root?
29
+ listener.before_interruption(exception: exception, event_logs: map_event_logs)
30
+
31
+ reset!
32
+ end
33
+
34
+ raise exception
35
+ end
36
+
37
+ def reset!
38
+ self.tree = Tracking::EMPTY_TREE
39
+ end
40
+
41
+ def record(result)
42
+ return if tree.frozen?
43
+
44
+ track(result, time: ::Time.now.getutc)
45
+ end
46
+
47
+ def record_and_then(type_arg, arg)
48
+ return yield if tree.frozen?
49
+
50
+ type = type_arg.instance_of?(::Method) ? :method : type_arg
51
+
52
+ current_and_then = { type: type, arg: arg }
53
+ current_and_then[:method_name] = type_arg.name if type == :method
54
+
55
+ tree.current.value[1] = current_and_then
56
+
57
+ scope, and_then = tree.current_value
58
+
59
+ result = nil
60
+
61
+ listener.around_and_then(scope: scope, and_then: and_then) { result = yield }
62
+
63
+ result
64
+ end
65
+
66
+ def reset_and_then!
67
+ return if tree.frozen?
68
+
69
+ tree.current.value[1] = Tracking::EMPTY_HASH
70
+ end
71
+
72
+ private
73
+
74
+ def start(name, desc)
75
+ name_and_desc = [name, desc]
76
+
77
+ tree.frozen? ? root_start(name_and_desc) : tree.insert!(name_and_desc)
78
+
79
+ scope = tree.current.value[0]
80
+
81
+ listener.on_start(scope: scope)
82
+
83
+ [tree.current, scope]
84
+ end
85
+
86
+ def finish(result)
87
+ node = tree.current
88
+
89
+ tree.move_up!
90
+
91
+ return unless node.root?
92
+
93
+ event_logs = map_event_logs
94
+
95
+ result.send(:event_logs=, event_logs)
96
+
97
+ listener.on_finish(event_logs: event_logs)
98
+
99
+ reset!
100
+ end
101
+
102
+ TreeNodeValueNormalizer = ->(id, (nam, des)) { [{ id: id, name: nam, desc: des }, Tracking::EMPTY_HASH] }
103
+
104
+ def root_start(name_and_desc)
105
+ self.root_started_at = now_in_milliseconds
106
+
107
+ self.listener = build_listener
108
+
109
+ self.records = []
110
+
111
+ self.tree = Tree.new(name_and_desc, normalizer: TreeNodeValueNormalizer)
112
+ end
113
+
114
+ def track(result, time:)
115
+ record = track_record(result, time)
116
+
117
+ records << record
118
+
119
+ listener.on_record(record: record)
120
+
121
+ record
122
+ end
123
+
124
+ def track_record(result, time)
125
+ result_data = result.data.to_h
126
+ result_data[:source] = result.send(:source)
127
+
128
+ root, = tree.root_value
129
+ parent, = tree.parent_value
130
+ current, and_then = tree.current_value
131
+
132
+ { root: root, parent: parent, current: current, result: result_data, and_then: and_then, time: time }
133
+ end
134
+
135
+ def now_in_milliseconds
136
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
137
+ end
138
+
139
+ def map_event_logs
140
+ duration = (now_in_milliseconds - root_started_at)
141
+
142
+ trace_id = Config.instance.trace_id.call
143
+
144
+ ids = { tree: tree.ids, matrix: tree.ids_matrix, level_parent: tree.ids_level_parent }
145
+
146
+ metadata = { duration: duration, trace_id: trace_id, ids: ids }
147
+
148
+ { version: Tracking::VERSION, records: records, metadata: metadata }
149
+ end
150
+
151
+ def build_listener
152
+ Config.instance.listener.new
153
+ rescue ::StandardError => e
154
+ err = "#{e.message} (#{e.class}); Backtrace: #{e.backtrace&.join(', ')}"
155
+
156
+ warn("Fallback to #{Listener::Null} because registered listener raised an exception: #{err}")
157
+
158
+ Listener::Null.new
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module EventLogs
5
+ module Tracking
6
+ require_relative 'tracking/enabled'
7
+ require_relative 'tracking/disabled'
8
+
9
+ VERSION = 1
10
+
11
+ EMPTY_ARRAY = [].freeze
12
+ EMPTY_HASH = {}.freeze
13
+ EMPTY_TREE = Tree.new(nil).freeze
14
+ EMPTY_IDS = { tree: EMPTY_ARRAY, matrix: EMPTY_HASH, level_parent: EMPTY_HASH }.freeze
15
+ EMPTY = {
16
+ version: VERSION,
17
+ records: EMPTY_ARRAY,
18
+ metadata: { duration: 0, ids: EMPTY_IDS, trace_id: nil }.freeze
19
+ }.freeze
20
+
21
+ def self.instance
22
+ ::BCDD::Result::Config.instance.feature.enabled?(:event_logs) ? Tracking::Enabled.new : Tracking::Disabled
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class BCDD::Result
4
- module Transitions
4
+ module EventLogs
5
5
  class Tree
6
6
  class Node
7
7
  attr_reader :id, :value, :parent, :normalizer, :children
@@ -89,10 +89,52 @@ class BCDD::Result
89
89
  move_to!(root)
90
90
  end
91
91
 
92
- NestedIds = ->(node) { [node.id, node.children.map(&NestedIds)] }
92
+ Ids = ->(node) { [node.id, node.children.map(&Ids)] }
93
93
 
94
- def nested_ids
95
- NestedIds[root]
94
+ def ids
95
+ Ids[root]
96
+ end
97
+
98
+ def ids_list
99
+ ids.flatten
100
+ end
101
+
102
+ IdsMatrix = ->(tree, row, col, memo, previous) do
103
+ last_row = previous[0]
104
+
105
+ tree.each_with_index do |node, index|
106
+ row = [(index + 1), last_row].max
107
+
108
+ id, leaf = node
109
+
110
+ memo[id] = previous == [row, col] ? [row, col + 1] : [row, col]
111
+
112
+ previous = memo[id]
113
+
114
+ IdsMatrix[leaf, row, col + 1, memo, previous]
115
+ end
116
+ end
117
+
118
+ def ids_matrix
119
+ current = [0, 0]
120
+
121
+ memo = { 0 => current }
122
+
123
+ IdsMatrix[ids[1], 1, 1, memo, current]
124
+
125
+ memo
126
+ end
127
+
128
+ IdsLevelParent = ->((id, node), parent = 0, level = 0, memo = {}) do
129
+ memo[id] = [level, parent]
130
+
131
+ node.each { |leaf| IdsLevelParent[leaf, id, level + 1, memo] }
132
+
133
+ memo
134
+ end
135
+
136
+ def ids_level_parent
137
+ IdsLevelParent[ids]
96
138
  end
97
139
  end
98
140
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module EventLogs
5
+ require_relative 'event_logs/listener'
6
+ require_relative 'event_logs/listeners'
7
+ require_relative 'event_logs/config'
8
+ require_relative 'event_logs/tree'
9
+ require_relative 'event_logs/tracking'
10
+
11
+ THREAD_VAR_NAME = :bcdd_result_event_logs_tracking
12
+
13
+ EnsureResult = ->(result) do
14
+ return result if result.is_a?(::BCDD::Result)
15
+
16
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: :event_logs)
17
+ end
18
+
19
+ def self.tracking
20
+ Thread.current[THREAD_VAR_NAME] ||= Tracking.instance
21
+ end
22
+ end
23
+
24
+ def self.event_logs(name: nil, desc: nil, &block)
25
+ EventLogs.tracking.exec(name, desc, &block)
26
+ end
27
+ end
@@ -38,13 +38,13 @@ class BCDD::Result
38
38
  module Addons
39
39
  module Continue
40
40
  private def Continue(value)
41
- Success.new(type: :continued, value: value, source: self)
41
+ _Result.Success(IgnoredTypes::CONTINUE, value)
42
42
  end
43
43
  end
44
44
 
45
45
  module Given
46
46
  private def Given(value)
47
- Success.new(type: :given, value: value, source: self)
47
+ _Result.Success(IgnoredTypes::GIVEN, value)
48
48
  end
49
49
  end
50
50
 
@@ -2,9 +2,7 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Failure < self
5
- require_relative 'failure/methods'
6
-
7
- include Methods
5
+ include ::BCDD::Failure
8
6
  end
9
7
 
10
8
  def self.Failure(type, value = nil)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module IgnoredTypes
5
+ LIST = ::Set[
6
+ GIVEN = :_given_,
7
+ CONTINUE = :_continue_
8
+ ].freeze
9
+
10
+ def self.include?(type)
11
+ LIST.member?(type)
12
+ end
13
+ end
14
+ end
@@ -32,13 +32,13 @@ class BCDD::Result
32
32
  end
33
33
 
34
34
  private def Continue(value)
35
- _ResultAs(Success, :continued, value)
35
+ _ResultAs(Success, IgnoredTypes::CONTINUE, value)
36
36
  end
37
37
  end
38
38
 
39
39
  module Given
40
40
  private def Given(value)
41
- _ResultAs(Success, :given, value)
41
+ _ResultAs(Success, IgnoredTypes::GIVEN, value)
42
42
  end
43
43
  end
44
44
 
@@ -2,9 +2,7 @@
2
2
 
3
3
  class BCDD::Result
4
4
  class Success < self
5
- require_relative 'success/methods'
6
-
7
- include Methods
5
+ include ::BCDD::Success
8
6
  end
9
7
 
10
8
  def self.Success(type, value = nil)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.12.0'
5
+ VERSION = '1.0.0'
6
6
  end
7
7
  end