recurify 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a343e37a72dc1fc5319317532fe1af000e31c41e
4
- data.tar.gz: d50087d4a127bce77547365be1b4b2a2f694b640
3
+ metadata.gz: f67467f617bc9f0301b2adea071f0399f6764157
4
+ data.tar.gz: b1406f9990dc5ce45a8b176b623f7ade80d0c2de
5
5
  SHA512:
6
- metadata.gz: 93687456b41c7422e1731da5205dc337664e2ddf205b3210f80421b9a0c81940b185db9f05835b5422bfcf2d787fb9be99c5a90b2edeb44652a35993ddd3838c
7
- data.tar.gz: 14ce0c66d4a0e9c77c2ee94fd50c59b0c73cd78f5c97239f30d103338444f60b5cb2254002a0b3582a131e2c79341d021152cebfd7231a6b923c0cff4b64d6b9
6
+ metadata.gz: 2866e6d97a2077b9161075adb1212aa83c6524f06a241b3803dd069c4c9c8a8c2e1d897834ffa612482689c662db88518fbf201174e0733d484320ae87b661c2
7
+ data.tar.gz: 41175f2ce891a1dbc2e3b522ebdba54f0d10cacfbbed277fd2454fe6ab4776cd2ccf36a6d9f49f0592c5322c12561b01791ce559a5170426187581aa2f18b107
data/lib/recurify/rule.rb CHANGED
@@ -1,31 +1,40 @@
1
+ require 'date'
2
+
3
+ require_relative 'rule_validation'
1
4
  require_relative 'rule_analysis'
2
5
  require_relative 'rule_translation'
3
6
 
4
7
  module Recurify
5
8
  # Represents a Recurrence Rule. Note that +Rule+ objects are *immutable* once
6
9
  # they are instantiated (i.e. they are value objects).
7
- #
8
- # @!attribute [r] frequency
9
- # @return [String]
10
- # @!attribute [r] interval
11
- # @return [Fixnum]
12
- # @!attribute [r] count
13
- # @return [Fixnum, nil]
14
- # @!attribute [r] starts_on
15
- # @return [Date]
16
- # @!attribute [r] ends_on
17
- # @return [Date, nil]
18
10
  class Rule
11
+ include RuleValidation
19
12
  include RuleAnalysis
20
13
  include RuleTranslation
21
14
 
22
15
  BASE_FREQUENCIES = %w(daily monthly).freeze
23
16
  SUGAR_FREQUENCIES = %w(weekly quarterly yearly).freeze
24
17
  SUPPORTED_FREQUENCIES = (BASE_FREQUENCIES + SUGAR_FREQUENCIES).freeze
25
- MIN_INTERVAL = 1
26
- MIN_COUNT = 1
27
18
 
28
- attr_reader :frequency, :interval, :count, :starts_on, :ends_on
19
+ attr_reader :frequency
20
+ # @!attribute [r] frequency
21
+ # @return [String]
22
+
23
+ attr_reader :interval
24
+ # @!attribute [r] interval
25
+ # @return [Fixnum]
26
+
27
+ attr_reader :count
28
+ # @!attribute [r] count
29
+ # @return [Fixnum, nil]
30
+
31
+ attr_reader :starts_on
32
+ # @!attribute [r] starts_on
33
+ # @return [Date]
34
+
35
+ attr_reader :ends_on
36
+ # @!attribute [r] ends_on
37
+ # @return [Date, nil]
29
38
 
30
39
  # Returns a new instance of +Rule+.
31
40
  #
@@ -87,46 +96,16 @@ module Recurify
87
96
  {
88
97
  frequency: SUPPORTED_FREQUENCIES.first,
89
98
  interval: MIN_INTERVAL,
90
- count: nil,
91
- starts_on: Date.today,
92
- ends_on: nil
99
+ starts_on: Date.today
93
100
  }
94
101
  end
95
102
 
96
- def attributes=(attributes)
97
- @frequency = attributes[:frequency].to_s
98
- @interval = attributes[:interval].to_i
99
- @count = attributes[:count].nil? ? nil : attributes[:count].to_i
100
- @starts_on = attributes[:starts_on].to_date
101
- @ends_on = attributes[:ends_on].nil? ? nil : attributes[:ends_on].to_date
102
- end
103
-
104
- # @return [void]
105
- def validate!
106
- validate_frequency!
107
- validate_interval!
108
- validate_count!
109
- end
110
-
111
- # @raise [InvalidRuleFrequency]
112
- # @return [void]
113
- def validate_frequency!
114
- return if SUPPORTED_FREQUENCIES.include?(frequency)
115
- fail InvalidRuleFrequency, "'#{frequency}' is not supported"
116
- end
117
-
118
- # @raise [InvalidRuleInterval]
119
- # @return [void]
120
- def validate_interval!
121
- return if interval >= MIN_INTERVAL
122
- fail InvalidRuleInterval, "'#{interval}' is not supported"
123
- end
124
-
125
- # @raise [InvalidRuleCount]
126
- # @return [void]
127
- def validate_count!
128
- return if count.nil? || count >= MIN_COUNT
129
- fail InvalidRuleCount, "'#{count}' is not supported"
103
+ def attributes=(attrs)
104
+ @frequency = attrs[:frequency].to_s
105
+ @interval = attrs[:interval].to_i
106
+ @count = attrs[:count].nil? ? nil : attrs[:count].to_i
107
+ @starts_on = attrs[:starts_on].to_date
108
+ @ends_on = attrs[:ends_on].nil? ? nil : attrs[:ends_on].to_date
130
109
  end
131
110
  end
132
111
  end
@@ -27,7 +27,9 @@ module Recurify
27
27
  #
28
28
  # @retrun [Boolean]
29
29
  def finite?
30
- !count.nil? || !ends_on.nil?
30
+ # @todo Checking of the #count is not needed anymore, once #normalize
31
+ # supports minifying of #ends_on.
32
+ !count.nil? || !normalized_ends_on.nil?
31
33
  end
32
34
 
33
35
  # Returns +true+ if +self+ is infinite. By definition, a +Rule+ is infinite
@@ -39,5 +41,107 @@ module Recurify
39
41
  def infinite?
40
42
  !finite?
41
43
  end
44
+
45
+ # Returns +true+ if +self+ starts before or on +upper+. If +upper == nil+,
46
+ # it is interpreted as "positive infinity". In that case, +#starts_before?+
47
+ # always returns +true+, because all +Rule+ objects must have a finite
48
+ # +#starts_on+ attribute.
49
+ #
50
+ # @param upper [#to_date, nil]
51
+ #
52
+ # @return [Boolean]
53
+ def starts_before?(upper)
54
+ upper.nil? || starts_on <= upper.to_date
55
+ end
56
+
57
+ # Returns +true+ if +self+ starts after or on +lower+. If +lower == nil+, it
58
+ # is interpreted as "negative infinity". In that case, +#starts_after?+
59
+ # always returns +true+, because all +Rule+ objects must have a finite
60
+ # +#starts_on+ attribute.
61
+ #
62
+ # @param lower [#to_date, nil]
63
+ #
64
+ # @return [Boolean]
65
+ def starts_after?(lower)
66
+ lower.nil? || starts_on >= lower.to_date
67
+ end
68
+
69
+ # Returns +true+ if +self+ starts after +lower+ *and* starts before
70
+ # +upper+ (including boundaries).
71
+ #
72
+ # @see #starts_before?
73
+ # @see #starts_after?
74
+ #
75
+ # @param lower [#to_date, nil]
76
+ # @param upper [#to_date, nil]
77
+ #
78
+ # @return [Boolean]
79
+ def starts_between?(lower: nil, upper: nil)
80
+ starts_after?(lower) && starts_before?(upper)
81
+ end
82
+
83
+ # Returns +true+ if +self+ ends before or on +upper+. If +upper == nil+, it
84
+ # is interpreted as "positive infinity". In that case, +#ends_before?+
85
+ # always returns +true+.
86
+ #
87
+ # @param upper [#to_date, nil]
88
+ #
89
+ # @return [Boolean]
90
+ def ends_before?(upper)
91
+ false ||
92
+ # Case (1): [anything] <= 'infinite' ==> true
93
+ upper.nil? ||
94
+ # Case (2): 'finite' <= 'finite' ==> depends on actual comparison
95
+ !normalized_ends_on.nil? && normalized_ends_on <= upper.to_date
96
+ end
97
+
98
+ # Returns +true+ if +self+ ends after or on +lower+. If +lower == nil+, it
99
+ # is interpreted as "negative infinity". In that case, +#ends_after?+ always
100
+ # returns +true+.
101
+ #
102
+ # @param lower [#to_date, nil]
103
+ #
104
+ # @return [Boolean]
105
+ def ends_after?(lower)
106
+ false ||
107
+ # Case (1): 'infinite' >= [anything] ==> true
108
+ normalized_ends_on.nil? ||
109
+ # Case (2): 'finite' >= 'infinite' ==> true
110
+ lower.nil? ||
111
+ # Case (3): 'finite' >= 'finite' ==> depends on actual comparison
112
+ normalized_ends_on >= lower.to_date
113
+ end
114
+
115
+ # Returns +true+ if +self+ ends after +lower+ *and* ends before +upper+
116
+ # (including boundaries).
117
+ #
118
+ # @see #ends_before?
119
+ # @see #ends_after?
120
+ #
121
+ # @param lower [#to_date, nil]
122
+ # @param upper [#to_date, nil]
123
+ #
124
+ # @return [Boolean]
125
+ def ends_between?(lower: nil, upper: nil)
126
+ ends_after?(lower) && ends_before?(upper)
127
+ end
128
+
129
+ # Returns +true+ if +self+ starts *and/or* ends between +lower+ and +upper+
130
+ # (including boundaries).
131
+ #
132
+ # @see #starts_between?
133
+ # @see #ends_between?
134
+ #
135
+ # @param lower [#to_date, nil]
136
+ # @param upper [#to_date, nil]
137
+ #
138
+ # @return [Boolean]
139
+ def overlaps?(lower: nil, upper: nil)
140
+ false ||
141
+ # Case (1): +lower+ <= starts_on <= +upper+ ==> true
142
+ starts_between?(lower: lower, upper: upper) ||
143
+ # Case (2): +lower+ <= normalized_ends_on <= +upper+ ==> true
144
+ ends_between?(lower: lower, upper: upper)
145
+ end
42
146
  end
43
147
  end
@@ -1,5 +1,9 @@
1
+ require 'forwardable'
2
+
1
3
  module Recurify
2
4
  module RuleTranslation # :nodoc:
5
+ extend Forwardable
6
+
3
7
  NORMALIZATION_MATRIX = {
4
8
  # Normalization is a no-op for base-frequencies ...
5
9
  'daily' => { target_frequency: 'daily', interval_multiplier: 1 },
@@ -13,27 +17,27 @@ module Recurify
13
17
  DENORMALIZATION_MATRIX = {
14
18
  'daily' => [
15
19
  # Applicable denormalizitions ordered by decreasing preferability ...
16
- { target_frequency: 'weekly', interval_divsor: 7 },
17
- { target_frequency: 'daily', interval_divsor: 1 }
20
+ { target_frequency: 'weekly', interval_divisor: 7 },
21
+ { target_frequency: 'daily', interval_divisor: 1 }
18
22
  ],
19
23
  'weekly' => [
20
24
  # Applicable denormalizitions ordered by decreasing preferability ...
21
- { target_frequency: 'weekly', interval_divsor: 1 }
25
+ { target_frequency: 'weekly', interval_divisor: 1 }
22
26
  ],
23
27
  'monthly' => [
24
28
  # Applicable denormalizitions ordered by decreasing preferability ...
25
- { target_frequency: 'yearly', interval_divsor: 12 },
26
- { target_frequency: 'quarterly', interval_divsor: 3 },
27
- { target_frequency: 'monthly', interval_divsor: 1 }
29
+ { target_frequency: 'yearly', interval_divisor: 12 },
30
+ { target_frequency: 'quarterly', interval_divisor: 3 },
31
+ { target_frequency: 'monthly', interval_divisor: 1 }
28
32
  ],
29
33
  'quarterly' => [
30
34
  # Applicable denormalizitions ordered by decreasing preferability ...
31
- { target_frequency: 'yearly', interval_divsor: 4 },
32
- { target_frequency: 'quarterly', interval_divsor: 1 }
35
+ { target_frequency: 'yearly', interval_divisor: 4 },
36
+ { target_frequency: 'quarterly', interval_divisor: 1 }
33
37
  ],
34
38
  'yearly' => [
35
39
  # Applicable denormalizitions ordered by decreasing preferability ...
36
- { target_frequency: 'yearly', interval_divsor: 1 }
40
+ { target_frequency: 'yearly', interval_divisor: 1 }
37
41
  ]
38
42
  }.freeze
39
43
 
@@ -47,6 +51,9 @@ module Recurify
47
51
  #
48
52
  # Additional translations may be added in the future.
49
53
  #
54
+ # @todo This method is not yet complete, because +#normalize+ should also
55
+ # minify the +#ends_on+ and +#count+ attributes (if possible).
56
+ #
50
57
  # @return [Rule]
51
58
  def normalize
52
59
  @_normalized_rule ||= begin
@@ -59,6 +66,30 @@ module Recurify
59
66
  end
60
67
  end
61
68
 
69
+ # @!method normalized_frequency
70
+ # @see Rule#frequency
71
+ # @see #normalize
72
+ # @return [String]
73
+ def_delegator :normalize, :frequency, :normalized_frequency
74
+
75
+ # @!method normalized_interval
76
+ # @see Rule#interval
77
+ # @see #normalize
78
+ # @return [Fixnum]
79
+ def_delegator :normalize, :interval, :normalized_interval
80
+
81
+ # @!method normalized_count
82
+ # @see Rule#count
83
+ # @see #normalize
84
+ # @return [Fixnum, nil]
85
+ def_delegator :normalize, :count, :normalized_count
86
+
87
+ # @!method normalized_ends_on
88
+ # @see Rule#ends_on
89
+ # @see #normalize
90
+ # @return [Date, nil]
91
+ def_delegator :normalize, :ends_on, :normalized_ends_on
92
+
62
93
  # Creates a new +Rule+, similar to +self+, but denormalized. Note that
63
94
  # +#denormalized+ is idempotent.
64
95
  #
@@ -80,19 +111,42 @@ module Recurify
80
111
 
81
112
  substitute(
82
113
  frequency: translate_with[:target_frequency],
83
- interval: interval / translate_with[:interval_divsor]
114
+ interval: interval / translate_with[:interval_divisor]
84
115
  )
85
116
  end
86
117
  end
87
118
 
119
+ # @!method denormalized_frequency
120
+ # @see Rule#frequency
121
+ # @see #denormalize
122
+ # @return [String]
123
+ def_delegator :denormalize, :frequency, :denormalized_frequency
124
+
125
+ # @!method denormalized_interval
126
+ # @see Rule#interval
127
+ # @see #denormalize
128
+ # @return [Fixnum]
129
+ def_delegator :denormalize, :interval, :denormalized_interval
130
+
131
+ # @!method denormalized_count
132
+ # @see Rule#count
133
+ # @see #denormalize
134
+ # @return [Fixnum, nil]
135
+ def_delegator :denormalize, :count, :denormalized_count
136
+
137
+ # @!method denormalized_ends_on
138
+ # @see Rule#ends_on
139
+ # @see #denormalize
140
+ # @return [Date, nil]
141
+ def_delegator :denormalize, :ends_on, :denormalized_ends_on
142
+
88
143
  private
89
144
 
90
145
  # @see DENORMALIZATION_MATRIX
91
146
  # @return [Hash<Symbol, Object>]
92
147
  def find_best_denormalization
93
- contenders = DENORMALIZATION_MATRIX[frequency]
94
- contenders.find do |contender|
95
- normalize.interval % contender[:interval_divsor] == 0
148
+ DENORMALIZATION_MATRIX[frequency].find do |contender|
149
+ normalize.interval % contender[:interval_divisor] == 0
96
150
  end
97
151
  end
98
152
  end
@@ -0,0 +1,36 @@
1
+ module Recurify
2
+ module RuleValidation # :nodoc:
3
+ MIN_INTERVAL = 1
4
+ MIN_COUNT = 1
5
+
6
+ private
7
+
8
+ # @return [void]
9
+ def validate!
10
+ validate_frequency!
11
+ validate_interval!
12
+ validate_count!
13
+ end
14
+
15
+ # @raise [InvalidRuleFrequency]
16
+ # @return [void]
17
+ def validate_frequency!
18
+ return if Rule::SUPPORTED_FREQUENCIES.include?(frequency)
19
+ fail InvalidRuleFrequency, "'#{frequency}' is not supported"
20
+ end
21
+
22
+ # @raise [InvalidRuleInterval]
23
+ # @return [void]
24
+ def validate_interval!
25
+ return if interval >= MIN_INTERVAL
26
+ fail InvalidRuleInterval, "'#{interval}' is not supported"
27
+ end
28
+
29
+ # @raise [InvalidRuleCount]
30
+ # @return [void]
31
+ def validate_count!
32
+ return if count.nil? || count >= MIN_COUNT
33
+ fail InvalidRuleCount, "'#{count}' is not supported"
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Recurify # :nodoc:
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.0.2'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recurify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Schiessl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-01 00:00:00.000000000 Z
11
+ date: 2015-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 3.3.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.3
69
83
  description:
70
84
  email: cs@proactive.cc
71
85
  executables: []
@@ -79,6 +93,7 @@ files:
79
93
  - lib/recurify/rule.rb
80
94
  - lib/recurify/rule_analysis.rb
81
95
  - lib/recurify/rule_translation.rb
96
+ - lib/recurify/rule_validation.rb
82
97
  - lib/recurify/version.rb
83
98
  homepage: https://github.com/cs/recurify
84
99
  licenses: