Skip to content
This repository was archived by the owner on Aug 16, 2025. It is now read-only.

Commit 901e854

Browse files
authored
MIGRATED to Codeberg
1 parent 4152c91 commit 901e854

File tree

1 file changed

+2
-318
lines changed

1 file changed

+2
-318
lines changed

README.md

Lines changed: 2 additions & 318 deletions
Original file line numberDiff line numberDiff line change
@@ -1,320 +1,4 @@
11
# Signalize
22

3-
Signalize is a Ruby port of the JavaScript-based [core Signals package](https://github.com/preactjs/signals) by the Preact project. Signals provides reactive variables, derived computed state, side effect callbacks, and batched updates.
4-
5-
Additional context as provided by the original documentation:
6-
7-
> Signals is a performant state management library with two primary goals:
8-
>
9-
> Make it as easy as possible to write business logic for small up to complex apps. No matter how complex your logic is, your app updates should stay fast without you needing to think about it. Signals automatically optimize state updates behind the scenes to trigger the fewest updates necessary. They are lazy by default and automatically skip signals that no one listens to.
10-
> Integrate into frameworks as if they were native built-in primitives. You don't need any selectors, wrapper functions, or anything else. Signals can be accessed directly and your component will automatically re-render when the signal's value changes.
11-
12-
While a lot of what we tend to write in Ruby is in the form of repeated, linear processing cycles (aka HTTP requests/responses on the web), there is increasingly a sense that we can look at concepts which make a lot of sense on the web frontend in the context of UI interactions and data flows and apply similar principles to the backend as well. Signalize helps you do just that.
13-
14-
**NOTE:** read the Contributing section below before submitting a bug report or PR.
15-
16-
## Installation
17-
18-
Install the gem and add to the application's Gemfile by executing:
19-
20-
$ bundle add signalize
21-
22-
If bundler is not being used to manage dependencies, install the gem by executing:
23-
24-
$ gem install signalize
25-
26-
## Usage
27-
28-
Signalize's public API consists of five methods (you can think of them almost like functions): `signal`, `untracked`, `computed`, `effect`, and `batch`.
29-
30-
### `signal(initial_value)`
31-
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-
34-
```ruby
35-
require "signalize"
36-
37-
counter = Signalize.signal(0)
38-
39-
# Read value from signal, logs: 0
40-
puts counter.value
41-
42-
# Write to a signal
43-
counter.value = 1
44-
```
45-
46-
You can include the `Signalize::API` mixin to access these methods directly in any context:
47-
48-
```ruby
49-
require "signalize"
50-
include Signalize::API
51-
52-
counter = signal(0)
53-
54-
counter.value += 1
55-
```
56-
57-
### `untracked { }`
58-
59-
In case when you're receiving a callback that can read some signals, but you don't want to subscribe to them, you can use `untracked` to prevent any subscriptions from happening.
60-
61-
```ruby
62-
require "signalize"
63-
include Signalize::API
64-
65-
counter = signal(0)
66-
effect_count = signal(0)
67-
fn = proc { effect_count.value + 1 }
68-
69-
effect do
70-
# Logs the value
71-
puts counter.value
72-
73-
# Whenever this effect is triggered, run `fn` that gives new value
74-
effect_count.value = untracked(&fn)
75-
end
76-
```
77-
78-
### `computed { }`
79-
80-
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".
81-
82-
```ruby
83-
require "signalize"
84-
include Signalize::API
85-
86-
name = signal("Jane")
87-
surname = signal("Doe")
88-
89-
full_name = computed do
90-
name.value + " " + surname.value
91-
end
92-
93-
# Logs: "Jane Doe"
94-
puts full_name.value
95-
96-
name.value = "John"
97-
name.value = "Johannes"
98-
# name.value = "..."
99-
# Setting value multiple times won't trigger a computed value refresh
100-
101-
# NOW we get a refreshed computed value:
102-
puts full_name.value
103-
```
104-
105-
### `effect { }`
106-
107-
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.
108-
109-
```ruby
110-
require "signalize"
111-
include Signalize::API
112-
113-
name = signal("Jane")
114-
surname = signal("Doe")
115-
full_name = computed { name.value + " " + surname.value }
116-
117-
# Logs: "Jane Doe"
118-
effect { puts full_name.value }
119-
120-
# Updating one of its dependencies will automatically trigger
121-
# the effect above, and will print "John Doe" to the console.
122-
name.value = "John"
123-
```
124-
125-
You can dispose of an effect whenever you want, thereby unsubscribing it from signal notifications.
126-
127-
```ruby
128-
require "signalize"
129-
include Signalize::API
130-
131-
name = signal("Jane")
132-
surname = signal("Doe")
133-
full_name = computed { name.value + " " + surname.value }
134-
135-
# Logs: "Jane Doe"
136-
dispose = effect { puts full_name.value }
137-
138-
# Destroy effect and subscriptions
139-
dispose.()
140-
141-
# Update does nothing, because no one is subscribed anymore.
142-
# Even the computed `full_name` signal won't change, because it knows
143-
# that no one listens to it.
144-
surname.value = "Doe 2"
145-
```
146-
147-
**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).
148-
149-
```ruby
150-
def my_method(signal_obj)
151-
effect do
152-
return if signal_obj.value > 5 # DON'T DO THIS!
153-
154-
puts signal_obj.value
155-
end
156-
157-
# more code here
158-
end
159-
```
160-
161-
Instead, try to resolve it using more explicit logic:
162-
163-
```ruby
164-
def my_method(signal_obj)
165-
should_exit = false
166-
167-
effect do
168-
should_exit = true && next if signal_obj.value > 5
169-
170-
puts signal_obj.value
171-
end
172-
173-
return if should_exit
174-
175-
# more code here
176-
end
177-
```
178-
179-
However, there's no issue if you pass in a method proc directly:
180-
181-
```ruby
182-
def my_method(signal_obj)
183-
@signal_obj = signal_obj
184-
185-
effect &method(:an_effect_method)
186-
187-
# more code here
188-
end
189-
190-
def an_effect_method
191-
return if @signal_obj.value > 5
192-
193-
puts @signal_obj.value
194-
end
195-
```
196-
197-
### `batch { }`
198-
199-
You can write to multiple signals within a batch, and flush the updates at all once (thereby notifying computed refreshes and effects).
200-
201-
```ruby
202-
require "signalize"
203-
include Signalize::API
204-
205-
name = signal("Jane")
206-
surname = signal("Doe")
207-
full_name = computed { name.value + " " + surname.value }
208-
209-
# Logs: "Jane Doe"
210-
dispose = effect { puts full_name.value }
211-
212-
batch do
213-
name.value = "Foo"
214-
surname.value = "Bar"
215-
end
216-
```
217-
218-
### `signal.subscribe { }`
219-
220-
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.
221-
222-
```ruby
223-
require "signalize"
224-
include Signalize::API
225-
226-
counter = signal(0)
227-
228-
counter.subscribe do |new_value|
229-
puts "The new value is #{new_value}"
230-
end
231-
232-
counter.value = 1 # logs the new value
233-
```
234-
235-
### `signal.peek`
236-
237-
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`.
238-
239-
```ruby
240-
require "signalize"
241-
include Signalize::API
242-
243-
counter = signal(0)
244-
effect_count = signal(0)
245-
246-
effect do
247-
puts counter.value
248-
249-
# Whenever this effect is triggered, increase `effect_count`.
250-
# But we don't want this signal to react to `effect_count`
251-
effect_count.value = effect_count.peek + 1
252-
end
253-
```
254-
255-
## Signalize Struct
256-
257-
An optional add-on to Signalize, the `Singalize::Struct` class lets you define multiple signal or computed variables to hold in struct-like objects. You can even add custom methods to your classes with a simple DSL. (The API is intentionally similar to `Data` in Ruby 3.2+, although these objects are of course mutable.)
258-
259-
Here's what it looks like:
260-
261-
```ruby
262-
require "signalize/struct"
263-
264-
include Signalize::API
265-
266-
TestSignalsStruct = Signalize::Struct.define(
267-
:str,
268-
:int,
269-
:multiplied_by_10
270-
) do # optional block for adding methods
271-
def increment!
272-
self.int += 1
273-
end
274-
end
275-
276-
struct = TestSignalsStruct.new(
277-
int: 0,
278-
str: "Hello World",
279-
multiplied_by_10: computed { struct.int * 10 }
280-
)
281-
282-
effect do
283-
puts struct.multiplied_by_10 # 0
284-
end
285-
286-
effect do
287-
puts struct.str # "Hello World"
288-
end
289-
290-
struct.increment! # above effect will now output 10
291-
struct.str = "Goodbye!" # above effect will now output "Goodbye!"
292-
```
293-
294-
If you ever need to get at the actual `Signal` object underlying a value, just call `*_signal`. For example, you could call `int_signal` for the above example to get a signal object for `int`.
295-
296-
Signalize structs require all of their members to be present when initializing…you can't pass only some keyword arguments.
297-
298-
Signalize structs support `to_h` as well as `deconstruct_keys` which is used for pattern matching and syntax like `struct => { str: }` to set local variables.
299-
300-
You can call `members` (as both object/class methods) to get a list of the value names in the struct.
301-
302-
Finally, both `inspect` and `to_s` let you debug the contents of a struct.
303-
304-
## Development
305-
306-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake test` to run the tests, or `bin/guard` or run them continuously in watch mode. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
307-
308-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
309-
310-
## Contributing
311-
312-
Signalize is considered a direct port of the [original Signals JavaScript library](https://github.com/preactjs/signals). This means we are unlikely to accept any additional features other than what's provided by Signals (unless it's completely separate, like our `Signalize::Struct` add-on). If Signals adds new functionality in the future, we will endeavor to replicate it in Signalize. Furthermore, if there's some unwanted behavior in Signalize that's also present in Signals, we are unlikely to modify that behavior.
313-
314-
However, if you're able to supply a bugfix or performance optimization which will help bring Signalize _more_ into alignment with its Signals counterpart, we will gladly accept your PR!
315-
316-
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/whitefusionhq/signalize/blob/main/CODE_OF_CONDUCT.md).
317-
318-
## License
319-
320-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
3+
> [!CAUTION]
4+
> This project [has migrated to Codeberg](https://codeberg.org/jaredwhite/signalize). Please update your references accordingly and do not link to this GitHub project. It has now been archived. Thank you!

0 commit comments

Comments
 (0)