1. Introduction
MetaRecord is a code generation tool using a Domain Self Language to help you define models and some of their behaviours, such as validation or representation.
MetaRecord provides a set of generators, which uses the models you defined in its DSL to generate code for several targets. Targets can be your own crails application, but may also include aurelia.js, comet.cpp, or Qt. Meaning you will be able to generate code for server and web, mobile or desktop clients.
1.1 Preparing your environment
MetaRecord is developed in Ruby. If you don't already have Ruby installed on your computer, use the following install guide.
You will then need to install MetaRecord itself, with the following command:
$ gem install meta-record
You may need administrator rights to perform that last command.
We also need to install a database backend that MetaRecord will generate code for. In this tutorial, we will use the ODB plugin, which you can add to your application with the following command:
$ crails plugins odb install
1.2 Adding the metarecord plugin
Use the following command to add the metarecord plugin to your crails application:
$ crails plugins metarecord install
The plugin installer will check your current configuration and pick the generators that are relevant to you. For instance, if you have the comet plugin installed, the comet generators will be enabled.
list
, add
and remove
sub-commands with the
$ crails plugins metarecord generators [sub-command]
command.
2. Creating models
Now that the metarecord plugin is installed, the scaffold
command will generate models accordingly.
Let's check it out with the following command:
$ crails scaffold model -m Article -p std::string/title std::string/content
Now that the metarecord plugin is installed, this command will create the following files:
File | Purpose |
---|---|
app/data/article.rb | description of your model fields and relationships |
app/models/article.hpp | your customizable model header |
app/models/article.cpp | model implementation |
Let's take a look at the data file for our new model:
app/data/article.rb
Model.add 'Article', hpp: 'app/models/article.hpp' do
resource_name 'article'
#order_by 'property'
visibility :protected
property 'std::string', 'title'
property 'std::string', 'content'
end
Let's see what each of these lines mean:
Model.add
is used to register a new model to MetaRecord. Every model
added in a data file will have an interface generated in your project's lib
directory, at a path relative to the data's filepath. In this example, it will be
lib/app/data/article.hpp
, and the interface's name will be MetaRecord::Article
.
resource_name
is used to identify the model type as a string. It can be used to find the
views related to the model, or to define a scope when rendering the model as JSON.
order_by
defines the default property to used to sort your result when making queries.
visibility
gives you the opportunity to define whether the properties you are about to
define should be defined as public
, protected
or private
. Note
that this only applies to the properties themselves, as public getters and setters will be generated
anyway.
property
defines new properties. The first argument is the C++ typename for your property:
note that it must be a type that ODB knows how to serialize (we'll cover this later). The second argument
is the C++ name for your property.
2.1 SQL properties
2.2 Default values
You may also use the default option to set a default value for a property:
app/data/article.rb
Model.add 'Article', hpp: 'app/models/article.hpp' do
resource_name 'article'
#order_by 'property'
visibility :protected
property 'std::string', 'title', default: "Untitled"
property 'std::string', 'content'
property 'unsigned int', 'view_count', default: 0
end
2.3 Validations
You may define a set of validations for your properties using the validate option. This
allows you to verify a couple of simple restrictions from your application code using the
bool is_valid()
method defined for each of your models.
2.3.1 Min/Max
2.3.3 Required
2.3.4 Uniqueness
2.3.5 Validating and reporting errors
Once you have defined validations for your model, you can use the is_valid method to check whether a model should be created or updated:
app/controllers/article.hpp
class ArticleController : public ApplicationController
{
public:
ArticleController(Crails::Context& context) : ApplicationController(context)
{
}
void create()
{
Article article;
article.edit(params["article"]);
if (article.is_valid())
{
database.save();
redirect_to("/articles/" + std::to_string(article.get_id()));
}
else
repsond_with(422);
}
};
Validation errors are stored in the errors property.
TODO
3. Relationships
Relationships bind a model to one or several other models. Not only will they help you query objects easily, they're very useful to help you fetch related objects in batch, rather than one by one. This is paramount to optimizing your application, limiting potential N+1 queries.
3.1 One to one
Whenver you want to associate a model with another model, you will need to use the has_one
relationship. Let's imagine we want to add an author to our articles, we'd create the author model first:
$ crails scaffold model -m Author -p std::string/name
Now that the Author model exists, let's add a reference to the article's author in the Article model:
app/data/article.rb
add_include 'app/models/author.hpp'
Model.add 'Article', hpp: 'app/models/article.hpp' do
resource_name 'article'
#order_by 'property'
visibility :protected
property 'std::string', 'title'
property 'std::string', 'content'
has_one 'Author', 'author'
end
To create this reference to the author model, we've added two lines to our article model description:
The first line calls the add_include
method: when referencing a model type from a model
description file, you need to use add_include
to register the header defining the final type of
your model. In this case, it'll be app/models/author.hpp
.
The second line is the has_one
call: the first parameter is the type name for the model
you're referencing, and the second one is the name you'll give to the relationship.
has_one
will generate the following methods:
- get_author_id
- get_author
- set_author(shared_ptr<Author>)
3.2 One to many
Let's say we now want to add comments to our article: there will be several comment for a single article. Let's create the comment model first:
$ crails scaffold model -m Comment -p std::string/author std::string/content
And we'll now register comments as a new relationship for the article model, used with has_many
:
app/data/article.rb
add_include 'app/models/author.hpp'
add_include 'app/models/comment.hpp'
Model.add 'Article', hpp: 'app/models/article.hpp' do
resource_name 'article'
#order_by 'property'
visibility :protected
property 'std::string', 'title'
property 'std::string', 'content'
has_one 'Author', 'author'
has_many 'Comment, 'comments'
end
This is very similar to the has_one
relationship: we've referenced the include file for our
comment model in our file's global scope, then registered a has_many
relationship in the model's
scope. The following methods will be generated for the Article model:
- get_comments_ids()
- get_comments()
- add_comment(shared_ptr<Comment>)
- remove_comment(const Comment&)
3.3 Joined relationships
Up until now, we've used relationships without specifying any option. By default, relationship are joined, meaning the content of a relationship will be automatically fetched whenever the related model gets loaded using a JOIN query.
Sometimes, you may not want to enable that behavior by default. The join option can be used to that end:
app/data/article.rb
add_include 'app/models/author.hpp'
add_include 'app/models/comment.hpp'
Model.add 'Article', hpp: 'app/models/article.hpp' do
resource_name 'article'
#order_by 'property'
visibility :protected
property 'std::string', 'title'
property 'std::string', 'content'
has_one 'Author', 'author', join: false
has_many 'Comment, 'comments', join: false
end