when writing a ref
attribute, how can we indicate the type? or is that deliberately omitted because datomic (and perhaps clojure) doesn't make use of types, only attributes? in some codebases, i see idents out there called "schema namespaces", and the attr would indicate it is associated with that schema (as would the ref
ed entity, perhaps). or is it best to have the attr namespaced to match the ref
ed entity?
i wonder if that last option - matching the attr namespace to the namespace of the ref
'ed entity - is always suitable. for example, you might have a :person/child
attr ref
to another :person
entity. then the namespaces wouldn't match.
entities are untyped (they’re just an ID to join facts together), so refs are also untyped
e.g. what makes an entity a “person”?
You could layer a type system on top. a common approach is to add additional attributes to the ref attribute itself that indicates (human or machine-readably) the range (such as type) may have
a more recent feature is to use a pair of entity specs with db/ensure: https://docs.datomic.com/on-prem/schema.html#entity-specs
spec the referent, spec the referred, and add an entity predicate to the referent that asserts that the referred conforms; then add a :db/ensure when you transact changes to the referrer
Is there a canonical direction in modeling parent-child relationships in datomic schemas? For example, consider a schema which represents books and their respective chapters. I see two ways.
1. have a :book/id
, :chapter/id
and :book/chapter
where book -> chapter. In order to delete the chapter you have retract the chapter entity and one of the (just realized refs solve this problem, ignore this).
2. have a relationship where chapter -> book. like db.cardinality/many
attributes in the book entity:chapter/book
. This is a db.cardinality/one
and has the benefit of only needing to retract the entity to delete. My main issue of this is that it seems reversed and for more complicated cases, it is not always clear it is like :child/parent
especially when there is domain specific terminology, Is there a standard convention like :chapter/book-parent
or :chapter/parent-book
to denote attributes of this type?
One difference is that you can use :db/isComponent
on :book/chapter
but probably not on :chapter/book
.
Was just about to say, but don’t you get a many-to-many relation from that
I'm not sure what you mean. https://docs.datomic.com/cloud/schema/schema-reference.html#db-iscomponent
I meant to say, if you make I’m confusing myself, ignore:book/chapter
a ref-many component to a chapter, then pulling`:book/_chapter` on a capter entity also gives a set of books I think. Anyhow — Not particularly important for this discussion
I think you might be right though. The entity API might return a set of "one" book.
Thanks, I did not now about :db/isComponent
and considering my own project nests further with other types and datascript
supports this, it seems like there is only one way to go if I want easy deletions.
@me1740 @lennart.buit isComponent also makes the “reverse” direction cardinality-one
(even if there are multiple referents!)
i agree that isComponent
is the best solution here. if in the future you find that components might not fit your data model, you could consider writing transaction functions that handle data cleanup from a business logic perspective.. "when this book is removed from the library, retract chapters and user highlights about those chapters and any reservations for the book". i use a combination of tx fns and a "retraction api" that cleans up the messy parts of the graph, like related unique tuple constraints that no longer make sense when parts of their value are retracted (such as book+chapter+critic+rating
-> nil+nil+critic+rating
)
i've often been tempted to add "reverse component references" between entities just for the sake of data cleanup, but after playing with the idea it felt wrong. components are great.. just saying that they might only get you so far before a tx fn becomes a better option 🙂
Ah yeah @favila, I started doubting indeed ^^, testing it in the repl confirms you are right. Thanks!