signalize 1.0.0 → 1.1.0

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
  SHA256:
3
- metadata.gz: 3f252599b06ea9725abc4dd66d0b23562cd5b245dd970106c441b22a0890fe42
4
- data.tar.gz: 1e46dc796f6bd28fc80292d0ef526ab5691642ec1fb34d213e547a9b9a0b3b22
3
+ metadata.gz: 023d2e474c347003b12bef1210945016175838261a5761dae81cb90d749fe51b
4
+ data.tar.gz: 04adf75c67d46adf30fabfdf996eea3ef1555678f96189444d32355571c5d12d
5
5
  SHA512:
6
- metadata.gz: b4eb905112dc61711495cca9cca462f37a1a50751b7d0434fd15b916276af57c73e08ac055d14d0e45534e8d82e8a2ad1576197772ffd544036cccc27d548c72
7
- data.tar.gz: 8e4bf5a24a1ede2912a1f0b9265598d616829d2d9b0ef28eece14dbb5859d1c1fd75c8a19bbe3eb6ee9bb5922b65a5079e7d578cc75788cac4d13f0f18aca247
6
+ metadata.gz: 1c6cc8aad7188bde45f3c5e535d8a7c9ceb17e0598f35ebe45791cf194b8489e799fb32680c130f4bd3f0c06e399ae900bfa8dd103fd7431fe779856fb1685cc
7
+ data.tar.gz: e3eacdfae3cecd69c3b17158e4ae5610119d83e778e369479c487a1ac42e88dcb554cb9c3389cf4d1e1689a337f1bab2f0e02230d6a22b59df3b4bb2993069c3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
+ # Changelog
2
+
1
3
  ## [Unreleased]
2
4
 
5
+ ## [1.1.0] - 2023-03-25
6
+
7
+ - Provide better signal/computed inspect strings (fixes #1)
8
+ - Use Concurrent::Map for thread-safe globals (fixes #3)
9
+
10
+ ## [1.0.1] - 2023-03-08
11
+
12
+ - Prevent early returns in effect blocks
13
+ - Use gem's error class (fixes #2)
14
+
3
15
  ## [1.0.0] - 2023-03-07
4
16
 
5
17
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,12 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- signalize (1.0.0)
4
+ signalize (1.1.0)
5
+ concurrent-ruby (~> 1.2)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
10
  coderay (1.1.3)
11
+ concurrent-ruby (1.2.2)
10
12
  ffi (1.15.5)
11
13
  formatador (1.1.0)
12
14
  guard (2.18.0)
@@ -44,6 +46,7 @@ GEM
44
46
 
45
47
  PLATFORMS
46
48
  arm64-darwin-21
49
+ x86_64-linux
47
50
 
48
51
  DEPENDENCIES
49
52
  guard
data/README.md CHANGED
@@ -32,6 +32,8 @@ Signalize's public API consists of four methods (you can think of them almost li
32
32
  The first building block is the `Signalize::Signal` class. You can think of this as a reactive value object which wraps an underlying primitive like String, Integer, Array, etc.
33
33
 
34
34
  ```ruby
35
+ require "signalize"
36
+
35
37
  counter = Signalize.signal(0)
36
38
 
37
39
  # Read value from signal, logs: 0
@@ -44,6 +46,7 @@ counter.value = 1
44
46
  You can include the `Signalize::API` mixin to access these methods directly in any context:
45
47
 
46
48
  ```ruby
49
+ require "signalize"
47
50
  include Signalize::API
48
51
 
49
52
  counter = signal(0)
@@ -56,6 +59,7 @@ counter.value += 1
56
59
  You derive computed state by accessing a signal's value within a `computed` block and returning a new value. Every time that signal value is updated, a computed value will likewise be updated. Actually, that's not quite accurate — the computed value only computes when it's read. In this sense, we can call computed values "lazily-evaluated".
57
60
 
58
61
  ```ruby
62
+ require "signalize"
59
63
  include Signalize::API
60
64
 
61
65
  name = signal("Jane")
@@ -82,6 +86,7 @@ puts full_name.value
82
86
  Effects are callbacks which are executed whenever values which the effect has "subscribed" to by referencing them have changed. An effect callback is run immediately when defined, and then again for any future mutations.
83
87
 
84
88
  ```ruby
89
+ require "signalize"
85
90
  include Signalize::API
86
91
 
87
92
  name = signal("Jane")
@@ -99,6 +104,7 @@ name.value = "John"
99
104
  You can dispose of an effect whenever you want, thereby unsubscribing it from signal notifications.
100
105
 
101
106
  ```ruby
107
+ require "signalize"
102
108
  include Signalize::API
103
109
 
104
110
  name = signal("Jane")
@@ -117,11 +123,62 @@ dispose.()
117
123
  surname.value = "Doe 2"
118
124
  ```
119
125
 
126
+ **IMPORTANT:** you cannot use `return` or `break` within an effect block. Doing so will raise an exception (due to it breaking the underlying execution model).
127
+
128
+ ```ruby
129
+ def my_method(signal_obj)
130
+ effect do
131
+ return if signal_obj.value > 5 # DON'T DO THIS!
132
+
133
+ puts signal_obj.value
134
+ end
135
+
136
+ # more code here
137
+ end
138
+ ```
139
+
140
+ Instead, try to resolve it using more explicit logic:
141
+
142
+ ```ruby
143
+ def my_method(signal_obj)
144
+ should_exit = false
145
+
146
+ effect do
147
+ should_exit = true && next if signal_obj.value > 5
148
+
149
+ puts signal_obj.value
150
+ end
151
+
152
+ return if should_exit
153
+
154
+ # more code here
155
+ end
156
+ ```
157
+
158
+ However, there's no issue if you pass in a method proc directly:
159
+
160
+ ```ruby
161
+ def my_method(signal_obj)
162
+ @signal_obj = signal_obj
163
+
164
+ effect &method(:an_effect_method)
165
+
166
+ # more code here
167
+ end
168
+
169
+ def an_effect_method
170
+ return if @signal_obj.value > 5
171
+
172
+ puts @signal_obj.value
173
+ end
174
+ ```
175
+
120
176
  ### `batch { }`
121
177
 
122
- You can write to multiple signals within a batch, and flush the updates at all once (thereby notifying computed refreshes and effects).
178
+ You can write to multiple signals within a batch, and flush the updates at all once (thereby notifying computed refreshes and effects).
123
179
 
124
180
  ```ruby
181
+ require "signalize"
125
182
  include Signalize::API
126
183
 
127
184
  name = signal("Jane")
@@ -132,8 +189,8 @@ full_name = computed { name.value + " " + surname.value }
132
189
  dispose = effect { puts full_name.value }
133
190
 
134
191
  batch do
135
- name.value = "Foo"
136
- surname.value = "Bar"
192
+ name.value = "Foo"
193
+ surname.value = "Bar"
137
194
  end
138
195
  ```
139
196
 
@@ -142,6 +199,7 @@ end
142
199
  You can explicitly subscribe to a signal signal value and be notified on every change. (Essentially the Observable pattern.) In your block, the new signal value will be supplied as an argument.
143
200
 
144
201
  ```ruby
202
+ require "signalize"
145
203
  include Signalize::API
146
204
 
147
205
  counter = signal(0)
@@ -158,17 +216,18 @@ counter.value = 1 # logs the new value
158
216
  If you need to access a signal's value inside an effect without subscribing to that signal's updates, use the `peek` method instead of `value`.
159
217
 
160
218
  ```ruby
219
+ require "signalize"
161
220
  include Signalize::API
162
221
 
163
222
  counter = signal(0)
164
223
  effect_count = signal(0)
165
224
 
166
225
  effect do
167
- puts counter.value
226
+ puts counter.value
168
227
 
169
- # Whenever this effect is triggered, increase `effect_count`.
170
- # But we don't want this signal to react to `effect_count`
171
- effect_count.value = effect_count.peek
228
+ # Whenever this effect is triggered, increase `effect_count`.
229
+ # But we don't want this signal to react to `effect_count`
230
+ effect_count.value = effect_count.peek + 1
172
231
  end
173
232
  ```
174
233
 
@@ -189,7 +248,3 @@ This project is intended to be a safe, welcoming space for collaboration, and co
189
248
  ## License
190
249
 
191
250
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
192
-
193
- ## Code of Conduct
194
-
195
- Everyone interacting in the Signalize project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/whitefusionhq/signalize/blob/main/CODE_OF_CONDUCT.md).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Signalize
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/signalize.rb CHANGED
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent"
3
4
  require_relative "signalize/version"
4
5
 
5
6
  module Signalize
6
7
  class Error < StandardError; end
7
8
 
8
9
  class << self
9
- def class_variablize(name)
10
+ def global_map_accessor(name)
10
11
  define_singleton_method "#{name}" do
11
- class_variable_get("@@#{name}")
12
+ GLOBAL_MAP[name]
12
13
  end
13
14
  define_singleton_method "#{name}=" do |value|
14
- class_variable_set("@@#{name}", value)
15
+ GLOBAL_MAP[name] = value
15
16
  end
16
17
  end
17
18
  end
18
19
 
19
20
  def self.cycle_detected
20
- raise "Cycle detected"
21
+ raise Signalize::Error, "Cycle detected"
21
22
  end
22
23
 
23
24
  RUNNING = 1 << 0
@@ -27,24 +28,26 @@ module Signalize
27
28
  HAS_ERROR = 1 << 4
28
29
  TRACKING = 1 << 5
29
30
 
31
+ GLOBAL_MAP = Concurrent::Map.new
32
+
30
33
  # Computed | Effect | nil
31
- @@eval_context = nil
32
- class_variablize :eval_context
34
+ global_map_accessor :eval_context
35
+ self.eval_context = nil
33
36
 
34
37
  # Effects collected into a batch.
35
- @@batched_effect = nil
36
- class_variablize :batched_effect
37
- @@batch_depth = 0
38
- class_variablize :batch_depth
39
- @@batch_iteration = 0
40
- class_variablize :batch_iteration
38
+ global_map_accessor :batched_effect
39
+ self.batched_effect = nil
40
+ global_map_accessor :batch_depth
41
+ self.batch_depth = 0
42
+ global_map_accessor :batch_iteration
43
+ self.batch_iteration = 0
41
44
 
42
45
  # NOTE: we have removed the global version optimization for Ruby, due to
43
46
  # the possibility of long-running server processes and the number reaching
44
47
  # a dangerously high integer value.
45
48
  #
46
- # @@global_version = 0
47
- # class_variablize :global_version
49
+ # global_map_accessor :global_version
50
+ # self.global_version = 0
48
51
 
49
52
  Node = Struct.new(
50
53
  :_version,
@@ -339,7 +342,7 @@ module Signalize
339
342
  end
340
343
 
341
344
  def end_effect(effect, prev_context, *_) # allow additional args for currying
342
- raise "Out-of-order effect" if eval_context != effect
345
+ raise Signalize::Error, "Out-of-order effect" if eval_context != effect
343
346
 
344
347
  cleanup_sources(effect)
345
348
  self.eval_context = prev_context
@@ -436,6 +439,10 @@ module Signalize
436
439
  end
437
440
 
438
441
  def peek = @value
442
+
443
+ def inspect
444
+ "#<#{self.class} value: #{peek.inspect}>"
445
+ end
439
446
  end
440
447
 
441
448
  class Computed < Signal
@@ -586,8 +593,13 @@ module Signalize
586
593
  finis = _start
587
594
 
588
595
  begin
596
+ compute_executed = false
589
597
  @_cleanup = _compute.() if (@_flags & DISPOSED).zero? && @_compute.nil?.!
598
+ compute_executed = true
590
599
  ensure
600
+ unless compute_executed
601
+ raise Signalize::Error, "Early return or break detected in effect block"
602
+ end
591
603
  finis.(nil) # TODO: figure out this weird shit
592
604
  end
593
605
  end
data/signalize.gemspec CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  # Uncomment to register a new dependency of your gem
30
- # spec.add_dependency "example-gem", "~> 1.0"
30
+ spec.add_dependency "concurrent-ruby", "~> 1.2"
31
31
 
32
32
  # For more information and examples about making a new gem, check out our
33
33
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: signalize
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared White
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-08 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2023-03-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: concurrent-ruby
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.2'
14
28
  description: A Ruby port of Signals, providing reactive variables, derived computed
15
29
  state, side effect callbacks, and batched updates.
16
30
  email: