updateFields method

Future<void> updateFields(
  1. String featureName,
  2. String modelName, {
  3. List<String> addFields = const [],
  4. List<String> removeFields = const [],
  5. bool dryRun = false,
})

Updates an existing model by adding and/or removing fields, then re-rendering the file from the model_with_fields template.

Removals are applied before additions, so a field can be replaced by combining --remove-field name --add-field name:newType. The test file is left untouched.

Implementation

Future<void> updateFields(
  String featureName,
  String modelName, {
  List<String> addFields = const [],
  List<String> removeFields = const [],
  bool dryRun = false,
}) async {
  if (featureName.isEmpty || modelName.isEmpty) {
    throw Exception('Feature name and model name are required.');
  }
  if (!isSnakeCase(featureName) || !isSnakeCase(modelName)) {
    throw Exception('Names must be in snake_case.');
  }
  if (addFields.isEmpty && removeFields.isEmpty) {
    throw Exception(
      'Nothing to update: specify --add-field or --remove-field.',
    );
  }

  final addedFields = parseModelFields(addFields);
  final removeNames = removeFields.toSet();

  final featureDir = getFeatureDir(featureName);
  final modelsDir = p.join(featureDir, 'data', 'models');
  final targetPath = p.join(modelsDir, '${modelName}_model.dart');
  final modelFile = File(targetPath);
  if (!modelFile.existsSync()) {
    throw Exception(
      'Model "$modelName" not found at $targetPath. '
      'Create it first with: rekeens g model $featureName $modelName',
    );
  }

  final existingFields = parseModelFileSource(
    modelFile.readAsStringSync(),
  ).fields;

  for (final name in removeNames) {
    if (!existingFields.any((f) => f.name == name)) {
      final available = existingFields.map((f) => f.name).join(', ');
      throw Exception(
        'Field "$name" not found in model "$modelName". '
        'Existing fields: $available',
      );
    }
  }
  final updatedFields = existingFields
      .where((f) => !removeNames.contains(f.name))
      .toList();
  for (final field in addedFields) {
    if (updatedFields.any((f) => f.name == field.name)) {
      throw Exception(
        'Field "${field.name}" already exists in model "$modelName". '
        'Remove it first or choose another name.',
      );
    }
    updatedFields.add(field);
  }
  if (updatedFields.isEmpty) {
    throw Exception(
      'Model "$modelName" must keep at least one field. '
      'Cannot remove all fields.',
    );
  }

  if (dryRun) {
    logDryRun('update model "$modelName"', targetPath);
    for (final name in removeNames) {
      logger.warn('DRY RUN: would remove field "$name"');
    }
    for (final field in addedFields) {
      logger.warn(
        'DRY RUN: would add field "${field.name}:${field.dartType}"',
      );
    }
    return;
  }

  final className = toPascalCase(modelName);
  final imports = _collectImports(updatedFields);
  await copyTemplate(
    templateSubPath: 'model_with_fields',
    targetDir: modelsDir,
    variables: {'model_name': modelName, 'class_name': className},
    lists: {
      'fields': _buildFieldVariables(updatedFields),
      if (imports.isNotEmpty) 'imports': imports,
    },
  );

  logger.success('Model "$modelName" updated in feature "$featureName".');
}