Modddels
HomeGithubPub.dev
v0.2
v0.2
  • Motivation
  • Setup
  • Generalities
    • Modddels Overview
    • Validations & Validation steps
    • Structure of a Modddel
    • Member parameters & Dependency parameters
    • Usage
      • Data equality & toString
      • Pattern matching
      • Reading the fields
      • Reading the failures
      • CopyWith
  • ValueObjects
    • ValueObjects Overview
    • Creating a ValueObject
  • Entities
    • Entities Overview
    • ContentValidation & ContentFailure
    • ValidationSteps in Entities
    • SimpleEntity
      • Creating a SimpleEntity
      • Special Cases
    • IterableEntity & Iterable2Entity
      • IterableEntity / Iterable2Entity kinds
      • The Type Template
      • Creating an IterableEntity / Iterable2Entity
      • InvalidMembers Description
      • Special Cases
      • Creating your own IterableEntity / Iterable2Entity kind
  • Advanced Notions
    • The NullFailure Annotation
    • Using multiple parameters annotations
    • Class Hierarchy of ValueObjects
    • Class Hierarchy of Entities
    • Models that are always valid / invalid
  • Union of Modddels
    • Union of Modddels Overview
    • Basic Usage
    • Case-modddels pattern matching
    • Default Factory Constructor
    • Shared Properties
    • The Validate Methods
    • CopyWith
  • Unit-Testing Of Modddels
    • Unit-Testing Overview
    • Available Tests
    • Customizing Tests
  • Additional Information
    • Using type aliases
    • Re-running the generator
    • All Available Modddels
    • VsCode Snippets
Powered by GitBook
On this page
  • 1. Imports and Part statements
  • 2. The @Modddel Annotation
  • 3. The class declaration
  • 4. The private empty constructor
  • 5. The factory constructor
  • Decorators and comments
  • Asserts & Sanitization
  • 6. The validate methods
  • 7. The Failure(s)
  1. Generalities

Structure of a Modddel

A Modddel typically looks like this :

// 1. Imports and Part statements
import 'package:modddels_annotation_fpdart/modddels_annotation_fpdart.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'age.modddel.dart';
part 'age.freezed.dart';

// 2. The `@Modddel` Annotation
@Modddel(
  validationSteps: [
    ValidationStep([
      Validation('legal', FailureType<AgeLegalFailure>()),
    ], name: 'Value'),
  ],
)
// 3. The class declaration
class Age extends SingleValueObject<InvalidAge, ValidAge> with _$Age {
  // 4. The private empty constructor
  Age._();

  // 5. The factory constructor
  factory Age(int value) {
    return _$Age._create(
      value: value,
    );
  }

  // 6. The validate methods
  @override
  Option<AgeLegalFailure> validateLegal(age) {
    if (age.value < 18) {
      return some(const AgeLegalFailure.minor());
    }
    return none();
  }
}

// 7. The Failure(s)
@freezed
class AgeLegalFailure extends ValueFailure with _$AgeLegalFailure{
  const factory AgeLegalFailure.minor() = _Minor;
}

1. Imports and Part statements

If you're using freezed for making the failures sealed classes, also import it and add the part statement filename.freezed.dart, where filename is the name of the file.

2. The @Modddel Annotation

The @Modddel annotation is where you can define the validationSteps of your modddel.

Example :

@Modddel(
  validationSteps: [
    ValidationStep([
      Validation('size', FailureType<UsernameSizeFailure>()),
      Validation('characters', FailureType<UsernameCharactersFailure>()),
    ], name: 'Form'),
    ValidationStep([
      Validation('reserved', FailureType<UsernameReservedFailure>()),
    ], name: 'Availability'),
  ],
)

As you can see, inside the validationSteps parameter, you provide a list of ValidationSteps in the order that you want them to be processed.

For each ValidationStep, you provide its name, as well as the list of validations it contains (the order doesn't matter here). The name is mainly used to make the name of the invalid-step union-case class name (Ex : 'Form' → 'InvalidUsernameForm'). It must begin with an uppercase letter, and it must be made of valid dart identifier characters (alphanumeric, underscore and dollar sign).

For each Validation, you provide its name, as well as the failureType. The name is used to make the name of the validation method (Ex : 'size' → 'validateSize'), as well as the name of the failure (Ex : 'size' → 'sizeFailure'). It must begin with a lowercase letter, and it must be made of valid dart identifier characters. The failureType is the type of the failure of the validation. You can either provide it as a typeArg : FailureType<UsernameSizeFailure>(), or you can provide it as a string : FailureType('UsernameSizeFailure') (the latter takes precedence).

It is recommended to provide the Failure class as a typeArg, so that you avoid typos. However, if the Failure class is not available during the code generation, you'll have to provide it as a string. This situation can occur when your failure class is generated by a builder (other than freezed) which runs after the modddels generator.

3. The class declaration

When declaring your modddel class, you should extend the appropriate modddel kind, such as SingleValueObject, MultiValueObject, SimpleEntity ... , and provide two type arguments. The first type argument should be 'Invalid' followed by the modddel name, and the second type argument should be 'Valid' followed by the modddel name.

Your modddel class should also mix in a mixin with the name of your modddel prefixed by _$ (Like freezed).

class Age extends SingleValueObject<InvalidAge, ValidAge> with _$Age {
  // ...

4. The private empty constructor

The modddel should only contain one generative constructor, which should be private and empty. Example : Age._(). You should never use this constructor to create an instance of the Modddel.

5. The factory constructor

The parameters of the factory constructor will be the list of properties the modddel contains. Parameters can be positional or named, optional or required, and can have default values.

The factory constructor should always return the result of calling the mixin's _create static method. This method is generated by the modddels generator, and you should pass to it all the parameters of the factory.

Example :

factory FullName(
    String firstName, {
    required String lastName,
}) {
    // Notice how all the parameters of the `_create` method are named.
    // This is so that you don't mix-up the order of the arguments.
    return _$FullName._create(
      firstName: firstName,
      lastName: lastName,
    );
}

Decorators and comments

You can document/decorate a field by documenting/decorating its parameter inside the factory constructor.

Example : Documenting firstName and annotating lastName with @Deprecated :

factory FullName(
    /// The firstName of the user.
    ///
    /// This will be visible to other users.
    String firstName, {
    @Deprecated('Will be removed') required String lastName,
  })

Asserts & Sanitization

The factory constructor allows you to do all sorts of data manipulation or sanitization before effectively creating the modddel.

Example :

factory FullName(
    String firstName, {
    required String lastName,
}) {
    assert(firstName.isNotEmpty && lastName.isNotEmpty);

    final sanitizedFirstName = firstName.trim();
    final sanitizedLastName = lastName.trim();

    return _$FullName._create(
      firstName: sanitizedFirstName,
      lastName: sanitizedLastName,
    );
}

6. The validate methods

After running the generator, your IDE will prompt you to override the "validate" method(s) :

This is the place where you can implement your validation logic. For example :

@override
Option<AgeLegalFailure> validateLegal(_ValidateAgeLegal age) {
    if (age.value < 18) {
      return some(const AgeLegalFailure.minor());
    }
    return none();
}

The age parameter contains all the fields of the Modddel. You can omit its type for a cleaner look :

@override
Option<AgeLegalFailure> validateLegal(age) {
    ...
}

You should NEVER access an instance member (property, method, getter...) from within the "validate" methods. Doing so will throw a runtime error.

@override
Option<AgeLegalFailure> validateLegal(age) {
    // Bad, will throw a runtime error.
    final val = this.value;
    // Good
    final val = age.value;
}

7. The Failure(s)

In the example, the Failure is created using freezed.

@freezed
class AgeLegalFailure extends ValueFailure with _$AgeLegalFailure{
  const factory AgeLegalFailure.minor() = _Minor;
}

Note that the failure should always extend ValueFailure if the modddel is a ValueObject, and it should extend EntityFailure if the modddel is an Entity.

The name of the ValueFailure sealed class can be anything, but as a good practice it's better to name it '{ModddelName}{ValidationName}Failure' (Ex: NameLengthFailure).

PreviousValidations & Validation stepsNextMember parameters & Dependency parameters

For the Modddels generator to work, you need to import modddels_annotation_fpdart or modddels_annotation_dartz , and add the part statement filename.modddel.dart, where filename is the name of the file.

The name parameter of the ValidationStep is optional. (See about its default values for ValueObjects, and for Entities).

Your factory constructor should be unnamed unless you want to create a .

union of modddels
this section
(depending on which package you installed)
this section