 	c1 (->C :c1 0 :c "c1-data")
 	c2 (->C :c2 0 :c "c2-data")]
     (is (= [[{}{}]{}{}] (data join)))
+    (is (= {} (data joined-model)))
 
     (attach join view)
     (is (= nil (data view)))


;;; attach lhs to all rhses such that any change to an rhs initiates an
;;; attempt to [re]join it to the lhs and vice versa.
(defn- join-views [join-fn [lhs-model & lhs-keys] & rhses]
  (let [indeces (atom [(apply vector (repeat (count rhses) {}))])]
    ;; view lhs
    (log2 :info (str " lhs: " lhs-model ", " lhs-keys))
    (attach lhs-model (lhs-view indeces 0 lhs-keys join-fn))
    ;; view rhses
    (doseq [[model key] rhses]
	(let [i (count @indeces)]   ; figure out offset for this index
	  (log2 :info (str " rhs: " model ", " i ", " key))
	  (swap! indeces conj {})	; add index for this model
	  ;; view rhs model
	  (attach model (rhs-view indeces i key lhs-keys join-fn))))
    indeces))

(deftype JoinModel [^String name ^Atom state ^Atom views]

   (fn [args] (apply join-fn v args))
   (permute [[]] (map (fn [index key] (index (key v))) rhs-indeces ks))))

(defn- lhs-upsert [[old-index & rhs-indeces] ks v join-fn joined-model]
  (let [new-index (mapv (fn [index key] (group index (key v) v)) old-index ks)
	new-indeces (apply vector new-index rhs-indeces)
	joins (derive-joins v rhs-indeces ks join-fn)]
    [new-indeces (fn [_] (on-upserts joined-model joins))]))

;;; TODO
(defn- lhs-delete [old-indeces i ks v join-fn])
(defn- lhs-upserts [old-indeces i ks vs join-fn])
(defn- lhs-deletes [old-indeces i ks vs join-fn])

(defn- lhs-view [indeces i keys join-fn joined-model]
  (reify
   View
   ;; singleton changes
   (on-upsert [_ upsertion] ((swap*! indeces lhs-upsert keys upsertion join-fn joined-model) []) nil) ;TODO - pass in views to notifier
   (on-delete [_ deletion]  (swap! indeces lhs-delete keys deletion join-fn) nil)
   ;; batch changes
   (on-upserts [_ upsertions] (swap! indeces lhs-upserts keys upsertions join-fn) nil)
