rails-patterns 0.6.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: rails-patterns 0.6.0 ruby lib
5
+ # stub: rails-patterns 0.10.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "rails-patterns".freeze
9
- s.version = "0.6.0"
9
+ s.version = "0.10.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Stevo".freeze]
14
- s.date = "2018-01-18"
14
+ s.date = "2021-12-10"
15
15
  s.description = "A collection of lightweight, standardized, rails-oriented patterns.".freeze
16
16
  s.email = "b.kosmowski@selleo.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  ]
21
21
  s.files = [
22
22
  ".document",
23
+ ".github/workflows/ruby.yml",
23
24
  ".rspec",
24
25
  "Gemfile",
25
26
  "Gemfile.lock",
@@ -32,19 +33,29 @@ Gem::Specification.new do |s|
32
33
  "lib/patterns/collection.rb",
33
34
  "lib/patterns/form.rb",
34
35
  "lib/patterns/query.rb",
36
+ "lib/patterns/rule.rb",
37
+ "lib/patterns/ruleset.rb",
35
38
  "lib/patterns/service.rb",
39
+ "lib/patterns/strong_ruleset.rb",
36
40
  "lib/rails-patterns.rb",
37
41
  "rails-patterns.gemspec",
42
+ "spec/helpers/custom_calculation.rb",
43
+ "spec/helpers/custom_calculation_script.rb",
44
+ "spec/helpers/rails_redis_cache_mock.rb",
38
45
  "spec/patterns/calculation_spec.rb",
39
46
  "spec/patterns/collection_spec.rb",
40
47
  "spec/patterns/form_spec.rb",
41
48
  "spec/patterns/query_spec.rb",
49
+ "spec/patterns/rule_spec.rb",
50
+ "spec/patterns/ruleset_spec.rb",
42
51
  "spec/patterns/service_spec.rb",
52
+ "spec/patterns/strong_ruleset_spec.rb",
43
53
  "spec/spec_helper.rb"
44
54
  ]
45
55
  s.homepage = "http://github.com/selleo/pattern".freeze
46
56
  s.licenses = ["MIT".freeze]
47
- s.rubygems_version = "2.6.10".freeze
57
+ s.required_ruby_version = Gem::Requirement.new(">= 2.5.0".freeze)
58
+ s.rubygems_version = "3.0.8".freeze
48
59
  s.summary = "A collection of lightweight, standardized, rails-oriented patterns.".freeze
49
60
 
50
61
  if s.respond_to? :specification_version then
@@ -54,24 +65,27 @@ Gem::Specification.new do |s|
54
65
  s.add_runtime_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
55
66
  s.add_runtime_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
56
67
  s.add_runtime_dependency(%q<virtus>.freeze, [">= 0"])
68
+ s.add_runtime_dependency(%q<ruby2_keywords>.freeze, [">= 0"])
57
69
  s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
58
- s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
59
- s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
70
+ s.add_development_dependency(%q<bundler>.freeze, ["~> 2.0"])
71
+ s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
60
72
  else
61
73
  s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
62
74
  s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
63
75
  s.add_dependency(%q<virtus>.freeze, [">= 0"])
76
+ s.add_dependency(%q<ruby2_keywords>.freeze, [">= 0"])
64
77
  s.add_dependency(%q<rspec>.freeze, [">= 0"])
65
- s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
66
- s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
78
+ s.add_dependency(%q<bundler>.freeze, ["~> 2.0"])
79
+ s.add_dependency(%q<juwelier>.freeze, [">= 0"])
67
80
  end
68
81
  else
69
82
  s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
70
83
  s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
71
84
  s.add_dependency(%q<virtus>.freeze, [">= 0"])
85
+ s.add_dependency(%q<ruby2_keywords>.freeze, [">= 0"])
72
86
  s.add_dependency(%q<rspec>.freeze, [">= 0"])
73
- s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
74
- s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
87
+ s.add_dependency(%q<bundler>.freeze, ["~> 2.0"])
88
+ s.add_dependency(%q<juwelier>.freeze, [">= 0"])
75
89
  end
76
90
  end
77
91
 
@@ -0,0 +1,16 @@
1
+ require 'active_support/all'
2
+ require 'active_support/testing/time_helpers'
3
+ require_relative 'rails_redis_cache_mock'
4
+ require_relative '../../lib/patterns/calculation'
5
+
6
+ CustomCalculation = Class.new(Patterns::Calculation) do
7
+ set_cache_expiry_every 1.hour
8
+ class_attribute :counter
9
+ self.counter = 0
10
+
11
+ private
12
+
13
+ def result
14
+ self.class.counter += 1
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'rails_redis_cache_mock'
2
+ require_relative 'custom_calculation'
3
+
4
+ CustomCalculation.result
@@ -0,0 +1,5 @@
1
+ class Rails
2
+ def self.cache
3
+ @cache ||= ActiveSupport::Cache::RedisCacheStore.new
4
+ end
5
+ end
@@ -14,6 +14,7 @@ RSpec.describe Patterns::Calculation do
14
14
  after do
15
15
  Object.send(:remove_const, :CustomCalculation) if defined?(CustomCalculation)
16
16
  Rails.cache.clear
17
+ ActiveSupport::Cache::RedisCacheStore.new.clear
17
18
  end
18
19
 
19
20
  describe ".result" do
@@ -59,6 +60,18 @@ RSpec.describe Patterns::Calculation do
59
60
 
60
61
  expect(CustomCalculation.result(nil, arg_1: 20, arg_2: 30)).to eq([20, 30])
61
62
  end
63
+
64
+ it 'executes calculation with given block' do
65
+ CustomCalculation = Class.new(Patterns::Calculation) do
66
+ private
67
+
68
+ def result
69
+ yield(subject)
70
+ end
71
+ end
72
+
73
+ expect(CustomCalculation.result(5) { |a| a * 3 }).to eq(15)
74
+ end
62
75
  end
63
76
 
64
77
  describe "caching" do
@@ -159,5 +172,41 @@ RSpec.describe Patterns::Calculation do
159
172
  expect(CustomCalculation.result).to eq 1
160
173
  expect(CustomCalculation.result).to eq 2
161
174
  end
175
+
176
+ describe "when RedisCacheStore is used" do
177
+ it "does not store data in cache if 'cache_expiry_period' is not set" do
178
+ client = Redis.new
179
+ class Rails
180
+ def self.cache
181
+ @cache ||= ActiveSupport::Cache::RedisCacheStore.new
182
+ end
183
+ end
184
+
185
+ CustomCalculation = Class.new(Patterns::Calculation) do
186
+ class_attribute :counter
187
+ self.counter = 0
188
+
189
+ private
190
+
191
+ def result
192
+ self.class.counter += 1
193
+ end
194
+ end
195
+
196
+ expect(CustomCalculation.result).to eq 1
197
+ expect(CustomCalculation.result).to eq 2
198
+ expect(client.keys).to be_empty
199
+ end
200
+ end
201
+
202
+ it "uses cache keys consistent between processes" do
203
+ `bundle exec ruby spec/helpers/custom_calculation.rb`
204
+ Process.spawn('bundle exec ruby spec/helpers/custom_calculation_script.rb')
205
+ Process.spawn('bundle exec ruby spec/helpers/custom_calculation_script.rb')
206
+ Process.spawn('bundle exec ruby spec/helpers/custom_calculation_script.rb')
207
+ Process.waitall
208
+
209
+ expect(Redis.new.keys.length).to eq 1
210
+ end
162
211
  end
163
212
  end
@@ -0,0 +1,44 @@
1
+ RSpec.describe Patterns::Rule do
2
+ after(:each) do
3
+ Object.send(:remove_const, :CustomRule) if defined?(CustomRule)
4
+ end
5
+
6
+ it 'requires subject as the first argument' do
7
+ CustomRule = Class.new(Patterns::Rule)
8
+
9
+ expect { CustomRule.new }.to raise_error ArgumentError
10
+ expect { CustomRule.new(Object.new) }.not_to raise_error
11
+ end
12
+
13
+ it 'requires #satisfied? method to be defined' do
14
+ InvalidCustomRule = Class.new(Patterns::Rule)
15
+ CustomRule = Class.new(Patterns::Rule) do
16
+ def satisfied?
17
+ true
18
+ end
19
+ end
20
+
21
+ expect { InvalidCustomRule.new(Object.new).satisfied? }.to raise_error NotImplementedError
22
+ expect { CustomRule.new(Object.new).satisfied? }.not_to raise_error
23
+ end
24
+
25
+ describe '#satisfied?' do
26
+ context 'when subject meets the conditions' do
27
+ it 'returns true' do
28
+ article = OpenStruct.new('published?' => true, 'deleted?' => false)
29
+
30
+ ArticleIsPublishedRule = Class.new(Patterns::Rule) do
31
+ def satisfied?
32
+ subject.published?
33
+ end
34
+
35
+ def not_applicable?
36
+ subject.deleted?
37
+ end
38
+ end
39
+
40
+ expect(ArticleIsPublishedRule.new(article).satisfied?).to eq true
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,260 @@
1
+ RSpec.describe Patterns::Ruleset do
2
+ context 'when empty ruleset is initialized' do
3
+ it 'raises an error' do
4
+ empty_ruleset_klass = Class.new(Patterns::Ruleset)
5
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
6
+ subject = double
7
+
8
+ with_mocked_rules do |rules|
9
+ rules << mock_rule(:rule_1)
10
+ custom_ruleset_klass.add_rule(:rule_1)
11
+
12
+ expect { custom_ruleset_klass.new(subject) }.not_to raise_error
13
+ end
14
+
15
+ expect { empty_ruleset_klass.new(subject) }.to raise_error Patterns::Ruleset::EmptyRuleset
16
+ end
17
+ end
18
+
19
+ describe '#forceable?' do
20
+ context 'all rules are forceable' do
21
+ it 'returns true' do
22
+ with_mocked_rules do |rules|
23
+ subject = double
24
+ rules << mock_rule(:rule_1, is_forceable: true)
25
+ rules << mock_rule(:rule_2, is_forceable: true)
26
+
27
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
28
+ custom_ruleset_klass.add_rule(:rule_1)
29
+ custom_ruleset_klass.add_rule(:rule_2)
30
+
31
+ expect(custom_ruleset_klass.new(subject).forceable?).to eq true
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'at least one rule is not forceable' do
37
+ it 'returns false' do
38
+ with_mocked_rules do |rules|
39
+ subject = double
40
+ rules << mock_rule(:rule_1, is_forceable: false, is_satisfied: false, is_applicable: true)
41
+ rules << mock_rule(:rule_2, is_forceable: true)
42
+
43
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
44
+ custom_ruleset_klass.add_rule(:rule_1)
45
+ custom_ruleset_klass.add_rule(:rule_2)
46
+
47
+ expect(custom_ruleset_klass.new(subject).forceable?).to eq false
48
+ end
49
+ end
50
+
51
+ context 'and rule is satisfied' do
52
+ it 'returns true' do
53
+ with_mocked_rules do |rules|
54
+ subject = double
55
+ rules << mock_rule(
56
+ :rule_1,
57
+ is_forceable: false,
58
+ is_satisfied: true,
59
+ is_applicable: true
60
+ )
61
+ rules << mock_rule(:rule_2, is_forceable: true)
62
+
63
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
64
+ custom_ruleset_klass.add_rule(:rule_1)
65
+ custom_ruleset_klass.add_rule(:rule_2)
66
+
67
+ expect(custom_ruleset_klass.new(subject).forceable?).to eq true
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'and rule is not applicable' do
73
+ it 'returns true' do
74
+ with_mocked_rules do |rules|
75
+ subject = double
76
+ rules << mock_rule(
77
+ :rule_1,
78
+ is_forceable: false,
79
+ is_satisfied: false,
80
+ is_applicable: false
81
+ )
82
+ rules << mock_rule(:rule_2, is_forceable: true)
83
+
84
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
85
+ custom_ruleset_klass.add_rule(:rule_1)
86
+ custom_ruleset_klass.add_rule(:rule_2)
87
+
88
+ expect(custom_ruleset_klass.new(subject).forceable?).to eq true
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#not_applicable?' do
96
+ context 'all rules are not applicable' do
97
+ it 'returns true' do
98
+ with_mocked_rules do |rules|
99
+ subject = double
100
+ rules << mock_rule(:rule_1, is_applicable: false)
101
+ rules << mock_rule(:rule_2, is_applicable: false)
102
+
103
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
104
+ custom_ruleset_klass.add_rule(:rule_1)
105
+ custom_ruleset_klass.add_rule(:rule_2)
106
+
107
+ expect(custom_ruleset_klass.new(subject).not_applicable?).to eq true
108
+ end
109
+ end
110
+ end
111
+
112
+ context 'at least one rule is applicable' do
113
+ it 'returns false' do
114
+ with_mocked_rules do |rules|
115
+ subject = double
116
+ rules << mock_rule(:rule_1, is_applicable: false)
117
+ rules << mock_rule(:rule_2, is_applicable: true)
118
+
119
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
120
+ custom_ruleset_klass.add_rule(:rule_1)
121
+ custom_ruleset_klass.add_rule(:rule_2)
122
+
123
+ expect(custom_ruleset_klass.new(subject).not_applicable?).to eq false
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#satisfied?' do
130
+ context 'all rules are satisfied' do
131
+ it 'returns true' do
132
+ with_mocked_rules do |rules|
133
+ subject = double
134
+ rules << mock_rule(:rule_1)
135
+ rules << mock_rule(:rule_2)
136
+
137
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
138
+ custom_ruleset_klass.add_rule(:rule_1)
139
+ custom_ruleset_klass.add_rule(:rule_2)
140
+
141
+ expect(custom_ruleset_klass.new(subject).satisfied?).to eq true
142
+ end
143
+ end
144
+ end
145
+
146
+ context 'at least one rule is not satisfied' do
147
+ it 'returns false' do
148
+ with_mocked_rules do |rules|
149
+ subject = double
150
+ rules << mock_rule(:rule_1)
151
+ rules << mock_rule(:rule_2, is_satisfied: false)
152
+
153
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
154
+ custom_ruleset_klass.add_rule(:rule_1)
155
+ custom_ruleset_klass.add_rule(:rule_2)
156
+
157
+ expect(custom_ruleset_klass.new(subject).satisfied?).to eq false
158
+ end
159
+ end
160
+
161
+ context 'when rule is not applicable' do
162
+ it 'returns true' do
163
+ with_mocked_rules do |rules|
164
+ subject = double
165
+ rules << mock_rule(:rule_1)
166
+ rules << mock_rule(:rule_2, is_satisfied: false, is_applicable: false)
167
+
168
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
169
+ custom_ruleset_klass.add_rule(:rule_1)
170
+ custom_ruleset_klass.add_rule(:rule_2)
171
+
172
+ expect(custom_ruleset_klass.new(subject).satisfied?).to eq true
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'when provided with force: true' do
178
+ context 'when rule is forceable' do
179
+ it 'returns true' do
180
+ with_mocked_rules do |rules|
181
+ subject = double
182
+ rules << mock_rule(:rule_1)
183
+ rules << mock_rule(:rule_2, is_satisfied: false, is_forceable: true)
184
+
185
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
186
+ custom_ruleset_klass.add_rule(:rule_1)
187
+ custom_ruleset_klass.add_rule(:rule_2)
188
+
189
+ expect(custom_ruleset_klass.new(subject).satisfied?(force: true)).to eq true
190
+ end
191
+ end
192
+ end
193
+
194
+ context 'when rule is not forceable' do
195
+ it 'returns false' do
196
+ with_mocked_rules do |rules|
197
+ subject = double
198
+ rules << mock_rule(:rule_1)
199
+ rules << mock_rule(:rule_2, is_satisfied: false, is_forceable: false)
200
+
201
+ custom_ruleset_klass = Class.new(Patterns::Ruleset)
202
+ custom_ruleset_klass.add_rule(:rule_1)
203
+ custom_ruleset_klass.add_rule(:rule_2)
204
+
205
+ expect(custom_ruleset_klass.new(subject).satisfied?(force: true)).to eq false
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ describe '#each' do
214
+ it 'yields all rules for ruleset' do
215
+ with_mocked_rules do |rules|
216
+ rules << (_, rule_1 = mock_rule(:rule_1))
217
+ rules << (_, rule_2 = mock_rule(:rule_2))
218
+ rules << (_, rule_3 = mock_rule(:rule_3))
219
+ custom_ruleset_klass_1 = Class.new(Patterns::Ruleset)
220
+ custom_ruleset_klass_1.add_rule(:rule_1)
221
+ custom_ruleset_klass_1.add_rule(:rule_2)
222
+ Ruleset2 = Class.new(Patterns::Ruleset)
223
+ Ruleset2.add_rule(:rule_3)
224
+ custom_ruleset_klass_1.add_rule(:ruleset_2)
225
+
226
+ ruleset = custom_ruleset_klass_1.new(double)
227
+
228
+ expect { |b| ruleset.each(&b) }.to yield_successive_args(rule_1, rule_2, rule_3)
229
+ ensure
230
+ remove_class(Ruleset2)
231
+ end
232
+ end
233
+ end
234
+
235
+ private
236
+
237
+ def mock_rule(rule_name, is_applicable: true, is_satisfied: true, is_forceable: true)
238
+ klass = Object.const_set(rule_name.to_s.classify, Class.new(Patterns::Rule))
239
+ rule = double(
240
+ not_applicable?: !is_applicable,
241
+ satisfied?: is_satisfied,
242
+ forceable?: is_forceable
243
+ )
244
+ allow(klass).to receive(:new).with(anything) { rule }
245
+ [klass, rule]
246
+ end
247
+
248
+ def with_mocked_rules
249
+ rules_storage = []
250
+ yield rules_storage
251
+ ensure
252
+ rules_storage.each do |rule_klass, _rule_instance|
253
+ remove_class(rule_klass)
254
+ end
255
+ end
256
+
257
+ def remove_class(klass)
258
+ Object.send(:remove_const, klass.name.to_sym)
259
+ end
260
+ end
@@ -10,7 +10,7 @@ RSpec.describe Patterns::Service do
10
10
  expect(DoSomething.call).to be_kind_of(DoSomething)
11
11
  end
12
12
 
13
- it "instantiates service object passing arguments to constructor" do
13
+ it "instantiates service object passing keyword arguments to constructor" do
14
14
  DoSomething = Class.new(Patterns::Service) do
15
15
  def initialize(argument_1:, argument_2:); end
16
16
  def call; end
@@ -25,6 +25,21 @@ RSpec.describe Patterns::Service do
25
25
  }.not_to raise_error
26
26
  end
27
27
 
28
+ it "instantiates service object passing positional arguments to constructor" do
29
+ DoSomething = Class.new(Patterns::Service) do
30
+ def initialize(argument_1, argument_2); end
31
+ def call; end
32
+ end
33
+
34
+ expect {
35
+ DoSomething.call
36
+ }.to raise_error ArgumentError
37
+
38
+ expect {
39
+ DoSomething.call(10, 20)
40
+ }.not_to raise_error
41
+ end
42
+
28
43
  it "calls #call method on service object instance" do
29
44
  Spy = Class.new do
30
45
  def self.some_method; end
@@ -0,0 +1,79 @@
1
+ RSpec.describe Patterns::StrongRuleset do
2
+ it 'inherites from Ruleset' do
3
+ custom_strong_ruleset_klass = Class.new(Patterns::StrongRuleset)
4
+ expect(custom_strong_ruleset_klass.ancestors).to include Patterns::Ruleset
5
+ end
6
+
7
+ context 'when any of rules is not applicable' do
8
+ it 'is not satisfied' do
9
+ with_mocked_rules do |rules|
10
+ subject = double
11
+ rules << mock_rule(:rule_1, is_applicable: false)
12
+ rules << mock_rule(:rule_2)
13
+
14
+ custom_ruleset_klass = Class.new(Patterns::StrongRuleset)
15
+ custom_ruleset_klass.add_rule(:rule_1)
16
+ custom_ruleset_klass.add_rule(:rule_2)
17
+
18
+ expect(custom_ruleset_klass.new(subject).satisfied?).to eq false
19
+ end
20
+ end
21
+
22
+ context 'when not applicable rule is not satisfied' do
23
+ it 'is not forceable' do
24
+ with_mocked_rules do |rules|
25
+ subject = double
26
+ rules << mock_rule(:rule_1, is_applicable: false, is_satisfied: false)
27
+ rules << mock_rule(:rule_2)
28
+
29
+ custom_ruleset_klass = Class.new(Patterns::StrongRuleset)
30
+ custom_ruleset_klass.add_rule(:rule_1)
31
+ custom_ruleset_klass.add_rule(:rule_2)
32
+
33
+ expect(custom_ruleset_klass.new(subject).forceable?).to eq false
34
+ end
35
+ end
36
+ end
37
+
38
+ it 'is not applicable' do
39
+ with_mocked_rules do |rules|
40
+ subject = double
41
+ rules << mock_rule(:rule_1, is_applicable: false)
42
+ rules << mock_rule(:rule_2)
43
+
44
+ custom_ruleset_klass = Class.new(Patterns::StrongRuleset)
45
+ custom_ruleset_klass.add_rule(:rule_1)
46
+ custom_ruleset_klass.add_rule(:rule_2)
47
+
48
+ expect(custom_ruleset_klass.new(subject).applicable?).to eq false
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def mock_rule(rule_name, is_applicable: true, is_satisfied: true, is_forceable: true)
56
+ klass = Object.const_set(rule_name.to_s.classify, Class.new(Patterns::Rule))
57
+ rule = double(
58
+ not_applicable?: !is_applicable,
59
+ applicable?: is_applicable,
60
+ satisfied?: is_satisfied,
61
+ forceable?: is_forceable
62
+ )
63
+ allow(klass).to receive(:new).with(anything) { rule }
64
+ [klass, rule]
65
+ end
66
+
67
+ def with_mocked_rules
68
+ rules_storage = []
69
+ yield rules_storage
70
+ ensure
71
+ rules_storage.each do |rule_klass, _rule_instance|
72
+ remove_class(rule_klass)
73
+ end
74
+ end
75
+
76
+ def remove_class(klass)
77
+ Object.send(:remove_const, klass.name.to_sym)
78
+ end
79
+ end
data/spec/spec_helper.rb CHANGED
@@ -20,6 +20,7 @@
20
20
  require "active_support/all"
21
21
  require "active_support/testing/time_helpers"
22
22
  require "pry"
23
+ require "redis"
23
24
  require "rails-patterns"
24
25
 
25
26
  RSpec.configure do |config|