Skip to content

Ten minute guide with RethinkDB Elixir

Ian Duggan edited this page Nov 2, 2016 · 4 revisions

Note: This guide is a work in progress, and is based off of the official Ten minute Guide!

Before you start:

How this guide varies from the official ten minute guide.

This guide is similar to the official RethinkDB ten minute guide, but is slightly different in that it creates an Elixir project and utilizes the IEX interactive shell. The advantage of this approach is that you're able to create a working module that better represents a real project, and shows you how to utilize it with IEX. If you haven't already setup your RethinkDB-Elixir project, please do that now. For the remainder of this guide we are going to assume that you have the example EX1 project up and running. You don't have to use the EX1 module name, and can substitute your own name if desired.

No that you have a basic project setup, we are going to start adding basic CRUD (Create,Read,Update,Delete) functionality to the ex1.ex file in the lib directory of your project.

Using IEX with your module

From you project directory type:

 /ex1$ iex -S mix

Import a few modules for convenience

From the iex prompt type:

import RethinkDB.Query

Open a connection

When you first start RethinkDB, the server opens a port for the client drivers (28015 by default). Let’s open a connection:

{:ok, conn} = RethinkDB.Connection.start_link([db: :test])

The variable connection is now initialized and we can run queries.

Create a new database

By default, RethinkDB creates a database named test. However, in the real world we know that you will need to define your own database. In this example we are creating a basic microblog that's focused on Sci-Fi TV shows. Create the database as follows:

db_create("sciblog") |> RethinkDB.run(conn)

Create a new table

Now that we have a new database defined, let’s create a table named authors within this database:

db("sciblog") |> table_create("authors") |> RethinkDB.run(conn)

The result will be something like:

{:ok,
 %RethinkDB.Record{data: %{"config_changes" => [%{"new_val" => %{"db" => "sciblog",
         "durability" => "hard", "id" => "1a500897-86a5-43c1-ad4c-6cbacbc1b80a",
         "indexes" => [], "name" => "authors", "primary_key" => "id",
         "shards" => [<shard data>],
         "write_acks" => "majority"}, "old_val" => nil}],
    "tables_created" => 1}, profile: nil}}

(The config_changes field contains metadata about the newly created table; for more details, read about the table_create command.) There are a couple of things you should note about this query:

  • First, we select the database test with the db command.
  • Then, we add the table_create command to create the actual table.
  • Lastly, we call run(query, conn, opts \ []) in order to send the query to the server. All ReQL queries follow this general structure. Now that we’ve created a table, let’s insert some data!

Reconnect to the New Database

If you wanted to, you could prefix all of your queries with db("sciblog"). For convenience, however, let's reconnect to the newly created database:

{:ok, conn} = RethinkDB.Connection.start_link([db: :sciblog])

Insert data

Let’s insert three new documents into the authors table:

table("authors") |> insert([%{ name: "William Adama", tv_show: "Battlestar Galactica",
                               posts: [
                                 %{title: "Decommissioning speech", content: "The Cylon War is long over..."},
                                 %{title: "We are at war", content: "Moments ago, this ship received word..."},
                                 %{title: "The new Earth", content: "The discoveries of the past few days..."}
                               ]
                             },
                            %{ name: "Laura Roslin", tv_show: "Battlestar Galactica",
                               posts: [
                                 %{title: "The oath of office", content: "I, Laura Roslin, ..."},
                                 %{title: "They look like us", content: "The Cylons have the ability..."}
                               ]
                            },
                            %{ name: "Jean-Luc Picard", tv_show: "Star Trek TNG",
                               posts: [
                                 %{title: "Civil rights", content: "There are some words I've known since..."}
                               ]
                            }]) |> RethinkDB.run(conn)

We should get back an object that looks like this:

{:ok,
 %RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0,
    "generated_keys" => ["8acf3301-e308-4b55-9345-5600a2f93fa6",
     "0d65b143-de20-49ac-91b4-816752ec1319",
     "9dfa83c3-3428-4309-8675-0b2575867f21"], "inserted" => 3, "replaced" => 0,
    "skipped" => 0, "unchanged" => 0}, profile: nil}}

The server should return an object with zero errors and three inserted documents. We didn’t specify any primary keys (by default, each table uses the id attribute for primary keys), so RethinkDB generated them for us. The generated keys are returned via the generated_keys attribute.

There are a couple of things to note about this query:

  • Each connection sets a default database to use during its lifetime (if you don’t specify one in connect, the default database is set to test). This way we can omit the db('test') command in our query. We won’t specify the database explicitly from now on, but if you want to prepend your queries with the db command, it won’t hurt.
  • The insert command accepts a single document or an array of documents if you want to batch inserts. We use an array in this query instead of running three separate insert commands for each document.

Retrieve documents

Now that we inserted some data, let’s see how we can query the database!

All documents in a table To retrieve all documents from the table authors, we can simply run the query:

table("authors") |> RethinkDB.run(conn)

The result is an array of the three previously inserted documents, along with the generated id values.

Since the table might contain a large number of documents, the database returns a cursor object. As you iterate through the cursor, the server will send documents to the client in batches as they are requested. We only have three documents in our example, so we can safely retrieve all the documents at once. The toArray function automatically iterates through the cursor and puts the documents into a JavaScript array.

Filter documents based on a condition Let’s try to retrieve the document where the name attribute is set to William Adama. We can use a condition to filter the documents by chaining a filter command to the end of the query:

table("authors") |> filter(%{name: "William Adama"}) |> RethinkDB.run(conn)

This query returns one document — the record for William Adama. The filter command evaluates the provided condition for every row in the table, and returns only the relevant rows. Here’s the new commands we used to construct the condition above:

  • filter will return documents that match.
  • In this case, the field name and the string "William Adama" will be matched.

Let’s use filter again to retrieve all authors who have more than two posts:

table("authors") |> filter(lambda fn (a) -> count(a["posts"]) > 2 end) |> RethinkDB.run(conn))

In this case, we’re using a predicate that returns true only if the length of the array in the field posts is greater than two. This predicate contains two commands we haven’t seen before:

  • The count command returns the size of the array.
  • The expression returns true if a value is greater than the specified value (in this case, if the number of posts is greater than two).

Retrieve documents by primary key

We can also efficiently retrieve documents by their primary key using the get command. We can use one of the ids generated in the previous example:

table("authors") |> RethinkDB.Query.get("9dfa83c3-3428-4309-8675-0b2575867f21") |> RethinkDB.run(conn)

Since primary keys are unique, the get command returns a single document. This way we can retrieve the document directly without converting a cursor to an array.

Learn more about how RethinkDB can efficiently retrieve documents with secondary indexes.

Update documents

Let’s update all documents in the authors table and add a type field to note that every author so far is fictional:

table("authors") |> update(%{type: "fictional"}) |> RethinkDB.run(conn)

Since we changed three documents, the result should look like this:

{:ok,
 %RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0, "inserted" => 0,
    "replaced" => 3, "skipped" => 0, "unchanged" => 0}, profile: nil}}

Note that we first selected every author in the table, and then chained the update command to the end of the query. We could also update a subset of documents by filtering the table first. Let’s update William Adama’s record to note that he has the rank of Admiral:

table("authors") |> filter(%{name: "William Adama"}) |> update(%{rank: "admiral"}) |> RethinkDB.run(conn)

Since we only updated one document, we get back this object:

{:ok,
 %RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0, "inserted" => 0,
    "replaced" => 1, "skipped" => 0, "unchanged" => 0}, profile: nil}}

The update command allows changing existing fields in the document, as well as values inside of arrays. Let’s suppose Star Trek archaeologists unearthed a new speech by Jean-Luc Picard that we’d like to add to his posts:

future: Code

After processing this query, RethinkDB will add an additional post to Jean-Luc Picard’s document.

Browse the API reference for many more array operations available in RethinkDB.

Delete documents

Suppose we’d like to trim down our database and delete every document with less than three posts (sorry Laura and Jean-Luc):

future: Code

Since we have two authors with less than two posts, the result is:

{
    "unchanged": 0,
    "skipped": 0,
    "replaced": 0,
    "inserted": 0,
    "errors": 0,
    "deleted": 2
}

Realtime feeds

RethinkDB inverts the traditional database architecture by exposing an exciting new access model – instead of polling for changes, the developer can tell RethinkDB to continuously push updated query results to applications in realtime.

To start a feed, open a new terminal and open a new RethinkDB connection. Then, run the following query:

future: Code

Now switch back to your first terminal. We’ll be updating and deleting some documents in the next two sections. As we run these commands, the feed will push notifications to your program. The code above will print the following messages in the second terminal:

future: Code

RethinkDB will notify your program of all changes in the authors table and will include the old value and the new value of each modified document. See the changefeeds documentation entry for more details on how to use realtime feeds in RethinkDB.

Learn more

Want to keep learning? Dive into the documentation: Read the introduction to RQL to learn about the ReQL concepts in more depth. Learn how to use map-reduce in RethinkDB. Learn how to use table joins in RethinkDB. Jump into the cookbook and browse through dozens of examples of common RethinkDB queries.