rails-patterns 0.4.0 → 0.8.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 +5 -5
- data/.github/workflows/ruby.yml +33 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +85 -75
- data/README.md +164 -6
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/patterns/calculation.rb +59 -0
- data/lib/patterns/form.rb +27 -10
- data/lib/patterns/query.rb +20 -15
- data/lib/patterns/rule.rb +25 -0
- data/lib/patterns/ruleset.rb +69 -0
- data/lib/patterns/service.rb +10 -6
- data/lib/patterns/strong_ruleset.rb +19 -0
- data/lib/rails-patterns.rb +4 -0
- data/rails-patterns.gemspec +28 -20
- data/spec/helpers/custom_calculation.rb +16 -0
- data/spec/helpers/custom_calculation_script.rb +4 -0
- data/spec/helpers/rails_redis_cache_mock.rb +5 -0
- data/spec/patterns/calculation_spec.rb +200 -0
- data/spec/patterns/form_spec.rb +96 -50
- data/spec/patterns/rule_spec.rb +44 -0
- data/spec/patterns/ruleset_spec.rb +260 -0
- data/spec/patterns/service_spec.rb +16 -1
- data/spec/patterns/strong_ruleset_spec.rb +79 -0
- data/spec/spec_helper.rb +6 -1
- metadata +35 -10
data/Rakefile
CHANGED
@@ -21,6 +21,7 @@ Juwelier::Tasks.new do |gem|
|
|
21
21
|
gem.description = "A collection of lightweight, standardized, rails-oriented patterns."
|
22
22
|
gem.email = "b.kosmowski@selleo.com"
|
23
23
|
gem.authors = ["Stevo"]
|
24
|
+
gem.required_ruby_version = ">= 2.5.0"
|
24
25
|
|
25
26
|
# dependencies defined in Gemfile
|
26
27
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Patterns
|
4
|
+
class Calculation
|
5
|
+
class_attribute :cache_expiry_every
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
@options = args.extract_options!
|
9
|
+
@subject = args.first
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.result(*args)
|
13
|
+
new(*args).cached_result
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
alias_method :result_for, :result
|
18
|
+
alias_method :calculate, :result
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.set_cache_expiry_every(period)
|
22
|
+
self.cache_expiry_every = period
|
23
|
+
end
|
24
|
+
|
25
|
+
def cached_result
|
26
|
+
if cache_expiry_period.blank?
|
27
|
+
result
|
28
|
+
else
|
29
|
+
Rails.cache.fetch(cache_key, expires_in: cache_expiry_period) do
|
30
|
+
result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :subject, :options
|
38
|
+
|
39
|
+
def result
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_key
|
44
|
+
"#{self.class.name}_#{hash_of(subject, options)}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.hash_of(*args)
|
48
|
+
Digest::SHA1.hexdigest(args.map(&:to_s).join(':'))
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash_of(*args)
|
52
|
+
self.class.hash_of(*args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def cache_expiry_period
|
56
|
+
self.class.cache_expiry_every
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/patterns/form.rb
CHANGED
@@ -49,8 +49,16 @@ module Patterns
|
|
49
49
|
self
|
50
50
|
end
|
51
51
|
|
52
|
+
def to_param
|
53
|
+
if resource.present? && resource.respond_to?(:to_param)
|
54
|
+
resource.to_param
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
52
60
|
def persisted?
|
53
|
-
if resource
|
61
|
+
if resource.present? && resource.respond_to?(:persisted?)
|
54
62
|
resource.persisted?
|
55
63
|
else
|
56
64
|
false
|
@@ -58,9 +66,7 @@ module Patterns
|
|
58
66
|
end
|
59
67
|
|
60
68
|
def model_name
|
61
|
-
@model_name ||=
|
62
|
-
new(:param_key).
|
63
|
-
new(param_key)
|
69
|
+
@model_name ||= OpenStruct.new(model_name_attributes)
|
64
70
|
end
|
65
71
|
|
66
72
|
def self.param_key(key = nil)
|
@@ -75,13 +81,24 @@ module Patterns
|
|
75
81
|
|
76
82
|
attr_reader :resource, :form_owner
|
77
83
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
84
|
+
def model_name_attributes
|
85
|
+
if self.class.param_key.present?
|
86
|
+
{
|
87
|
+
param_key: self.class.param_key,
|
88
|
+
route_key: self.class.param_key.pluralize,
|
89
|
+
singular_route_key: self.class.param_key
|
90
|
+
}
|
91
|
+
elsif resource.present? && resource.respond_to?(:model_name)
|
92
|
+
{
|
93
|
+
param_key: resource.model_name.param_key,
|
94
|
+
route_key: resource.model_name.route_key,
|
95
|
+
singular_route_key: resource.model_name.singular_route_key
|
96
|
+
}
|
97
|
+
else
|
98
|
+
raise NoParamKey
|
99
|
+
end
|
83
100
|
end
|
84
|
-
|
101
|
+
|
85
102
|
def build_original_attributes
|
86
103
|
return {} if resource.nil?
|
87
104
|
base_attributes = resource.respond_to?(:attributes) && resource.attributes.symbolize_keys
|
data/lib/patterns/query.rb
CHANGED
@@ -6,17 +6,17 @@ module Patterns
|
|
6
6
|
|
7
7
|
def initialize(*args)
|
8
8
|
@options = args.extract_options!
|
9
|
-
@relation = args.first ||
|
9
|
+
@relation = args.first || base_relation
|
10
10
|
|
11
11
|
if relation.nil?
|
12
12
|
raise(
|
13
|
-
|
14
|
-
|
13
|
+
RelationRequired,
|
14
|
+
"Queries require a base relation defined. Use .queries method to define relation."
|
15
15
|
)
|
16
16
|
elsif !relation.is_a?(ActiveRecord::Relation)
|
17
17
|
raise(
|
18
|
-
|
19
|
-
|
18
|
+
RelationRequired,
|
19
|
+
"Queries accept only ActiveRecord::Relation as input"
|
20
20
|
)
|
21
21
|
end
|
22
22
|
end
|
@@ -29,20 +29,25 @@ module Patterns
|
|
29
29
|
query.tap do |relation|
|
30
30
|
unless relation.is_a?(ActiveRecord::Relation)
|
31
31
|
raise(
|
32
|
-
|
33
|
-
|
32
|
+
RelationRequired,
|
33
|
+
"#query method should return object of ActiveRecord::Relation class"
|
34
34
|
)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.queries(subject)
|
40
|
-
self.base_relation =
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
self.base_relation = subject
|
41
|
+
end
|
42
|
+
|
43
|
+
def base_relation
|
44
|
+
return nil if self.class.base_relation.nil?
|
45
|
+
|
46
|
+
if self.class.base_relation.is_a?(ActiveRecord::Relation)
|
47
|
+
self.class.base_relation
|
48
|
+
elsif self.class.base_relation < ActiveRecord::Base
|
49
|
+
self.class.base_relation.all
|
50
|
+
end
|
46
51
|
end
|
47
52
|
|
48
53
|
private
|
@@ -55,8 +60,8 @@ module Patterns
|
|
55
60
|
|
56
61
|
def query
|
57
62
|
raise(
|
58
|
-
|
59
|
-
|
63
|
+
NotImplementedError,
|
64
|
+
"You need to implement #query method which returns ActiveRecord::Relation object"
|
60
65
|
)
|
61
66
|
end
|
62
67
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Rule
|
2
|
+
def initialize(subject)
|
3
|
+
@subject = subject
|
4
|
+
end
|
5
|
+
|
6
|
+
def satisfied?
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def not_applicable?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def applicable?
|
15
|
+
!not_applicable?
|
16
|
+
end
|
17
|
+
|
18
|
+
def forceable?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :subject
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Ruleset
|
2
|
+
class EmptyRuleset < StandardError; end
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :rule_names
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.rules
|
9
|
+
(rule_names || []).map do |rule_name|
|
10
|
+
rule_name.to_s.classify.constantize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.add_rule(rule_name)
|
15
|
+
self.rule_names ||= []
|
16
|
+
self.rule_names << rule_name.to_sym
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(subject = nil)
|
21
|
+
raise EmptyRuleset if self.class.rules.empty?
|
22
|
+
|
23
|
+
@rules = self.class.rules.map { |rule| rule.new(subject) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def satisfied?(force: false)
|
27
|
+
rules.all? do |rule|
|
28
|
+
rule.satisfied? ||
|
29
|
+
rule.not_applicable? ||
|
30
|
+
(force && rule.forceable?)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def not_satisfied?
|
35
|
+
!satisfied?
|
36
|
+
end
|
37
|
+
|
38
|
+
def applicable?
|
39
|
+
!not_applicable?
|
40
|
+
end
|
41
|
+
|
42
|
+
def not_applicable?
|
43
|
+
rules.all?(&:not_applicable?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def forceable?
|
47
|
+
rules.all? do |rule|
|
48
|
+
rule.forceable? ||
|
49
|
+
rule.not_applicable? ||
|
50
|
+
rule.satisfied?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def each(&block)
|
55
|
+
return enum_for(:each) unless block_given?
|
56
|
+
|
57
|
+
rules.each do |rule_or_ruleset|
|
58
|
+
if rule_or_ruleset.is_a?(Ruleset)
|
59
|
+
rule_or_ruleset.each(&block)
|
60
|
+
else
|
61
|
+
yield rule_or_ruleset
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :rules
|
69
|
+
end
|
data/lib/patterns/service.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
require 'ruby2_keywords'
|
2
|
+
|
1
3
|
module Patterns
|
2
4
|
class Service
|
3
5
|
attr_reader :result
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
service
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
class << self
|
8
|
+
ruby2_keywords def call(*args)
|
9
|
+
new(*args).tap do |service|
|
10
|
+
service.instance_variable_set(
|
11
|
+
"@result",
|
12
|
+
service.call
|
13
|
+
)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# StrongRuleset is not satisfied and not forceable if any of rules is not applicable
|
2
|
+
|
3
|
+
class StrongRuleset < Ruleset
|
4
|
+
def satisfied?(force: false)
|
5
|
+
rules.all? do |rule|
|
6
|
+
(rule.applicable? && rule.satisfied?) || (force && rule.forceable?)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def not_applicable?
|
11
|
+
rules.any?(&:not_applicable?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def forceable?
|
15
|
+
rules.all? do |rule|
|
16
|
+
(rule.applicable? && rule.forceable?) || rule.satisfied?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rails-patterns.rb
CHANGED
data/rails-patterns.gemspec
CHANGED
@@ -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.
|
5
|
+
# stub: rails-patterns 0.8.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rails-patterns".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.8.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 = "
|
14
|
+
s.date = "2020-09-01"
|
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",
|
@@ -28,47 +29,54 @@ Gem::Specification.new do |s|
|
|
28
29
|
"Rakefile",
|
29
30
|
"VERSION",
|
30
31
|
"lib/patterns.rb",
|
32
|
+
"lib/patterns/calculation.rb",
|
31
33
|
"lib/patterns/collection.rb",
|
32
34
|
"lib/patterns/form.rb",
|
33
35
|
"lib/patterns/query.rb",
|
36
|
+
"lib/patterns/rule.rb",
|
37
|
+
"lib/patterns/ruleset.rb",
|
34
38
|
"lib/patterns/service.rb",
|
39
|
+
"lib/patterns/strong_ruleset.rb",
|
35
40
|
"lib/rails-patterns.rb",
|
36
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",
|
45
|
+
"spec/patterns/calculation_spec.rb",
|
37
46
|
"spec/patterns/collection_spec.rb",
|
38
47
|
"spec/patterns/form_spec.rb",
|
39
48
|
"spec/patterns/query_spec.rb",
|
49
|
+
"spec/patterns/rule_spec.rb",
|
50
|
+
"spec/patterns/ruleset_spec.rb",
|
40
51
|
"spec/patterns/service_spec.rb",
|
52
|
+
"spec/patterns/strong_ruleset_spec.rb",
|
41
53
|
"spec/spec_helper.rb"
|
42
54
|
]
|
43
55
|
s.homepage = "http://github.com/selleo/pattern".freeze
|
44
56
|
s.licenses = ["MIT".freeze]
|
45
|
-
s.
|
57
|
+
s.required_ruby_version = Gem::Requirement.new(">= 2.5.0".freeze)
|
58
|
+
s.rubygems_version = "3.1.2".freeze
|
46
59
|
s.summary = "A collection of lightweight, standardized, rails-oriented patterns.".freeze
|
47
60
|
|
48
61
|
if s.respond_to? :specification_version then
|
49
62
|
s.specification_version = 4
|
63
|
+
end
|
50
64
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
60
|
-
s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
61
|
-
s.add_dependency(%q<virtus>.freeze, [">= 0"])
|
62
|
-
s.add_dependency(%q<rspec>.freeze, [">= 0"])
|
63
|
-
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
64
|
-
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
65
|
-
end
|
65
|
+
if s.respond_to? :add_runtime_dependency then
|
66
|
+
s.add_runtime_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
67
|
+
s.add_runtime_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
68
|
+
s.add_runtime_dependency(%q<virtus>.freeze, [">= 0"])
|
69
|
+
s.add_runtime_dependency(%q<ruby2_keywords>.freeze, [">= 0"])
|
70
|
+
s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
|
71
|
+
s.add_development_dependency(%q<bundler>.freeze, ["~> 2.0"])
|
72
|
+
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
66
73
|
else
|
67
74
|
s.add_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
|
68
75
|
s.add_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
|
69
76
|
s.add_dependency(%q<virtus>.freeze, [">= 0"])
|
77
|
+
s.add_dependency(%q<ruby2_keywords>.freeze, [">= 0"])
|
70
78
|
s.add_dependency(%q<rspec>.freeze, [">= 0"])
|
71
|
-
s.add_dependency(%q<bundler>.freeze, ["~>
|
79
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 2.0"])
|
72
80
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
73
81
|
end
|
74
82
|
end
|