syncWithoutDetaching method

Future<OrmMigrationRecord> syncWithoutDetaching(
  1. String relationName,
  2. List ids, {
  3. Map<String, dynamic>? pivotData,
})
inherited

Syncs a manyToMany relationship without detaching existing records.

Unlike sync, this method only attaches new IDs while keeping existing pivot records intact. Useful when you want to add new relations without removing existing ones.

Example:

final post = await Post.query().find(1);
// Currently has tags: [1, 2]
await post.syncWithoutDetaching('tags', [2, 3, 4]);
// Now has tags: [1, 2, 3, 4] (1 kept, 2 kept, 3 and 4 added)

Implementation

Future<TModel> syncWithoutDetaching(
  String relationName,
  List<dynamic> ids, {
  Map<String, dynamic>? pivotData,
}) 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(
      'syncWithoutDetaching() can only be used with manyToMany relations. '
      'Relation "$relationName" is ${relationDef.kind}',
    );
  }

  if (ids.isEmpty) {
    return _self();
  }

  // Get existing attached IDs
  final existingIds = await _getPivotRelatedIds(relationDef, def, resolver);

  // Filter out IDs that already exist
  final newIds = ids.where((id) => !existingIds.contains(id)).toList();

  // Only attach the new ones
  if (newIds.isNotEmpty) {
    await attach(relationName, newIds, pivotData: pivotData);
  } else {
    // Reload relation to sync cache even if nothing changed
    await load(relationName);
  }

  return _self();
}