Skip to content

Commit 30ce2ef

Browse files
committedMar 3, 2025
Add support for removing models in automatic migration generator
Implement the ability to detect when a model has been removed from the codebase and generate appropriate migration operations. This includes: - Add RemoveModel variant to OperationInner enum in migrations.rs - Create RemoveModelBuilder for constructing remove operations - Implement make_remove_model_operation in migration_generator.rs - Update relevant utility functions to handle the new operation type - Add tests for the new functionality Fixes #207
1 parent 098fc5a commit 30ce2ef

File tree

2 files changed

+386
-11
lines changed

2 files changed

+386
-11
lines changed
 

‎cot-cli/src/migration_generator.rs

+158-3
Original file line numberDiff line numberDiff line change
@@ -498,14 +498,18 @@ impl MigrationGenerator {
498498
&format!("Model '{}'", &migration_model.model.name),
499499
);
500500

501-
todo!();
501+
let op = DynOperation::RemoveModel {
502+
table_name: migration_model.model.table_name.clone(),
503+
model_ty: migration_model.model.resolved_ty.clone(),
504+
fields: migration_model.model.fields.clone(),
505+
};
502506

503-
// line below should be removed once todo is implemented
504-
#[allow(unreachable_code)]
505507
print_status_msg(
506508
StatusType::Removed,
507509
&format!("Model '{}'", &migration_model.model.name),
508510
);
511+
512+
op
509513
}
510514

511515
fn generate_migration_file_content(&self, migration: GeneratedMigration) -> String {
@@ -937,6 +941,12 @@ impl GeneratedMigration {
937941
because it doesn't create a new model"
938942
)
939943
}
944+
DynOperation::RemoveModel { .. } => {
945+
unreachable!(
946+
"RemoveModel operation shouldn't be a dependency of CreateModel \
947+
because it doesn't create a new model"
948+
)
949+
}
940950
};
941951
trace!(
942952
"Removing foreign keys from {} to {}",
@@ -965,6 +975,10 @@ impl GeneratedMigration {
965975
// removing it shouldn't ever affect whether a graph is cyclic
966976
unreachable!("AddField operation should never create cycles")
967977
}
978+
DynOperation::RemoveModel { .. } => {
979+
// RemoveModel doesn't create dependencies, it only removes a model
980+
unreachable!("RemoveModel operation should never create cycles")
981+
}
968982
}
969983
}
970984

@@ -1060,6 +1074,10 @@ impl GeneratedMigration {
10601074

10611075
ops
10621076
}
1077+
DynOperation::RemoveModel { .. } => {
1078+
// RemoveModel Doesnt Add Foreign Keys
1079+
Vec::new()
1080+
}
10631081
})
10641082
.collect()
10651083
}
@@ -1198,6 +1216,11 @@ pub enum DynOperation {
11981216
model_ty: syn::Type,
11991217
field: Field,
12001218
},
1219+
RemoveModel {
1220+
table_name: String,
1221+
model_ty: syn::Type,
1222+
fields: Vec<Field>,
1223+
},
12011224
}
12021225

12031226
/// Returns whether given [`Field`] is a foreign key to given type.
@@ -1241,6 +1264,19 @@ impl Repr for DynOperation {
12411264
.build()
12421265
}
12431266
}
1267+
Self::RemoveModel {
1268+
table_name, fields, ..
1269+
} => {
1270+
let fields = fields.iter().map(Repr::repr).collect::<Vec<_>>();
1271+
quote! {
1272+
::cot::db::migrations::Operation::remove_model()
1273+
.table_name(::cot::db::Identifier::new(#table_name))
1274+
.fields(&[
1275+
#(#fields,)*
1276+
])
1277+
.build()
1278+
}
1279+
}
12441280
}
12451281
}
12461282
}
@@ -1728,6 +1764,125 @@ mod tests {
17281764
}
17291765
}
17301766

1767+
#[test]
1768+
fn test_make_remove_model_operation() {
1769+
let migration_model = ModelInSource {
1770+
model_item: parse_quote! {
1771+
struct UserModel {
1772+
#[model(primary_key)]
1773+
id: i32,
1774+
name: String,
1775+
}
1776+
},
1777+
model: Model {
1778+
name: format_ident!("UserModel"),
1779+
vis: syn::Visibility::Inherited,
1780+
original_name: "UserModel".to_string(),
1781+
resolved_ty: parse_quote!(UserModel),
1782+
model_type: Default::default(),
1783+
table_name: "user_model".to_string(),
1784+
pk_field: Field {
1785+
field_name: format_ident!("id"),
1786+
column_name: "id".to_string(),
1787+
ty: parse_quote!(i32),
1788+
auto_value: true,
1789+
primary_key: true,
1790+
unique: false,
1791+
foreign_key: None,
1792+
},
1793+
fields: vec![Field {
1794+
field_name: format_ident!("name"),
1795+
column_name: "name".to_string(),
1796+
ty: parse_quote!(String),
1797+
auto_value: false,
1798+
primary_key: false,
1799+
unique: false,
1800+
foreign_key: None,
1801+
}],
1802+
},
1803+
};
1804+
1805+
let generator = MigrationGenerator::new(
1806+
PathBuf::from("/fake/path/Cargo.toml"),
1807+
"test_crate".to_string(),
1808+
MigrationGeneratorOptions::default(),
1809+
);
1810+
1811+
let operation = generator.make_remove_model_operation(&migration_model);
1812+
1813+
match &operation {
1814+
DynOperation::RemoveModel {
1815+
table_name, fields, ..
1816+
} => {
1817+
assert_eq!(table_name, "user_model");
1818+
assert_eq!(fields.len(), 1);
1819+
assert_eq!(fields[0].column_name, "name");
1820+
}
1821+
_ => panic!("Expected DynOperation::RemoveModel"),
1822+
}
1823+
}
1824+
1825+
#[test]
1826+
fn generate_operations_with_removed_model() {
1827+
let app_models = vec![];
1828+
1829+
let migration_model = ModelInSource {
1830+
model_item: parse_quote! {
1831+
struct UserModel {
1832+
#[model(primary_key)]
1833+
id: i32,
1834+
name: String,
1835+
}
1836+
},
1837+
model: Model {
1838+
name: format_ident!("UserModel"),
1839+
vis: syn::Visibility::Inherited,
1840+
original_name: "UserModel".to_string(),
1841+
resolved_ty: parse_quote!(UserModel),
1842+
model_type: Default::default(),
1843+
table_name: "user_model".to_string(),
1844+
pk_field: Field {
1845+
field_name: format_ident!("id"),
1846+
column_name: "id".to_string(),
1847+
ty: parse_quote!(i32),
1848+
auto_value: true,
1849+
primary_key: true,
1850+
unique: false,
1851+
foreign_key: None,
1852+
},
1853+
fields: vec![Field {
1854+
field_name: format_ident!("name"),
1855+
column_name: "name".to_string(),
1856+
ty: parse_quote!(String),
1857+
auto_value: false,
1858+
primary_key: false,
1859+
unique: false,
1860+
foreign_key: None,
1861+
}],
1862+
},
1863+
};
1864+
1865+
let migration_models = vec![migration_model.clone()];
1866+
1867+
let generator = MigrationGenerator::new(
1868+
PathBuf::from("/fake/path/Cargo.toml"),
1869+
"test_crate".to_string(),
1870+
MigrationGeneratorOptions::default(),
1871+
);
1872+
1873+
let (_modified_models, operations) =
1874+
generator.generate_operations(&app_models, &migration_models);
1875+
1876+
assert_eq!(operations.len(), 1);
1877+
1878+
match &operations[0] {
1879+
DynOperation::RemoveModel { table_name, .. } => {
1880+
assert_eq!(table_name, "user_model");
1881+
}
1882+
_ => panic!("Expected DynOperation::RemoveModel"),
1883+
}
1884+
}
1885+
17311886
#[test]
17321887
fn generate_operations_with_modified_model() {
17331888
let app_model = ModelInSource {

0 commit comments

Comments
 (0)