saveRelation<TRelated extends Model<TRelated>> method

Future<TRelated> saveRelation<TRelated extends Model<TRelated>>(
  1. String relationName,
  2. TRelated related
)
inherited

Saves a related model through a hasOne or hasMany relationship.

This sets the foreign key on the related model to point to this model, then persists the related model.

Example:

final author = await Author.query().find(1);
final post = Post(title: 'New Post', publishedAt: DateTime.now());
await author.saveRelation('posts', post);
// post.authorId is now author.id

Implementation

Future<TRelated> saveRelation<TRelated extends Model<TRelated>>(
  String relationName,
  TRelated related,
) 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(
      'saveRelation() can only be used with hasOne or hasMany relations. '
      'Relation "$relationName" is ${relationDef.kind}',
    );
  }

  // Get this model's primary key value (the local key for hasOne/hasMany)
  // Default to 'id' if localKey is not specified
  final localKey = relationDef.localKey ?? 'id';
  final localKeyValue = _getAttributeValue(localKey, def);
  if (localKeyValue == null) {
    throw StateError(
      'Model ${def.modelName} local key "$localKey" value is null',
    );
  }

  // Get related model definition
  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,
  );

  // Convert related model to map and add/override foreign key
  final relatedMap = relatedDef.toMap(
    related,
    registry: context.codecRegistry,
  );
  relatedMap[fkField.columnName] = localKeyValue;

  // Use upsert to save (handles both insert and update cases)
  final repo = context.repository<TRelated>();
  final saved = await repo.upsert(relatedMap);

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

  return saved;
}