Documentation

TOPICS
LANGUAGE

Activity Feed Tutorial

In this tutorial, you’ll explore the Activity Feed pattern. If you are following along in code, you will need to make sure you’ve gone through the previous tutorials, starting with Hello World and ending with Temporality, before beginning this one.

Activity feeds allow users to see events as they occur from things that they follow. These could be other users, content tags, organizations, or anything else you can index with FaunaDB. You’ll use the people you created in the Social Graph tutorial as authors for your posts, to compose an activity feed query. This query returns a given user’s viewpoint, showing new posts from people they follow.

Temporal and event options can be added to any FaunaDB query, they are emitted from the objects the query specifies. If you are looking at events from an index, underlying instance changes will only be reflected when they involve the indexed fields. This can be useful to provide a sparse event feed of only fields of interest. Some supported patterns include auditing and synchronizing external systems like full text indexes or mobile application user interfaces. See the further reading section at the end of this article for more patterns.

Getting Started

The combined data and indexes from the other tutorials should already be in your my_app database.

Setup Indexes

Your existing posts don’t have authors, because the People class didn’t exist when we created the posts. So we’ll run a FaunaDB query to add an author to each blog post. Before we do that we’ll create some indexes that we’ll be using later.

We don’t have an index that will return all posts, so we need to add one to support this operation. This is done by creating an index without any terms defined, with the posts class as it’s source.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create_index": {
            "object": {
              "name": "all_posts",
              "source": { "@ref": "classes/posts" }
            }
          }
        }'

client.Query(
  CreateIndex(
    Obj("name", "all_posts", "source", Ref("classes/posts"))));

client.query(
  CreateIndex(
    Obj(
      "name", Value("all_posts"),
      "source", Ref("classes/posts")
    )));

client.Query(
    f.CreateIndex(
        f.Obj{"name": "all_posts", "source": f.Ref("classes/posts")},
    ),
)

client.query(
  CreateIndex(
    Obj("name" -> "all_posts", "source" -> Ref("classes/posts"))))

client.query(
  q.create_index(
    {"name": "all_posts", "source": Ref("classes/posts")}
  ))

$client.query do
  create_index name: 'all_posts', source: ref('classes/posts')
end

client.query(
    CreateIndex(
        Obj(
            "name" => "all_posts",
            "source" => Ref("classes/posts")
        )
    )
)

client.query(
  q.CreateIndex(
    { name: "all_posts", source: Ref("classes/posts") }));

You’ll also create an index on posts_by_author, and an index on followees_by_follower to support social graph activity feed queries. We want to find all the posts for each author, from all the authors a given user follows.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create_index": {
            "object": {
              "name": "posts_by_author",
              "source": { "@ref": "classes/posts" },
              "terms": [ { "object": { "field": [ "data", "author" ] } } ]
            }
          }
        }'

client.Query(
  CreateIndex(
    Obj(
      "name", "posts_by_author",
      "source", Ref("classes/posts"),
      "terms", Arr(Obj("field", Arr("data", "author")))
    )));

client.query(
  CreateIndex(
    Obj(
      "name", Value("posts_by_author"),
      "source", Ref("classes/posts"),
      "terms", Arr(Obj("field", Arr(Value("data"), Value("author"))))
    )));

client.Query(
    f.CreateIndex(
        f.Obj{
            "name": "posts_by_author",
            "source": f.Ref("classes/posts"),
            "terms": f.Arr{f.Obj{"field": f.Arr{"data", "author"}}},
        },
    ),
)

client.query(
  CreateIndex(
    Obj(
      "name" -> "posts_by_author",
      "source" -> Ref("classes/posts"),
      "terms" -> Arr(Obj("field" -> Arr("data", "author")))
    )))

client.query(
  q.create_index(
    {
      "name": "posts_by_author",
      "source": Ref("classes/posts"),
      "terms": [{"field": ["data", "author"]}]
    }
  ))

$client.query do
  create_index name: 'posts_by_author',
               source: ref('classes/posts'),
               terms: [{ field: ['data', 'author'] }]
end

client.query(
    CreateIndex(
        Obj(
            "name" => "posts_by_author",
            "source" => Ref("classes/posts"),
            "terms" => Arr(Obj("field" => Arr("data", "author")))
        )
    )
)

client.query(
  q.CreateIndex(
    {
      name: "posts_by_author",
      source: Ref("classes/posts"),
      terms: [{ field: ["data", "author"] }]
    }));

You may recognize this as the inverse of the followers_by_followee index you created in the Social Graph tutorial.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create_index": {
            "object": {
              "name": "followees_by_follower",
              "source": { "@ref": "classes/relationships" },
              "terms": [ { "object": { "field": [ "data", "follower" ] } } ],
              "values": [ { "object": { "field": [ "data", "followee" ] } } ]
            }
          }
        }'

client.Query(
  CreateIndex(
    Obj(
      "name", "followees_by_follower",
      "source", Ref("classes/relationships"),
      "terms", Arr(Obj("field", Arr("data", "follower"))),
      "values", Arr(Obj("field", Arr("data", "followee")))
    )));

client.query(
  CreateIndex(
    Obj(
      "name", Value("followees_by_follower"),
      "source", Ref("classes/relationships"),
      "terms", Arr(Obj("field", Arr(Value("data"), Value("follower")))),
      "values", Arr(Obj("field", Arr(Value("data"), Value("followee"))))
    )));

client.Query(
    f.CreateIndex(
        f.Obj{
            "name": "followees_by_follower",
            "source": f.Ref("classes/relationships"),
            "terms": f.Arr{f.Obj{"field": f.Arr{"data", "follower"}}},
            "values": f.Arr{f.Obj{"field": f.Arr{"data", "followee"}}},
        },
    ),
)

client.query(
  CreateIndex(
    Obj(
      "name" -> "followees_by_follower",
      "source" -> Ref("classes/relationships"),
      "terms" -> Arr(Obj("field" -> Arr("data", "follower"))),
      "values" -> Arr(Obj("field" -> Arr("data", "followee")))
    )))

client.query(
  q.create_index(
    {
      "name": "followees_by_follower",
      "source": Ref("classes/relationships"),
      "terms": [{"field": ["data", "follower"]}],
      "values": [{"field": ["data", "followee"]}]
    }
  ))

$client.query do
  create_index name: 'followees_by_follower',
               source: ref('classes/relationships'),
               terms: [{ field: ['data', 'follower'] }],
               values: [{ field: ['data', 'followee'] }]
end

client.query(
    CreateIndex(
        Obj(
            "name" => "followees_by_follower",
            "source" => Ref("classes/relationships"),
            "terms" => Arr(Obj("field" => Arr("data", "follower"))),
            "values" => Arr(Obj("field" => Arr("data", "followee")))
        )
    )
)

client.query(
  q.CreateIndex(
    {
      name: "followees_by_follower",
      source: Ref("classes/relationships"),
      terms: [{ field: ["data", "follower"] }],
      values: [{ field: ["data", "followee"] }]
    }));

Add authors to posts

Here is what a post looks like before we add authors:


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{ "get": { "@ref": "classes/posts/192903209792046592" } }'

client.Query(Get(Ref("classes/posts/192903209792046592")));

client.query(Get(Ref("classes/posts/192903209792046592")));

client.Query(f.Get(f.Ref("classes/posts/192903209792046592")))

client.query(Get(Ref("classes/posts/192903209792046592")))

client.query(q.get(Ref("classes/posts/192903209792046592")))

$client.query do
  get ref('classes/posts/192903209792046592')
end

client.query(Get(Ref("classes/posts/192903209792046592")))

client.query(q.Get(Ref("classes/posts/192903209792046592")));

=> HTTP/1.1 200 OK
{
  "resource": {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225686876942,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ]
    }
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903209792046592" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225686876942,
  "data": {
    "title": "My dog and other marvels",
    "tags": [ "pet", "cute", "funny" ]
  }
}

Now let’s update all the posts with authors. We’ll do that by iterating over authors, and updating a few posts with each author. You should have 6 posts, and we’ll distribute them through three of the authors.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "let": {
            "ns": [ 0, 1, 2, 3, 4, 5 ],
            "authors": [
              {
                "match": { "index": "people_by_name" },
                "terms": "alice"
              },
              {
                "match": { "index": "people_by_name" },
                "terms": "alice"
              },
              {
                "match": { "index": "people_by_name" },
                "terms": "bob"
              },
              {
                "match": { "index": "people_by_name" },
                "terms": "bob"
              },
              {
                "match": { "index": "people_by_name" },
                "terms": "carol"
              },
              {
                "match": { "index": "people_by_name" },
                "terms": "carol"
              }
            ]
          },
          "in": {
            "map": {
              "lambda": "n",
              "expr": {
                "update": {
                  "select": { "var": "n" },
                  "from": { "paginate": { "match": { "index": "all_posts" } } }
                },
                "params": {
                  "object": {
                    "data": {
                      "object": {
                        "author": {
                          "select": "ref",
                          "from": {
                            "get": {
                              "select": { "var": "n" },
                              "from": { "var": "authors" }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "collection": { "var": "ns" }
          }
        }'

client.Query(
  Let(
    Obj(
      "ns", Arr(0, 1, 2, 3, 4, 5),
      "authors", Arr(
        Match(Index("people_by_name"), "alice"),
        Match(Index("people_by_name"), "alice"),
        Match(Index("people_by_name"), "bob"),
        Match(Index("people_by_name"), "bob"),
        Match(Index("people_by_name"), "carol"),
        Match(Index("people_by_name"), "carol")
      )
    ),
    Map(
      Var("ns"),
      n => Update(
        Select(n, Paginate(Match(Index("all_posts")))),
        Obj(
          "data", Obj(
            "author", Select("ref", Get(Select(n, Var("authors"))))
          )
        )))));

client.query(
  Let(
    "ns", Arr(
      Value(0),
      Value(1),
      Value(2),
      Value(3),
      Value(4),
      Value(5)
    ),
    "authors", Arr(
      Match(Index(Value("people_by_name")), Value("alice")),
      Match(Index(Value("people_by_name")), Value("alice")),
      Match(Index(Value("people_by_name")), Value("bob")),
      Match(Index(Value("people_by_name")), Value("bob")),
      Match(Index(Value("people_by_name")), Value("carol")),
      Match(Index(Value("people_by_name")), Value("carol"))
    )
  ).in(
    Map(
      Var("ns"),
      Lambda(
        Value("n"),
        Update(
          Select(
            Var("n"),
            Paginate(Match(Index(Value("all_posts"))))),
          Obj(
            "data", Obj(
              "author", Select(
                Value("ref"),
                Get(Select(Var("n"), Var("authors"))))
            )
          ))))
  ));

client.Query(
    f.Let(
        f.Obj{
            "ns": f.Arr{0, 1, 2, 3, 4, 5},
            "authors": f.Arr{
                f.MatchTerm(f.Index("people_by_name"), "alice"),
                f.MatchTerm(f.Index("people_by_name"), "alice"),
                f.MatchTerm(f.Index("people_by_name"), "bob"),
                f.MatchTerm(f.Index("people_by_name"), "bob"),
                f.MatchTerm(f.Index("people_by_name"), "carol"),
                f.MatchTerm(f.Index("people_by_name"), "carol"),
            },
        },
        f.Map(
            f.Var("ns"),
            f.Lambda(
                "n",
                f.Update(
                    f.Select(
                        f.Var("n"),
                        f.Paginate(
                            f.Match(f.Index("all_posts")),
                        ),
                    ),
                    f.Obj{
                        "data": f.Obj{
                            "author": f.Select(
                                "ref",
                                f.Get(
                                    f.Select(
                                        f.Var("n"),
                                        f.Var("authors"),
                                    ),
                                ),
                            ),
                        },
                    },
                ),
            ),
        ),
    ),
)

client.query(
  Let {
    val ns = Arr(0, 1, 2, 3, 4, 5)
    val authors = Arr(
      Match(Index("people_by_name"), "alice"),
      Match(Index("people_by_name"), "alice"),
      Match(Index("people_by_name"), "bob"),
      Match(Index("people_by_name"), "bob"),
      Match(Index("people_by_name"), "carol"),
      Match(Index("people_by_name"), "carol")
    )
    Map(
      ns,
      Lambda { n =>
        Update(
          Select(n, Paginate(Match(Index("all_posts")))),
          Obj(
            "data" -> Obj("author" -> Select("ref", Get(Select(n, authors))))
          ))
      })
  })

client.query(
  q.let(
    {
      "ns": [0, 1, 2, 3, 4, 5],
      "authors": [
        q.match(q.index("people_by_name"), "alice"),
        q.match(q.index("people_by_name"), "alice"),
        q.match(q.index("people_by_name"), "bob"),
        q.match(q.index("people_by_name"), "bob"),
        q.match(q.index("people_by_name"), "carol"),
        q.match(q.index("people_by_name"), "carol")
      ]
    },
    q.map_expr(
      lambda n: q.update(
        q.select(n, q.paginate(q.match(q.index("all_posts")))),
        {
          "data": {
            "author": q.select("ref", q.get(q.select(n, q.var("authors"))))
          }
        }
      ),
      q.var("ns")
    )
  ))

$client.query do
  let(ns: [0, 1, 2, 3, 4, 5], authors: [
    match(index('people_by_name'), 'alice'),
    match(index('people_by_name'), 'alice'),
    match(index('people_by_name'), 'bob'),
    match(index('people_by_name'), 'bob'),
    match(index('people_by_name'), 'carol'),
    match(index('people_by_name'), 'carol')
  ]) do
    map ns do |n|
      update(select(n, paginate(match(index('all_posts')))),
             data: { author: select('ref', get(select(n, authors))) })
    end
  end
end

client.query(
    Let(
        bindings: [
        ns: Arr(0, 1, 2, 3, 4, 5),
        authors: Arr(
            Match(
                index: Index("people_by_name"),
                terms: "alice"
            ),
            Match(
                index: Index("people_by_name"),
                terms: "alice"
            ),
            Match(index: Index("people_by_name"), terms: "bob"),
            Match(index: Index("people_by_name"), terms: "bob"),
            Match(
                index: Index("people_by_name"),
                terms: "carol"
            ),
            Match(
                index: Index("people_by_name"),
                terms: "carol"
            )
        )
        ],
        in: Map(
            collection: Var("ns"),
            to: { n in
                Update(
                    ref: Select(
                        path: n,
                        from: Paginate(
                            Match(index: Index("all_posts"))
                        )
                    ),
                    to: Obj(
                        "data" => Obj(
                            "author" => Select(
                                path: "ref",
                                from: Get(Select(path: n, from: authors))
                            )
                        )
                    )
                )
            }
        )
    )
)

client.query(
  q.Let(
    {
      ns: [0, 1, 2, 3, 4, 5],
      authors: [
        q.Match(q.Index("people_by_name"), "alice"),
        q.Match(q.Index("people_by_name"), "alice"),
        q.Match(q.Index("people_by_name"), "bob"),
        q.Match(q.Index("people_by_name"), "bob"),
        q.Match(q.Index("people_by_name"), "carol"),
        q.Match(q.Index("people_by_name"), "carol")
      ]
    },
    q.Map(
      q.Var("ns"),
      function(n) {
        return q.Update(
          q.Select(n, q.Paginate(q.Match(q.Index("all_posts")))),
          {
            data: {
              author: q.Select("ref", q.Get(q.Select(n, q.Var("authors"))))
            }
          });
      })));

=> HTTP/1.1 200 OK
{
  "resource": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952478720" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Homebound",
        "tags": [ "nostalgia", "travel" ],
        "author": { "@ref": "classes/people/192903210332062208" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903210030072320" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Theory of time",
        "tags": [ "philosophy" ],
        "author": { "@ref": "classes/people/192903210332062208" }
      }
    }
  ]
}

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

=> [
  {
    "ref": { "@ref": "classes/posts/192903209680896512" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "What I had for breakfast ..",
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792046592" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "My dog and other marvels",
      "tags": [ "pet", "cute", "funny" ],
      "author": { "@ref": "classes/people/192903210332065280" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209792047616" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952477696" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "All Aboard",
      "tags": [ "ship", "travel" ],
      "author": { "@ref": "classes/people/192903210332064256" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903209952478720" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Homebound",
      "tags": [ "nostalgia", "travel" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  },
  {
    "ref": { "@ref": "classes/posts/192903210030072320" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699095165,
    "data": {
      "title": "Theory of time",
      "tags": [ "philosophy" ],
      "author": { "@ref": "classes/people/192903210332062208" }
    }
  }
]

Clever readers will note that now they all have the same new timestamp, so after this we’ll add some more posts with authors to extend the history. In this tutorial we’ll focus on RSS style feeds of immutable messages. In a future tutorial we’ll explore cache-coherency / external indexer feeds for mutable instances.

Add history

Now you’ll need to create a couple of posts so we have some history to query. First a new post by Alice:


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create": { "class": "posts" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "title": "McTaggart's Illusion",
                  "author": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  },
                  "tags": [ "philosophy", "time" ]
                }
              }
            }
          }
        }'

client.Query(
  Create(
    Class("posts"),
    Obj(
      "data", Obj(
        "title", "McTaggart's Illusion",
        "author", Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "tags", Arr("philosophy", "time")
      )
    )));

client.query(
  Create(
    Class(Value("posts")),
    Obj(
      "data", Obj(
        "title", Value("McTaggart's Illusion"),
        "author", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("alice")))),
        "tags", Arr(Value("philosophy"), Value("time"))
      )
    )));

client.Query(
    f.Create(
        f.Class("posts"),
        f.Obj{
            "data": f.Obj{
                "title": "McTaggart's Illusion",
                "author": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "alice"),
                    ),
                ),
                "tags": f.Arr{"philosophy", "time"},
            },
        },
    ),
)

client.query(
  Create(
    Class("posts"),
    Obj(
      "data" -> Obj(
        "title" -> "McTaggart's Illusion",
        "author" -> Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "tags" -> Arr("philosophy", "time")
      )
    )))

client.query(
  q.create(
    q.class_expr("posts"),
    {
      "data": {
        "title": "McTaggart's Illusion",
        "author": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "alice"))
        ),
        "tags": ["philosophy", "time"]
      }
    }
  ))

$client.query do
  create class_('posts'),
         data: {
           title: "McTaggart's Illusion",
           author: select('ref', get(match(index('people_by_name'), 'alice'))),
           tags: ['philosophy', 'time']
         }
end

client.query(
    Create(
        at: Class("posts"),
        Obj(
            "data" => Obj(
                "title" => "McTaggart's Illusion",
                "author" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "alice"
                        )
                    )
                ),
                "tags" => Arr("philosophy", "time")
            )
        )
    )
)

client.query(
  q.Create(
    q.Class("posts"),
    {
      data: {
        title: "McTaggart's Illusion",
        author: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "alice"))),
        tags: ["philosophy", "time"]
      }
    }));

=> HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/posts/192903222838428160" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699165542,
    "data": {
      "title": "McTaggart's Illusion",
      "author": { "@ref": "classes/people/192903210332065280" },
      "tags": [ "philosophy", "time" ]
    }
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222838428160" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699165542,
  "data": {
    "title": "McTaggart's Illusion",
    "author": { "@ref": "classes/people/192903210332065280" },
    "tags": [ "philosophy", "time" ]
  }
}

We’ll use the moment of this transaction as our activity start time. In the case of a feed reader polling, it requests articles published since the last connection.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '1520225699165542'

client.Query(1520225699165542);

client.query(Value(1520225699165542));

client.Query(1520225699165542)

client.query(1520225699165542)

client.query(1520225699165542)

$client.query do
  1520225699165542
end

client.query(1520225699165542)

client.query(1520225699165542);

=> HTTP/1.1 200 OK
{ "resource": 1520225699165542 }

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

=> 1520225699165542

Now we’ll add two more posts. The first by Bob:


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create": { "class": "posts" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "title": "Right brain navigation",
                  "author": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  },
                  "tags": [ "travel" ]
                }
              }
            }
          }
        }'

client.Query(
  Create(
    Class("posts"),
    Obj(
      "data", Obj(
        "title", "Right brain navigation",
        "author", Select("ref", Get(Match(Index("people_by_name"), "bob"))),
        "tags", Arr("travel")
      )
    )));

client.query(
  Create(
    Class(Value("posts")),
    Obj(
      "data", Obj(
        "title", Value("Right brain navigation"),
        "author", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("bob")))),
        "tags", Arr(Value("travel"))
      )
    )));

client.Query(
    f.Create(
        f.Class("posts"),
        f.Obj{
            "data": f.Obj{
                "title": "Right brain navigation",
                "author": f.Select(
                    "ref",
                    f.Get(f.MatchTerm(f.Index("people_by_name"), "bob")),
                ),
                "tags": f.Arr{"travel"},
            },
        },
    ),
)

client.query(
  Create(
    Class("posts"),
    Obj(
      "data" -> Obj(
        "title" -> "Right brain navigation",
        "author" -> Select("ref", Get(Match(Index("people_by_name"), "bob"))),
        "tags" -> Arr("travel")
      )
    )))

client.query(
  q.create(
    q.class_expr("posts"),
    {
      "data": {
        "title": "Right brain navigation",
        "author": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "bob"))
        ),
        "tags": ["travel"]
      }
    }
  ))

$client.query do
  create class_('posts'),
         data: {
           title: 'Right brain navigation',
           author: select('ref', get(match(index('people_by_name'), 'bob'))),
           tags: ['travel']
         }
end

client.query(
    Create(
        at: Class("posts"),
        Obj(
            "data" => Obj(
                "title" => "Right brain navigation",
                "author" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "bob"
                        )
                    )
                ),
                "tags" => Arr("travel")
            )
        )
    )
)

client.query(
  q.Create(
    q.Class("posts"),
    {
      data: {
        title: "Right brain navigation",
        author: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "bob"))),
        tags: ["travel"]
      }
    }));

=> HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/posts/192903222900294144" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699224886,
    "data": {
      "title": "Right brain navigation",
      "author": { "@ref": "classes/people/192903210332064256" },
      "tags": [ "travel" ]
    }
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222900294144" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699224886,
  "data": {
    "title": "Right brain navigation",
    "author": { "@ref": "classes/people/192903210332064256" },
    "tags": [ "travel" ]
  }
}

The second is by Carol, who isn’t following herself. We won’t see Carol’s post in the queries below, because it is not included in the join query we’ll run.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "create": { "class": "posts" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "title": "Zeno's Paradox",
                  "author": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "carol"
                      }
                    }
                  },
                  "tags": [ "philosophy" ]
                }
              }
            }
          }
        }'

client.Query(
  Create(
    Class("posts"),
    Obj(
      "data", Obj(
        "title", "Zeno's Paradox",
        "author", Select("ref", Get(Match(Index("people_by_name"), "carol"))),
        "tags", Arr("philosophy")
      )
    )));

client.query(
  Create(
    Class(Value("posts")),
    Obj(
      "data", Obj(
        "title", Value("Zeno's Paradox"),
        "author", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("carol")))),
        "tags", Arr(Value("philosophy"))
      )
    )));

client.Query(
    f.Create(
        f.Class("posts"),
        f.Obj{
            "data": f.Obj{
                "title": "Zeno's Paradox",
                "author": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "carol"),
                    ),
                ),
                "tags": f.Arr{"philosophy"},
            },
        },
    ),
)

client.query(
  Create(
    Class("posts"),
    Obj(
      "data" -> Obj(
        "title" -> "Zeno's Paradox",
        "author" -> Select("ref", Get(Match(Index("people_by_name"), "carol"))),
        "tags" -> Arr("philosophy")
      )
    )))

client.query(
  q.create(
    q.class_expr("posts"),
    {
      "data": {
        "title": "Zeno's Paradox",
        "author": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "carol"))
        ),
        "tags": ["philosophy"]
      }
    }
  ))

$client.query do
  create class_('posts'),
         data: {
           title: "Zeno's Paradox",
           author: select('ref', get(match(index('people_by_name'), 'carol'))),
           tags: ['philosophy']
         }
end

client.query(
    Create(
        at: Class("posts"),
        Obj(
            "data" => Obj(
                "title" => "Zeno's Paradox",
                "author" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "carol"
                        )
                    )
                ),
                "tags" => Arr("philosophy")
            )
        )
    )
)

client.query(
  q.Create(
    q.Class("posts"),
    {
      data: {
        title: "Zeno's Paradox",
        author: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "carol"))),
        tags: ["philosophy"]
      }
    }));

=> HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/posts/192903222914974208" },
    "class": { "@ref": "classes/posts" },
    "ts": 1520225699235472,
    "data": {
      "title": "Zeno's Paradox",
      "author": { "@ref": "classes/people/192903210332062208" },
      "tags": [ "philosophy" ]
    }
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/192903222914974208" },
  "class": { "@ref": "classes/posts" },
  "ts": 1520225699235472,
  "data": {
    "title": "Zeno's Paradox",
    "author": { "@ref": "classes/people/192903210332062208" },
    "tags": [ "philosophy" ]
  }
}

The Activity Feed Pattern

A common pattern in social applications is the ability to show recent updates to a user, based on what the user is interested in. In the Social Graph tutorial, you created a collection of users, and established follower relationships between them. Now you’ll query to create an activity feed tailored to a specific user, of posts that have been created or updated since they last logged in.

A basic join (before temporality)

Before we even start querying blog posts, a basic sanity check. Here are the authors Carol follows.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": { "get": { "var": "person" } }
          },
          "collection": {
            "paginate": {
              "match": { "index": "followees_by_follower" },
              "terms": {
                "select": "ref",
                "from": {
                  "get": {
                    "match": { "index": "people_by_name" },
                    "terms": "carol"
                  }
                }
              }
            }
          }
        }'

client.Query(
  Map(
    Paginate(
      Match(
        Index("followees_by_follower"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "carol"))))),
    person => Get(person)));

client.query(
  Map(
    Paginate(
        Match(
          Index(Value("followees_by_follower")),
          Select(
            Value("ref"),
            Get(
              Match(
                Index(Value("people_by_name")),
                Value("carol")))))),
    Lambda(Value("person"), Get(Var("person")))));

client.Query(
    f.Map(
        f.Paginate(
            f.MatchTerm(
                f.Index("followees_by_follower"),
                f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(
                            f.Index("people_by_name"),
                            "carol",
                        ),
                    ),
                ),
            ),
        ),
        f.Lambda("person", f.Get(f.Var("person"))),
    ),
)

client.query(
  Map(
    Paginate(
      Match(
        Index("followees_by_follower"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "carol"))))),
    Lambda { person => Get(person) }))

client.query(
  q.map_expr(
    lambda person: q.get(person),
    q.paginate(
      q.match(
        q.index("followees_by_follower"),
        q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "carol"))
        )
      )
    )
  ))

$client.query do
  map paginate(match(index('followees_by_follower'),
                 select('ref',
                        get(match(index('people_by_name'), 'carol'))))) do |person|
    get(person)
  end
end

client.query(
    Map(
        collection: Paginate(
            Match(
                index: Index("followees_by_follower"),
                terms: Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "carol"
                        )
                    )
                )
            )
        ),
        to: { person in Get(person) }
    )
)

client.query(
  q.Map(
    q.Paginate(
      q.Match(
        q.Index("followees_by_follower"),
        q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "carol"))))),
    function(person) {
      return q.Get(person);
    }));

=> HTTP/1.1 200 OK
{
  "resource": {
    "data": [
      {
        "ref": { "@ref": "classes/people/192903210332064256" },
        "class": { "@ref": "classes/people" },
        "ts": 1520225687236965,
        "data": { "name": "bob" }
      },
      {
        "ref": { "@ref": "classes/people/192903210332065280" },
        "class": { "@ref": "classes/people" },
        "ts": 1520225687236965,
        "data": { "name": "alice" }
      }
    ]
  }
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/people/192903210332064256" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "bob" }
    },
    {
      "ref": { "@ref": "classes/people/192903210332065280" },
      "class": { "@ref": "classes/people" },
      "ts": 1520225687236965,
      "data": { "name": "alice" }
    }
  ]
}

The query to load posts from people followed by a particular user is basic. We just created the posts_by_author and followees_by_follower indexes, and can use the existing people_by_name index.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "map": { "lambda": "post", "expr": { "get": { "var": "post" } } },
          "collection": {
            "paginate": {
              "join": {
                "match": { "index": "followees_by_follower" },
                "terms": {
                  "select": "ref",
                  "from": {
                    "get": {
                      "match": { "index": "people_by_name" },
                      "terms": "carol"
                    }
                  }
                }
              },
              "with": { "index": "posts_by_author" }
            }
          }
        }'

client.Query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author"))),
    post => Get(post)));

client.query(
  Map(
    Paginate(
        Join(
          Match(
            Index(Value("followees_by_follower")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("carol"))))),
          Index(Value("posts_by_author")))),
    Lambda(Value("post"), Get(Var("post")))));

client.Query(
    f.Map(
        f.Paginate(
            f.Join(
                f.MatchTerm(
                    f.Index("followees_by_follower"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "carol",
                            ),
                        ),
                    ),
                ),
                f.Index("posts_by_author"),
            ),
        ),
        f.Lambda("post", f.Get(f.Var("post"))),
    ),
)

client.query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author"))),
    Lambda { post => Get(post) }))

client.query(
  q.map_expr(
    lambda post: q.get(post),
    q.paginate(
      q.join(
        q.match(
          q.index("followees_by_follower"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "carol"))
          )
        ),
        q.index("posts_by_author")
      )
    )
  ))

$client.query do
  map paginate(join match(index('followees_by_follower'),
                 select('ref',
                        get(match(index('people_by_name'), 'carol')))) index('posts_by_author')) do |post|
    get(post)
  end
end

client.query(
    Map(
        collection: Paginate(
            Join(
                Match(
                    index: Index("followees_by_follower"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "carol"
                            )
                        )
                    )
                ),
                with: Index("posts_by_author")
            )
        ),
        to: { post in Get(post) }
    )
)

client.query(
  q.Map(
    q.Paginate(
      q.Join(
        q.Match(
          q.Index("followees_by_follower"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "carol")))),
        q.Index("posts_by_author"))),
    function(post) {
      return q.Get(post);
    }));

=> HTTP/1.1 200 OK
{
  "resource": {
    "data": [
      {
        "ref": { "@ref": "classes/posts/192903209680896512" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "What I had for breakfast ..",
          "author": { "@ref": "classes/people/192903210332065280" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209792046592" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "My dog and other marvels",
          "tags": [ "pet", "cute", "funny" ],
          "author": { "@ref": "classes/people/192903210332065280" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209792047616" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "Considering my ride",
          "tags": [ "philosophy", "travel" ],
          "author": { "@ref": "classes/people/192903210332064256" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209952477696" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "All Aboard",
          "tags": [ "ship", "travel" ],
          "author": { "@ref": "classes/people/192903210332064256" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903222838428160" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699165542,
        "data": {
          "title": "McTaggart's Illusion",
          "author": { "@ref": "classes/people/192903210332065280" },
          "tags": [ "philosophy", "time" ]
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903222900294144" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699224886,
        "data": {
          "title": "Right brain navigation",
          "author": { "@ref": "classes/people/192903210332064256" },
          "tags": [ "travel" ]
        }
      }
    ]
  }
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

This returns every post from every person the current user is following. But we are only really interested in the recent changes. If we use events mode, and provide the timestamp we used earlier, we can look at the activity that we created in this tutorial, from the perspective of each of the users.

Querying with Events

There are three new or updated blog posts since we ran the setup query to add authors to our schema: “McTaggart’s Illusion”, “Right brain navigation”, and “Zeno’s Paradox”, but Carol is not following the author of “Zeno’s Paradox” so we should expect to see “McTaggart’s Illusion” and “Right brain navigation” in the result of our next query.

It is the same as the previous query but with the events option, and After set to the time of the beginning of this tutorial. The raw events are interesting, but you’ll probably want to Map over the results in some way. In this case we get the latest version of each blog post.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "map": {
            "lambda": "event",
            "expr": {
              "get": { "select": "resource", "from": { "var": "event" } }
            }
          },
          "collection": {
            "paginate": {
              "join": {
                "match": { "index": "followees_by_follower" },
                "terms": {
                  "select": "ref",
                  "from": {
                    "get": {
                      "match": { "index": "people_by_name" },
                      "terms": "carol"
                    }
                  }
                }
              },
              "with": { "index": "posts_by_author" }
            },
            "after": 1520225699165542,
            "events": true
          }
        }'

client.Query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author")),
      after: 1520225699165542,
      events: true),
    event => Get(Select("resource", event))));

client.query(
  Map(
    Paginate(
        Join(
          Match(
            Index(Value("followees_by_follower")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("carol"))))),
          Index(Value("posts_by_author"))))
      .after(Value(1520225699165542))
      .events(Value(true)),
    Lambda(
      Value("event"),
      Get(Select(Value("resource"), Var("event"))))));

client.Query(
    f.Map(
        f.Paginate(
            f.Join(
                f.MatchTerm(
                    f.Index("followees_by_follower"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "carol",
                            ),
                        ),
                    ),
                ),
                f.Index("posts_by_author"),
            ),
            f.After(1520225699165542),
            f.Events(true),
        ),
        f.Lambda(
            "event",
            f.Get(f.Select("resource", f.Var("event"))),
        ),
    ),
)

client.query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author")),
      cursor = After(1520225699165542),
      events = true),
    Lambda { event => Get(Select("resource", event)) }))

client.query(
  q.map_expr(
    lambda event: q.get(q.select("resource", event)),
    q.paginate(
      q.join(
        q.match(
          q.index("followees_by_follower"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "carol"))
          )
        ),
        q.index("posts_by_author")
      ),
      after=1520225699165542,
      events=True
    )
  ))

$client.query do
  map paginate(join match(index('followees_by_follower'),
                 select('ref',
                        get(match(index('people_by_name'), 'carol')))) index('posts_by_author'),
           after: 1520225699165542,
           events: true) do |event|
    get(select('resource', event))
  end
end

client.query(
    Map(
        collection: Paginate(
            Join(
                Match(
                    index: Index("followees_by_follower"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "carol"
                            )
                        )
                    )
                ),
                with: Index("posts_by_author")
            ),
            after: 1520225699165542,
            events: true
        ),
        to: { event in
            Get(Select(path: "resource", from: event))
        }
    )
)

client.query(
  q.Map(
    q.Paginate(
      q.Join(
        q.Match(
          q.Index("followees_by_follower"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "carol")))),
        q.Index("posts_by_author")),
      { after: 1520225699165542, events: true }),
    function(event) {
      return q.Get(q.Select("resource", event));
    }));

=> HTTP/1.1 200 OK
{
  "resource": {
    "before": { "ts": 1520225699165542, "action": "create" },
    "data": [
      {
        "ref": { "@ref": "classes/posts/192903222838428160" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699165542,
        "data": {
          "title": "McTaggart's Illusion",
          "author": { "@ref": "classes/people/192903210332065280" },
          "tags": [ "philosophy", "time" ]
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903222900294144" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699224886,
        "data": {
          "title": "Right brain navigation",
          "author": { "@ref": "classes/people/192903210332064256" },
          "tags": [ "travel" ]
        }
      }
    ]
  }
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903222838428160" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699165542,
      "data": {
        "title": "McTaggart's Illusion",
        "author": { "@ref": "classes/people/192903210332065280" },
        "tags": [ "philosophy", "time" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903222900294144" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699224886,
      "data": {
        "title": "Right brain navigation",
        "author": { "@ref": "classes/people/192903210332064256" },
        "tags": [ "travel" ]
      }
    }
  ]
}

Here it is with Before, which shows the blog posts older than “McTaggart’s Illusion”.


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "map": {
            "lambda": "event",
            "expr": {
              "get": { "select": "resource", "from": { "var": "event" } }
            }
          },
          "collection": {
            "paginate": {
              "join": {
                "match": { "index": "followees_by_follower" },
                "terms": {
                  "select": "ref",
                  "from": {
                    "get": {
                      "match": { "index": "people_by_name" },
                      "terms": "carol"
                    }
                  }
                }
              },
              "with": { "index": "posts_by_author" }
            },
            "before": 1520225699165542,
            "events": true
          }
        }'

client.Query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author")),
      before: 1520225699165542,
      events: true),
    event => Get(Select("resource", event))));

client.query(
  Map(
    Paginate(
        Join(
          Match(
            Index(Value("followees_by_follower")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("carol"))))),
          Index(Value("posts_by_author"))))
      .before(Value(1520225699165542))
      .events(Value(true)),
    Lambda(
      Value("event"),
      Get(Select(Value("resource"), Var("event"))))));

client.Query(
    f.Map(
        f.Paginate(
            f.Join(
                f.MatchTerm(
                    f.Index("followees_by_follower"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "carol",
                            ),
                        ),
                    ),
                ),
                f.Index("posts_by_author"),
            ),
            f.Before(1520225699165542),
            f.Events(true),
        ),
        f.Lambda(
            "event",
            f.Get(f.Select("resource", f.Var("event"))),
        ),
    ),
)

client.query(
  Map(
    Paginate(
      Join(
        Match(
          Index("followees_by_follower"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "carol")))),
        Index("posts_by_author")),
      cursor = Before(1520225699165542),
      events = true),
    Lambda { event => Get(Select("resource", event)) }))

client.query(
  q.map_expr(
    lambda event: q.get(q.select("resource", event)),
    q.paginate(
      q.join(
        q.match(
          q.index("followees_by_follower"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "carol"))
          )
        ),
        q.index("posts_by_author")
      ),
      before=1520225699165542,
      events=True
    )
  ))

$client.query do
  map paginate(join match(index('followees_by_follower'),
                 select('ref',
                        get(match(index('people_by_name'), 'carol')))) index('posts_by_author'),
           before: 1520225699165542,
           events: true) do |event|
    get(select('resource', event))
  end
end

client.query(
    Map(
        collection: Paginate(
            Join(
                Match(
                    index: Index("followees_by_follower"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "carol"
                            )
                        )
                    )
                ),
                with: Index("posts_by_author")
            ),
            before: 1520225699165542,
            events: true
        ),
        to: { event in
            Get(Select(path: "resource", from: event))
        }
    )
)

client.query(
  q.Map(
    q.Paginate(
      q.Join(
        q.Match(
          q.Index("followees_by_follower"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "carol")))),
        q.Index("posts_by_author")),
      { before: 1520225699165542, events: true }),
    function(event) {
      return q.Get(q.Select("resource", event));
    }));

=> HTTP/1.1 200 OK
{
  "resource": {
    "after": { "ts": 1520225699165542, "action": "create" },
    "data": [
      {
        "ref": { "@ref": "classes/posts/192903209680896512" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "What I had for breakfast ..",
          "author": { "@ref": "classes/people/192903210332065280" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209792046592" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "My dog and other marvels",
          "tags": [ "pet", "cute", "funny" ],
          "author": { "@ref": "classes/people/192903210332065280" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209792047616" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "Considering my ride",
          "tags": [ "philosophy", "travel" ],
          "author": { "@ref": "classes/people/192903210332064256" }
        }
      },
      {
        "ref": { "@ref": "classes/posts/192903209952477696" },
        "class": { "@ref": "classes/posts" },
        "ts": 1520225699095165,
        "data": {
          "title": "All Aboard",
          "tags": [ "ship", "travel" ],
          "author": { "@ref": "classes/people/192903210332064256" }
        }
      }
    ]
  }
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/192903209680896512" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "What I had for breakfast ..",
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792046592" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "My dog and other marvels",
        "tags": [ "pet", "cute", "funny" ],
        "author": { "@ref": "classes/people/192903210332065280" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209792047616" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    },
    {
      "ref": { "@ref": "classes/posts/192903209952477696" },
      "class": { "@ref": "classes/posts" },
      "ts": 1520225699095165,
      "data": {
        "title": "All Aboard",
        "tags": [ "ship", "travel" ],
        "author": { "@ref": "classes/people/192903210332064256" }
      }
    }
  ]
}

We’ve been using MapF to fetch the corresponding version of the post for each event, but if you leave off the mapping stage you can see the rest of the underlying event metadata. See the timeline tutorial for more event details, and examples of event pagination.

Here are the raw events:


curl https://db.fauna.com/ \
    -u fnACrVRybLACAOytRXDMleFgdUZKXcJfMdzyjsRq: \
    -d '{
          "paginate": {
            "join": {
              "match": { "index": "followees_by_follower" },
              "terms": {
                "select": "ref",
                "from": {
                  "get": {
                    "match": { "index": "people_by_name" },
                    "terms": "carol"
                  }
                }
              }
            },
            "with": { "index": "posts_by_author" }
          },
          "before": 1520225699165542,
          "events": true
        }'

client.Query(
  Paginate(
    Join(
      Match(
        Index("followees_by_follower"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "carol")))),
      Index("posts_by_author")),
    before: 1520225699165542,
    events: true));

client.query(
  Paginate(
      Join(
        Match(
          Index(Value("followees_by_follower")),
          Select(
            Value("ref"),
            Get(
              Match(
                Index(Value("people_by_name")),
                Value("carol"))))),
        Index(Value("posts_by_author"))))
    .before(Value(1520225699165542))
    .events(Value(true)));

client.Query(
    f.Paginate(
        f.Join(
            f.MatchTerm(
                f.Index("followees_by_follower"),
                f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(
                            f.Index("people_by_name"),
                            "carol",
                        ),
                    ),
                ),
            ),
            f.Index("posts_by_author"),
        ),
        f.Before(1520225699165542),
        f.Events(true),
    ),
)

client.query(
  Paginate(
    Join(
      Match(
        Index("followees_by_follower"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "carol")))),
      Index("posts_by_author")),
    cursor = Before(1520225699165542),
    events = true))

client.query(
  q.paginate(
    q.join(
      q.match(
        q.index("followees_by_follower"),
        q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "carol"))
        )
      ),
      q.index("posts_by_author")
    ),
    before=1520225699165542,
    events=True
  ))

$client.query do
  paginate join match(index('followees_by_follower'),
                 select('ref',
                        get(match(index('people_by_name'), 'carol')))) index('posts_by_author'),
           before: 1520225699165542,
           events: true
end

client.query(
    Paginate(
        Join(
            Match(
                index: Index("followees_by_follower"),
                terms: Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "carol"
                        )
                    )
                )
            ),
            with: Index("posts_by_author")
        ),
        before: 1520225699165542,
        events: true
    )
)

client.query(
  q.Paginate(
    q.Join(
      q.Match(
        q.Index("followees_by_follower"),
        q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "carol")))),
      q.Index("posts_by_author")),
    { before: 1520225699165542, events: true }));

=> HTTP/1.1 200 OK
{
  "resource": {
    "after": { "ts": 1520225699165542, "action": "create" },
    "data": [
      {
        "ts": 1520225699095165,
        "action": "create",
        "resource": { "@ref": "classes/posts/192903209680896512" }
      },
      {
        "ts": 1520225699095165,
        "action": "create",
        "resource": { "@ref": "classes/posts/192903209792046592" }
      },
      {
        "ts": 1520225699095165,
        "action": "create",
        "resource": { "@ref": "classes/posts/192903209792047616" }
      },
      {
        "ts": 1520225699095165,
        "action": "create",
        "resource": { "@ref": "classes/posts/192903209952477696" }
      }
    ]
  }
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

=> {
  "after": { "ts": 1520225699165542, "action": "create" },
  "data": [
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209680896512" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792046592" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209792047616" }
    },
    {
      "ts": 1520225699095165,
      "action": "create",
      "resource": { "@ref": "classes/posts/192903209952477696" }
    }
  ]
}

Further Reading

This tutorial dealt with the simple case of creating a blog and associating some posts with it. The next tutorial looks at authentication.