Datomic: schema for many relationships with reset operation

I am looking for feedback on an approach to modeling relationships specific to many in Datomic.

Problem

Suppose I want to create a Datomic scheme for a domain where Man has a list of favorite films. For example, Johnyour favorite films: Gladiator, Star Warsand Fight Club.

The most obvious scheme for modeling this in Datomic is the power attribute - a lot, for example:

#{["John" :person/favorite-movies "Gladiator"]
  ["John" :person/favorite-movies "Star Wars"]
  ["John" :person/favorite-movies "Fight Club"]}

This approach makes it easy to add or remove movies from the list (just use :db/addand :db/retract), but I find it impractical to reset the entire list of films - you essentially need to calculate the difference between the old and the new, and it should work as a transaction function. This gets even worse when list items are not scalars.

Alternative approach

As an alternative approach, I am considering introducing indirection using a given object:

#{["John" :person/favorite-movies 42]
  [42 :set.string/contains "Gladiator"]
  [42 :set.string/contains "Star Wars"]
  [42 :set.string/contains "Fight Club"]}

With this approach :person/favorite-movies, this is a power indicator, one, the ref-typed attribute, and :set.string/containsa multi-valued attribute with a string. Resetting the list is just a matter of creating a new set object:

[{:db/id "John"
  :person/favorite-movies {:db/id (d/tempid :db.part/user)
                           :set.string/contains ["Gladiator" 
                                                 "The Lord of the Rings"
                                                 "A Clockwork Orange"
                                                 "True Romance"]}}]

Are the limitations of this approach for modeling all-to-many relationships known?


Edit: less trivial use case

, , -, ref-typed Datomic.

, 'reset' , " ".

: , Answer Question, Option s. Answer Question. Answer - Option.

:

  • :answer/id: (, )
  • :option/id: (-, )
  • :answer/selectedOptions (ref-typed, cardinality-many)
+3
2

, .

(A - vs B - , ) , , . IMHO, : , .

, B? .

, //, , , , . :submittedTime , ( Datomic history ).


:

A "reset" ; - , ( " " ), . Datofu.

0
  • : .
  • favorite-movies, attr (:set.string/contains ). , : :person/favorite-movies :person.favorite-movies/items, .
  • . :person/favorite-movies, , , .
  • " " " , ". .
  • "" . : reset (.. ), . , .

- . , , . , , , . ( , .)

"reset , " , , , , , - , . tx, :

{:db/ident :db.fn/resetAttribute
 :db/doc   "Unconditionally set an entity attribute values to those provided,
retracting all other existing values.

Values must be a collection (list, seq, vector), even for cardinality-one
attributes. An empty collection (or nil) will retract all values. The values
themselves must be primitive, i.e. no map forms are permitted for refs, use
tempids directly. If the attribute is-component, removed values will be
:db.fn/retractEntity-ed."
 :db/fn
 #db/fn {:lang   "clojure"
         :params [db ent attr values]
         :code   (let [eid       (datomic.api/entid db ent)
                       aid       (datomic.api/entid db attr)
                       {:keys [value-type is-component]} (datomic.api/attribute db aid)
                       newvalues (if (= value-type :db.type/ref)
                                   (into #{} (map #(if (string? %) % (d/entid db %))) values)
                                   (into #{} values))
                       oldvalues (into #{} (map :v) (datomic.api/datoms db :eavt eid aid))]
                   (-> []
                       (into (comp
                               (remove newvalues)
                               (map (if is-component
                                      #(do [:db.fn/retractEntity %])
                                      #(do [:db/retract eid aid %]))))
                         oldvalues)
                       (into (comp
                               (remove oldvalues)
                               (map #(do [:db/add eid aid %])))


                    newvalues)))}}

:

[:db.fn/resetAttribute [:person/id "John"] :person/favorite-movies
  ["Gladiator" "The Lord of the Rings" "A Clockwork Orange" "True Romance"]]]

;; Or to retract *all* existing values:
[:db.fn/resetAttribute [:person/id "John"] :person/favorite-movies nil]
+1

Source: https://habr.com/ru/post/1672243/


All Articles