5

I'm using EmberJs and Ember-Data in a Google App Engine project which uses NDB. In the database I have Host, Probe and Check entities. The database model doesn't really matter as long as I have my REST api in order but for clarity here are my database Classes:

class Host(ndb.Model):
    hostName = ndb.StringProperty()

hostKey = ndb.Key('Host', 'SomeHostId')

class Probe(ndb.Model):
    checkName = ndb.StringProperty()

probeKey = ndb.Key('Host', 'SomeHostId', 'Probe', 'SomeProbeId')

class Check(ndb.Model):
    checkName = ndb.StringProperty()

checkKey = ndb.Key('Host', 'SomeHostId', 'Probe', 'SomeProbeId', 'Check', 'SomeCheckId')

I've added the keys in order to show that each host has some probes running on them and each probe performs some checks.

  • Host
    • Probe
      • Check

In my App.Js I have defined the following models:

App.Host = DS.Model.extend({
    hostName: DS.attr('string')
    probes: DS.hasMany('probe',{async:true})
});

App.Probe = DS.Model.extend({
    host: DS.belongsTo('host'),
    probeName: DS.attr('string')
    checks: DS.hasMany('check',{async:true})
});

App.Check = DS.Model.extend({
    probe: DS.belongsTo('probe'),
    hostName: DS.attr('string')
});

I have defined the following router:

App.Router.map(function() {
    this.resource('hosts', function(){
        this.resource('host', { path:':host_id'}, function(){
            this.resource('probes', function(){
                this.resource('probe', { path:':probe_id'}, function(){
                    this.resource('checks', function(){
                        this.resource('check', { path:':check_id'}, function(){

                        });
                    });
                });
            });
        });
    });
});

And in AppEngine if have built the following URL paths:

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    webapp2.Route('/hosts', HostsHandler),
    webapp2.Route('/hosts/<hostId>/', HostHandler),
    webapp2.Route('/hosts/<hostId>/probes', ProbesHandler),
    webapp2.Route('/hosts/<hostId>/probes/<probeId>/checks', ChecksHandler),
    webapp2.Route('/hosts/<hostId>/probes/<probeId>/checks/<checkId>/', CheckHandler)
])

http://example.com/hosts returns:

{
    "hosts": [
        {
            "hostName": "SomeHostName1",
            "id": "SomeHostId1"
        },
        {
            "hostName": "SomeHostName2",
            "id": "SomeHostId2"
        }
    ]
}

http://example.com/hosts/SomeHostId1/probes returns:

{
    "probes": [
        {
            "probeName": "SomeProbeName1",
            "id": "SomeProbeId1",
            "host_id": "SomeHostId1"
        },
        {
            "probeName": "SomeProbeName2",
            "id": "SomeProbeId2",
            "host_id": "SomeHostId1"
        }
    ]
}

http://example.com/hosts/SomeHostId1/probes/SomeProbeId1/checks returns:

{
    "checks": [
        {
            "checkName": "SomeCheckName1",
            "id": "SomeCheckId1",
            "probe_id": "SomeProbeId1"
        },
        {
            "checkName": "SomeCheckName2",
            "id": "SomeCheckId2",
            "probe_id": "SomeProbeId1"
        }
    ]
}

My templates are:

<script type="text/x-handlebars" id="host">
  <h3>{{hostName}}</h3>
  {{#link-to 'probes' probes}}probes{{/link-to}}

  {{outlet}}
</script>

<script type="text/x-handlebars" id="probes">
  {{#each probe in probes}}
    Probe: {{probe.probeName}}
    {{#link-to 'checks' probe.checks}}checks{{/link-to}}
  {{/each}}

  {{outlet}}
</script>

<script type="text/x-handlebars" id="checks">
  {{#each check in checks}}
    Check: {{check.checkName}}
  {{/each}}
</script>

Now I have all this... but no clue how to tie it up together so that Ember-Data makes the right http requests. So far I've only seen request go to http://example.com/modelName/

Sjuul Janssen
  • 1,772
  • 1
  • 14
  • 28

3 Answers3

3

Currently Ember Data does not support this type of nested routes for API endpoints. There's been some talk about this, but it doesn't seem to be making any forward progress.

Jeremy Green
  • 8,547
  • 1
  • 29
  • 33
3

I don't know anything about App engine, but if you could obtain a config like this, for ember-data rest adapter

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    webapp2.Route('/hosts', HostsHandler),
    webapp2.Route('/hosts/<hostId>', HostHandler),
    webapp2.Route('/probes', ProbesHandler),
    webapp2.Route('/probes/<probeId>', ProbesHandler),
    webapp2.Route('/checks/', CheckHandler)
    webapp2.Route('/checks/<checkId>/', CheckHandler)
])

And the response to http://example.com/hosts should return a json array hosts:[{},{}] and to http://example.com/hosts/1 a json representing a host object host:{} and the same for the other AppEngine routes

Edu
  • 2,520
  • 1
  • 15
  • 15
  • Loading in all checks at once might be a bad idea. I kept out one level of nesting (checkDataItems) for clarity. I have situations where we have 1000+ hosts running 1+ probe running 15+ checks. And each check can have 1-20 data items. It might run ok if I only get check. But if I then were to load the checkDataItems all at once as well... that might take a while. – Sjuul Janssen Oct 11 '13 at 11:48
  • 1
    You can add params to the query, and hit http://example.com/checks?probe_id=1 with something like store.findQuery('check', { probe_id: "1" }).then() in your chechks route model hook – Edu Oct 11 '13 at 13:07
  • yea that was my backup implementation. – Sjuul Janssen Oct 11 '13 at 13:17
2

You have defined the Host model twice, I think that shouldn't have been the case. I am pretty new to ember and haven't used async:true feature, but I have been able to do things like(but I hadn't used nested route):

App.Host = DS.Model.extend({
    hostName: DS.attr('string')
    probes: DS.hasMany('probe')
});

App.Probe = DS.Model.extend({
    probeName: DS.attr('string')
    checks: DS.hasMany('check')
});

App.Check = DS.Model.extend({
    checkName: DS.attr('string')
});

and you can spin up a rest api for host that returns :

{
    "hosts": [
        {
            "hostName": "SomeHostName1",
            "id": "SomeHostId1",
            "probes":["p1","p2"]
        },
        {
            "hostName": "SomeHostName2",
            "id": "SomeHostId2",
            "probes":["p2","p3"]
        }
    ],
    "probes": [
        {
            "probeName": "SomeProbeName1",
            "id": "p1",
            "checks":["c1","c2"]
        },
        {
            "probeName": "SomeProbeName2",
            "id": "p2",
            "checks":["c2","c3"]
        }
    ],
    "checks": [
        {
            "checkName": "SomeCheckName1",
            "id": "c1"
        },
        {
            "checkName": "SomeCheckName2",
            "id": "c2"
        }
    ]
} 

In my case I didn't have nested route but I think we should be able to set the controller content from the master payload somehow since all the required content are in store already! I don't know if it was of any help, but this is something I would also like to know the answer of.

Deewendra Shrestha
  • 2,313
  • 1
  • 23
  • 53
  • Adding the Id's of the child entities is going to cost me a second database query since you can't (and shouldn't) do joins in google datastore. However, I think running a second query might be acceptable since I'm only asking for the id properties. – Sjuul Janssen Oct 13 '13 at 14:45
  • PS You were right, it should have been a check model. I've updated the post now :) – Sjuul Janssen Oct 13 '13 at 14:51
  • I am trying to do the same as in your answer, I haven't got as far as running the code yet... what does the controller look like for this?? AFAICT in the ember docs a controller only ever represents one model class (either single object or list of same type) – Anentropic Feb 06 '14 at 14:20
  • The given json structure would be underlying model/content for HugsController which would be an ArrayController, and hence the json structure contains multiple hosts(and all probes/checks associated with it). If you were thinking of a single host, then you would create a HostController which would be an ObjectController and the underlying json(model) would be {"host":{"hostName":..., "probles":["p1","p2"]}, "probes":[{"id":"p1",...}, {"id":"p2",...}}]}. Hopefully you got the jist. – Deewendra Shrestha Feb 06 '14 at 16:02