Skip to content

Commit b183824

Browse files
committed
insure async methods dont start after observer is unmounted, plus improvements to stock ticker
1 parent 762d503 commit b183824

File tree

5 files changed

+45
-33
lines changed

5 files changed

+45
-33
lines changed

ruby/examples/misc/stock-tickers/app/hyperstack/components/app.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ class App < HyperComponent
44
UL do
55
@symbols.sort.each do |symbol|
66
LI(key: symbol) do
7-
DisplayTicker(symbol: symbol, key: symbol)
7+
DisplayTicker(symbol: symbol)
88
.on(:cancel) { mutate @symbols.delete(symbol) }
99
end
1010
end
1111
end
12-
INPUT(placeholder: 'enter a stock symbol').on(:key_down) do |evt|
12+
INPUT(placeholder: 'enter a new stock symbol')
13+
.on(:key_down) do |evt|
1314
next unless evt.key_code == 13
14-
mutate @symbols << evt.target.value
15+
mutate @symbols << evt.target.value.upcase
1516
evt.target.value = ''
1617
end
1718
end

ruby/examples/misc/stock-tickers/app/hyperstack/components/display_ticker.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ class DisplayTicker < HyperComponent
22
param :symbol
33
param :on_cancel, type: Proc
44
before_mount { @ticker = StockTicker.new(params.symbol, 10.seconds) }
5-
render(DIV) do
5+
6+
def status
67
case @ticker.status
78
when :loading
8-
SPAN { "#{params.symbol.upcase} loading..."}
9+
'loading...'
910
when :success
10-
SPAN { "#{params.symbol.upcase} current price: #{@ticker.price}" }
11+
"current price: #{@ticker.price}"
1112
when :failed
12-
SPAN { "#{params.symbol.upcase} failed to get quote: #{@ticker.reason}"}
13+
"failed to get quote: #{@ticker.reason}"
1314
end
15+
end
16+
17+
render(DIV) do
18+
SPAN { "#{params.symbol.upcase} #{status}" }
1419
BUTTON { 'cancel' }.on(:click) { params.on_cancel } unless @ticker.status == :loading
1520
end
1621
end
Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
class StockTicker
22
include Hyperstack::State::Observable
33

4+
attr_reader :symbol
45
state_reader :price
5-
attr_reader :symbol
66
state_reader :status
77
state_reader :reason
88

9-
def initialize(symbol, update_interval = 5.minutes)
9+
def initialize(symbol, update_interval = 5.seconds)
10+
puts "creating ticker for #{symbol}"
1011
@symbol = symbol
1112
@status = :loading
1213
@update_interval = update_interval
@@ -16,24 +17,21 @@ def initialize(symbol, update_interval = 5.minutes)
1617
def fetch
1718
puts "fetching #{@symbol}"
1819
HTTP.get("https://api.iextrading.com/1.0/stock/#{@symbol}/delayed-quote")
19-
.then do |response|
20-
mutate @status = :success
21-
mutate @price = response.json[:delayedPrice]
22-
after(@update_interval) { fetch }
23-
end
24-
.fail do |response|
25-
mutate @status = :failed
26-
mutate @reason = response.body.empty? ? 'Network error' : response.body
27-
after(@update_interval) { fetch } unless @reason == "Unknown symbol"
28-
end
20+
.then do |resp|
21+
mutate @status = :success, @price = resp.json[:delayedPrice]
22+
after(@update_interval) { fetch }
23+
end
24+
.fail do |resp|
25+
mutate @status = :failed, @reason = resp.body.empty? ? 'Network error' : resp.body
26+
after(@update_interval) { fetch } unless @reason == 'Unknown symbol'
27+
end
2928
end
3029

3130
before_unmount do
3231
# just to demonstrate use of before_unmount - this is not needed
3332
# but if there were some other cleanups you needed you could put them here
3433
# however intervals (every) delays (after) and websocket receivers are all
3534
# cleaned up automatically
36-
puts "cancelling #{@symbol}"
35+
puts "cancelling #{@symbol} ticker"
3736
end
38-
3937
end

ruby/hyper-state/lib/hyperstack/internal/auto_unmount.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ def self.included(base)
88
end
99
end
1010

11+
def unmounted?
12+
@__hyperstack_internal_auto_unmount_unmounted
13+
end
14+
1115
def unmount
1216
run_callback(:before_unmount)
1317
AutoUnmount.objects_to_unmount[self].each(&:unmount)
@@ -20,31 +24,29 @@ def unmount
2024
nil
2125
end
2226
end
27+
@__hyperstack_internal_auto_unmount_unmounted = true
2328
end
2429

2530
def every(*args, &block)
31+
return if unmounted?
2632
super.tap do |id|
2733
sself = self
2834
id.define_singleton_method(:unmount) { abort }
29-
id.define_singleton_method(:manually_unmount) do
30-
AutoUnmount.objects_to_unmount[sself].delete(id)
31-
end
3235
AutoUnmount.objects_to_unmount[self] << id
3336
end
3437
end
3538

3639
def after(*args, &block)
40+
return if unmounted?
3741
super.tap do |id|
3842
sself = self
3943
id.define_singleton_method(:unmount) { abort }
40-
id.define_singleton_method(:manually_unmount) do
41-
AutoUnmount.objects_to_unmount[sself].delete(id)
42-
end
4344
AutoUnmount.objects_to_unmount[self] << id
4445
end
4546
end
4647

4748
def receives(broadcaster, *args, &block)
49+
return if unmounted?
4850
broadcaster.receiver(self, *args, &block).tap do |id|
4951
# TODO: broadcaster classes should already defined manually_unmount
5052
# id.define_singleton_method(:manually_unmount) do

ruby/hyper-state/spec/internal/auto_unmount_spec.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@ def after
1818
end
1919

2020
%i[every after].each do |meth|
21-
it "will pass through to the super class when mounting and abort calls to the #{meth} method when unmounting" do
21+
it "will pass through calls to #{meth} to the super class when mounted, and abort calls to the #{meth} method when unmounting" do
2222
timer = double('timer')
2323
expect(@async_obj).to receive(:"#{meth}_called").and_return(timer)
2424
@async_obj.send(meth)
2525
expect(timer).to receive(:abort)
2626
@async_obj.unmount
2727
end
28-
it "will not auto_unmount #{meth} objects if requested using the manually_unmount method" do
29-
timer = double('timer')
30-
expect(@async_obj).to receive(:"#{meth}_called").and_return(timer)
31-
@async_obj.send(meth).manually_unmount
32-
expect(timer).not_to receive(:abort)
28+
29+
it "will ignore calls to the #{meth} method once unmounted" do
3330
@async_obj.unmount
31+
expect(@async_obj).not_to receive(:"#{meth}_called")
32+
expect(@async_obj.send(meth)).to be_nil
3433
end
3534
end
3635

@@ -42,6 +41,13 @@ def after
4241
@async_obj.unmount
4342
end
4443

44+
it "will ignore calls to the receives method once unmounted" do
45+
broadcaster = double('Broadcaster')
46+
@async_obj.unmount
47+
expect(@async_obj).not_to receive(:receiver)
48+
expect(@async_obj.receives(broadcaster)).to be_nil
49+
end
50+
4551
it 'will unmount objects referenced by instance variables' do
4652
unmountable_object = double('unmountable_object')
4753
regular_object = double('regular_object')

0 commit comments

Comments
 (0)