In order to get and store data with MongoDB, ReactiveMongo provides an extensible mechanism to appropriately read and write data from/to BSON.
This makes usage of MongoDB much less verbose and more natural.
Getting values follows the same principle using getAsTry(String) method. This method is parametrized with a type that can be transformed into a BSONValue using a BSONReader instance that is implicitly available in the scope (again, the default readers are already imported if you imported reactivemongo.api.bson._.) If the value could not be found, or if the reader could not deserialize it (often because the type did not match), None will be returned.
In order to read values of custom types, a custom instance of BSONReader, or of BSONDocumentReader, must be resolved (in the implicit scope).
A BSONReader for a custom value class:
Once a custom BSONReader (or BSONDocumentReader) is defined, it can thus be used in aDocument.getAsTry[MyValueType]("docProperty").
A BSONDocumentReader for a custom case class:
Once a custom BSONDocumentReader can be resolved, it can be used when working with a query result.
The A type parameter (e.g. with A being Person, Macros.reader[Person]) defines a type for a case class, or for a sealed trait with subclasses.
This type will be the basis for the auto-generated implementation.
Some other types with matching apply-unapply might work but behaviour is undefined. Since macros will match the apply-unapply pair you are free to overload these methods in the companion object.
Case class mapping:
For the case classes, the fields get mapped into BSON properties with respective names, and BSON handlers are pulled from implicit scope to (de)serialize them (in the previous Person example, the handlers for String are resolved for the name property).
So in order to use custom types as properties in case classes, the appropriate handlers are in scope.
For example if you have case class Foo(bar: Bar) and want to create a handler for it is enough to put an implicit handler for Bar in it’s companion object. That handler might itself be macro generated, or written by hand.
The macros are currently limited to case classes whose constructor doesn’t take more than 22 parameters (due to Scala not generating apply and unapply in the other cases).
The default values for the class properties can be used by BSON reader when the corresponding BSON value is missing, with MacroOptions.ReadDefaultValues.
Sealed trait and union types:
Sealed traits are also supported as union types, with each of their subclasses considered as a disjoint case.
These ‘Opts’ suffixed macros can be used to explicitly define the UnionType.
As for the UnionType definition, Foo \/ Bar \/ Baz is interpreted as type Foo or type Bar or type Baz. The option AutomaticMaterialization is used there to automatically try to materialize the handlers for the sub-types (disabled by default).
The other options available to configure the typeclasses generation at compile time are the following.
Verbose: Print out generated code during compilation.
SaveClassName: Indicate to the BSONWriter to add a “className” field in the written document along with the other properties. The value for this meta field is the fully qualified name of the class. This is the default behaviour when the target type is a sealed trait (the “className” field is used as discriminator).
Some annotations are also available to configure the macros.
The @Key annotation allows to specify the field name for a class property.
For example, it is convenient to use when you’d like to leverage the MongoDB _id index but you don’t want to actually use _id in your code.
The @Ignore can be applied on the class properties to be ignored.
The @Flatten can be used to indicate to the macros that the representation of a property must be flatten rather than a nested document.
The @DefaultValue can be used with MacroOptions.ReadDefaultValues to specify a default value only used when reading from BSON.
The @Reader allows to indicate a specific BSON reader that must be used for a property, instead of resolving such reader from the implicit scope.
In a similar way, the @Writer allows to indicate a specific BSON writer that must be used for a property, instead of resolving such writer from the implicit scope.
The mapped type can also be defined inside other classes, objects or traits but not inside functions (known macro limitation). In order to work you should have the case class in scope (where you call the macro), so you can refer to it by it’s short name - without package. This is necessary because the generated implementations refer to it by the short name to support nested declarations. You can work around this with local imports.
The following handlers are provided by ReactiveMongo, to read and write the BSON values.
An optional value can be added to a document using the Option type (e.g. for an optional string, Option[String]).
Using BSONBooleanLike, it is possible to read the following BSON values as boolean.
true if > 0
true if > 0
Using BSONNumberLike, it is possible to read the following BSON values as number.