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
You can learn the basics of the ODB plugin by following the Getting started guide.

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.

You can list, add or remove available generators using the 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:

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:

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

3.4 Many to many