Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

$elemMatch doesn't work with backbone-relational? #5

Open
aroman opened this issue Mar 3, 2012 · 5 comments
Open

$elemMatch doesn't work with backbone-relational? #5

aroman opened this issue Mar 3, 2012 · 5 comments

Comments

@aroman
Copy link

aroman commented Mar 3, 2012

First off, this project is awesome.

I have data that looks like this:

courses = [
    {
        title: "Biology",
        assignments: [
            {
                done: false,
                due: "2012-03-05T05:00:00.000Z",
             },
            {
                done: false,
                due: "2012-03-05T05:00:00.000Z",
             },
            {
                done: true,
                due: "2012-03-06T05:00:00.000Z",
             }
        ]
    },
    {
        title: "Math",
        assignments: [
            {
                done: true,
                due: "2012-03-05T05:00:00.000Z",
             },
            {
                done: false,
                due: "2012-03-03T05:00:00.000Z",
             },
            {
                done: true,
                due: "2012-03-06T05:00:00.000Z",
             }
        ]
    }

So basically a collection of courses which have a list of assignments.

I'm using backbone-relational to model this; courses is a QueryCollection, the individual course model is a RelationalModel, and the individual assignment model is a RelationalModel as well.

The query I want to do is to find all of the assignments (in all courses) that are due within a specific range and are not done.

From the examples (and being somewhat familiar with Mongo -- it's what I use on the server) I think that would look like this:

courses.query({
    assignments: {
      $elemMatch: {
        "done": true,
        "date": "2012-03-05T05:00:00.000Z" 
      }
    }
  });

(I'm just matching one specific date, not a range, for simplicity purposes).

The problem is that the above query does not return anything. It seems as if it's not applying the $elemMatch criteria to the 'assignments' field of each course.

Any idea why that might be? Feel free to call me names if I'm doing something incredibly dumb here -- my brain is a bit fried from staring at my computer all day ;)

Thanks!

@davidgtonge
Copy link
Owner

Hi, its a while since I've used Backbone Relational, but if I remember it will set up a backbone collection for each list of assignments. Backbone Query is expecting raw data rather than another collection. So to get this to work you would probable need to define a custom method on your courses model that will return the raw array of the assignments data. You could then use the new computed operator. I'm not at my computer at the moment, but I'll write a more complete example later. I'll also think if there is a more elegant way to combine with backbone relational

@aroman
Copy link
Author

aroman commented Mar 4, 2012

Thanks for the fast reply.

I took your suggestion of using computed properties + a static serialization of the collection via a method on the collection (I just used .toJSON). That worked as I would have expected, and it seems like a reasonable workaround.

That said, it doesn't work the way I want it to. elemMatch does what it does in MongoDB; it doesn't return the specific elements matched, it returns the document that has the array it is being called on. Is there any way of having the query return the specific array slices that matched the $elemMatch critera, or do I need to denormalize my data further?

@davidgtonge
Copy link
Owner

No problem, thanks for the feedback I'm thinking of adding a $relationMatch operator which would avoid the need to create a new method and use the $computed operator, however this would still return the "course" models from your data.

When I've worked with Backbone Relational I've worked with data from a relational database and I got round the problem your having by having for example a global store of all the assignments, and they would each have a course ID. However with your current data structure, I don't think that way would work.

I suppose I could add an operator that would return the child models, but I'm not sure how common the use case if for this. Essentially you'd be querying a collection of courses but would get back a collection of assignments. I'd probably do a custom method to achieve what your trying to do. Something like this:

CourseModel = Backbone.RelationModel.extend({
  relations: {} // Make sure you define a Collection Type for assignments 
                   // that is extended from Backbone.QueryCollection,
  assignment_query: function(date, done) {
     // probably need to check if any assignments were returned before doing the query
    return this.get("assigments").query({
      date: date,
      done: done
    });
  }
});

CourseCollection = Backbone.QueryCollection.extend({
  get_assignments: function(date, done) {
    var assignments = this.map(function(model) {
      return model.assignment_query(date, done);
    });
    return _.flatten(assignments, true);
  }
});

Hope this helps.

@aroman
Copy link
Author

aroman commented Mar 4, 2012

+1 for the $relationMatch operator. Though it wouldn't be helpful for my use case, I can imagine it would be nice to at least have a section in the README about working with relational models.

I have actually been thinking about whether I ought to simply have a single non-relational collection of assignments (which is how they are stored in MongoDB -- I do some map/reduce logic to get them into the assignments-per-course structure I work with in Backbone). Thinking along those lines is actually what led me to this project :)

At this point I think I might be able to go either way. It's kind of an optimization question though, so I'll go with the assignments-per-course structure, and go the extra mile to get things for a range of dates.

So that said, your code worked superbly -- several orders of magnitude faster than my previous hack to do the same thing.

I guess I'll stick with that, then, with an eye open for refactoring into a denormalized collection of top-level assignments.

Again, thanks so much for your help and detailed responses.

@wulftone
Copy link

You can use the $cb operator to get the query result for a one-level nested attribute in a backbone-relational world:

var arr = courses.query( {
  assignments: {
    $cb: function(attr) {
      return attr.get('done') == true
    }
  }
});

However, it returns a regular array (filled with backbone models), so you'll need to make a new collection if you want to use the results as such:

var finished_assignments = new AssignmentsCollection(arr);

: )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants