'Nim - How to access mytype of a field that has the type Option[mytype] at compile time?

Heyho,

While coding my way through some generics I stumbled upon an issue with one of my generic functions. I have 2 types such as this

import std/options
import norm

type
    A = ref object of Model
        name: string
    B = ref object of Model
        name: string
        myA: Option[A]

norm, an ORM in nim for sqlite, has the capability for me to grab the sql-tablename a model belongs to at compile time by just having a type that inherits from Model and calling table() on it.

For various reasons I want to be able to figure out the name of all tables, that a given Model links to. In this case, B links to the type A, but I need to call A.table() to get that tablename at compile-time (which could be anything if the {.tableName.} pragma is in use).

However, I can't seem to find a way to access my type, since I can't call the typical get() method of the options module at compile time. What is my way out here?



Solution 1:[1]

Thanks to the immensely helpful folks at the nim-discord server (shoutout to leorize there) I was able to solve this issue. In fact, I only made this question so I can google the answer easier myself.

There are multiple ways of going about this:

  1. Try to access the type directly

The generic parameter of Option is called T. Within that T is the type that you're looking for.

proc getRelatedFieldName[M: Model, O:Model](targetType: typedesc[O], sourceType: typedesc[M]): Option[string] =
    let source = sourceType()
    for sourceFieldName, sourceFieldValue in source[].fieldPairs:
        when sourceFieldValue is Option:
            when sourceFieldValue.get() is Model:
                when O.table() == sourceFieldValue.T.table():
                    return some(sourceFieldName) 

    return none(string)


echo A.getRelatedFieldName(B) # returns "some('myA')"

If you do this with not a typedesc but actual types, you may want to consider using typeof(sourceFieldValue).T.table() instead.

  1. Use typetrait's genericParams function

You can use the [typetraits][1] library and its genericParams function.

genericParams(B).get(0)

With genericParams as a tool you can then do interesting things, like iterating over all fields of a type (not an instance!) at compile time, check whether a given field is an Option of a Model and compare tablenames

proc getRelatedFieldName[M: Model, O:Model](targetType: typedesc[O], sourceType: typedesc[M]): Option[string] =
    let source = sourceType()
    for sourceFieldName, sourceFieldValue in source[].fieldPairs:
        when sourceFieldValue is Option:
            when sourceFieldValue.get() is Model:
                when O.table() == genericParams(sourceFieldValue.type()).get(0).table():
                    return some(sourceFieldName) 

    return none(string)


echo A.getRelatedFieldName(B) # returns "some('myA')"

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1