rails-patterns 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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.4.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
@@ -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&.respond_to?(:persisted?)
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 ||= Struct.
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 param_key
79
- param_key = self.class.param_key
80
- param_key ||= resource&.respond_to?(:model_name) && resource.model_name.param_key
81
- raise NoParamKey if param_key.blank?
82
- param_key
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
@@ -6,17 +6,17 @@ module Patterns
6
6
 
7
7
  def initialize(*args)
8
8
  @options = args.extract_options!
9
- @relation = args.first || self.class.base_relation
9
+ @relation = args.first || base_relation
10
10
 
11
11
  if relation.nil?
12
12
  raise(
13
- RelationRequired,
14
- "Queries require a base relation defined. Use .queries method to define relation."
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
- RelationRequired,
19
- "Queries accept only ActiveRecord::Relation as input"
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
- RelationRequired,
33
- "#query method should return object of ActiveRecord::Relation class"
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
- if subject.is_a?(ActiveRecord::Relation)
42
- subject
43
- elsif subject < ActiveRecord::Base
44
- subject.all
45
- end
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
- NotImplementedError,
59
- "You need to implement #query method which returns ActiveRecord::Relation object"
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
@@ -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
- def self.call(*args)
6
- new(*args).tap do |service|
7
- service.instance_variable_set(
8
- "@result",
9
- service.call
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
@@ -2,4 +2,8 @@ require "patterns"
2
2
  require "patterns/query"
3
3
  require "patterns/service"
4
4
  require "patterns/collection"
5
+ require "patterns/calculation"
5
6
  require "patterns/form"
7
+ require "patterns/rule"
8
+ require "patterns/ruleset"
9
+ require "patterns/strong_ruleset"
@@ -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.4.0 ruby lib
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.4.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 = "2017-04-21"
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.rubygems_version = "2.6.10".freeze
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
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 4.2.6"])
53
- s.add_runtime_dependency(%q<actionpack>.freeze, [">= 4.2.6"])
54
- s.add_runtime_dependency(%q<virtus>.freeze, [">= 0"])
55
- s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
56
- s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
57
- s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
58
- else
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, ["~> 1.0"])
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