redstone_mapper is a set of utilities for handling common tasks in web applications, including:
Encoding and decoding of objects and data validation can also be used on the client side. redstone_mapper provides a pub transformer that prevents dart2js from generating a bloated javascript file.
Example: Using redstone_mapper with Redstone.dart
import 'package:redstone/redstone.dart' as app;
import 'package:redstone_mapper/mapper.dart';
import 'package:redstone_mapper/plugin.dart';
main() {
//When using redstone_mapper as a Redstone.dart plugin,
//you can use the @Decode and @Encode annotations.
app.addPlugin(getMapperPlugin());
app.setupConsoleLog();
app.start();
}
class User {
//The @Field annotation is used to specify
//the fields that can be serialized.
@Field()
String username;
@Field()
String password;
}
//The @Decode annotation specifies that a parameter value
//must be decoded from the request. By default, it will expect
//that the request has a JSON body.
@app.Route('/services/users/add', methods: const[app.POST])
addUser(@Decode() User user) {
// ...
}
//The @Encode annotation specifies that a route
//response can be encoded to JSON.
@app.Route('/services/users/list')
@Encode()
List<User> listUsers() {
// ...
}
To properly encode and decode an object, its class must have every serializable member
annotated with @Field
.
class User {
@Field()
String username;
@Field()
String password;
}
It’s important to always define the type of the class member, so it can be properly encoded and decoded. If the field is a List or a Map, be sure to specify its parameters. Example:
class User {
@Field()
String name;
@Field()
List<Address> adresses;
}
class Address {
@Field()
String description;
@Field()
int number;
}
However, it’s not recommended to use other classes that have type parameters, since it’s not guaranteed that it will be properly encoded and decoded.
It’s also possible to annotate getters and setters:
class User {
String _name;
@Field()
String get name() => _name;
@Field()
set name(String value) => _name = value;
}
When encoding or decoding an object to JSON, you can use the view
parameter
to map a class member to its corresponding JSON field:
class User {
@Field(view: "user_name")
String name;
@Field()
String pass;
}
Also, if you are encoding or decoding an object to the database, you can use the model
parameter to map a class member to its corresponding database field:
class User {
//JSON: 'user_name' DATABASE: 'USERNAME'
@Field(view: "user_name", model: "USERNAME")
String name;
//JSON: 'pass' DATABASE: 'PASSWORD'
@Field(model: "PASSWORD")
String pass;
}
Besides, every class that can be encoded or decoded must provide a default constructor, with no required arguments.
The Validator
class provides a simple and flexible way to build a set of validation rules.
var userValidator = new Validator()
..add("username", const NotEmpty())
..add("password", const Range(min: 6. required: true));
// ...
Map user = {"username": "user", "password": "pass"};
ValidationError err = userValidator.execute(user);
if (err != null) {
// ...
}
To validate objects, you must provide the target class to the constructor. Also,
you must annotate with @Field
all members that can be validated.
class User {
@Field()
String username;
@Field()
String password;
}
var userValidator = new Validator(User)
..add("username", const NotEmpty())
..add("password", const Range(min: 6. required: true));
// ...
var user = new User()
..username = "user"
..password = "pass";
ValidationError err = userValidator.execute(user);
if (err != null) {
// ...
}
Alternatively, you can set the rules directly in the class.
class User {
@Field()
@NotEmpty()
String username;
@Field()
@Range(min: 6, required: true)
String password;
}
var userValidator = new Validator(User, true);
You can also inherit from the Schema
class, which will provide a Validator
for you.
class User extends Schema {
@Field()
@NotEmpty()
String username;
@Field()
@Range(min: 6, required: true)
String password;
}
...
User user = new User()
..username = "user"
..password = "pass";
var err = user.validate();
if (err != null) {
// ...
}
redstone_mapper
already provides the following basic rules, that you can use
to build a Validator
:
NotEmpty
:
Range
:
Matches
:
OnlyNumbers
:
You can easily build new rules by just inheriting from the ValidationRule
class.
To install redstone_mapper as a Redstone.dart plugin, you just have to import plugin.dart
and
call getMapperPlugin()
:
import 'package:redstone/redstone.dart' as app;
import 'package:redstone_mapper/plugin.dart';
import 'package:redstone/redstone.dart' as app;
import 'package:redstone_mapper/mapper.dart';
import 'package:redstone_mapper/plugin.dart';
main() {
//When using redstone_mapper as a Redstone.dart plugin,
//you can use the @Decode and @Encode annotations.
app.addPlugin(getMapperPlugin());
app.setupConsoleLog();
app.start();
}
Also, if getMapperPlugin()
receives an instance of DatabaseManager
, then the plugin will manage
the database connections for you. For more information, see one of the redstone_mapper extensions, such as
redstone_mapper_pg or
redstone_mapper_mongo.
To use with other server-side frameworks, or on the client side, you just have to import mapper_factory.dart
and call bootstrapMapper()
from the main()
function:
import 'package:redstone/redstone.dart' as app;
import 'package:redstone_mapper/mapper_factory.dart';
main() {
bootstrapMapper();
// ...
}
To encode and decode objects, you can use the encode()
and decode()
top level function from mapper.dart
:
import 'dart:convert';
import 'package:redstone_mapper/mapper.dart';
class User {
@Field()
String username;
@Field()
String password;
}
User user = new User()
..username = "user"
..password = "pass";
String userJson = JSON.encode(encode(user));
When using on the client side, be sure to set redstone_mapper’s transformer to your pubspec.yaml file, so dart2js won’t generate a bloated javascript file:
name: my_app
version: 0.1.0
dependencies:
redstone: any
redstone_mapper: any
transformers:
- redstone_mapper
Polymer applications usually doesn’t have an entry-point (a dart script with the main
function), so
you have to provide one. Also, the entry-point has to import all libraries that contains encodable classes,
so the transformer will be able to map them. You can see a working example which uses
polymer and redstone_mapper here.
redstone_mapper provides integration with database drivers through extensions. Currently, the following extensions are available:
Note that redstone_mapper doesn’t aim to be a full ORM/ODM framework. It just provides some helper functions to easily encode and decode objects to the database. It won’t generate database queries, neither hide the default driver API from you. That means you can use the redstone_mapper functions only when it’s useful for you, and ignore it when it’s just an extra overhead.
Dart already has support for several databases, including: MongoDb, Redis, CouchDb, MySql, PostgreSql, and so on. I’ll try to provide new extensions over time, but if you are interested, you can help me on this task.
Building a redstone_mapper extension is really easy, and you can start by taking a look at the source code of redstone_mapper_pg and redstone_mapper_mongo. If you are willing to build a extension, please let me know :)