detach method
Detaches related models in a manyToMany relationship.
Deletes pivot table records. If no IDs are provided, detaches all.
After detaching, the relation is reloaded to sync the cache.
Example:
final post = await Post.query().find(1);
await post.detach('tags', [1, 2]); // Detach specific tags
await post.detach('tags'); // Detach all tags
Implementation
Future<TModel> detach(String relationName, [List<dynamic>? ids]) 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.manyToMany) {
throw ArgumentError(
'detach() can only be used with manyToMany relations. '
'Relation "$relationName" is ${relationDef.kind}',
);
}
// Get this model's primary key value
final pk = def.primaryKeyField;
if (pk == null) {
throw StateError('Model ${def.modelName} must have a primary key');
}
final pkValue = _primaryKeyValue(def);
if (pkValue == null) {
throw StateError('Model ${def.modelName} primary key value is null');
}
final pivotTable = relationDef.through;
if (pivotTable == null) {
throw StateError(
'Relation "$relationName" is missing pivot table name (through)',
);
}
final pivotForeignKey = relationDef.pivotForeignKey!;
final pivotRelatedKey = relationDef.pivotRelatedKey!;
// Get the related model definition to determine column types
final relatedModelName = relationDef.targetModel;
final relatedDef = resolver.registry.expectByName(relatedModelName);
final relatedPk = relatedDef.primaryKeyField;
if (relatedPk == null) {
throw StateError(
'Related model $relatedModelName must have a primary key',
);
}
// Build delete keys
final List<Map<String, Object?>> deleteKeys;
if (ids != null && ids.isNotEmpty) {
// Detach specific IDs
deleteKeys = ids
.map((id) => {pivotForeignKey: pkValue, pivotRelatedKey: id})
.toList();
} else {
// Detach all - query for existing pivot records first
final pivotDef = _createPivotDefinition(pivotTable, def.schema, {
pivotForeignKey: pk,
pivotRelatedKey: relatedPk,
});
final selectPlan = QueryPlan(
definition: pivotDef,
filters: [
FilterClause(
field: pivotForeignKey,
operator: FilterOperator.equals,
value: pkValue,
),
],
);
final results = await resolver.runSelect(selectPlan);
deleteKeys = results
.map(
(row) => {
pivotForeignKey: row[pivotForeignKey],
pivotRelatedKey: row[pivotRelatedKey],
},
)
.toList();
}
if (deleteKeys.isNotEmpty) {
final pivotDef = _createPivotDefinition(pivotTable, def.schema, {
pivotForeignKey: pk,
pivotRelatedKey: relatedPk,
});
final deleteRows = deleteKeys
.map((keys) => MutationRow(values: const {}, keys: keys))
.toList();
final plan = MutationPlan.delete(definition: pivotDef, rows: deleteRows);
await resolver.runMutation(plan);
}
// Reload the relation to sync cache
await load(relationName);
return _self();
}