Implementation of the Active Record pattern with some specialties from the Dart language.
In order to use ActiveRecord, you have to subclass the Collection
class.
Variables of the Collection are defined by overriding the get variables
method.
You can override this method in a variety of styles: For instance, if you simply want
to define a String variable (like e.g. name), the result of get variables
should
be `["name"]. If you want to specify type of the variable as well as constraints or
validations, you've got two options: You can either hand over a list, with
the name as first, the type as second, constraints and validations as fourth and fifth
argument, or hand over a Variable object.
Example of List approach:
get variables => [["name", "Integer"]];
Currently supported types are String, Integer, Double, Bool, Text and Datetime.
Another important point is overriding the get adapter
method. If you don't
override this method, the collection will use the defaultAdapter
as adapter,
which is by default null. If the defaultAdapter is null and it is the first time
the defaultAdapter is looked up, ActiveRecord will search the directory from where
you ran the current script for a database.yaml file and use the adapter with
the specified environment (default: development, you can change this by setting
the top level environment
variable in ActiveRecord). If it can't find a
database.yaml file there, it will also lookup the parent directory for
that file. If it still can't find a file there, it will set the defaultAdapter
to null. An example of a specification of a postgres adapter can be found in
test/database.yaml. Adapters are available
via the ActiveMigration library,
which also features generation and execution of Migration files. Contribution
of further adapters as well as contribution to this repository are appreciated.
Creation of Collection instances can be done via the get nu
method of the
Collection class. This immediatly returns a Model instance. You can then
Manipulate this Model until you want to save it.
To save it, call the save
method on the Model instance. This returns a
future containing the saved Model.
For finding, there are four methods of each Collection instance:
This returns a Future containing the Model with the specified id. Only a single Model instance is returned.
Returns all Models of the Collection. You may specify limit and/or offset, if you don't want all or just models after a specific number of models.
This executes the given sql command on the underlying adapter (doesn't have to be sql, but in the initial versithreeon this was the name). Be sure to put the search parameters into the params List because then the adapter can do prepared statements.
This is the adapter independent way of finding variables. Simply specify a map of variable names and the corresponding values, which will then be returned.
Destroying is very simple: Get a Model which has been persisted via the
various find methods and call the destroy
method on it. This returns
a Future with a boolean indicating the success of the destroy operation.
In order to provide a consistent persistence, there are two ways of providing checks to the given variables: Validations and Constraints. Constraints are checks which run on the adapters, Validations are checks which run inside ActiveRecord. This results in Validations being always available and Constraints being available only on some Adapters.
Methods, which are available on Model instances can be specified inside the collection. A Model method has a Model as the first parameter. So, a Model method could look like this:
void say(Model m, String text) {
print(m["name"] + " wants to say $text");
}
Relations can be specified by overriding the appropriate methods. Those methods are:
- hasMany
- belongsTo
Relations can be specified in two ways: One way is to directly specify the type of the other collection and the other way is to specify a Relation object.
Example:
List get belongsTo => [AnotherCollection];
To get related Models of a Model instance, simply call a method with
the lowercase name of the related collection. If it is a "hasMany" relation,
add an "s". Also, if it is a "hasMany" relation, you can create Model instances
of the related Collection by calling nu
on the result of this method.
Example:
// Receiving models
mymodel.anothercollections.get.then((anotherCollectionModels) => ...);
// Creating models
var newRelatedModel = mymodel.anothercollections.nu;
Some methods on models should be triggered in specific states of the Lifecycle of a Model. For this, there are Lifecycle methods. Current Lifecycle methods are:
- beforeCreate(Model m)
- afterCreate(Model m)
- beforeUpdate(Model m)
- afterUpdate(Model m)
- beforeDestroy(Model m)
- afterDestroy(Model m)