Skip to content

Commit 1988c19

Browse files
authored
Improve event matching (#8)
* Improve event matching by allowing event vectors and predicates in the required-events vector * Changes to clarify fn docs and implementation * Changed via local copy forward-events-fx * improved unregister code
1 parent c8849dc commit 1988c19

File tree

3 files changed

+134
-34
lines changed

3 files changed

+134
-34
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,9 @@ A `rule` is a map with the following fields:
356356

357357
- `:when` one of `:seen?`, `:seen-both?`. `:seen-all-of?`, `:seen-any-of?`
358358
`:seen?`, `:seen-both?` and `:seen-all-of?` are interchangeable.
359-
- `:events` either a single keyword, or a seq of keywords, presumably event ids
359+
- `:events` either a single keyword, or a collection of keywords, presumably event ids.
360+
a collection can also contain whole event vectors that will be matched,
361+
or event predicates that return true or false when passed an event vector.
360362
- `:dispatch` can be a single vector representing one event to dispatch.
361363
- `:dispatch-n` to dispatch multiple events, must be a coll where each elem represents one event to dispatch.
362364
- `:halt?` optional boolean. If true, the flow enters teardown and stops.

src/day8/re_frame/async_flow_fx.cljs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
(:require
33
[re-frame.core :as re-frame]
44
[clojure.set :as set]
5-
[day8.re-frame.forward-events-fx]))
5+
#_[day8.re-frame.forward-events-fx]))
66

77
(defn dissoc-in
88
"Dissociates an entry from a nested associative structure returning a new
99
nested structure. keys is a sequence of keys. Any empty maps that result
1010
will not be present in the new structure.
11-
The key thing is that 'm' remains identical? to istelf if the path was never present"
11+
The key thing is that 'm' remains identical? to itself if the path was never present"
1212
[m [k & ks :as keys]]
1313
(if ks
1414
(if-let [nextmap (get m k)]
@@ -19,14 +19,61 @@
1919
m)
2020
(dissoc m k)))
2121

22+
23+
(defn as-callback-pred
24+
"Looks at the required-events items and returns a predicate which
25+
will either
26+
- match only the event-keyword if a keyword is supplied
27+
- match the entire event vector if a collection is supplied
28+
- returns a callback-pred if it is a fn"
29+
[callback-pred]
30+
(when callback-pred
31+
(cond (fn? callback-pred) callback-pred
32+
(keyword? callback-pred) (fn [[event-id _]]
33+
(= callback-pred event-id))
34+
(coll? callback-pred) (fn [event-v]
35+
(= callback-pred event-v))
36+
:else (throw
37+
(ex-info (str (pr-str callback-pred)
38+
" isn't an event predicate")
39+
{:callback-pred callback-pred})))))
40+
41+
(re-frame/reg-fx
42+
:forward-events
43+
(let [id->listen-fn (atom {})
44+
process-one-entry (fn [{:as m :keys [unregister register events dispatch-to]}]
45+
(let [_ (assert (map? m) (str "re-frame: effects handler for :forward-events expected a map or a list of maps. Got: " m))
46+
_ (assert (or (= #{:unregister} (-> m keys set))
47+
(= #{:register :events :dispatch-to} (-> m keys set))) (str "re-frame: effects handler for :forward-events given wrong map keys" (-> m keys set)))]
48+
(if unregister
49+
(re-frame/remove-post-event-callback unregister)
50+
(let [events-preds (map as-callback-pred events)
51+
post-event-callback-fn (fn [event-v _]
52+
(when (some (fn [pred] (pred event-v))
53+
events-preds)
54+
(re-frame/dispatch (conj dispatch-to event-v))))]
55+
(re-frame/add-post-event-callback register post-event-callback-fn)))))]
56+
(fn [val]
57+
(cond
58+
(map? val) (process-one-entry val)
59+
(sequential? val) (doall (map process-one-entry val))
60+
:else (re-frame/console :error ":forward-events expected a map or a list of maps, but got: " val)))))
61+
2262
(defn seen-all-of?
2363
[required-events seen-events]
24-
(empty? (set/difference required-events seen-events)))
64+
(let [callback-preds (map as-callback-pred required-events)]
65+
(every?
66+
(fn [pred] (some pred seen-events))
67+
callback-preds)))
2568

2669

2770
(defn seen-any-of?
2871
[required-events seen-events]
29-
(some? (seq (set/intersection seen-events required-events))))
72+
(let [callback-preds (map as-callback-pred required-events)]
73+
(some?
74+
(some
75+
(fn [pred] (some pred seen-events))
76+
callback-preds))))
3077

3178

3279
(defn startable-rules
@@ -121,9 +168,9 @@
121168
;; A new event has been forwarded, so work out what should happen:
122169
;; 1. does this new event mean we should dispatch another?
123170
;; 2. remember this event has happened
124-
(let [[_ [forwarded-event-id & args]] event-v
171+
(let [[_ forwarded-event] event-v
125172
{:keys [seen-events rules-fired]} (get-state db)
126-
new-seen-events (conj seen-events forwarded-event-id)
173+
new-seen-events (conj seen-events forwarded-event)
127174
ready-rules (startable-rules rules new-seen-events rules-fired)
128175
halt? (some :halt? ready-rules)
129176
ready-rules-ids (->> ready-rules (map :id) set)

test/day8/re_frame/async_flow_fx_test.cljs

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,55 @@
22
(:require [cljs.test :refer-macros [is deftest]]
33
[day8.re-frame.async-flow-fx :as core]))
44

5+
56
(deftest test-all-events-seen?
6-
(is (= (core/seen-all-of? #{:a} #{:a}) true))
7-
(is (= (core/seen-all-of? #{:a} #{:a :b}) true))
8-
(is (= (core/seen-all-of? #{:a :b} #{:a :b}) true))
9-
(is (= (core/seen-all-of? #{:a} #{:b}) false))
10-
(is (= (core/seen-all-of? #{:a :b} #{:a :c}) false))
11-
(is (= (core/seen-all-of? #{:a} #{:b :c}) false))
7+
(is (= (core/seen-all-of? #{:a} #{[:a]}) true))
8+
(is (= (core/seen-all-of? #{:a} #{[:a] [:b]}) true))
9+
(is (= (core/seen-all-of? #{:a :b} #{[:a] [:b]}) true))
10+
(is (= (core/seen-all-of? #{:a} #{[:b]}) false))
11+
(is (= (core/seen-all-of? #{:a :b} #{[:a] [:c]}) false))
12+
(is (= (core/seen-all-of? #{:a} #{[:b] [:c]}) false))
1213
(is (= (core/seen-all-of? #{:a} #{}) false)))
1314

15+
(deftest test-all-events-seen-vec?
16+
(is (= (core/seen-all-of? #{[:a]} #{[:a]}) true))
17+
(is (= (core/seen-all-of? #{[:a]} #{[:a] [:b]}) true))
18+
(is (= (core/seen-all-of? #{[:a] [:b]} #{[:a] [:b]}) true))
19+
(is (= (core/seen-all-of? #{:a [:b]} #{[:a] [:b]}) true))
20+
(is (= (core/seen-all-of? #{[:a] [:b :c]} #{[:a] [:b]}) false))
21+
(is (= (core/seen-all-of? #{[:a]} #{[:b]}) false))
22+
(is (= (core/seen-all-of? #{[:a] [:b]} #{[:a] [:c]}) false))
23+
(is (= (core/seen-all-of? #{(fn [[e _]]
24+
(keyword? e))} #{[:b] [:c]}) true))
25+
(is (= (core/seen-all-of? #{[:a]} #{}) false)))
26+
1427

1528
(deftest test-any-events-seen?
16-
(is (= (core/seen-any-of? #{:a} #{:a}) true))
17-
(is (= (core/seen-any-of? #{:a :b} #{:a :b}) true))
18-
(is (= (core/seen-any-of? #{:a :b} #{:a :c}) true))
19-
(is (= (core/seen-any-of? #{:a} #{:b}) false))
29+
(is (= (core/seen-any-of? #{:a} #{[:a]}) true))
30+
(is (= (core/seen-any-of? #{:a [:b]} #{[:a] [:b]}) true))
31+
(is (= (core/seen-any-of? #{:a [:b]} #{[:a] [:c]}) true))
32+
(is (= (core/seen-any-of? #{:a} #{[:b]}) false))
2033
(is (= (core/seen-any-of? #{:a} #{}) false)))
2134

35+
(deftest test-any-events-seen-vec?
36+
(is (= (core/seen-any-of? #{[:a]} #{[:a]}) true))
37+
(is (= (core/seen-any-of? #{[:a] [:b]} #{[:a] [:b]}) true))
38+
(is (= (core/seen-any-of? #{[:a] [:b]} #{[:a] [:c]}) true))
39+
(is (= (core/seen-any-of? #{[:a]} #{[:b]}) false))
40+
(is (= (core/seen-any-of? #{[:a]} #{}) false)))
2241

2342
(deftest test-newly-startable-tasks
2443
(let [rules [{:id 1 :when core/seen-all-of? :events #{:a :b}}
2544
{:id 2 :when core/seen-all-of? :events #{:a}}]]
26-
(is (= (core/startable-rules rules #{:c} #{})
45+
(is (= (core/startable-rules rules #{[:c]} #{})
2746
[]))
28-
(is (= (core/startable-rules rules #{:a} #{2})
47+
(is (= (core/startable-rules rules #{[:a]} #{2})
2948
[]))
30-
(is (= (core/startable-rules rules #{:a} #{1})
49+
(is (= (core/startable-rules rules #{[:a]} #{1})
3150
[(nth rules 1)]))
32-
(is (= (core/startable-rules rules #{:a :b} #{2})
51+
(is (= (core/startable-rules rules #{[:a] [:b]} #{2})
3352
[(nth rules 0)]))
34-
(is (= (core/startable-rules rules #{:a} #{})
53+
(is (= (core/startable-rules rules #{[:a]} #{})
3554
[(nth rules 1)]))))
3655

3756

@@ -73,35 +92,67 @@
7392

7493
;; event :no should cause nothing to happen
7594
(is (= (handler-fn
76-
{:db {:p {:seen-events #{:33}
95+
{:db {:p {:seen-events #{[:33]}
7796
:rules-fired #{}}}}
7897
[:test-id [:no]])
79-
{:db {:p {:seen-events #{:33 :no}
80-
:rules-fired #{}}}}))
98+
{:db {:p {:seen-events #{[:33] [:no]}
99+
:rules-fired #{}}}}))
81100

82101
;; new event should not cause a new dispatch because task is already started (:id 0 is in :rules-fired)
83102
(is (= (handler-fn
84-
{:db {:p {:seen-events #{:1}
103+
{:db {:p {:seen-events #{[:1]}
85104
:rules-fired #{0}}}}
86105
[:test-id [:1]])
87-
{:db {:p {:seen-events #{:1} :rules-fired #{0}}}}))
106+
{:db {:p {:seen-events #{[:1]} :rules-fired #{0}}}}))
88107

89108
;; new event should cause a dispatch
90109
(is (= (handler-fn
91110
{:db {:p {:seen-events #{}
92111
:rules-fired #{}}}}
93112
[:test-id [:1]])
94-
{:db {:p {:seen-events #{:1} :rules-fired #{0}}}
95-
:dispatch-n [[:2]]}))
113+
{:db {:p {:seen-events #{[:1]} :rules-fired #{0}}}
114+
:dispatch-n [[:2]]}))
96115

97116
;; make sure :seen-any-of? works
98117
(is (= (handler-fn
99118
{:db {:p {:seen-events #{}
100119
:rules-fired #{}}}}
101120
[:test-id [:4]])
102-
{:db {:p {:seen-events #{:4} :rules-fired #{2}}}
103-
:dispatch-n [[:6]]}))))
121+
{:db {:p {:seen-events #{[:4]} :rules-fired #{2}}}
122+
:dispatch-n [[:6]]}))))
123+
124+
125+
(deftest test-vector-handling
126+
(let [flow {:first-dispatch [:start]
127+
:id :test-id
128+
:db-path [:p]
129+
:rules [{:id 0 :when :seen? :events [[:1 :a]] :dispatch [:2]}
130+
{:id 2 :when :seen-any-of? :events [[:4 :b] :5] :dispatch [:6]}
131+
]}
132+
handler-fn (core/make-flow-event-handler flow)]
133+
134+
;; new event should cause a dispatch
135+
(is (= (handler-fn
136+
{:db {:p {:seen-events #{}
137+
:rules-fired #{}}}}
138+
[:test-id [:1 :a]])
139+
{:db {:p {:seen-events #{[:1 :a]} :rules-fired #{0}}}
140+
:dispatch-n [[:2]]}))
104141

142+
;; new event shouldn't cause a dispatch
143+
(is (= (handler-fn
144+
{:db {:p {:seen-events #{}
145+
:rules-fired #{}}}}
146+
[:test-id [:1]])
147+
{:db {:p {:seen-events #{[:1]} :rules-fired #{}}}}))
148+
149+
;; make sure :seen-any-of? works
150+
(is (= (handler-fn
151+
{:db {:p {:seen-events #{}
152+
:rules-fired #{}}}}
153+
[:test-id [:4 :b]])
154+
{:db {:p {:seen-events #{[:4 :b]} :rules-fired #{2}}}
155+
:dispatch-n [[:6]]}))))
105156

106157
(deftest test-halt1
107158
(let [flow {:first-dispatch [:start]
@@ -113,7 +164,7 @@
113164
handler-fn (core/make-flow-event-handler flow)]
114165
;; halt event should clean up
115166
(is (= (handler-fn
116-
{:db {:p {:seen-events #{:1}
167+
{:db {:p {:seen-events #{[:1]}
117168
:rules-fired #{0}}}}
118169
[:test-id [:3]])
119170
{:db {}
@@ -122,7 +173,7 @@
122173

123174
;; halt event should clean up and dispatch
124175
(is (= (handler-fn
125-
{:db {:p {:seen-events #{:1}
176+
{:db {:p {:seen-events #{[:1]}
126177
:rules-fired #{0}}}}
127178
[:test-id [:6]])
128179
{:db {}
@@ -138,7 +189,7 @@
138189
:first-dispatch [:1]
139190
:rules [{:when :seen? :events :3 :halt? true}]}
140191
handler-fn (core/make-flow-event-handler flow)]
141-
(is (= (handler-fn {:db {:p {:seen-events #{:33} :rules-fired #{}}}}
192+
(is (= (handler-fn {:db {:p {:seen-events #{[:33]} :rules-fired #{}}}}
142193
[:blah [:3]])
143194
{:db {}
144195
:deregister-event-handler :blah

0 commit comments

Comments
 (0)