oakes / odoyle-rules

A rules engine for Clojure(Script)
The Unlicense
530 stars 20 forks source link

What blocks then conditions don't allow for custom functions. #35

Open drewverlee opened 4 months ago

drewverlee commented 4 months ago

Here is a failing test that should pass, but doesn't, that demonstrates the issue:

(deftest allow-for-custom-then-functions
  (let [falsy  (fn [new old] false)
        fired? (atom false)]
    (-> (reduce o/add-rule (o/->session)
                (o/ruleset
                  {::rule1
                   [:what
                    [pid ::foo foo {:then falsy}]
                    :then
                    (println "fired")
                    (reset! fired? true)]}))
        (o/insert 1 ::foo "")
        o/fire-rules)
    (is (= @fired? false))))
drewverlee commented 4 months ago

maybe this is by design? Here is where i assume this is happening at a glance:

(if-let [[then-type then] (-> node :condition :opts :then)]
                                      (case then-type
                                        :bool (fn [session new-fact old-fact]
                                                then)
                                        :func (if-let [old-fact (:old-fact token)]
                                                (fn [session new-fact old-fact]
                                                  (then (:value new-fact) (:value old-fact)))
                                                (fn [session new-fact old-fact]
                                                  true)))
                                      (fn [session new-fact old-fact]
                                        true))
drewverlee commented 4 months ago

yep, this seems to be by design. Or at least, as the code above illustrates it always fires the first time:

(deftest allow-for-custom-then-functions
  (let [falsy  (fn [new old] false)
        fired (atom 0)]
    (-> (reduce o/add-rule (o/->session)
                (o/ruleset
                  {::rule1
                   [:what
                    [pid ::foo foo {:then falsy}]
                    :then
                    (swap! fired inc)]}))
        (o/insert 1 ::foo "")
        (o/insert 1 ::foo "")
        (o/insert 1 ::foo "")
        o/fire-rules)
    (is (= 0 @fired))))

result:

FAIL in () (NO_SOURCE_FILE:1110)
expected: (= 0 (clojure.core/deref fired))
  actual: (not (= 0 1))

Is this clear in the docs? Does it have to be this way? It seems counter intuitive.