Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find select field #99

Closed
hafarooki opened this issue Dec 11, 2018 · 8 comments
Closed

Find select field #99

hafarooki opened this issue Dec 11, 2018 · 8 comments

Comments

@hafarooki
Copy link

I have nations with settlements with members, I'm trying to get all of the members of the nation through the settlements.

        val members: List<SLPlayerId> = Settlement.COL
            .find(Settlement::nation eq id).projection(Settlement::members)
            .map { it.members }
            .flatten()

This gives me this error:

[08:01:22 ERROR]: [SLCore] [ACF] Caused by: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class net.starlegacy.slcore.mongo.Settlement] value failed for JSON property territory due to missing (therefore NULL) value for creator parameter territory which is a non-nullable type
[08:01:22 ERROR]: [SLCore] [ACF]  at [Source: de.undercouch.bson4jackson.io.LittleEndianInputStream@24d5132b; pos: 148] (through reference chain: net.starlegacy.slcore.mongo.Settlement["territory"])

Is there a way to get the value without attempting to construct the whole object?

@zigzago
Copy link
Member

zigzago commented Dec 11, 2018

You have two options:

  1. project into Document (ie Map):
val members: List<SLPlayerId> = Settlement.COL
            .withDocumentClass<Document>()
            .find(Settlement::nation eq id)
            .projection(Settlement::members)
            .map { it.getString(Settlement::members.name) }
            .flatten()

This will not work if members is a complex object.

  1. project into dedicated object
data class SettlementWithMembersOnly(val members: Array<Array<SLPlayerId>>)

val members: List<SLPlayerId> = Settlement.COL
            .withDocumentClass<SettlementWithMembersOnly>()
            .find(Settlement::nation eq id)
            .projection(Settlement::members)
            .map { it.members }
            .flatten()

Also I'm going to add a projection extension method, in order to project "out of the box" one, two or three fields.

HTH

@hafarooki
Copy link
Author

Members is just a set of IDs so that'll work, thanks. Should I leave the issue open for the addition or close it?

@hafarooki
Copy link
Author

Also, is there a slack or somewhere better to ask questions?

@hafarooki
Copy link
Author

hafarooki commented Dec 12, 2018

Okay... I made a generic utility for getting specific values, will this work?

fun <T> MongoCollection<T>.getValues(id: Any, vararg properties: KProperty<*>): Map<KProperty<*>, Any> {
    val map: MutableMap<KProperty<*>, Any> = mutableMapOf()

    val results: FindIterable<Document> = this.withDocumentClass<Document>()
        .find(Filters.eq("_id", id))
        .projection(*properties)

    for (document: Document in results) {
        for (property: KProperty<*> in properties) {
            val path: String = property.path()
            val value: Any = document[path] ?: error("Missing value for path $path")
            map[property] = value
        }
    }

    return map
}

@Suppress("UNCHECKED_CAST")
fun <T> Map<KProperty<*>, *>.getProperty(property: KProperty<T>): T = getValue(property) as T

Used like this:

    fun getName(settlementId: Id<Settlement>): String? {
        return Settlement.COL.getValues(settlementId, Settlement::name).getProperty(Settlement::name)
    }

@hafarooki
Copy link
Author

hafarooki commented Dec 12, 2018

Actually.. that wouldn't work with anything more complex than strings/numbers. Maybe this instead?:

inline fun <reified R> Map<KProperty<*>, *>.getProperty(property: KProperty<R>): R {
    val value: Any = get(property) ?: error("Property ${property.path()} not in collection")
    return value as R
}

inline fun <reified I, reified R> Map<KProperty<*>, *>.convertProperty(property: KProperty<R>, convert: (I) -> R): R {
    return convert.invoke(getProperty(property) as I)
}

Uses:

    fun getName(settlementId: Id<Settlement>): String? = Settlement.COL
        .getValues(settlementId, Settlement::name)
        .getProperty(Settlement::name)

    fun getLeader(settlementId: Id<Settlement>): Id<SLPlayer>? = Settlement.COL
        .getValues(settlementId, Settlement::leader)
        .convertProperty(Settlement::leader) { objectId: ObjectId -> objectId.toId() }

@hafarooki
Copy link
Author

hafarooki commented Dec 12, 2018

https://gist.github.com/c76df079bbc16fd318da551ef7e256f1 bit cleaner version, instead of using the confusing map class it extends LinkedHashMap to be ProjectedResults

EDIT: Updated it a lot, it's much improved over the original and very flexible

@zigzago
Copy link
Member

zigzago commented Dec 12, 2018

to ask questions use gitter https://gitter.im/kmongoo/Lobby or google groups https://groups.google.com/forum/#!forum/kmongo

Your solution is interesting but will not work with complex objects. I'm going to provide a patch tomorrow (so keep open this ticket).

Thanks

@hafarooki
Copy link
Author

Okay, hopefully you'll find what I came up with helpful :)

zigzago added a commit that referenced this issue Dec 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants