This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
If I create a collection of entities using nesting like:
[{:db/id order-id
:order/lineItems [{:lineItem/product chocolate
:lineItem/quantity 1}
{:lineItem/product whisky
:lineItem/quantity 2}]}]
And I want to update some of these nested lineItems, what’s the best way to do that?Should I just retract the order entity and create a new one with the same entity-id?
I’d make an attribute {:lineItem/id (d/squuid) } and have that be a :db.unique/identity.
When you make changes, something like
{:db/id [:lineItem/id “lkasjdlkas”] :lineItem/quantity 2}
[{:db/id order-id
:order/id (d/squuid)
:order/lineItems [{:lineItem/id (d/squuid)
:lineItem/product chocolate
:lineItem/quantity 1}
{:lineItem/id (d/squuid)
:lineItem/product whisky
:lineItem/quantity 2}]}]
@currentoor: if you update the properties in existing lineItems, Datomic will take care of it if you send the map through (assuming the entity and component entity IDs are present)
I wrote a DB function to cope with it (after some advice from @tcrayford here)
@zentrope: Thanks, but lineItems are components entities and I’d like to interact with them as part of Orders.
@raymcdermott: Thanks, I’ll look into that.
@currentoor: let me know if it’s useful and I will write that blog post
Will do, it looks like exactly what I need!
@raymcdermott: so how would I use it? Like this?
[:component-crud {:db/id order-id
:order/lineItems [{:lineItem/product chocolate
:lineItem/quantity 1}
{:lineItem/product whisky
:lineItem/quantity 2}]}
:order/lineItems]
where the value of :order/lineItems
has been updated.
very close but the updated items should have the entity IDs (that were created automatically on insert)
the general idea is just to send back in the updated map and let the function work it out
(defn- save-new-cart [conn cart]
"New: any embedded skus will be created as component entities"
(let [temp-id (d/tempid :db.part/user)
tx-data (conj [] (assoc cart :db/id temp-id))
tx @(d/transact conn tx-data)
{:keys [db-after tempids]} tx
cart-id (d/resolve-tempid db-after tempids temp-id)]
(d/pull db-after '[*] cart-id)))
(defn- save-updated-cart [conn cart]
"Update: embedded skus will be handled by the DB CRUD function"
(let [tx-data [[:component/crud cart :cart/sku-counts]]
tx @(d/transact conn tx-data)
db-after (:db-after tx)]
(d/pull db-after '[*] (:db/id cart))))
(defn save-cart! [cart]
(let [conn (d/connect uri)]
(if (:db/id cart)
(save-updated-cart conn cart)
(save-new-cart conn cart))))
Hmm, I was thinking about this a little bit differently. I’d like to only keep track of the outer Order entity and not worry about the entity-ids of the nested lineItems. Instead I can pass new lineItems (inside the order) in and the db-function can lookup the current lineItems and do a data-value based comparison to figure out the additions/retractions needed.
additions would be easy - you would need to provide some key or composite key for the comparisons
@currentoor That's why I give lineItems unique IDs. When you pull-api the order out, you have all the information you need to construct retractions.
For instance, in a web form, when the user "deletes" the line items, you can add that to a "dropped items" structure. Throw the whole thing back to the server and you can just loop through and delete.
Yeah, db/ids or you can make your own as well to avoid relying on those, but either way, doing set math on the data in your client turns out to be reasonably simple.
@zentrope: I see your point but in my use-case the nested entity is an implementation detail that should not be exposed outside.
Yeah. I guess the surprising thing is you can just assert a set of values for an attribute and thus replace what was already there. So, db function, or client-side stuff.
yeah I definitely want this to be an atomic function otherwise data can get into a weird state
@raymcdermott: I think we may be talking about two different things, I’ll go try out my ideas first. I’m probably mistaken about something here.
@currentoor: no worries, let me know how you get on
@raymcdermott: is there an advantage to using datomic.api/function
? I’ve been writing them as regular functions in regular namespaces (requiring them into the transactor).
@raymcdermott, @zentrope: This is what I had in mind. It appears to be working. https://gist.github.com/currentoor/dcdfbe4d8e99513a4135
Awesome!
more or less, yes. I have a bunch of data in postgres that I want to move over into Datomic. I have both schemas.
it's not too complex, but the source tables are relatively normalized (= lots of nested entities)
yeah. Are you looking to automate the schema translation or just do it once manually?
automated would be nice -- I'll probably have to do it more than once -- but that's not a major consideration.
sure. In which case maybe the mbrainz example from datomic itself? Mbrainz was originally a sql database (if you're looking for an example of how to translate schema)
hmm... I thought mbrainz was meant to be loaded from an already-datomicized restore file. is the generating code in the repo somewhere?
oh, wait... I don't mean translating the schema ... I already did that. I just want to move the data.
I don't think doing automated translation would ever work that well, but "never say never" 😉
yeah... I would think that an automated schema translation would probably work but hamstring your datalog into some unnatural shapes
meanwhile, I'm also stuck trying to figure out what the datomic console url looks like for a sql backend, so that's going well for me too. 😛
"uri is a Datomic db uri with the dbname missing" doesn't make sense with datomic:sql://<DB-NAME>?jdbc:
@currentoor: the main thing is atomicity
@curtosis: The URI arg for Datomic console is exactly that, omit the <DB-NAME>
— e.g., datomic:sql://?jdbc:
is my local URI (w/user,password changed to datomic).
@bkamphaus: I'm clearly an idiot.... I tried that, which didn't work at all... until I noticed that I wasn't specifying a port.
Ah, got it, yeah full invocation for me looks like: bin/console default $(pg-uri "") -p 1121
— helper to generate the local pg-uri because I can’t be arsed to do all the typing.
Oh, right, as in it just prints the usage instead of complaining about which specific thing was missing?
I've at least got the console talking to it, and successfully transacted my schema and the static data (from groovysh)
but thanks for pointing out that it was correct, no matter how wrong it looked... that helped see that something else was broken.
so now I'm back to the hard part, of walking my sql results and building entity maps \o/
Gotcha. FWIW, I’ll make note of the implied feature request for a more specific error message.
@raymcdermott: what's not atomic about my implementation?
Are you talking about d/function?