offthegrass
offthegrass

Reputation: 456

Meteor: how to find a subdocument within a document (minimongo)

The short version

To find a document within a collection, it's

collection.findOne([...], [...])

How do you find a subdocument within a document?

The long version

I have a collection entry as below:

db.projects.find()

{
  title: 'company.com'
  company: 'A Company'
  companyID: Random.id()
  category: 'website'
  starred: false
  timeline: {
    ideas: {
      task: {
        name: 'task1'
        completed: true
        todos: [
          {todo: 'a todo', completed: false, todoID: Random.id()}
          {todo: 'a todo', completed: false, todoID: Random.id()}
          {todo: 'a todo', completed: false, todoID: Random.id()}
        ]
      }
      task: {
        name: 'task2'
        completed: false
        todos: [
          {todo: 'another todo', completed: false, todoID: Random.id()}
          {todo: 'another todo', completed: false, todoID: Random.id()}
          {todo: 'another todo', completed: false, todoID: Random.id()}
        ]
      }
    }
    development: {
    ...
    }
    production: {
    ...
    }
  }
}

(written in coffeescript)

The entry is in my Projects collection. It's published on the server:

server/publications.js

Meteor.publish('projects', function() {
  return Projects.find();
});

.. and subscribed to by the client:

client/projects.js

Meteor.subscribe('projects');

Simple. Works as expected.

Next, I use a Session variable to store a project when it is selected:

Session.set('selectedProject', this.id); 

and call it when required:

Session.get('selectedProject');

All fine.

Now I want to search the ideas entry of the selectedProject and find the first task that has completed: false.

After a few hours of reading, I think I'm close with the following:

 ({
   currentTask: function() {
     return Projects.findOne({
       _id: Session.get('selectedProject', {
         'timeline.ideas.task.completed': false
       })
     }, {
       fields: 'timeline.ideas.task'
     });
   }
 });

I guess ^ that might try to return one project and not one task?

it returns this error:

Exception in template helper: Error: Match error: Failed Match.OneOf or Match.Optional validation

in theory it should be something like

selectedProject.findOne(....)

do I look at using $elemMatch on the server? or am I missing something simple?

Upvotes: 1

Views: 803

Answers (1)

offthegrass
offthegrass

Reputation: 456

See this answer which uses aggregation.

Meteor users: at the time of writing (version 1.0.4.1), aggregation on the client is not supported.

I wanted to keep the code on the client, and also wanted reactivity, so here's my solution:

db.projects

// simplified structure

{
      title: 'awebsite.com'
      company: 'a company'
      companyID: Random.id()
      category: 'website'
      starred: false
      tasks: [
          {
            completed: true
            name: 'task1'
            category: 'ideas'
            todos: [
              {todo: 'something', completed: false, todoID: Random.id()}
              {todo: 'something', completed: false, todoID: Random.id()}
              {todo: 'something', completed: false, todoID: Random.id()}
            ]
          }
          {
            completed: false
            name: 'task2'
            category: 'ideas'
            todos: [
              {todo: 'something', completed: false, todoID: Random.id()}
              {todo: 'something', completed: false, todoID: Random.id()}
              {todo: 'something', completed: false, todoID: Random.id()}
            ]
          }
        ]
    }

../projects.coffee

Meteor.subscribe 'projects'
Tasks = new (Mongo.Collection)(null)   //use (null) to create client-only collection

Template.projects.rendered = ->
  results = Projects.findOne { title: 'awebsite.com' },
    fields: tasks: 1

  _.each results.tasks, (task) ->
    Tasks.insert (task)

Template.projects.helpers
  currentTask: ->
    Tasks.findOne completed: false

Upvotes: 1

Related Questions