Thinky — How to Query Document(s) by Field of Nested Objects

Thinky is a lightweight Node.js ORM for RethinkDB. The library helps you to define models representing the objects you’re using within your code. Further, you can define various relations between each of your models. Primarily, Thinky helps you to CRUD documents in RethinkDB out of your Node.js application.

This guide will show you how to query documents by fields of nested objects. We’ve published further Thinky posts, there might be something interesting for you:

Thinky Series Overview

Query Documents by Field of Nested Object

Let’s set up the context for the practical part. Assume that you’re saving user information and there’s a nested object for the user’s address that contains the street, zip code and city name. Your User model will look like this:

var User = thinky.createModel("User", {  
  id: type.string().default(r.uuid()),
  username: type.string(),
  password: type.number(),
  address: {
    street: type.string(),
    zip: type.number(),
    city: type.string()
  }
})

In the following, you’ll compose a query to filter documents on the city field of the nested address object. Use Thinky’s filter() method to query the database table of Users and further specify a filter to only find users where the related city name matches the given query value.

RethinkDB’s filter(predicate) method requires a predicate that evaluates to a true or false statement. Only objects in thesequence with a true predicate are part of the final result. The result of filter() is an array of values. If nothing was found or matched your data, the result array is empty.

You have three options to be applied using the filter() method which are shown in the sections below.

Option 1 — filter() With an Object

The filter(predicate) method allows you to specify an object that matches the selected keys of your model and the appropriate values.

var _ = require('lodash')  
var when = require('when')  
var User = require('./models/user')

var findUsersByCity = function (city) {  
  // filter users by city name (in nested address object)
  return User.filter({
    address: {
      city: city
    }
  }).run()
}

As you can see, you need to pass a nested object to the filter() method, like filter({ address: { city: 'Magdeburg' } }), to fetch only users that match your comparison value (predicate).

Option 2 — filter() With a Function

Besides an object with key-value-pairs that restrict the result, you can pass a callback function that returns the individual documents and you need to apply the actual filter.

var _ = require('lodash')  
var when = require('when')  
var User = require('./models/user')

var findUsersByCity = function (city) {  
  // filter users by city name (in nested address object)
  return User.filter(function (user) {
    return user('address')('city').eq(city)
  }).run()
}

The callback returns the user documents and you need to access the city field by selecting it like this: user('address')('city'). Afterwards, you need to compare the given value with your restriction so that the result is either true or false. Remember: a predicate is required.

Option 3 — filter() With Document Reference

There’s a third option: the document reference using RethinkDB’s row command. The row command returns the currently visited document and you can further apply your desired filters.

var _ = require('lodash')  
var when = require('when')  
var User = require('./models/user')

var findUsersByCity = function (city) {  
  // filter users by city name (in nested address object)
  return User.filter(
    r.row('address')('city').eq(city)
  ).run()
}

This option is kind of close to the show solution above using a callback function. Anyway, it’s a convenient way to have more complex filters that just the { key: value }-predicate object (presented as option 1).

A Complete Example

The following code snippet presents a more complete example by also evaluating the result set of the query.

var _ = require('lodash')  
var when = require('when')  
var User = require('./models/user')

var findUsersByCity = function (city) {  
  // filter users by city name (in nested address object)
  return User.filter({
    address: {
      city: city
    }
  }).run().then(function (users) {
    if (_.isEmpty(users)) {
      // nothing found
      return when.reject('No user found for city: ' + city)
    }

    // return or perform desired operations on document(s)
    // we just return in a resolving Promise :)
    return when.resolve(users)
  })
}

As you can see, within the code snippet above you can just to pass a nested object to the filter() method, like filter({ address: { city: city } }) and there’s no need for a more complex design to fetch only users that match your comparison value. The result of filter() is an array of values that match the given criteria.

Outlook

You’ve learned how to query documents by fields of nested objects using Thinky. Next week, we’ll continue on Thinky and show you how to add custom methods to your documents by extending the foundational model.

If you’ve questions or ideas on how to improve this post, leave them in the comments below or shout out @futurestud_io.

Make it rock & enjoy coding!

Explore the Library

Find interesting tutorials and solutions for your problems.

Miscellaneous