statefully 0.1.3 → 0.1.4

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: 97acdf9e907dd51ad5227ef53828030d0943d35d
4
- data.tar.gz: 3d9564c5fa59479e584e38c94ebdf504e1e1894d
3
+ metadata.gz: 4f804d2bf7a4e93cce2e444f0bf61ecbf93fcc40
4
+ data.tar.gz: 9fdf666b486671a6a45cab5e5c4bb885ffcb2baf
5
5
  SHA512:
6
- metadata.gz: 94467d76d84d115d674c84db44022a6c4e76d81e417b1ca48eb613734802fd80c420f4a79832aa248f1622e9260c26d1bdfb8c72f9c73f078a14df230cde96b5
7
- data.tar.gz: 732e6d9e831787099ae318d706a0c6d95280ea8ee06217e1bc0b3f9a30bd058d150b8053a972ffd631a626e3e75937cac403a5fc9e14609f0413f870e6da9894
6
+ metadata.gz: d4d379b599bf47f4cfb48ec9a5d7450be76e07f8debacc0c1eb18d603fe0a846d2103b69e8cc53951350e12ed17a8ea9bac4a030728365113001b1007a0c6ac2
7
+ data.tar.gz: 9ed5595f56f566543fdc0bd69d71ef917ba674d2abf49502670c3d92d1fe021780c3304d8056ca87da4681443bf112491d3b2138b098ce16eec16533ebc7a9e8
data/lib/statefully.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'statefully/change'
1
2
  require 'statefully/diff'
3
+ require 'statefully/errors'
2
4
  require 'statefully/inspect'
3
5
  require 'statefully/state'
@@ -0,0 +1,52 @@
1
+ module Statefully
2
+ # Change is a tuple of current and previous value of a field in a {Diff}.
3
+ class Change
4
+ # Returns the current {State} field value
5
+ #
6
+ # @return [Object] current {State} field value
7
+ # @api public
8
+ # @example
9
+ # Statefully::Change.new(current: 7, previous: 8).current
10
+ # => 7
11
+ attr_reader :current
12
+
13
+ # Returns the previous {State} field value
14
+ #
15
+ # @return [Object] previous {State} field value
16
+ # @api public
17
+ # @example
18
+ # Statefully::Change.new(current: 7, previous: 8).previous
19
+ # => 8
20
+ attr_reader :previous
21
+
22
+ # Constructor for the {Change} object
23
+ # @param current [Object] current {State} field value
24
+ # @param previous [Object] previous {State} field value
25
+ # @api public
26
+ # @example
27
+ # Statefully::Change.new(current: 7, previous: 8)
28
+ # => #<Statefully::Change current=7, previous=8>
29
+ def initialize(current:, previous:)
30
+ @current = current
31
+ @previous = previous
32
+ end
33
+
34
+ # Internal-only method used to determine whether there was any change
35
+ # @api private
36
+ def none?
37
+ @current == @previous
38
+ end
39
+
40
+ # Human-readable representation of the {Change} for console inspection
41
+ #
42
+ # @return [String]
43
+ # @api semipublic
44
+ # @example
45
+ # Statefully::Change.new(current: 7, previous: 8)
46
+ # => #<Statefully::Change current=7, previous=8>
47
+ def inspect
48
+ "#<#{self.class.name} " \
49
+ "#{Inspect.from_fields(current: current, previous: previous)}>"
50
+ end
51
+ end # class Change
52
+ end # module Statefully
@@ -2,124 +2,299 @@ require 'set'
2
2
  require 'singleton'
3
3
 
4
4
  module Statefully
5
- module Diff
5
+ # {Diff} is a difference between two neighboring instances of {State}.
6
+ #
7
+ # @abstract
8
+ class Diff
9
+ # Create is the only public interface to the Diff class
10
+ #
11
+ # @param current [Statefully::State] current state
12
+ # @param previous [Statefully::State] previous state
13
+ #
14
+ # @return [Statefully::Diff] Difference between states.
15
+ # @api public
16
+ # @example
17
+ # previous = Statefully::State.create
18
+ # current = previus.succeed(key: 'val')
19
+ # Statefully::Diff.create(current, previous)
20
+ # => #<Statefully::Diff::Changed added={key: "val"}>
21
+ #
6
22
  # This method reeks of :reek:FeatureEnvy (of current).
7
- def create(current, previous)
23
+ def self.create(current:, previous:)
8
24
  return current.diff if current.failed? || current.finished?
9
- changes = Builder.new(current, previous).build
25
+ changes = Builder.new(current: current, previous: previous).build
26
+ return Created.new(**changes).freeze if previous.none?
10
27
  changes.empty? ? Unchanged.instance : Changed.new(**changes).freeze
11
28
  end
12
- module_function :create
13
29
 
14
- class Changed
15
- attr_reader :added, :changed
30
+ # Check if a {Diff} is empty
31
+ #
32
+ # An empty {Diff} means that is there are no changes in properties between
33
+ # current and previous {State}.
34
+ #
35
+ # @return [Boolean]
36
+ # @api public
37
+ # @example
38
+ # Statefully::Diff::Unchanged.instance.empty?
39
+ # => true
40
+ def empty?
41
+ true
42
+ end
16
43
 
17
- def empty?
18
- false
44
+ # Hash of added properties and their values
45
+ #
46
+ # @return [Hash<Symbol, Object>]
47
+ # @api public
48
+ # @example
49
+ # Statefully::Diff::Unchanged.instance.added
50
+ # => {}
51
+ def added
52
+ {}
53
+ end
54
+
55
+ # Hash of changed properties and their current and previous values
56
+ #
57
+ # @return [Hash<Symbol, Statefully::Diff::Change>]
58
+ # @api public
59
+ # @example
60
+ # Statefully::Diff::Unchanged.instance.added.changed
61
+ # => {}
62
+ def changed
63
+ {}
64
+ end
65
+
66
+ # Check if a key has been added
67
+ #
68
+ # @param key [Symbol]
69
+ # @return [Boolean]
70
+ # @api public
71
+ # @example
72
+ # diff = Statefully::Diff::Changed.new(added: {key: 7})
73
+ # diff.added?(:key)
74
+ # => true
75
+ # diff.added?(:other)
76
+ # => false
77
+ def added?(key)
78
+ added.key?(key)
79
+ end
80
+
81
+ # Check if a key has been changed
82
+ #
83
+ # @param key [Symbol]
84
+ # @return [Boolean]
85
+ # @api public
86
+ # @example
87
+ # diff = Statefully::Diff::Changed.new(
88
+ # changed: {key: Statefully::Change.new(current: 7, previous: 8)},
89
+ # )
90
+ # diff.changed?(:key)
91
+ # => true
92
+ # diff.changed?(:other)
93
+ # => false
94
+ def changed?(key)
95
+ changed.key?(key)
96
+ end
97
+
98
+ # {Changed} is a {Diff} which contains changes between two successful
99
+ # {State}s.
100
+ class Changed < Diff
101
+ # Hash of added properties and their values
102
+ #
103
+ # @return [Hash<Symbol, Object>]
104
+ # @api public
105
+ # @example
106
+ # Statefully::Diff::Changed.new(added: {key: 7}).added
107
+ # => {:key => 7}
108
+ attr_reader :added
109
+
110
+ # Hash of changed properties and their current and previous values
111
+ #
112
+ # @return [Hash<Symbol, Change>]
113
+ # @api public
114
+ # @example
115
+ # Statefully::Diff::Changed.new(
116
+ # changed: {key: Statefully::Change.new(current: 7, previous: 8)},
117
+ # )
118
+ # => {:key=>#<Statefully::Change current=7, previous=8>}
119
+ attr_reader :changed
120
+
121
+ # Constructor for {Diff::Changed}
122
+ #
123
+ # @param added [Hash<Symbol, Object>] added fields
124
+ # @param changed [Hash<Symbol, Change>] [changed fields]
125
+ # @api public
126
+ # @example
127
+ # Statefully::Diff::Changed.new(added: {key: 7})
128
+ # => #<Statefully::Diff::Changed added={key: 7}>
129
+ def initialize(added: {}, changed: {})
130
+ @added = added.freeze
131
+ @changed = changed.freeze
19
132
  end
20
133
 
21
- def inspect
22
- "#<#{self.class.name} #{inspect_details}>"
134
+ # Check if a {Diff} resulted from creating a {State}
135
+ #
136
+ # @return [Boolean]
137
+ # @api public
138
+ # @example
139
+ # Stateful::State.created.created?
140
+ # => true
141
+ #
142
+ # Stateful::State.created.succeed.created?
143
+ # => false
144
+ def created?
145
+ false
23
146
  end
24
147
 
25
- def added?(key)
26
- added.key?(key)
148
+ # Check if a {Diff} is empty
149
+ #
150
+ # An empty {Diff} means that there are no changes in properties between
151
+ # current and previous {State}.
152
+ #
153
+ # @return [Boolean]
154
+ # @api public
155
+ # @example
156
+ # Statefully::Diff::Changed.new(added: {key: 7}).empty?
157
+ # => false
158
+ def empty?
159
+ added.empty? && changed.empty?
27
160
  end
28
161
 
29
- def changed?(key)
30
- changed.key?(key)
162
+ # Human-readable representation of the {Change} for console inspection
163
+ #
164
+ # @return [String]
165
+ # @api semipublic
166
+ # @example
167
+ # Statefully::Diff::Changed.new(added: {key: 7})
168
+ # => #<Statefully::Diff::Changed added={key: 7}>
169
+ def inspect
170
+ details = [self.class.name]
171
+ details << inspect_details unless empty?
172
+ "#<#{details.join(' ')}>"
31
173
  end
32
174
 
33
175
  private
34
176
 
177
+ # Helper method to print out added and changed fields
178
+ # @return [String]
179
+ # @api private
35
180
  def inspect_details
36
181
  [inspect_added, inspect_changed].compact.join(', ')
37
182
  end
38
183
 
184
+ # Helper method to print out added fields
185
+ # @return [String]
186
+ # @api private
39
187
  def inspect_added
40
188
  added.empty? ? nil : "added=#{Inspect.from_hash(added)}"
41
189
  end
42
190
 
191
+ # Helper method to print out changed fields
192
+ # @return [String]
193
+ # @api private
43
194
  def inspect_changed
44
195
  changed.empty? ? nil : "changed=#{Inspect.from_hash(changed)}"
45
196
  end
46
-
47
- def initialize(added:, changed:)
48
- @added = added.freeze
49
- @changed = changed.freeze
50
- end
51
197
  end # class Changed
52
198
 
53
- module NoChanges
54
- def empty?
55
- true
56
- end
57
-
58
- def added
59
- {}
199
+ module SingletonInspect
200
+ # Human-readable representation of the {Diff} singleton
201
+ #
202
+ # @return [String]
203
+ # @api private
204
+ def inspect
205
+ "#<#{self.class.name}>"
60
206
  end
61
-
62
- def changed
63
- {}
207
+ end # module SingletonInspect
208
+ private_constant :SingletonInspect
209
+
210
+ # {Created} represents a difference between a null and non-null {State}.
211
+ class Created < Changed
212
+ # Check if a {Diff} resulted from creating a {State}
213
+ #
214
+ # @return [Boolean]
215
+ # @api public
216
+ # @example
217
+ # Stateful::State.created.created?
218
+ # => true
219
+ #
220
+ # Stateful::State.created.succeed.created?
221
+ # => false
222
+ def created?
223
+ true
64
224
  end
65
- end # module NoChanges
66
- private_constant :NoChanges
225
+ end # class Created
67
226
 
68
- class Unchanged
227
+ # {Unchanged} represents a lack of difference between two {State}s.
228
+ class Unchanged < Diff
69
229
  include Singleton
70
- include NoChanges
71
-
72
- def inspect
73
- "#<#{self.class.name}>"
74
- end
230
+ include SingletonInspect
75
231
  end # class Unchanged
76
232
 
77
- class Failed
78
- include NoChanges
233
+ # {Failed} represents a difference between a succesful and failed {State}.
234
+ class Failed < Diff
235
+ # Error that caused the {State} to fail
236
+ #
237
+ # @return [StandardError]
238
+ # @api public
239
+ # @example
240
+ # Statefully::Diff::Failed.new(RuntimeError.new('Boom!')).error
241
+ # => #<RuntimeError: Boom!>
79
242
  attr_reader :error
80
243
 
244
+ # Constructor for {Diff::Failed}
245
+ #
246
+ # @param error [StandardError] error that caused the {State} to fail
247
+ # @api semipublic
248
+ # @example
249
+ # Statefully::Diff::Failed.new(RuntimeError.new('Boom!'))
250
+ # => #<Statefully::Diff::Failed error=#<RuntimeError: Boom!>>
81
251
  def initialize(error)
82
252
  @error = error
83
253
  end
84
254
 
255
+ # Human-readable representation of the {Diff::Failed}
256
+ #
257
+ # @return [String]
258
+ # @api semipublic
259
+ # @example
260
+ # Statefully::Diff::Failed.new(RuntimeError.new('Boom!'))
261
+ # => #<Statefully::Diff::Failed error=#<RuntimeError: Boom!>>
85
262
  def inspect
86
263
  "#<#{self.class.name} error=#{error.inspect}>"
87
264
  end
88
265
  end # class Failed
89
266
 
90
- class Finished < Unchanged
267
+ # {Failed} represents a difference between a succesful and finished {State}.
268
+ class Finished < Diff
269
+ include Singleton
270
+ include SingletonInspect
91
271
  end # class Finished
92
272
 
93
- class Change
94
- attr_reader :current, :previous
95
-
96
- def initialize(current, previous)
97
- @current = current
98
- @previous = previous
99
- end
100
-
101
- def none?
102
- @current == @previous
103
- end
104
-
105
- def inspect
106
- "#<#{self.class.name} " \
107
- "#{Inspect.from_fields(current: current, previous: previous)}>"
108
- end
109
- end # class Change
110
-
111
273
  class Builder
112
- def initialize(current, previous)
274
+ # Constructor for the {Builder} object
275
+ #
276
+ # @param current [State] current {State}
277
+ # @param previous [State] previous {State}
278
+ # @api private
279
+ def initialize(current:, previous:)
113
280
  @current = current
114
281
  @previous = previous
115
282
  end
116
283
 
284
+ # Build a Hash of added and changed {State} fields
285
+ #
286
+ # @return [Hash]
287
+ # @api private
117
288
  def build
118
289
  empty? ? {} : { added: added, changed: changed }
119
290
  end
120
291
 
121
292
  private
122
293
 
294
+ # List added fields
295
+ #
296
+ # @return [Hash]
297
+ # @api private
123
298
  def added
124
299
  @added ||=
125
300
  (current_keys - previous_keys)
@@ -127,6 +302,10 @@ module Statefully
127
302
  .to_h
128
303
  end
129
304
 
305
+ # List changed fields
306
+ #
307
+ # @return [Hash]
308
+ # @api private
130
309
  def changed
131
310
  @changed ||=
132
311
  (current_keys & previous_keys)
@@ -135,22 +314,43 @@ module Statefully
135
314
  .reject { |_, val| val.none? }
136
315
  end
137
316
 
317
+ # Change for individual key
318
+ #
319
+ # @param [Symbol] key name
320
+ #
321
+ # @return [Change]
322
+ # @api private
138
323
  def change_for(key)
139
- Change.new(@current.fetch(key), @previous.fetch(key)).freeze
324
+ Change.new(
325
+ current: @current.fetch(key),
326
+ previous: @previous.fetch(key),
327
+ ).freeze
140
328
  end
141
329
 
330
+ # Check if the nothing has changed
331
+ #
332
+ # @return [Boolean]
333
+ # @api private
142
334
  def empty?
143
335
  added.empty? && changed.empty?
144
336
  end
145
337
 
338
+ # Return the set of keys for the current {State}
339
+ #
340
+ # @return [Set<Symbol>]
341
+ # @api private
146
342
  def current_keys
147
343
  Set.new(@current.keys)
148
344
  end
149
345
 
346
+ # Return the set of keys for previous {State}
347
+ #
348
+ # @return [Set<Symbol>]
349
+ # @api private
150
350
  def previous_keys
151
351
  Set.new(@previous.keys)
152
352
  end
153
353
  end # class Builder
154
354
  private_constant :Builder
155
- end # module Diff
355
+ end # class Diff
156
356
  end # module Statefully