Search code examples
gostructdryembedding

Access all fields from parent method


I'm developing an application where data is stored in mongodb. There are several collections and of course all of them have some common fields (like Id, creation date, etc) and methods (for example Insert). In my vision, I need to create base model struct with needed fields and methods, and then embed this struct into my models. Unfortunately, this doesn't work because method defined for base model doesn't see child fields.

I don't know how to explain further. Here is code in playground: https://play.golang.org/p/_x-B78g4TV

It uses json instead of mgo, but idea is still the same.

I want the output to be:

Saving to 'my_model_collection'

{"_id":42, "foo": "Some value for foo", "bar": "Here we set some value for bar"}

Not:

Saving to 'my_model_collection'

{"_id":42}

Writing that insert method for each my model seems to be against DRY, so what is correct/idiomatic way to achieve this in Go?


Solution

  • This is not possible, for details see my answer: Can embedded struct method have knowledge of parent/child?

    You may do 2 things:

    1. Abandon method and make it a helper / utility function

    The idea is to make Insert() detached from BaseModel and make it a simple function, and you pass the document to it which you want to save.

    I personally prefer this option, as it requires less hassle and maintenance. It could look like this:

    func Insert(doc interface{}) {
        j, _ := json.Marshal(doc)
        fmt.Println(string(j))
    }
    

    You also had a "typo" in the tags:

    type MyModel struct {
        *BaseModel
        Foo string `json:"foo"`
        Bar string `json:"bar"`
    }
    

    Using it:

    Insert(m)
    

    Output (try it on the Go Playground):

    {"_id":42,"foo":"Some value for foo","bar":"Here we set some value for bar"}
    

    2. Pass the (pointer to) the wrapper to the BaseModel

    In this approach, you have to pass a pointer to the embedder struct so the BaseModel.Insert() method will have a pointer to it, and may use that to save / marshal. This is basically manually maintaining a "reference" to the struct that embeds us and is being saved/marshalled.

    This is how it could look like:

    type BaseModel struct {
        Id             int `json:"_id"`
        collectionName string
    
        wrapper interface{}
    }
    

    And then in the Insert() method save the wrapper:

    func (m *BaseModel) Insert() {
        fmt.Printf("Saving to '%v'\n", m.collectionName)
        j, _ := json.Marshal(m.wrapper)
        fmt.Println(string(j))
    }
    

    Creation is slightly more complex:

    func NewMyModel() *MyModel {
        mm := &MyModel{
            Foo: "Some value for foo",
        }
        mm.BaseModel = NewBaseModel("my_model_collection", mm)
        return mm
    }
    

    But output is as you wish:

    Saving to 'my_model_collection'
    {"_id":42,"foo":"Some value for foo","bar":"Here we set some value for bar"}
    

    Try it on the Go Playground.