brule 0.3.0 → 0.3.1

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: 13f788107d09cf82f121f314df0fd7247e808d4895a369afc56315aceaa21e84
4
- data.tar.gz: 92d514d8305373969d48069d5627432d8d13e136ae0f38f1a94b1368ebbfb63c
3
+ metadata.gz: 67b697b560f27e984a2ec669e672dee5b1cab777071c88d1b0ea7f1d358e4067
4
+ data.tar.gz: 3d9c7e21ca6fecc1a8b3d1b9120e15894dfc7c7e5ecd055d24fb118fb9ee11e2
5
5
  SHA512:
6
- metadata.gz: fba662d4ee2caaffba41aba4368069c1ab1aab6730fcd0b84a73a7158a799e1e34149eac0f7e37627a56bf9c16ea0f4af26298e0ef15ce0a76df0487ddbcf895
7
- data.tar.gz: 4f798d703d68a4e109b19c69469e1c86c2d16762ba45bb3cacc5cbf8ec3fe61ea64d57e1d942428d488655ea24068a541f79875071d2b3578f66d13bec02432b
6
+ metadata.gz: b7b7e87db8e466cda35e0330774818f03a75ce5d925f4dcdc394e0bdb1751a5037141bf4490cd602434d3ee56caee16526b40b3fb3fb9d8ddf4e19224a029032
7
+ data.tar.gz: 21d8166b5a3aac5cc63ef8cd1414bd2defc05eab1548d85697081d1c3d678ffc42cf9ac960399a61308db6fb99fa60e7e60610a4490ea25d1b33aa8dad13b649
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- This project aims at providing a set of tools to build somehow complex
2
- business processes or rules. It helps when:
1
+ This project aims at providing a set of tools to build complex and evolving
2
+ business rules. It helps when:
3
3
 
4
- * You decided to split the process into smaller parts working together.
5
- * You need to keep the rules evolving while maintaining the old versions.
6
- * You want to persist the rules that lead to the result of a process.
4
+ * You decided to split the computation into smaller parts working together.
5
+ * You need to keep old versions of the rules working.
6
+ * You want to persist the rules leading to a result.
7
7
  * You want to make your tests independent from your production data.
8
- * You want to introspect what lead to the result of a process.
8
+ * You want to introspect the computation of a result.
9
9
 
10
10
  ## How does it work
11
11
 
@@ -50,7 +50,7 @@ module Pricing
50
50
  end
51
51
 
52
52
  class OrderTotal < Brule::Rule
53
- config_reader :unit_price, :item_count
53
+ context_reader :unit_price, :item_count
54
54
  context_writer :price
55
55
 
56
56
  def apply
@@ -1,98 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module Brule
6
- module RuleHelpers
7
- def context_reader(*symbols)
8
- symbols.each do |symbol|
9
- define_method(symbol) { context.fetch(symbol) }
10
- end
11
- end
12
-
13
- def context_writer(*symbols)
14
- symbols.each do |symbol|
15
- define_method("#{symbol}=") { |value| context[symbol] = value }
16
- end
17
- end
18
-
19
- def context_accessor(*symbols)
20
- context_reader(*symbols)
21
- context_writer(*symbols)
22
- end
23
-
24
- def config_reader(*symbols)
25
- symbols.each do |symbol|
26
- define_method(symbol) { config.fetch(symbol) }
27
- end
28
- end
29
- end
30
-
31
- Rule = Struct.new(:config) do
32
- extend RuleHelpers
33
-
34
- attr_accessor :context
35
-
36
- def trigger?
37
- true
38
- end
39
-
40
- def apply
41
- raise NotImplementedError
42
- end
43
- end
44
-
45
- class Context
46
- extend Forwardable
47
-
48
- def_delegators :@content, :[], :fetch, :fetch_values, :key?, :[]=, :merge!
49
-
50
- def self.wrap(context)
51
- context.is_a?(self) ? context : new(context)
52
- end
53
-
54
- def initialize(hash = {})
55
- @content = hash
56
- end
57
-
58
- def initialize_copy(orig)
59
- super
60
- @content = orig.instance_variable_get(:@content).dup
61
- end
62
- end
63
-
64
- class Engine
65
- attr_reader :context
66
-
67
- def initialize(rules:)
68
- @rules = rules
69
- @history = {}
70
- end
71
-
72
- def call(context = {})
73
- @history = {}
74
- @context = Context.wrap(context)
75
- snapshot!(tag: :initial)
76
- @rules.each { |rule| apply(rule) }
77
- result
78
- end
79
-
80
- def history(key:)
81
- @history.map { |tag, content| [tag, content.fetch(key, nil)] }
82
- end
83
-
84
- private
85
-
86
- def snapshot!(tag:)
87
- @history[tag] = @context.dup
88
- end
89
-
90
- def apply(rule)
91
- rule.context = @context
92
- return unless rule.trigger?
93
-
94
- rule.apply
95
- snapshot!(tag: rule)
96
- end
97
- end
4
+ autoload :Context, 'brule/context'
5
+ autoload :Engine, 'brule/engine'
6
+ autoload :Rule, 'brule/rule'
7
+ autoload :Utils, 'brule/utils'
98
8
  end
@@ -0,0 +1,22 @@
1
+ require 'forwardable'
2
+
3
+ module Brule
4
+ class Context
5
+ extend Forwardable
6
+
7
+ def_delegators :@content, :[], :fetch, :fetch_values, :key?, :[]=, :merge!
8
+
9
+ def initialize(hash = {})
10
+ @content = hash
11
+ end
12
+
13
+ def initialize_copy(orig)
14
+ super
15
+ @content = orig.instance_variable_get(:@content).dup
16
+ end
17
+
18
+ def self.wrap(context)
19
+ context.is_a?(self) ? context : new(context)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Brule
2
+ class Engine
3
+ attr_reader :context
4
+
5
+ def initialize(rules:)
6
+ @rules = rules
7
+ @history = {}
8
+ end
9
+
10
+ def call(context = {})
11
+ @history = {}
12
+ @context = Context.wrap(context)
13
+ snapshot!(tag: :initial)
14
+ @rules.each { |rule| apply(rule) }
15
+ result
16
+ end
17
+
18
+ def history(key:)
19
+ @history.map { |tag, content| [tag, content.fetch(key, nil)] }
20
+ end
21
+
22
+ private
23
+
24
+ def snapshot!(tag:)
25
+ @history[tag] = @context.dup
26
+ end
27
+
28
+ def apply(rule)
29
+ rule.context = @context
30
+ return unless rule.trigger?
31
+
32
+ rule.apply
33
+ snapshot!(tag: rule.to_tag)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module Brule
2
+ Rule = Struct.new(:config) do
3
+ attr_accessor :context
4
+
5
+ def trigger?
6
+ true
7
+ end
8
+
9
+ def apply
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def to_tag
14
+ self
15
+ end
16
+
17
+ def self.context_reader(*symbols)
18
+ symbols.each do |symbol|
19
+ define_method(symbol) { context.fetch(symbol) }
20
+ end
21
+ end
22
+
23
+ def self.context_writer(*symbols)
24
+ symbols.each do |symbol|
25
+ define_method("#{symbol}=") { |value| context[symbol] = value }
26
+ end
27
+ end
28
+
29
+ def self.context_accessor(*symbols)
30
+ context_reader(*symbols)
31
+ context_writer(*symbols)
32
+ end
33
+
34
+ def self.config_reader(*symbols)
35
+ symbols.each do |symbol|
36
+ define_method(symbol) { config.fetch(symbol) }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ module Brule
2
+ module Utils
3
+ autoload :Either, 'brule/utils/either'
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'forwardable'
2
+
3
+ module Brule
4
+ module Utils
5
+ class Either < Brule::Rule
6
+ TooManyMatches = Class.new(StandardError)
7
+ NoMatchFound = Class.new(StandardError)
8
+
9
+ extend Forwardable
10
+
11
+ def_delegators :only_match, :apply, :trigger?, :to_tag
12
+
13
+ config_reader :rules
14
+
15
+ private
16
+
17
+ def only_match
18
+ matches = rules.select do |rule|
19
+ rule.context = context
20
+ rule.trigger?
21
+ end
22
+
23
+ if matches.size >= 2
24
+ raise TooManyMatches, "Rules #{matches.join(', ')} are all a match"
25
+ end
26
+
27
+ if matches.empty?
28
+ raise NoMatchFound, "No rules from #{rules.join(', ')} is a match"
29
+ end
30
+
31
+ matches.first
32
+ end
33
+ end
34
+ end
35
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brule
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolas Zermati
@@ -46,6 +46,11 @@ extra_rdoc_files: []
46
46
  files:
47
47
  - README.md
48
48
  - lib/brule.rb
49
+ - lib/brule/context.rb
50
+ - lib/brule/engine.rb
51
+ - lib/brule/rule.rb
52
+ - lib/brule/utils.rb
53
+ - lib/brule/utils/either.rb
49
54
  homepage: https://github.com/nicoolas25/brule-rb
50
55
  licenses:
51
56
  - MIT