doido_generators/generators/
model.rs1use crate::generator::{GeneratedFile, Generator};
2use crate::generators::field::Field;
3use crate::generators::migration_support::{
4 create_table_imports, create_table_up, register_migration, MIGRATION_LIB_BASE,
5 MIGRATION_SRC_DIR,
6};
7use crate::generators::{to_pascal, to_snake, to_table_name};
8use chrono::Utc;
9use doido_core::Result;
10
11const MODELS_MOD_BASE: &str = include_str!("../../templates/new/app/models/mod.rs");
13const ENTITIES_MOD_BASE: &str = include_str!("../../templates/new/app/models/_entities/mod.rs");
15const MODELS_MOD_PATH: &str = "app/models/mod.rs";
17const ENTITIES_MOD_PATH: &str = "app/models/_entities/mod.rs";
18
19pub struct ModelGenerator;
20
21impl Generator for ModelGenerator {
22 fn name(&self) -> &str {
23 "model"
24 }
25
26 fn generate(&self, args: &[&str]) -> Result<Vec<GeneratedFile>> {
27 let name = args.first().copied().ok_or_else(|| {
28 doido_core::anyhow::anyhow!("model generator requires a name argument")
29 })?;
30 let snake = to_snake(name);
31 let model_name = to_pascal(name);
32 let table_name = to_table_name(name);
35
36 let fields = Field::parse_all(&args[1..])?;
38
39 let entity = crate::templates::get("models/entity.rs.template")
40 .replace("{table_name}", &table_name)
41 .replace("{fields}", &model_fields(&fields));
42
43 let extension = crate::templates::get("models/model.rs.template")
44 .replace("{Model}", &model_name)
45 .replace("{table_name}", &table_name);
46
47 let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
49 let migration_module = format!("m{timestamp}_create_{table_name}_table");
50 let migration = crate::templates::get("models/migration.rs.template")
51 .replace("{migration_name}", &migration_module)
52 .replace("{migration_imports}", &create_table_imports(&fields))
53 .replace("{up_body}", &create_table_up(&table_name, &fields))
54 .replace("{table_name}", &table_name);
55
56 let lib_path = format!("{MIGRATION_SRC_DIR}/lib.rs");
59 let existing =
60 std::fs::read_to_string(&lib_path).unwrap_or_else(|_| MIGRATION_LIB_BASE.to_string());
61 let lib = register_migration(&existing, &migration_module);
62
63 let models_mod_existing = std::fs::read_to_string(MODELS_MOD_PATH)
66 .unwrap_or_else(|_| MODELS_MOD_BASE.to_string());
67 let models_mod = register_model_module(&models_mod_existing, &snake);
68
69 let entities_mod_existing = std::fs::read_to_string(ENTITIES_MOD_PATH)
70 .unwrap_or_else(|_| ENTITIES_MOD_BASE.to_string());
71 let entities_mod = register_entity_module(&entities_mod_existing, &table_name);
72
73 let model_test = crate::templates::get("models/model_test.rs.template")
76 .replace("{Model}", &model_name)
77 .replace("{singular}", &snake);
78
79 Ok(vec![
80 GeneratedFile {
81 path: format!("app/models/_entities/{table_name}.rs"),
82 content: entity,
83 },
84 GeneratedFile {
85 path: format!("app/models/{snake}.rs"),
86 content: extension,
87 },
88 GeneratedFile {
89 path: ENTITIES_MOD_PATH.to_string(),
90 content: entities_mod,
91 },
92 GeneratedFile {
93 path: format!("{MIGRATION_SRC_DIR}/{migration_module}.rs"),
94 content: migration,
95 },
96 GeneratedFile {
97 path: lib_path,
98 content: lib,
99 },
100 GeneratedFile {
101 path: MODELS_MOD_PATH.to_string(),
102 content: models_mod,
103 },
104 GeneratedFile {
105 path: format!("tests/{snake}_model_test.rs"),
106 content: model_test,
107 },
108 ])
109 }
110}
111
112fn model_fields(fields: &[Field]) -> String {
115 fields
116 .iter()
117 .map(|f| format!(" {}\n", f.model_field()))
118 .collect()
119}
120
121fn register_model_module(models_mod: &str, module: &str) -> String {
125 doido_model::entities::register_model_module(models_mod, module)
126}
127
128fn register_entity_module(entities_mod: &str, module: &str) -> String {
130 doido_model::entities::register_entity_module(entities_mod, module)
131}