1

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?

Community
  • 1
  • 1
user6743038
  • 149
  • 1
  • 11

3 Answers3

2

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.

Community
  • 1
  • 1
icza
  • 389,944
  • 63
  • 907
  • 827
  • > You also had a "typo" in the tags: Yeah. Copy-paste :) What about collectionName? I like the idea of having it inside model. What would be good solution? Probably `Insert` should accept my own interface (instead of empty one), which implements `GetCollectionName` method? Or there is better way? Also I didn't get the "Pass the pointer" way. – user6743038 Oct 24 '16 at 10:42
  • 1
    @user6743038 Yes, your own interface with `CollectionName()` method is better. What I presented is just the principle. Also for the other method, see edited answer. I included a complete, runnable example for that too. – icza Oct 24 '16 at 10:56
1

In Golang, you can't override a parent method, because that's not how polymorphism works. The Insert method will apply on the BaseModel member, and not on MyModel.

Also, you're trying to use mgo in an improper way. If you want to insert documents in collections, then you already have an Insert method for a Collection struct which works on interface{} types (same as json.Marshal).

Of course, you can have a BaseModel that will contain fields shared by all of your models. In fact, GORM uses a similar approach and provides a Model struct to be included in every child model.

T. Claverie
  • 11,380
  • 1
  • 17
  • 28
  • Yeah, but to insert something into collection, I need it's name first. That's why I have `collectionName` field. I don't want to pull another variable/const in every package, where I want to save model. Thanks for pointing to GORM, I didn't use/see it before. As I see, they solved problem of naming tables simply by pluralising model name. I'd like to have ability to set collection name to whatever I want. Any advices? – user6743038 Oct 24 '16 at 10:42
-1

Well known problem ;o) Member variables (like collectionName) which name starts with lower letter are not visible from other packages (like json). Therefore change struct to:

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

and world will be better place to live in.

lofcek
  • 1,165
  • 2
  • 9
  • 18
  • This is not what I'm asking about. `collectionName` struct field is intentionally started with lower case, because this is not model field, but rather service field which stores collection into which model should be stored – user6743038 Oct 24 '16 at 10:11