- Documentation
- Quickstart
- Introduction
- Importing thinky
- Schemas
- Relations
- Virtuals
- Feeds
- FAQ
- API
-
- Model
- - getTableName
- - define
- - defineStatic
- - ensureIndex
- - hasOne
- - belongsTo
- - hasMany
- - hasAndBelongsToMany
- - save
- - pre
- - post
- - Query's methods
- - EventEmitter's methods
- - EventEmitter's methods
for documents
- Document
- - getModel
- - merge
- - validate
- - validateAll
- - save
- - saveAll
- - getOldValue
- - isSaved
- - setSaved
- - delete
- - deleteAll
- - purge
- - addRelation
- - removeRelation
- - getFeed
- - closeFeed
- - EventEmitter's methods
- Query
- - getJoin
- - addRelation
- - removeRelation
- - run
- - execute
- - ReQL methods
- - Overwritten ReQL methods
Query
A query is created when you call a method on a Model or by calling
var query = new thinky.Query(Model, rawQuery);
A Query object is a wrapper of an actual ReQL query and a model.
The model is used only if you call the run method. The results returned by the
database will be converted to instances of the model stored.
getJoin
query.getJoin(modelToGet) -> query
Retrieve the joined documents of the given model.
By default, if modelToGet is not provided, getJoin will keep recursing and will
retrieve all the joined documents.
To avoid infinite recursion, getJoin will not recurse in a field that contains a document from
a model that was previously fetched.
The option modelToGet can be an object where each field is a joined document that will also be retrieved.
Two options are also available:
_apply: The function to apply on the joined sequence/document_array: Set it tofalseto not coerce the joined sequence to an array
For example you can have
Users.getJoin({
account: true // retrieve the joined document that will be stored in account
})
Users.getJoin({
accounts: {
_apply: function(sequence) {
return sequence.orderBy("sold") // Retrieve all the accounts ordered by sold
}
},
company: true // Retrieve the company of the user
})
Example: Retrieve a user and its account
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasOne(Account, "account", "id", "userId")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: true})
.run().then(function(user) {
/*
* user = {
* id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* name: "Michel",
* account: {
* id: "3851d8b4-5358-43f2-ba23-f4d481358901",
* userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* sold: 2420
* }
* }
*/
});
Example: Retrieve a user and the number of accounts
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasMany(Account, "accounts", "id", "userId")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({
_apply: function(seq) { return seq.count() },
_array: false
}).run().then(function(user) {
/*
* user = {
* id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* name: "Michel",
* account: 3
* }
*/
});
Example: Retrieve a user and only its account.
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasOne(Account, "account", "id", "userId")
User.hasAndBelongsToMany(User, "friends", "id", "id")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: true})
.run().then(function(user) {
/*
* user = {
* id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* name: "Michel",
* account: {
* id: "3851d8b4-5358-43f2-ba23-f4d481358901",
* userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* sold: 2420
* }
* }
*/
});
Example Retrieve a user and all two accounts with the smallest sold.
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasMany(Account, "accounts", "id", "userId")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({
accounts: {
_apply: function(sequence) {
return sequence.orderBy('sold').limit(2)
}
}
}).run().then(function(user) {
/*
* user = {
* id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* name: "Michel",
* accounts: [{
* id: "3851d8b4-5358-43f2-ba23-f4d481358901",
* userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* sold: 2420
* }, {
* id: "db7ac1e8-0160-4e57-bf98-144ad5f93feb",
* userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* sold: 5832
* }]
* }
*/
});
Example: Retrieve a user, its account and its solds.
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasOne(Account, "account", "id", "userId");
User.hasAndBelongsToMany(User, "friends", "id", "id");
var Bill = thinky.createModel("Bill", {
id: type.string(),
sold: type.number(),
accountId: type.string()
});
Account.hasMany(Bill, "bills", "id", "accountId");
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a").getJoin({account: {bills: true}})
.run().then(function(user) {
/*
* user = {
* id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* name: "Michel",
* account: {
* id: "3851d8b4-5358-43f2-ba23-f4d481358901",
* userId: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a",
* sold: 2420
* bills: [
* {
* id: "6b48ca51-6363-4065-8acf-497454da9616",
* sold: 421,
* accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
* },
* {
* id: "d2d79e33-e65f-4043-b214-ca190f5e7d52",
* sold: 921,
* accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
* },
* {
* id: "279ac0f5-b249-4a34-a873-ecd318468dac",
* sold: 185,
* accountId: "3851d8b4-5358-43f2-ba23-f4d481358901"
* }
* ]
* }
* }
*/
});
addRelation
query.addRelation(field, joinedDocument) -> Promise
Add a relation for the given joinedDocument returned by query. This method does not saved both documents, only create the
relation between the two of them.
The value for joinedDocument must be an object with different requirement depending of the relation:
- for a
hasOnerelation, the primary key of the document must be defined - for a
hasManyrelation, the primary key of the document must be defined - for a
belongsTorelation, the primary key of the document or the value of the right key must be defined - for a
hasAndBelongsToManyrelation, the primary key of the document or the value of the right key must be defined
The promise get resolved with different value depending of the relation:
- for a
hasOnerelation, the joined document is returned - for a
hasManyrelation, the joined document is returned - for a
belongsTorelation, the document returned byqueryis returned. - for a
hasAndBelongsToManyrelation,trueis returned.
Note that query must return a unique object, not a sequence.
Example: Link the user with id 0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a with the account which id is
3851d8b4-5358-43f2-ba23-f4d481358901 for a hasOne relation.
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasOne(Account, "account", "id", "userId")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.addRelation("account", {id: "3851d8b4-5358-43f2-ba23-f4d481358901"})
Example: Link the user with id 0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a with the account which id is
3851d8b4-5358-43f2-ba23-f4d481358901 for a belongsTo relation
var User = thinky.createModel("User", {
id: type.string(),
name: type.string(),
_account: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
sold: type.number(),
accountNumber: type.string()
});
User.hasOne(Account, "account", "_account", "accountNumber")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.addRelation("account", {accountNumber: "XXXX-XXXX-XXXX"})
// Or with the primary key
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.addRelation("account", {id: "3851d8b4-5358-43f2-ba23-f4d481358901"})
Example: Make two users friends
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
User.hasAndBelongsToMany(User, "friends", "id", "id")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.addRelation("friends", {id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a"})
removeRelation
query.removeRelation(field[, joinedDocument]) -> query
Remove the relation for the joinedDocument returned by query stored in field. By default, all
the relations are destroyed. The argument joinedDocument can be defined for hasMany and hasAndBelongsToMany
relations to remove a unique relation.
- For
hasMany, the joinedDocument must be an object with the primary key of the joined document. - For
hasAndBelongsToMany, the joinedDocument must be an object with the primary key of the joined document or the right key.
Example: Remove an account from a user
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
var Account = thinky.createModel("Account", {
id: type.string(),
userId: type.string(),
sold: type.number()
});
User.hasOne(Account, "account", "id", "userId")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.addRelation("account")
.run()
Example: Remove all the friends of a given user
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
User.hasAndBelongsToMany(User, "friends", "id", "id")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.removeRelation("friends")
.run()
Example: Delete one friendship
var User = thinky.createModel("User", {
id: type.string(),
name: type.string()
});
User.hasAndBelongsToMany(User, "friends", "id", "id")
User.get("0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a")
.removeRelation("friends", {id: "0e4a6f6f-cc0c-4aa5-951a-fcfc480dd05a"})
.run()
run
query.run([options][, callback]) -> Promise
Execute the query and convert the results as instances of the model. A cursor will automatically be replaced by the array of all the results.
If you do not want to use a promise, you can directly pass a callback to run.
Example: Retrieve all the users
User.run().then(function(result) {
// process `result`
})
execute
query.execute([options][, callback]) -> Promise
Execute the query but do not parse the response from the server, for example a raw cursor can be returned.
If you do not want to use a promise, you can directly pass a callback to execute.
Example: Return all the ids of the users
User.map(r.row("id")).execute().then(function(cursor) {
cursor.each(function(err, userId) {
console.log(userId);
}
})
Note: The same query with run would have thrown an error because a string
cannot be converted to an instance of User.
ReQL methods
All the methods defined in ReQL can be called on a Query object.
The methods filter and orderBy can be automatically optimized. A model will
automatically fetch the indexes of its table, and at the time you call filter
or orderBy (and not when you call run), if an index is available, thinky will
automatically use an index.
The command filter is optimized only in case an object is passed as the first argument.
The first field in lexicographic order that match an index will be replaced with getAll.
The command orderBy is optimized only when a string is passed as the first argument.
Note: The current behavior is to look at the indexes of the model stored in the
query. If you use r.table(Model.getTableName()) instead of Model in a nested
query, you may have unexpected/broken optimizations. Use an anonymous function if you
need to prevent thinky from optimizing your query.
Note: If you created your table with {init: false} indexes will not be fetched, and
the optimizer will not be as efficient as it could without the init option.
Example: Return the number of users
User.count().execute().then(function(total) {
console.log(total+" users in the database");
});
Example: Return the users who are exactly 18 years old.
User.filter({age: 18}).run().then(function(result) {
// result is an array of instances of `User`
});
Overwritten ReQL methods
A few methods have slightly different behavior than the original ReQL commands.
- The
getcommand will return an error if no document is found (instead ofnull). This lets you easily chain commands aftergetwithout having to user.branch.
User.get(1).run().then(function(result) {
// ...
});
- The commands
updateandreplacewill have their first argument partially validated, if the first argument is an object. The validation will be performed with all fields set as optional. Once the queries has been executed, thinky will validate the returned values, and if an error occur during validation, the changes will be reverted (so another query will be issued).
Note: Because reverting the changes require a round trip, this operation is not atomic and may overwrite another write.
Note: If your queries does update something, you will get for a point-write (.get(...).update/replace(...)):
- the updated document if the query did update the document
undefinedif no document is updated
For a range write (table.update/replace, table.filter(...).update/replace), you will get an array of the
updated documents. If no document is updated, you will get an empty array.
Typically, this may result in the document being {id: 1, foo: "bar"}.
Model = thinky.createModel("User", {
id: type.number(),
age: type.number()
});
var promises = [];
// Suppose that the document is {id: 1, age: 18}
promises.push(Model.get(1).update({age: r.expr("string")}).run());
promises.push(Model.get(1).update({age: 20).run());
/*
What may happen is:
- The document becomes {id: 1, age: "string"}
- The document becomes {id: 1, age: 20}
- The document is reverted to {id: 1, age: 18}
*/
Functional Utilities
Thinky provides some Functional instance methods for easily plugging Queries
into functional computation chains or pipelines that handle promises or node-style callbacks
such as Ramda.js or async for example.
- Query.prototype.bindRun()
returns an instance of Query.prototype.run bound to the current instance of Query
- Query.prototype.bindExecute()
returns an instance of Query.prototype.execute bound to the current instance of Query
A contrived example might look like:
var R = require('ramda');
var User = thinky.createModel("User", {
id: type.number(),
age: type.number(),
admin: type.boolean().default(false)
});
/* Somewhere else */
R.composeP(
User.get(req.session.id).bindRun(), // No need to declare an extraneous variable to bind the instance of Query to
isAdmin // Some other promised function
).then(function(allowed){
// Do your admin stuff here;
});
/* async example */
async.parallel({
users: User.orderBy('id').bindRun(),
posts: Post.orderby('id').bindRun()
}, function (err, accumulator) {
/*
accumulator example:
{
users: [{your users}]
posts: [{your posts}]
}
*/
});