rule_box 0.1.3 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 329170f893810594f25f10974e41e1955e5b53d96864233bfe63351fc075f41f
4
- data.tar.gz: 9970c839c1d7ab16076c9abd5d851d85b9ea9ca6fe8591e89d891926c0cdb62a
3
+ metadata.gz: 27b80b3f8ec0341fbd5540a97820428a7473fb2efe3bc6d9120f485cd481c878
4
+ data.tar.gz: 4b44f91e618f1e77e830bb9853d9b1fc6cd066aea51505b04491a3684f866b53
5
5
  SHA512:
6
- metadata.gz: bf1c291c01a00d47ee38c4231eee1012bded36a983c688038e56bb962e85174600b7a1d7155cbeea9978960cd92a0e8942b9413582d5cf24b3a2fa9e52fd30b2
7
- data.tar.gz: 86606f7fede5d0a29beb1bd58a7c920a1c336061ff4500052fdc7fd206633026930e32146d9f4a9cd3a8e8f3b52565dfe45009963ebf581d7f3851175dfb25c9
6
+ metadata.gz: ad5848afa5b2b38485f7a4eff85dccca01e8f67fea7bbe42516b23c6e57a0c8bfec265881fcc14783297740bcbf611c2d8feb8ec10c489a13f4bfbe43e1e5ab5
7
+ data.tar.gz: 1d597a2071d512d353a2b0c0a8d9e5e301297a4d447c054c72ca6286d489452bdfe0966a35e4ce7deb3b16366f2b34aa105a3b727b4ef06cc4f3dc899ccdac23
data/Gemfile CHANGED
@@ -6,4 +6,10 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rake', '~> 12.0'
9
- gem 'rspec', '~> 3.0'
9
+
10
+ group :development, :test do
11
+ gem 'byebug', '~> 11.1', '>= 11.1.3'
12
+ gem 'rspec', '~> 3.0'
13
+ gem 'rubocop', '~> 1.28', '>= 1.28.2', require: false
14
+ gem 'simplecov', '~> 0.21.2', require: false
15
+ end
data/Gemfile.lock CHANGED
@@ -1,34 +1,65 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rule_box (0.1.3)
4
+ rule_box (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- diff-lcs (1.4.4)
9
+ ast (2.4.2)
10
+ byebug (11.1.3)
11
+ diff-lcs (1.5.0)
12
+ docile (1.4.0)
13
+ parallel (1.22.1)
14
+ parser (3.1.2.0)
15
+ ast (~> 2.4.1)
16
+ rainbow (3.1.1)
10
17
  rake (12.3.3)
11
- rspec (3.9.0)
12
- rspec-core (~> 3.9.0)
13
- rspec-expectations (~> 3.9.0)
14
- rspec-mocks (~> 3.9.0)
15
- rspec-core (3.9.2)
16
- rspec-support (~> 3.9.3)
17
- rspec-expectations (3.9.2)
18
+ regexp_parser (2.4.0)
19
+ rexml (3.2.5)
20
+ rspec (3.11.0)
21
+ rspec-core (~> 3.11.0)
22
+ rspec-expectations (~> 3.11.0)
23
+ rspec-mocks (~> 3.11.0)
24
+ rspec-core (3.11.0)
25
+ rspec-support (~> 3.11.0)
26
+ rspec-expectations (3.11.0)
18
27
  diff-lcs (>= 1.2.0, < 2.0)
19
- rspec-support (~> 3.9.0)
20
- rspec-mocks (3.9.1)
28
+ rspec-support (~> 3.11.0)
29
+ rspec-mocks (3.11.1)
21
30
  diff-lcs (>= 1.2.0, < 2.0)
22
- rspec-support (~> 3.9.0)
23
- rspec-support (3.9.3)
31
+ rspec-support (~> 3.11.0)
32
+ rspec-support (3.11.0)
33
+ rubocop (1.29.0)
34
+ parallel (~> 1.10)
35
+ parser (>= 3.1.0.0)
36
+ rainbow (>= 2.2.2, < 4.0)
37
+ regexp_parser (>= 1.8, < 3.0)
38
+ rexml (>= 3.2.5, < 4.0)
39
+ rubocop-ast (>= 1.17.0, < 2.0)
40
+ ruby-progressbar (~> 1.7)
41
+ unicode-display_width (>= 1.4.0, < 3.0)
42
+ rubocop-ast (1.17.0)
43
+ parser (>= 3.1.1.0)
44
+ ruby-progressbar (1.11.0)
45
+ simplecov (0.21.2)
46
+ docile (~> 1.1)
47
+ simplecov-html (~> 0.11)
48
+ simplecov_json_formatter (~> 0.1)
49
+ simplecov-html (0.12.3)
50
+ simplecov_json_formatter (0.1.4)
51
+ unicode-display_width (2.1.0)
24
52
 
25
53
  PLATFORMS
26
54
  ruby
27
55
 
28
56
  DEPENDENCIES
57
+ byebug (~> 11.1, >= 11.1.3)
29
58
  rake (~> 12.0)
30
59
  rspec (~> 3.0)
60
+ rubocop (~> 1.28, >= 1.28.2)
31
61
  rule_box!
62
+ simplecov (~> 0.21.2)
32
63
 
33
64
  BUNDLED WITH
34
- 2.1.4
65
+ 2.3.13
data/README.pt-br.md CHANGED
@@ -59,7 +59,7 @@ Existem alguns *helpers* na classe *Strategy* que irão ajudá-lo.
59
59
 
60
60
  ```ruby
61
61
  strategy.model # Retorna o "Model"
62
- strategy.set_status # Seta o valor semáforo: [:red, :yellow, :green]
62
+ strategy.set_status # Seta o valor status: [:red, :yellow, :green]
63
63
  strategy.add_error # Adiciona uma mensagem de erro
64
64
  ```
65
65
 
@@ -92,7 +92,7 @@ class User
92
92
  attr_accessor :name
93
93
 
94
94
  # lista de regras de negócio
95
- rules_of_insert Rules::CheckName
95
+ rules Rules::CheckName
96
96
 
97
97
  end
98
98
  ```
@@ -103,21 +103,21 @@ require 'rule_box/facade'
103
103
 
104
104
  user = User.new
105
105
  facade = Rulebox::Facade.new
106
- facade.insert user
106
+ facade.exec user
107
107
  puts facade.status # :red
108
108
  puts facade.errors # ["Nome não pode ficar em branco"]
109
109
 
110
110
  user = User.new
111
111
  user.name = 'Ana'
112
112
  facade = Rulebox::Facade.new
113
- facade.insert user
113
+ facade.exec user
114
114
  puts facade.status # :red
115
115
  puts facade.errors # ["Nome deve conter pelo menos 4 caracteres"]
116
116
 
117
117
  user = User.new
118
118
  user.name = 'Alex'
119
119
  facade = Rulebox::Facade.new
120
- facade.insert user
120
+ facade.exec user
121
121
  puts facade.status # :green
122
122
  puts facade.errors # []
123
123
 
@@ -167,35 +167,68 @@ class User
167
167
  include RuleBox::Mapper
168
168
  attr_accessor :name, :age
169
169
 
170
- rules_of_insert Rules::CheckName, Rules::CheckAge, Rules::SaveModel
170
+ rules Rules::CheckName, Rules::CheckAge, Rules::SaveModel
171
171
 
172
172
  end
173
173
 
174
174
  ```
175
175
 
176
176
 
177
- ## Desenvolvimento
177
+ ## Hooks
178
+
179
+ Existem alguns hooks que podem auxiliar na chamadas das regras de negócio
180
+
181
+ | Hook | Descrição |
182
+ |------------------|----------------------------------|
183
+ | after_rule | depois de cada regra |
184
+ | after_rules | depois de todas as regras |
185
+ | around_rule | na execução de cada regra |
186
+ | around_rules | na execução de todas as regras |
187
+ | customize_result | customiza o retorno do resultado |
188
+ | before_rule | antes de cada regra |
189
+ | before_rules | antes de todas as regras |
190
+ | rescue_from | captura uma exeção |
191
+
192
+ Exemplo
193
+ ```ruby
194
+
195
+ User.before_rules { puts 'before all' }
196
+ User.before_rule do |facade|
197
+ puts 'before'
198
+ puts facade.steps.last
199
+ end
200
+ User.after_rule do |facade|
201
+ puts facade.steps.last
202
+ puts 'after'
203
+ end
204
+ User.before_rules { puts 'after all' }
178
205
 
179
- Uma vez no modo de desenvolvimento, ele pode exibir as etapas (steps) que ocorreram no Facade.
180
206
 
181
- ```ruby
182
207
  user = User.new
183
208
  user.name = 'Carlos'
184
209
  user.age = 19
185
210
 
186
211
  facade = RuleBox::Facade.new
187
- facade.show_steps = true
188
- facade.insert user
212
+ facade.exec user
189
213
 
190
214
  # Saída do Log
191
- # [2020-07-29T08:09:00.212-03:00] { method: insert, model: User, args: {} }
192
- # [2020-07-29T08:09:00.212-03:00] amount of rules 3
215
+ # before all
216
+ # before
193
217
  # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::CheckName.
218
+ # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::CheckName.
219
+ # after
220
+ # before
221
+ # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::CheckAge.
194
222
  # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::CheckAge.
223
+ # after
224
+ # before
195
225
  # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::SaveModel.
196
- # [2020-07-29T08:09:00.212-03:00] finalized the process on the facade.
226
+ # [2020-07-29T08:09:00.212-03:00] executing of rule: Rules::SaveModel.
227
+ # after
228
+ # after all
197
229
  ```
198
230
 
231
+
199
232
  ## License
200
233
 
201
234
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuleBox
4
+ module ExecutionHook
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ new_hooks = %i[
11
+ after_rule after_rules around_rule around_rules
12
+ customize_result
13
+ before_rule before_rules
14
+ ]
15
+
16
+ new_hooks.each do |rule|
17
+ define_method rule do |&block|
18
+ hooks[rule] = block
19
+ end
20
+ end
21
+
22
+ def call_hook(type, arg = nil)
23
+ block = hooks[type.to_sym]
24
+ return nil unless block.is_a? Proc
25
+
26
+ block.call(arg)
27
+ end
28
+
29
+ def clear_hooks!
30
+ @rescue_handlers = []
31
+ @hooks = {}
32
+ end
33
+
34
+ def has_hook?(name)
35
+ hooks[name.to_sym].is_a? Proc
36
+ end
37
+
38
+ def rescue_from(*klasses, &block)
39
+ klasses.each do |klass|
40
+ match = klass < Exception || klass == Exception
41
+ raise ArgumentError, "#{klass.inspect} must be an Exception class" unless match
42
+
43
+ rescue_handlers.concat [[klass, block]]
44
+ end
45
+ end
46
+
47
+ def rescue_handlers
48
+ @rescue_handlers ||= []
49
+ end
50
+
51
+ def resolve_exception!(facade)
52
+ rescue_handlers.each do |exception, block|
53
+ next unless facade.exception.is_a? exception
54
+
55
+ block.call facade
56
+ return true
57
+ end
58
+
59
+ nil
60
+ end
61
+
62
+ private
63
+
64
+ def hooks
65
+ @hooks ||= {}
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,201 +1,257 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class RuleBox::Facade
4
- attr_reader :model, :status, :data, :bucket, :errors, :steps, :executed, :_current_method
5
- attr_accessor :show_steps
6
-
7
- def initialize(**dependencies)
8
- errors = []
9
- settings.dependencies.each do |key, block|
10
- begin
11
- if dependencies.key? key
12
- value = dependencies[key]
13
- block&.call(value, errors)
14
- else
15
- errors << "missing keyword: #{key}"
16
- end
17
- rescue StandardError => e
18
- errors << e.message
3
+ module RuleBox
4
+ class Facade
5
+ include RuleBox::MethodHelper
6
+ include RuleBox::ExecutionHook
7
+
8
+ attr_reader :entity, :exception, :strategies, :current_strategy, :current_method, :bucket, :executed, :last_result,
9
+ :status
10
+
11
+ attr_clones :errors, :steps
12
+
13
+ def initialize(**dependencies)
14
+ set_dependencies! dependencies
15
+ @executed = false
16
+ end
17
+
18
+ def add_error(msg)
19
+ if msg.is_a? Array
20
+ msg.each { |message| add_error(message) }
21
+ else
22
+ @errors << msg
19
23
  end
20
24
  end
21
25
 
22
- raise errors.join("\n") unless errors.empty?
26
+ def exec(method = :perform, entity, **args)
27
+ perform method, entity, **args
28
+ end
23
29
 
24
- dependencies.each { |key, value| keys["current_#{key}".to_sym] = value }
25
- @executed = false
26
- @show_steps = settings.show_steps
27
- end
30
+ def get(key)
31
+ keys[key.to_s].clone
32
+ end
28
33
 
29
- def insert(model, **args)
30
- execute(:insert, model, args)
31
- end
34
+ def run
35
+ return unless @next_run
32
36
 
33
- def select(model, **args)
34
- execute(:select, model, args)
35
- end
37
+ send @next_run
38
+ true
39
+ end
36
40
 
37
- def update(model, **args)
38
- execute(:update, model, args)
39
- end
41
+ def set_status(status)
42
+ @status = status
43
+ end
40
44
 
41
- def delete(model, **args)
42
- execute(:delete, model, args)
43
- end
45
+ def instance_values
46
+ hash = {
47
+ current_strategy: @current_strategy&.instance_values,
48
+ strategies: (@strategies || []).map(&:instance_values)
49
+ }
44
50
 
45
- def _current_class
46
- @_current_class ||=
47
- if model.nil?
48
- nil
49
- elsif model.is_a?(Class) || model.is_a?(Module)
50
- model
51
- elsif model.is_a?(Symbol) || model.is_a?(String)
52
- Object.const_get RuleBox::Util.camelize(model.to_s)
53
- else
54
- Object.const_get model.class.name
55
- end
56
- end
51
+ keys.each { |k, v| hash[k.to_sym] = v.clone }
57
52
 
58
- def attributes
59
- attrs = {}
60
- %i[model status data bucket executed errors steps _current_method].each do |key|
61
- value = send key
62
- attrs[key] = value
53
+ %i[
54
+ entity exception current_class current_method bucket
55
+ executed last_result status errors steps
56
+ ].each { |key| hash[key] = send(key).clone }
57
+
58
+ hash
63
59
  end
64
- attrs
65
- end
66
60
 
67
- def to_json(**args)
68
- JSON.generate(as_json(args), args)
69
- end
61
+ private
70
62
 
71
- def as_json(**options)
72
- options.key?(:root) ? { self.class.name.to_sym => attributes } : attributes
73
- end
63
+ def add_step(value)
64
+ new_value = "[#{DateTime.now.strftime('%FT%T.%L%:z')}] #{value}"
65
+ @steps << new_value
66
+ end
74
67
 
75
- def to_s
76
- as_json(root: true).to_s
77
- end
68
+ def around(around_method, current_method)
69
+ @next_run = current_method
70
+ hooks.each { |hook| hook.call_hook around_method, self }
71
+ @next_run = nil
72
+ end
78
73
 
79
- private
74
+ def build_bucket
75
+ {}
76
+ end
80
77
 
81
- def method_missing(method, *parameters)
82
- super if parameters.empty?
83
- super if method.to_s.end_with? '='
78
+ def check_executed!
79
+ raise 'Process already executed' if @executed
80
+ end
84
81
 
85
- model = parameters[0]
86
- args = parameters[1] || {}
87
- execute(method, model, args)
88
- end
82
+ def check_strategies!(class_strategies)
83
+ raise "class [#{current_class}] without mapped rules to [#{current_method}]'" if class_strategies.empty?
89
84
 
90
- def respond_to_missing?(method, _include_private = false)
91
- return false if @executed || method.to_s.end_with?('=')
85
+ classes = class_strategies.reject { |strategy| strategy < RuleBox::Strategy }
86
+ unless classes.size.zero?
87
+ raise "class [#{classes.map(&:name).join(', ')}] must extends RuleBox::Strategy or your subclass."
88
+ end
89
+ end
92
90
 
93
- super
94
- end
91
+ def current_class
92
+ entity.class
93
+ end
95
94
 
96
- def execute(method, model, args = {})
97
- raise 'Process already executed' if @executed
95
+ def execute_all
96
+ hooks.each { |h| h.call_hook(:before_rules, self) }
98
97
 
99
- @executed = true
100
- @status = :green
101
- @bucket = RuleBox::Hash.new
102
- args.each { |key, value| bucket[key] = value } if args.is_a? Hash
103
- pre_process(method, model)
104
- end
98
+ if hooks.any? { |h| h.has_hook? :around_rules }
99
+ around :around_rules, :execute_strategies
100
+ else
101
+ execute_strategies
102
+ end
103
+ hooks.each { |h| h.call_hook(:after_rules, self) }
104
+ end
105
105
 
106
- def pre_process(method, model)
107
- @model = model
108
- @_current_method = method
109
- class_name = _current_class
110
- @errors = []
111
- @steps = []
106
+ def execute_one
107
+ hooks.each { |h| h.call_hook(:before_rule, self) }
112
108
 
113
- add_step "{ method: #{method}, model: #{class_name}, args: #{bucket} }"
114
- strategies = class_name.strategies(method)&.map { |strategy| strategy.new(self) }
115
- unless strategies
116
- raise "class [#{class_name}] without mapped rules to [#{method}]'"
109
+ if hooks.any? { |h| h.has_hook? :around_rule }
110
+ around :around_rule, :execute_strategy
111
+ else
112
+ execute_strategy
113
+ end
114
+ hooks.each { |h| h.call_hook(:after_rule, self) }
117
115
  end
118
116
 
119
- process(strategies)
120
- end
117
+ def execute_strategies
118
+ @strategies.each do |strategy|
119
+ @current_strategy = strategy
120
+ execute_one
121
+ break if status == failure_status
122
+ end
123
+ rescue Exception => e
124
+ @exception = e
125
+ ensure
126
+ add_step 'finalized the process on the facade.'
127
+ end
121
128
 
122
- def process(strategies)
123
- add_step "amount of rules #{strategies.count}"
129
+ def execute_strategy
130
+ add_step "executing of rule: #{@current_strategy.class.name}."
124
131
 
125
- strategies.each do |strategy|
126
- add_step "executing of rule: #{strategy.class.name}."
127
- strategy.process
132
+ strategy_method = @current_strategy.method(:perform)
128
133
 
129
- break if status == :red
134
+ @last_result =
135
+ if strategy_method.parameters.empty?
136
+ strategy_method.call
137
+ else
138
+ strategy_method.call(@last_result)
139
+ end
130
140
  end
131
- rescue Exception => e
132
- errors << settings.default_error_message
133
- @status = :red
134
- block = settings.resolve
135
- block.call(e, RuleBox::Proxy.new(self)) if block.is_a? Proc
136
- ensure
137
- add_step 'finalized the process on the facade.'
138
- return self
139
- end
140
141
 
141
- def settings
142
- RuleBox::Facade.settings
143
- end
142
+ def failure_status
143
+ :red
144
+ end
144
145
 
145
- def add_step(value)
146
- new_value = "[#{DateTime.now.strftime('%FT%T.%L%:z')}] #{value}"
147
- puts new_value if show_steps || settings.show_steps
148
- steps << new_value
149
- end
146
+ def has_hook?(hook_method)
147
+ entity.class.has_hook? hook_method
148
+ end
150
149
 
151
- def marshal_dump
152
- instance_variables.map { |name| [name, instance_variable_get(name)] }.to_h
153
- end
150
+ def hooks
151
+ [current_class, self.class, RuleBox]
152
+ end
154
153
 
155
- def marshal_load(variables)
156
- variables.each { |key, value| instance_variable_set(key, value) }
157
- end
154
+ def initialize_variables!(method, entity, args)
155
+ @executed = true
156
+ @entity = entity
157
+ @current_method = method
158
+ @status = start_status
159
+ @bucket = build_bucket
160
+ @steps = []
161
+ @errors = []
162
+ args.each { |key, value| bucket[key] = value }
163
+ end
158
164
 
159
- def keys
160
- @keys ||= {}
161
- end
165
+ def initialize_strategies!(method, entity)
166
+ class_strategies = entity.class.strategies(method)
167
+ check_strategies!(class_strategies)
168
+ @strategies = class_strategies.map { |klass| klass.new(self) }
169
+ end
162
170
 
163
- # class Methods
164
- class << self
165
- def configure
166
- yield(settings) if block_given?
171
+ def keys
172
+ @keys ||= {}
167
173
  end
168
174
 
169
- def clear_configuration
170
- @settings = Settings.new
175
+ def perform(method, entity, **args)
176
+ check_executed!
177
+ initialize_variables!(method, entity, args)
178
+ initialize_strategies!(method, entity)
179
+ add_step "initialize { method: #{method}, entity: #{current_class}, rules: #{@strategies.count} }"
180
+ execute_all
181
+ resolve_exception!
182
+ return_result
171
183
  end
172
184
 
173
- def settings
174
- @settings ||= Settings.new
185
+ def return_result
186
+ result = nil
187
+ hooks.each do |hook|
188
+ result = hook.call_hook(:customize_result, self)
189
+ break if result
190
+ end
191
+
192
+ result || last_result
175
193
  end
176
194
 
177
- private
195
+ def resolve_exception!
196
+ return unless @exception.is_a?(Exception)
178
197
 
179
- class Settings
180
- attr_reader :dependencies, :resolve
181
- attr_writer :default_error_message
182
- attr_accessor :show_steps
198
+ resolved = hooks.map { |hook| hook.resolve_exception!(self) }.compact
199
+ raise @exception if resolved.length.zero?
200
+ end
183
201
 
184
- def initialize
185
- @dependencies = {}
186
- @show_steps = false
187
- end
202
+ def set(key, value)
203
+ keys[key.to_s] = value
204
+ end
188
205
 
206
+ def set_dependencies!(dependencies)
207
+ self.class.set_dependencies(self, dependencies)
208
+ end
209
+
210
+ def start_status
211
+ :green
212
+ end
213
+
214
+ # class Methods
215
+ class << self
189
216
  def add_dependency(key, &block)
190
217
  dependencies[key.to_sym] = block
191
218
  end
192
219
 
193
- def resolver_exception(&block)
194
- @resolve = block
220
+ def configure(&block)
221
+ block.call(self)
195
222
  end
196
223
 
197
- def default_error_message
198
- @default_error_message || 'unhandled error'
224
+ def clear_configuration!
225
+ @dependencies = {}
226
+ clear_hooks!
227
+ end
228
+
229
+ def set_dependencies(facade, dependencies)
230
+ errors = []
231
+
232
+ self.dependencies.each do |key, block|
233
+ if dependencies.key? key
234
+ value = dependencies[key]
235
+ block&.call(value, errors)
236
+ facade.send(:set, key, value)
237
+ else
238
+ errors << "missing keyword: #{key}"
239
+ end
240
+ rescue StandardError => e
241
+ errors << e.message
242
+ end
243
+
244
+ raise errors.join("\n") unless errors.empty?
245
+ end
246
+
247
+ def use_case!
248
+ define_method(:use_case) { entity }
249
+ end
250
+
251
+ private
252
+
253
+ def dependencies
254
+ @dependencies ||= {}
199
255
  end
200
256
  end
201
257
  end
@@ -1,62 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RuleBox::Mapper
4
- def self.included(klass)
5
- klass.extend(ClassMethods)
6
- mapped << klass
7
- end
8
-
9
- def self.mapped
10
- @mapped ||= Set.new
11
- end
12
-
13
- module ClassMethods
14
- def rules_of_insert(*rules)
15
- add_rules :insert, rules
16
- end
17
-
18
- def rules_of_update(*rules)
19
- add_rules :update, rules
3
+ module RuleBox
4
+ module Mapper
5
+ extend RuleBox::ExecutionHook
6
+
7
+ if const_defined? 'ActiveSupport::Concern'
8
+ # to Rails project
9
+ extend ActiveSupport::Concern
10
+ included { include RuleBox::ExecutionHook }
11
+ else
12
+ def self.included(klass)
13
+ klass.include RuleBox::ExecutionHook
14
+ klass.extend ClassMethods
15
+ mapped << klass
16
+ end
20
17
  end
21
18
 
22
- def rules_of_delete(*rules)
23
- add_rules :delete, rules
19
+ def self.mapped
20
+ @mapped ||= Set.new
24
21
  end
25
22
 
26
- def rules_of_select(*rules)
27
- add_rules :select, rules
28
- end
23
+ module ClassMethods
24
+ def rules(*rules)
25
+ add_rules :perform, rules
26
+ end
29
27
 
30
- def rules_of(method, *rules)
31
- add_rules method.to_sym, rules
32
- end
28
+ def rules_of(method, *rules)
29
+ add_rules method.to_sym, rules
30
+ end
33
31
 
34
- def strategies(method)
35
- current_rules[method]
36
- end
32
+ def strategies(method)
33
+ current_rules[method]
34
+ end
37
35
 
38
- def show_strategies
39
- current_rules.map do |method, strategies|
40
- {
41
- method: method,
42
- strategies: strategies.map do |strategy|
43
- {
44
- name: strategy.name,
45
- description: strategy.description
46
- }
47
- end
48
- }
36
+ def show_strategies
37
+ current_rules.map do |method, strategies|
38
+ {
39
+ method: method,
40
+ strategies: strategies.map do |strategy|
41
+ {
42
+ name: strategy.name,
43
+ description: strategy.description
44
+ }
45
+ end
46
+ }
47
+ end
49
48
  end
50
- end
51
49
 
52
- private
50
+ private
53
51
 
54
- def add_rules(method, rules)
55
- current_rules[method] = rules
56
- end
52
+ def add_rules(method, rules)
53
+ current_rules[method] = rules
54
+ end
57
55
 
58
- def current_rules
59
- @current_rules ||= {}
56
+ def current_rules
57
+ @current_rules ||= {}
58
+ end
60
59
  end
61
60
  end
62
61
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuleBox
4
+ module MethodHelper
5
+ def self.included(klass)
6
+ klass.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def attr_clones(*methods, to: nil)
11
+ methods.each do |method|
12
+ define_method(method) do
13
+ resource = to || self
14
+ resource.instance_variable_get("@#{method}").clone
15
+ end
16
+ end
17
+ end
18
+
19
+ def delegate_methods(*methods, to:, set_private: false)
20
+ methods.each do |method|
21
+ define_method method do |*rest, **restkey|
22
+ object = send to
23
+ object.send method, *rest, **restkey
24
+ end
25
+
26
+ private method if set_private
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,45 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class RuleBox::Strategy
4
- include RuleBox::Getter
3
+ module RuleBox
4
+ class Strategy
5
+ include RuleBox::MethodHelper
6
+ attr_reader :facade
5
7
 
6
- def process
7
- raise 'Must implement this method'
8
- end
8
+ delegate_methods :add_error, :current_method, :bucket, :entity, :errors,
9
+ :executed, :last_result, :get, :set_status, :status, :steps, :use_case,
10
+ to: :facade, set_private: true
9
11
 
10
- def set_status(status)
11
- @facade.instance_variable_set :@status, status
12
- end
12
+ def initialize(facade = nil)
13
+ @facade = facade
14
+ end
13
15
 
14
- def add_error(msg)
15
- if msg.is_a? Array
16
- @facade.errors.concat(msg)
17
- else
18
- @facade.errors << msg
16
+ def perform
17
+ raise 'Must implement this method'
19
18
  end
20
- end
21
19
 
22
- def model
23
- @facade.model
24
- end
20
+ def instance_values
21
+ hash = { strategy_name: self.class.name }
22
+ instance_variables.each do |name|
23
+ next if name == :@facade
25
24
 
26
- def data
27
- @facade.data
28
- end
25
+ hash[name[1..]] = instance_variable_get(name)
26
+ end
29
27
 
30
- def data=(data)
31
- @facade.instance_variable_set :@data, data
32
- end
28
+ hash
29
+ end
33
30
 
34
- def bucket
35
- @facade.bucket
36
- end
31
+ class << self
32
+ attr_reader :description
37
33
 
38
- class << self
39
- attr_reader :description
34
+ def desc(description)
35
+ @description = description
36
+ end
40
37
 
41
- def desc(description)
42
- @description = description
38
+ def perform(&block)
39
+ define_method :perform do |result = nil|
40
+ instance_exec(result, &block)
41
+ end
42
+ end
43
43
  end
44
44
  end
45
45
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuleBox
4
+ class UseCaseBase
5
+ include RuleBox::Mapper
6
+
7
+ def initialize(**args)
8
+ args.each do |key, value|
9
+ key = key.to_s
10
+ attributes[key] = value if attribute_names.include? key
11
+ end
12
+ end
13
+
14
+ def to_s
15
+ names = self.class.name.split('::').reverse.join
16
+ names.gsub(/::/, '/')
17
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
18
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
19
+ .tr('-', '_')
20
+ .downcase
21
+ end
22
+
23
+ def attribute_names
24
+ []
25
+ end
26
+
27
+ def attributes
28
+ @attributes ||= {}
29
+ end
30
+
31
+ class << self
32
+ def attributes(*names)
33
+ names = names.map(&:to_s)
34
+ define_method(:attribute_names) { names }
35
+
36
+ names.each do |name|
37
+ define_method(name) { attributes[name] }
38
+ define_method("#{name}=") { |value| attributes[name] = value }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuleBox
4
- VERSION = '0.1.3'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/rule_box.rb CHANGED
@@ -2,22 +2,34 @@
2
2
 
3
3
  require 'time'
4
4
  require 'json'
5
+ require 'set'
6
+
7
+ require 'rule_box/execution_hook'
5
8
 
6
9
  module RuleBox
10
+ include RuleBox::ExecutionHook
7
11
  class Error < StandardError; end
8
12
 
9
13
  class << self
10
14
  def show_mapped_classes
11
15
  RuleBox::Mapper.mapped.to_a
12
16
  end
17
+
18
+ def configure
19
+ block_given? ? yield(settings) : settings
20
+ end
21
+
22
+ private
23
+
24
+ def settings
25
+ self
26
+ end
13
27
  end
14
28
  end
15
29
 
30
+ require 'rule_box/method_helper'
16
31
  require 'rule_box/facade'
17
- require 'rule_box/getter'
18
- require 'rule_box/hash'
19
32
  require 'rule_box/mapper'
20
- require 'rule_box/proxy'
21
33
  require 'rule_box/strategy'
22
- require 'rule_box/util'
34
+ require 'rule_box/use_case_base'
23
35
  require 'rule_box/version'
data/rule_box.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'This gem is focused in giving a strong and concrete way to manage your business rules, mixing the best of both worlds in Facade and Strategy, bringing you a simplified way to apply these Design Patterns into your project.'
13
13
  spec.homepage = 'https://github.com/ralphsbaesso/rule_box'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
16
16
 
17
17
  # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
18
18
 
@@ -25,6 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
27
  end
28
+ spec.files -= %w[.gitignore .rspec .rspec_status]
29
+
28
30
  spec.bindir = 'exe'
29
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
32
  spec.require_paths = ['lib']
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rule_box
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ralph Baesso
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-09-27 00:00:00.000000000 Z
12
+ date: 2022-08-09 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: This gem is focused in giving a strong and concrete way to manage your
15
15
  business rules, mixing the best of both worlds in Facade and Strategy, bringing
@@ -21,8 +21,6 @@ executables: []
21
21
  extensions: []
22
22
  extra_rdoc_files: []
23
23
  files:
24
- - ".gitignore"
25
- - ".rspec"
26
24
  - ".travis.yml"
27
25
  - CODE_OF_CONDUCT.md
28
26
  - Gemfile
@@ -34,13 +32,12 @@ files:
34
32
  - bin/console
35
33
  - bin/setup
36
34
  - lib/rule_box.rb
35
+ - lib/rule_box/execution_hook.rb
37
36
  - lib/rule_box/facade.rb
38
- - lib/rule_box/getter.rb
39
- - lib/rule_box/hash.rb
40
37
  - lib/rule_box/mapper.rb
41
- - lib/rule_box/proxy.rb
38
+ - lib/rule_box/method_helper.rb
42
39
  - lib/rule_box/strategy.rb
43
- - lib/rule_box/util.rb
40
+ - lib/rule_box/use_case_base.rb
44
41
  - lib/rule_box/version.rb
45
42
  - rule_box.gemspec
46
43
  homepage: https://github.com/ralphsbaesso/rule_box
@@ -57,14 +54,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
54
  requirements:
58
55
  - - ">="
59
56
  - !ruby/object:Gem::Version
60
- version: 2.3.0
57
+ version: 3.0.0
61
58
  required_rubygems_version: !ruby/object:Gem::Requirement
62
59
  requirements:
63
60
  - - ">="
64
61
  - !ruby/object:Gem::Version
65
62
  version: '0'
66
63
  requirements: []
67
- rubygems_version: 3.0.9
64
+ rubygems_version: 3.2.32
68
65
  signing_key:
69
66
  specification_version: 4
70
67
  summary: RuleBox
data/.gitignore DELETED
@@ -1,15 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- .idea/
11
-
12
- # rspec failure tracking
13
- .rspec_status
14
- /.rakeTasks
15
- /*.gem
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuleBox::Getter
4
- def initialize(facade)
5
- raise 'Must pass an "RFacade"' unless facade.is_a? RuleBox::Facade
6
-
7
- @facade = facade
8
- end
9
-
10
- def model
11
- model = @facade.model
12
- if model.is_a?(Class) || model.is_a?(Module)
13
- model.to_s
14
- else
15
- model.clone
16
- end
17
- end
18
-
19
- def status
20
- @facade.status
21
- end
22
-
23
- def data
24
- @facade.data.clone
25
- end
26
-
27
- def bucket
28
- @facade.bucket.clone
29
- end
30
-
31
- def errors
32
- @facade.errors.clone
33
- end
34
-
35
- def steps
36
- @facade.steps.clone
37
- end
38
-
39
- def _current_method
40
- @facade._current_method
41
- end
42
-
43
- def executed
44
- @facade.executed
45
- end
46
-
47
- def to_json(**args)
48
- @facade.to_json(args)
49
- end
50
-
51
- def as_json(**options)
52
- options.key?(:root) ? { self.class.name.to_sym => @facade.attributes } : @facade.attributes
53
- end
54
-
55
- def to_s
56
- @facade.as_json(root: true).to_s
57
- end
58
-
59
- def respond_to_missing?(*several_variants)
60
- super unless method.to_s.start_with? 'current_'
61
- end
62
-
63
- private
64
-
65
- def method_missing(method)
66
- super unless method.to_s.start_with? 'current_'
67
-
68
- keys = @facade.send :keys
69
- keys[method].clone
70
- end
71
- end
data/lib/rule_box/hash.rb DELETED
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RuleBox::Hash < Hash
4
- def []=(key, value)
5
- super(key.to_sym, value)
6
- end
7
-
8
- def [](key)
9
- super(key.to_sym)
10
- end
11
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RuleBox::Proxy
4
- include RuleBox::Getter
5
- end
data/lib/rule_box/util.rb DELETED
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuleBox::Util
4
- class << self
5
- def camelize(string)
6
- strings = string.split('_')
7
- new_string = ''
8
- strings.each do |word|
9
- new_string += word[0].upcase + word[1..-1]
10
- end
11
- new_string.gsub('/', '::')
12
- end
13
- end
14
- end