createQuietlyRelation<TRelated extends Model<TRelated>> method

Future<TRelated> createQuietlyRelation<TRelated extends Model<TRelated>>(
  1. String relationName,
  2. Object attributes
)
inherited

Creates a related model without firing model events.

Same as createRelation but bypasses model event dispatching. No creating/created/saving/saved events are fired during insertion.

The attributes parameter accepts:

  • A tracked model instance (TRelated).
  • An InsertDto or UpdateDto instance.
  • A Map<String, Object?> containing field/column values.

Example:

final author = await Author.query().find(1);
final post = await author.createQuietlyRelation<Post>('posts', {
  'title': 'New Post',
  'published_at': DateTime.now(),
});

// Using a DTO:
final post2 = await author.createQuietlyRelation<Post>('posts',
  PostInsertDto(title: 'DTO Post', publishedAt: DateTime.now()),
);

Implementation

Future<TRelated> createQuietlyRelation<TRelated extends Model<TRelated>>(
  String relationName,
  Object attributes,
) async {
  final def = expectDefinition();
  final resolver = _resolveResolverFor(def);

  final relationDef = def.relations.cast<RelationDefinition?>().firstWhere(
    (r) => r?.name == relationName,
    orElse: () => null,
  );

  if (relationDef == null) {
    throw ArgumentError(
      'Relation "$relationName" not found on ${def.modelName}',
    );
  }

  if (relationDef.kind != RelationKind.hasOne &&
      relationDef.kind != RelationKind.hasMany) {
    throw ArgumentError(
      'createQuietlyRelation() can only be used with hasOne or hasMany relations. '
      'Relation "$relationName" is ${relationDef.kind}',
    );
  }

  // Get this model's local key value
  final localKey = relationDef.localKey ?? 'id';
  final localKeyValue = _getAttributeValue(localKey, def);
  if (localKeyValue == null) {
    throw StateError(
      'Model ${def.modelName} local key "$localKey" value is null',
    );
  }

  // Add foreign key to attributes
  final foreignKey = relationDef.foreignKey;
  final context = _requireQueryContext(resolver);
  final relatedDef =
      resolver.registry.expectByName(relationDef.targetModel)
          as ModelDefinition<TRelated>;
  final fkField = relatedDef.fields.firstWhere(
    (f) => f.columnName == foreignKey || f.name == foreignKey,
  );

  // Normalize the input to a map using MutationInputHelper
  final helper = MutationInputHelper<TRelated>(
    definition: relatedDef,
    codecs: context.codecRegistry,
  );
  final normalizedMap = helper.insertInputToMap(
    attributes,
    applySentinelFiltering: attributes is TRelated,
  );
  final attributesWithFk = {
    ...normalizedMap,
    fkField.columnName: localKeyValue,
  };

  // Create the related model using query with events suppressed
  final relatedQuery = Query<TRelated>(
    definition: relatedDef,
    context: context,
  );

  final results = await relatedQuery.withoutEvents().insertManyInputs([
    attributesWithFk,
  ]);
  final created = results.first;

  // Update relation cache
  if (relationDef.kind == RelationKind.hasOne) {
    _asRelations.setRelation(relationName, created);
  } else {
    final existing = _asRelations.getRelation<List<TRelated>>(relationName);
    if (existing != null) {
      _asRelations.setRelation(relationName, [...existing, created]);
    } else {
      _asRelations.setRelation(relationName, [created]);
    }
  }

  return created;
}