Is there a way with dao to only allow a field to b...
# exposed
d
Is there a way with dao to only allow a field to be directly set for a new entity? Example in 🧵
Copy code
class Login(id: EntityID<UUID>) : UUIDEntity(id) {
    var username: String by Logins.username
    private var password by Logins.hashedPassword

    companion object : UUIDEntityClass<Login>(Logins)

    // Only allowed on new entities
    fun setPassword(password: String) {
        this.password = BCrypt.hashpw(password, BCrypt.gensalt())
    }

    // Only allowed on existing entities.
    fun updatePassword(oldPassword: String, newPassword: String) {
        check(checkPassword(oldPassword)) { "Old password does not match" }
        this.password = BCrypt.hashpw(newPassword, BCrypt.gensalt())
    }

    fun checkPassword(password: String): Boolean = BCrypt.checkpw(password, this.password)
}
o
It's a good question actually, At the first moment I thought that it would be nice to create a base abstract class
BaseLogin
, and after that 2 children
NewLogin
and
ExistingLogin
But it looks like it's not a variant due to cache mechanism, that is caches entities per Table, not per Entity class (if I'm right)... it means that if you create
NewLogin
entity, take id, and try to get
ExistingLogin
, it would try to return existing
NewLogin
and fail with class cast exception... it could be "fixed" with
removeFromCache
So the variant I made at the first attempt looks like this:
Copy code
abstract class BaseLogin(id: EntityID<UUID>) : UUIDEntity(id) {
    protected var password by Logins.hashedPassword

    companion object : UUIDEntityClass<Login>(Logins)
}

class NewLogin(id: EntityID<UUID>) : BaseLogin(id) {
    companion object : UUIDEntityClass<NewLogin>(Logins) {
        override fun findById(id: EntityID<UUID>) = error("Use Login entity instead")
        override fun forEntityIds(ids: List<EntityID<UUID>>) = error("Use Login entity instead")
        override fun all() = error("Use Login entity instead")
        override fun searchQuery(op: Op<Boolean>) = error("Use Login entity instead")

        fun create(init: NewLogin.() -> Unit): NewLogin {
            return new(init)
        }
    }

    fun asLogin(): Login {
        val id = this.id.value
        flush()
        removeFromCache(this)
        return Login[id]
    }

    fun setNewPassword(password: String) {
        this.password = password
    }
}

open class Login(id: EntityID<UUID>) : BaseLogin(id) {
    companion object : UUIDEntityClass<Login>(Logins) {
        override fun new(init: Login.() -> Unit) = error("Use NewLogin entity instead")
        override fun new(id: UUID?, init: Login.() -> Unit) = error("Use NewLogin entity instead")
    }

    fun updatePassword(oldPassword: String, newPassword: String) {
        check(checkPassword(oldPassword)) { "Old password does not match" }
        this.password = newPassword
    }

    private fun checkPassword(password: String): Boolean = password == this.password
}

@Test
fun test() {
    withTables(Logins) {
        val entity = NewLogin
            .new {
                setNewPassword("p1")
            }
            .asLogin()


        entity.updatePassword("p1", "p2")

        assertEquals("p2", Logins.selectAll().first()[Logins.hashedPassword])
    }
}
I'd prefer to make one function
create()
instead of combination
new{}.asLogin()
, but that function should use
new
internally, so it will not be possible to prevent call of
new
anyway, and it's necessary to control what happens in the cache, because without
removeFromCache
sequential calls of
Login.get()
and
NewLogin.get()
(and vice versa of course) would cause class cast exception.