Nate
Nate

Reputation: 13

Grails criteria: id not in hasMany list

I want to find all unviewed messages for the current user.

A message can be intended for mutiple users to view, so having a boolean to indicate whether a message was viewed will not work. So a message has a list of viewees.

The simplified domain classes are as follows:

   class Message {
        static hasMany = [ viewees: User ]
        String content 
    }

    class User {
        String name
    }

To find messages that the user has viewed is trivial:

Message.withCriteria {
    viewees {
       idEq(currentUser.id)
    }
}

So to find messages the user hasn't viewed i thought i could just add a not{ } like so:

Message.withCriteria {
    viewees {
        not {
            idEq(currentUser.id)
        }
    }
}

But this will return all messages that has a viewee who isn't the current user. Which means it will return messages where another user is in the viewee list, regardless if the current user is in the viewee list.

And besides that, return multiple instances of the same message if there are multiple viewees.

The criteria builder also has inList:

not { 
    inList( propertyName, list ) 
}

But i need the opposite of this. Something like:

not { 
    inList( currentUser.id, 'viewees' ) 
}

But that isn't supported.

Is there anything like this that i could use? Or is there a different way to go about this? Perhaps changing the domain model in some way?

Upvotes: 1

Views: 1140

Answers (2)

Martin Hauner
Martin Hauner

Reputation: 1733

I would suggest to explicitly model the read/unread information by creating an extra domain class (something like this):

class UserMessage {
    enum State = {READ, UNREAD}

    State state = State.UNREAD

    Message message
    User user
}

If a User receives a new message it is added to UserMessages as unread and when the User reads the message you change the state to READ. You can now easily check the state of the messages received by a single user.

This has the advantage that you don't add user specific information to the Message. I don't consider tracking of who has seen the message as a responsibility of the Message (of course it depends on the meaning of Message in your business domain).

This extra class is also useful if you have more user specific information that is related to the message, for example a deleted flag (don't show it again for this user) or a starred flag (I want to keep it).

Upvotes: 0

Alidad
Alidad

Reputation: 5538

You can use HQL to get what you are looking for. A simple query like this:

class UserDomain {

    String name

    static List<MyMessage> findUnseenMessagesForUser(UserDomain user) {
        MyMessage.executeQuery(
                'select m from MyMessage m where :user not in elements(m.views)',
                [user: user]
        )
    }
}

/

void "test something"() {
        setup:
        def m1 = new MyMessage(content: "message 1")
        def m2 = new MyMessage(content: "message 2")
        def m3 = new MyMessage(content: "message 3").save(flush: true)

        def u1 = new UserDomain(name: "user1").save(flush: true)
        def u2 = new UserDomain(name: "user2").save(flush: true)
        def u3 = new UserDomain(name: "user3").save(flush: true)
        def u4 = new UserDomain(name: "user4").save(flush: true)


        when:
        m1.addToViews u1

        m2.addToViews u1
        m2.addToViews u3
        m2.addToViews u4

        m1.save(flush: true)
        m2.save(flush: true)



        then:
        UserDomain.findUnseenMessagesForUser (u2). containsAll ([m1, m2, m3])
        UserDomain.findUnseenMessagesForUser(u1).containsAll([m3])
    }

Here is an example and test case

Upvotes: 1

Related Questions