Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I have dynamic properties in go on the google app engine datastore

I want to do something like the Expando Model that python supports on app engine.

Sometimes you don't want to declare your properties ahead of time. A special model subclass, Expando, changes the behavior of its entities so that any attribute assigned (as long as it doesn't start with an underscore) is saved to the Datastore.

How can I do this in Go?

like image 530
jshen Avatar asked Feb 11 '23 08:02

jshen


1 Answers

Note beforehand:

There are 2 APIs. The one with import path appengine/datastore uses channels as arguments. The other with import path google.golang.org/appengine/datastore uses slices. Adjust the example below to your case. See this question for details: How to correctly import Golang appengine?


The key to an entity with dynamic properties is the PropertyLoadSaver interface. By implementing this interface you can dynamically–at save time–construct the properties of the entity to be saved.

Also to not have to do this yourself the Go AppEngine platform provides a PropertyList type which is basically a list (a slice) of properties Property and it also implements PropertyLoadSaver.

So the Expando model in Go is PropertyList. Just add the properties you want your entity to have, and save this PropertyList value.

Here is an example:

c := appengine.NewContext(r)

props := datastore.PropertyList{
    datastore.Property{Name: "time", Value: time.Now()},
    datastore.Property{Name: "email", Value: "[email protected]"},
}

k := datastore.NewIncompleteKey(c, "DynEntity", nil)
key, err := datastore.Put(c, k, &props)
c.Infof("%v %v", key, err)

This example saves an entity named "DynEntity" with 2 dynamic properties: "time" and "email".

As the PropertyList type is a slice, you can also use the builtin append() function to add properties to it, so you could also initialize props like this:

var props datastore.PropertyList
props = append(props, datastore.Property{Name:"time", Value: time.Now()})
props = append(props, datastore.Property{Name:"email", Value: "[email protected]"})

Turning a map into a dynamic entity

The PropertyLoadSaver interface is not complicated, we can implement it ourselves. In the following example I implement it on a custom type which is a simple map:

type DynEnt map[string]interface{}

func (d *DynEnt) Load(props []datastore.Property) error {
    // Note: you might want to clear current values from the map or create a new map
    for _, p := range props {
        (*d)[p.Name] = p.Value
    }
    return nil
}

func (d *DynEnt) Save() (props []datastore.Property, err error) {
    for k, v := range *d {
        props = append(props, datastore.Property{Name: k, Value: v})
    }
    return
}

Here's how the implementation would look like with the "old" interface that used channels instead of slices:

type DynEnt map[string]interface{}

func (d *DynEnt) Load(ch <-chan datastore.Property) error {
    // Note: you might want to clear current values from the map or create a new map
    for p := range ch { // Read until channel is closed
        (*d)[p.Name] = p.Value
    }
    return nil
}

func (d *DynEnt) Save(ch chan<- datastore.Property) error {
    defer close(ch) // Channel must be closed
    for k, v := range *d {
        ch <- datastore.Property{Name: k, Value: v}
    }
    return nil
}

Now we can use our DynEnt type just like any other maps in Go, and since it implements PropertyLoadSaver, it can be saved as an entity (and any entities can be loaded into it):

c := appengine.NewContext(r)

d := DynEnt{"email": "[email protected]", "time": time.Now()}

k := datastore.NewIncompleteKey(c, "DynEntity", nil)
key, err := datastore.Put(c, k, &d)
c.Infof("%v %v", key, err)
like image 114
icza Avatar answered Feb 13 '23 03:02

icza