attach method

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

Attaches related models in a manyToMany relationship.

Inserts new pivot table records to establish the many-to-many relationship. Optionally accepts pivot data for additional columns on the pivot table.

After attaching, the relation is reloaded to sync the cache.

Example:

final post = await Post.query().find(1);
await post.attach('tags', [1, 2, 3]);
// Pivot records created for tag IDs 1, 2, 3

// With pivot data:
await post.attach('tags', [4], pivotData: {'order': 1});

Implementation

Future<TModel> attach(
  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(
      'attach() can only be used with manyToMany relations. '
      'Relation "$relationName" is ${relationDef.kind}',
    );
  }

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

  // 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');
  }

  // Build pivot table rows
  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',
    );
  }

  final rows = ids.map((id) {
    final row = <String, dynamic>{
      pivotForeignKey: pkValue,
      pivotRelatedKey: id,
    };
    if (pivotData != null) {
      row.addAll(pivotData);
    }
    return row;
  }).toList();

  // Build pivot table definition with proper column types
  final pivotDef = _createPivotDefinition(pivotTable, def.schema, {
    pivotForeignKey: pk,
    pivotRelatedKey: relatedPk,
    ...?pivotData?.map((key, _) => MapEntry(key, null)),
  });

  final plan = MutationPlan.insert(definition: pivotDef, rows: rows);

  await resolver.runMutation(plan);

  // Reload the relation to sync cache
  await load(relationName);

  return _self();
}