1. Introduction
Most tutorials use SQL databases, but the Crails Framework also supports other databases, such as MongoDB. In this tutorial, we'll see:
- how to install MongoDB support for the Crails Framework
- how to create and setup an application with a MongoDB database,
- how to perform basic queries
This tutorial assumes that you already have a MongoDB server running on your machine.
1.1 Installation
MongoDB support can optionally be installed by Crails' installation script, assuming that you
already have the the MongoDB C++ driver installed on your
system.
If you didn't install it then, or didn't use the installation script at all, here's how to
install libcrails-mongodb
and its dependencies on your system:
1.1.1 mongocxx
We will first need to install the MongoDB C++ driver.
Go to the installation manual
and follow the instructions.
1.1.2 libcrails-mongodb
Next we will install libcrails-mongodb
, using build2:
bpkg create -d crails-mongodb-build-gcc cc config.cxx=g++
cd crails-mongodb-build-gcc
bpkg add 'https://github.com/crails-framework/libcrails-mongodb.git#master'
bpkg fetch
bpkg build libcrails-mongodb '?sys:libmongocxx/*' '?sys:libcrails-databases/*' '?sys:libcrails/*'
bpkg install --all --recursive config.install.root=/usr/local config.install.sudo=sudo
Note that the bpkg build
command imports several packages from your system:
libmongocxx, libcrails-databases and libcrails. These package should already
be installed on your system.
And that's it, you're all set for the next step.
2. Enabling MongoDB
2.1 Creating an application with a MongoDB database
Creating an application from scratch with mongodb support is pretty simple. Just run the following command:
$ crails new --name blog --database mongodb --formats html
Your new application has been created in the blog directory.
2.2 Adding a MongoDB database to an existing application
If you want to add mongodb support to an already existing application, go to the application's folder and run the following command:
$ crails plugins mongodb install
2.3 MongoDB database settings
Like with all database supported by Crails, the connection details
are specified in the config/databases.cpp
file. Let's
see how it looks like after having installed MongoDB support:
config/databases.cpp
#include <crails/databases.hpp>
using namespace Crails;
using namespace std;
const Databases::Settings Databases::settings = {
{
Production, {
}
},
{
Development, {
{"mongo",{{"name", "blog"},{"port", static_cast<unsigned int>(27017)}}}
}
},
{
Test, {
}
}
};
Right now, we only have one database configuration, internally named mongo, which a MongoDB will store in a database named blog. This configuration is only valid in the "Production" environment: let's see how we could setup MongoDB servers for other environments:
config/databases.cpp
#include <crails/databases.hpp>
using namespace Crails;
using namespace std;
const Databases::Settings Databases::settings = {
{
Production, {
{"mongo",{{"name", "blog"},{"host", "mongo-server.org"},{"port", static_cast<unsigned int>(27017)}}}
}
},
{
Development, {
{"mongo",{{"name", "blog_dev"},{"port", static_cast<unsigned int>(27017)}}}
}
},
{
Test, {
{"mongo",{{"name", "blog_test"},{"port", static_cast<unsigned int>(27017)}}}
}
}
};
mongo is the default internal name for MongoDB databases: you should not change it unless your application needs to work with multiple MongoDB databases.
3. Controllers & Models
3.1 Scaffolding
If mongodb is the only database backend enabled in your application, then the scaffolding commands will generate your controllers and models with MongoDB support in mind.
Let's generate a whole set of controller, model and views with the following command:
$ crails scaffold resource \ --model article \ --format html \ --property std::string/title std::string/author std::string/content
3.2 Models
Let's take a look at the generated MongoDB model:
app/models/article.hpp
#pragma once
#include <crails/mongodb/model.hpp>
class Article : public Crails::MongoDB::ModelInterface
{
public:
static const std::string collection_name;
static const std::string scope;
static const std::string plural_scope;
static const std::string view;
void set_id(const std::string& value) { id = value; }
std::string get_id() const override { return id; }
std::string to_json() const override;
void edit(Data) override;
void merge_data(Data) const;
void set_author(const std::string& value) { this->author = value; }
const std::string& get_author() const { return author; }
void set_content(const std::string& value) { this->content = value; }
const std::string& get_content() const { return content; }
void set_title(const std::string& value) { this->title = value; }
const std::string& get_title() const { return title; }
private:
std::string id;
std::string author;
std::string content;
std::string title;
};
First thing to note is that the model inherits Crails::MongoDB::ModelInterface. This interface helps other Crails MongoDB related tools to persist and load your models from the results of requests made through mongocxx's API.
Next thing to notice are the global constants defined by this class:
- collection_name is the name of the collection in which the models will be stored
- scope will be used as key when packing or unpacking a model from json or form-data queries
- plural_scope will designate an array of this type when rendering json
- view should point to the path of the default view for this object
Lastly, there are two methods that you should keep track of, as you'll need to update them whenver you add or remove properties to the model:
void edit(Data) override;
void merge_data(Data) const;
The edit method updates the model from the content of a Data object.
The merge_data does the opposite, putting the content of a model into a Data object.
Let's take a look at their implementation:
app/models/article.cpp
#include "article.hpp"
const std::string Article::collection_name = "Article";
const std::string Article::scope = "article";
const std::string Article::plural_scope = "articles";
const std::string Article::view = "";
void Article::edit(Data params)
{
if (params["author"].exists())
set_author(params["author"]);
if (params["content"].exists())
set_content(params["content"]);
if (params["title"].exists())
set_title(params["title"]);
}
void Article::merge_data(Data out) const
{
out["author"] = this->author;
out["content"] = this->content;
out["title"] = this->title;
}
std::string Article::to_json() const
{
DataTree out;
merge_data(out);
return out.to_json();
}
3.3 Controllers
Let's now take a look at our controller:
app/controllers/article.cpp
#pragma once
#include "app/controllers/application.hpp"
#include "app/models/article.hpp"
#include <crails/mongodb/controller.hpp>
class Article;
class ArticleController : public Crails::MongoDB::Controller<ApplicationController>
{
public:
ArticleController(Crails::Context&);
void initialize() override;
void finalize() override;
void index();
void show();
void create();
void update();
void destroy();
void new_();
void edit();
protected:
std::shared_ptr<Article> find_model(std::string id);
void require_model(std::string id);
void find_list();
std::shared_ptr<Article> model;
std::vector<Article> model_list;
};
Our controller inherits from
Crails::MongoDB::Controller: this allows you to interact with your MongoDB databases simply by using a
Crails::MongoDB::Connection object always available from your controller, under the property name database.
On top of it, mongodb related operation will be timed, and time spent on these operations will be reported in the application logs after each query.
3.1 Querying for models
Our controller uses the method find_list
to figure out what the article
index should contain. In this chapter, we'll learn how to make more advanced queries
by adding options to our article index. Let's first see what the method looks like:
app/controllers/article.cpp
void ArticleController::find_list()
{
Crails::MongoDB::Result<Article> results;
database.find(results);
model_list = results.to_vector();
std::cout << model_list.size() << std::endl;
vars["models"] = &model_list;
}
We first create a
Crails::MongoDB::Result object, before calling database.find(results)
to fetch all the objects from the article collection.
These objects are then converted to the Article model type and stored into the model_list
vector. Lastly, the vector is made available to the views through the models shared variable.
Let's try to add an option to search among articles. We'll be using libmongocxx document builder to create a query that checks the articles' title attribute against a regular expression:
app/controllers/article.cpp
void ArticleController::find_list()
{
Crails::MongoDB::Result<Article> results;
std::string search_query = params["query"].defaults_to<std::string>("");
bsoncxx::document::view index_query{};
if (search_query.length() > 0)
{
index_query = bsoncxx::builder::stream::document{}
<< "title"
<< bsoncxx::builder::stream::open_document
<< "$regex" << search_query
<< bsoncxx::builder::stream::close_document
<< bsoncxx::builder::stream::finalize;
}
database.find(results, index_query);
model_list = results.to_vector();
std::cout << model_list.size() << std::endl;
vars["models"] = &model_list;
}
This example show you that database.find(results)
can
also take a query as an optional second argument. This type of this
second parameter is bsoncxx::document::view_or_value
,
meaning you are allowed to provide either
bsoncxx::document::view
or
bsoncxx::document::value
.
In this example, we built a bsoncxx::document::view
using bsoncxx's stream-based document builder. You can
learn more about how to provide bson documents from
mongocxx "Working with BSON" manual.