https://kotlinlang.org logo
#ktor
Title
# ktor
m

Martin Gaens

04/05/2022, 11:24 PM
Hey guys! I'm using Ktor with FreeMarker and Exposed and I'm having this slight technical difficulty when using FreeMarker. The Exposed library produces objects which have properties on properties (by using foreign key references to other tables). Here's an example:
Copy code
routing {
    get("/") {
        call.respond(
            FreeMarkerContent(
                "index.ftl",
                mapOf(
                    "apartments" to transaction { Apartment.all().toList() }
                )
             )
         )
    }
}
The transaction produces a
List
of `Apartment`s. Now, the problem is, that the
Apartment
object holds a property called
Address
but this property is from a different table. So, in order to access the `Address`' name, we have to call
address.name
. This is the FreeMarker code:
Copy code
<ol>
    <#list apartments as apartment>
        <li>${apartment.address.name} ${apartment.apartmentNumber} ${apartment.room}</li>
    </#list>
</ol>
And this is the error it produces:
Copy code
freemarker.core._TemplateModelException: An error has occurred when reading existing sub-variable "address"; see cause exception! The type of the containing value was: extended_hash+string (com.gitlab.djsushi.data.table.Apartment wrapped into f.e.b.StringModel)
r

Ron S

04/06/2022, 9:33 AM
I assume that "Appartment" is the Exposed Entity of your actual table class. Relationships are loaded lazily by default in Exposed. So you explicitely need to specify the relationships you want to include while fetching with the Entity.with(...) function. In the freemarker view outside of the transaction scope where you are accessing the relationship property the transaction is already closed and Exposed fails lazily fetching the requested data ("address"). Thats propably also what the "cause by" exception tells you... Im not an expert, however.
So if I'm right you have either two options 1. call .with(Apartment::address) (im not that sure if it works though) 2. fetch all the required data by using the DSL api and convert the ResultRow to a data class
m

Martin Gaens

04/07/2022, 12:51 PM
Thank you very much for your help and sorry for the late reply. The
with()
option doesn't work, so I'll have to go with the other option, unfortunately. But I feel like it's kind of dirty and repetitive. Is there any common practice for this kind of thing?
r

Ron S

04/07/2022, 1:02 PM
I started using Exposed around two months ago, so my experience is pretty limited. Your point that its very repetetive bothers me too... Especially since at my current project (JSON-API) there are multiple user types that have access to different fields of the resources. I have so many @Serializable data classes that just hold the data of my entity in various forms. But I figured out for myself that this is the best way for me.
On my research few months ago I also came across this project in GitHub https://github.com/TouK/krush that is some sort of compiler plugin that aims at reducing boilerplate. But I havent taken a close look though.
m

Martin Gaens

04/07/2022, 1:08 PM
Damn, thank you for this. I'll take a look into that.
Oh and by the way, this is how I managed to "fix" the issue:
Copy code
get("/") {
    call.respond(
        ThymeleafContent(
            template = "addresses",
            model = mapOf(
                "apartments" to transaction {
                    Apartment.all().map {
                        mapOf(
                            "address" to it.address.name,
                            "apartmentNumber" to it.apartmentNumber,
                            "room" to it.room
                        )
                    }
                },
                "addresses" to transaction {
                    Address.all().toList().map { it.name }
                }
            )
        )
    )
}
No need for any extra
Entity
classes or anything like that, just make a
Map
and put all the stuff you need into it and pass it to Thymeleaf. The only disadvantage is IDE autocompletion and syntax highlighting. Now, IntelliJ is unable to do it properly, but it wasn't as good even before that. And yeah I switched to Thymeleaf from FreeMarker, because it seemed like the better option. Although FreeMarker has better IDE support from intelliJ in my opinion.
r

Ron S

04/10/2022, 8:50 AM
Yeah, using a map is probably the better solution for you here. And I also use Thymeleaf because of internationalization for my mail delivery.
14 Views