Fork me on GitHub
Ben Kamphaus02:10:47

@zentrope: I think my reply from a little earlier today to @wambat may be relevant here: > one strategy for this would be to define a cas-like transaction function that operates with logic: “if card/many attr on this entity still has the same set of values, retract that set and assert this new set"


I can do that, but I worry that there’s an overlap between those. I can use the clojure diff function to figure out which ones to retract and add, and leave the others: but, oy. It’s not so easy to compare these entities.


Does db.fn/cas account for that somehow, or is a “replace” generally okay?

Ben Kamphaus02:10:51

what are the correct semantics? is it an assertion/re-assertion of all the refs? are you using nested maps w/unique identity attr where you’re potentially upserting on a component ref?


Seems like it breaks history (the :db/ids are different).


I have an “event” with “attendees” (similar to the user/club relationship).


The user clicks who’s invited and who isn’t, then I ship the “who should be in the list” to the server.


So hard to explain in tiny slack sentences. ;)


:event/attendees -> [{:attendee/status :enum :attendee/user :ref}]


So, theoretically, there are 10 attendees already recorded. User “unclicks” one, so I ship down the new “version” of the event with the 9 remaining attendees.


In an SQL type system, I could just delete all attendees, then write the 9 back down. Brute force, but eh.


If I don’t do that here, it feels like I’m losing the continuity of history.

Ben Kamphaus02:10:12

does that match the semantic you want to preserve? i.e., the user asserted “these are the 9 attendees”? i.e. you consider it a re-assertion that those are attendees, not a correction or change of just the diff as the correct semantic for the click?

Ben Kamphaus02:10:41

this is all fairly similar to the blog post here, actually, as I think about it:


Well, it’s like you load up the entire “event” document, tweak this or that, then save the whole thing back.


Yes. Describes the problem. I don’t see the solution, though.


Send adds and retracts singly for recording?

Ben Kamphaus02:10:48

the solution statement is admittedly fairly buried: > Given the set of checkbox states, you should do the diff in the web tier as soon as you pull data out of the form.


> At this point, you should submit adds and retracts only for the new facts I created -- not a set with an add or retract for every UI element.

Ben Kamphaus02:10:42

right, the diff in the web tier being the source of adds and retracts that are only for new facts.


Yeah, I get it.


Sure makes the transaction simpler.


I guess I’ll need to ship around the entity IDs, or add a uuid to the membership man-in-the-middle entity.


Otherwise, I’ve got nothing to grab onto for a retract without doing a look up.

Ben Kamphaus03:10:12

so you’re reifying a component attendance entity, a card/many on event, which has attendee status and attendee/user which is your ref. Hm.


Yeah, the attendee middleman is just a way to annotate the link between and event and a user with “yes/no/maybe” information.


Same as user to club with a membership link for “veep” “pres” and so on.


I could just add a uuid attribute for that link, then use an entity retract using, [:db.fn/retractEntity [:attendee/id “uuid”]].

Ben Kamphaus03:10:07

how do you limit choices for invitees?

Ben Kamphaus03:10:45

and do you intend a constraint in the other direction? i.e. a user can only have one ‘attendee' relationship to an event?


That’s done in the UI. A list of all the people, then I consult the set of “attendees” to indicate if they’re invited.


I have :event/attendees which is an isComponent=true and I populate it with an enum and a user reference.


An event can have any number of attendees, but no user is duplicated in that list.


An attendee can attend many events.


Pretty typical, I imagine.


If it weren’t for this extra info (attendence status), things would be simple. ;)

Ben Kamphaus03:10:45

I’m just thinking of the constraints implied by the cardinality and the component entities there to allow attributes on the ref.

Ben Kamphaus03:10:07

i.e. with a card/many attendees that was itself a ref, you would get the constraint of no more than one attendance per user per event trivially from set behavior, but with those reified, you must have some other application or transaction function logic to prevent a multiple attendance from a user for the same event being asserted, since the attendance or what have you will be its own entity.


I do have the second case. I’m not sure how to do the first.


:event/attendees, card:many, isComponent:true, type:ref right?


:attendee/status card:one :type:enum

Ben Kamphaus03:10:59

right, you can’t do the trivial case in either direction I suppose if you want attributes on the events <-> users many-to-many


That’s the struggle.

Ben Kamphaus03:10:58

obligatory comment on the struggle being real


Would be nice if there was a “set” type.

Ben Kamphaus03:10:43

I will say re: one of your previous comments that it’s fairly typical to see globally unique IDs, either domain or e.g. sqUUID, provided for all entities in a lot of Datomic data modeling.


If I add a UUID to the attendence entity, I can at least pull them into the UI as tourist information, then use that in a transaction to craft appropriate retract elements.


Yeah? I’ve been doing that, too — but only for domain entities, not these implementation specific linking strategies.


I’ll rethink that.

Ben Kamphaus03:10:56

I’m thinking on the linking strategy entities — i.e., if you assert, retract, then re-assert, is it correct to generate a new attendance linking entity, vs. e.g. re-asserting the previous attendance.

Ben Kamphaus03:10:41

assuming the user’s view of history is agnostic (i.e. they don’t care whether or not there was previous invitation they retracted), a new assertion is probably correct (?)


I think so.


It just shows, say, that Susan was invited, then un-invited, then invited again, and here’s the dates where that happened.


Truthfully, all that really matters is the end result. I could just bulk retract and re-create on every change.


No user would care, but I personally feel like I’m wasting Datomic, and that if I can solve this, it might come in handy when it actually really matters.


Doesn't Stu’s “chocolate” analogy indicate that it’s good to see what chocolate was a fave, then it wasn’t, then it was again?


Well, he makes the point that retracting/adding (when the user made no actual change) is a non-skillful thing to do, but the implication is that the user does make those changes.


Everything he says makes sense, but is only problematic with those linking entities.

Ben Kamphaus03:10:58

I guess I’m missing some detail in the difficulty w/retraction


If I load up all the attendee entities into the UI, but don’t include :db/id, then I have no ID to use in a retract clause.


So, either I have to retrieve the :db/id or make something else that’s an identity attribute on that entity.

Ben Kamphaus03:10:06

retract the entity where:

[?event :event/attendees ?a]
[?a :attendee/user ?u]


You can use a where in a retraction?

Ben Kamphaus03:10:38

possibly ?event :id ?id and ?u :userid ?id or whatnot? I.e. if you ensure the uniqueness there will only be one entity with those refs?


But then I have to query to find the things to retract. Which I could do.


But I’d prefer just [:db.fn/retractEntity [:attendee/id #uuid “kajlkasd”]]. Done!

Ben Kamphaus03:10:11

right, or keep the id w/o exposing in UI, or something else, but yes uuid/lookup rer will be more terse


What if you had an “attendee” entity that had an attribute for the user and an attribute for the event (and attendence status, etc) and an ID.


But the event doesn’t have an :event/attendees.


I guess it amounts to the same thing.

Ben Kamphaus03:10:29

right, you'll have reverse ref :attendees/_event, and it’s indexed in :vaet, but I think the cases discussed remain the same?


The basic lesson: in the UI, keep a list of things to retract, and things to assert, things that don’t change.


And if you’re doing that, you might as well ship up something that can act as an entity identifier.


Use those for things that need to be retracted.


Things that need to be asserted won’t have that identifier.


Other lesson: Everything gets a UUID of some sort.


Yep! Works.


No transaction functions and query before transacting stuff in the app.


(defn do-update-event!
  [conn {:keys [id name description datetime duration location link asserts retracts]}]

  (let [uninvites (for [attendee-id retracts]
                   [:db.fn/retractEntity [:attendee/id attendee-id]])

        invites (for [uid asserts]
                  {:db/id (d/tempid :lattice)
                   :attendee/id (d/squuid)
                   :attendee/status :attendee.status/unknown
                   :attendee/user [:user/id uid]})

        event {:db/id [:event/id id]
               :event/name name
               :event/description description
               :event/date datetime
               :event/duration (* duration 60)
               :event/location location
               :event/link link
               :event/attendees (set invites)}

        tx (apply conj [event] uninvites)]

    @(d/transact conn tx)))


Hi folks!, did anyone try to work with aws lambda and datomic pro starter edition ? I'm struggling with that!topic/datomic/OYJ4ghelmF0

Ben Kamphaus17:10:18

@tangrammer: we don’t really have an expectation that the peer lib should work on (or be well suited to) AWS Lambda.


Thanks @bkamphaus ! really appreciate your help!


Hey everybody! Can anybody tell me how to group by day in Datalog?


[:find (count ?p) ?date
 :with ?date
 [?p :purchase/ext-purchase-id ?ext-id]
 [?p :purchase/occurred-at ?date]


Example value: #inst "2013-01-09T11:46:23.000-00:00"


Write a func in clj and call it to return y/m/d for the timestamp? I was trying to do this in the console ...

Alex Miller (Clojure team)18:10:52

if you're on Java 8, there are probably some good options in the new java 8 instant stuff. otherwise, there are (deprecated) getters for these things on java.util.Date (which is likely the instance you have). some other options are to construct a Calendar instance or to format the date to a string.


thx alexmiller I'll post back when I get it