sea_orm_codegen/entity/writer/
dense.rs

1use super::*;
2use crate::{Relation, RelationType};
3use heck::ToSnakeCase;
4
5impl EntityWriter {
6    #[allow(clippy::too_many_arguments)]
7    pub fn gen_dense_code_blocks(
8        entity: &Entity,
9        with_serde: &WithSerde,
10        date_time_crate: &DateTimeCrate,
11        schema_name: &Option<String>,
12        serde_skip_deserializing_primary_key: bool,
13        serde_skip_hidden_column: bool,
14        model_extra_derives: &TokenStream,
15        model_extra_attributes: &TokenStream,
16        _column_extra_derives: &TokenStream,
17        _seaography: bool,
18        impl_active_model_behavior: bool,
19    ) -> Vec<TokenStream> {
20        let mut imports = Self::gen_import(with_serde);
21        imports.extend(Self::gen_import_active_enum(entity));
22        let mut code_blocks = vec![
23            imports,
24            Self::gen_dense_model_struct(
25                entity,
26                with_serde,
27                date_time_crate,
28                schema_name,
29                serde_skip_deserializing_primary_key,
30                serde_skip_hidden_column,
31                model_extra_derives,
32                model_extra_attributes,
33            ),
34        ];
35        if impl_active_model_behavior {
36            code_blocks.push(Self::impl_active_model_behavior());
37        }
38        code_blocks
39    }
40
41    #[allow(clippy::too_many_arguments)]
42    pub fn gen_dense_model_struct(
43        entity: &Entity,
44        with_serde: &WithSerde,
45        date_time_crate: &DateTimeCrate,
46        schema_name: &Option<String>,
47        serde_skip_deserializing_primary_key: bool,
48        serde_skip_hidden_column: bool,
49        model_extra_derives: &TokenStream,
50        model_extra_attributes: &TokenStream,
51    ) -> TokenStream {
52        let table_name = entity.table_name.as_str();
53        let column_names_snake_case = entity.get_column_names_snake_case();
54        let column_rs_types = entity.get_column_rs_types(date_time_crate);
55        let if_eq_needed = entity.get_eq_needed();
56        let primary_keys: Vec<String> = entity
57            .primary_keys
58            .iter()
59            .map(|pk| pk.name.clone())
60            .collect();
61        let attrs: Vec<TokenStream> = entity
62            .columns
63            .iter()
64            .map(|col| {
65                let mut attrs: Punctuated<_, Comma> = Punctuated::new();
66                let is_primary_key = primary_keys.contains(&col.name);
67                if !col.is_snake_case_name() {
68                    let column_name = &col.name;
69                    attrs.push(quote! { column_name = #column_name });
70                }
71                if is_primary_key {
72                    attrs.push(quote! { primary_key });
73                    if !col.auto_increment {
74                        attrs.push(quote! { auto_increment = false });
75                    }
76                }
77                if let Some(ts) = col.get_col_type_attrs() {
78                    attrs.extend([ts]);
79                    if !col.not_null {
80                        attrs.push(quote! { nullable });
81                    }
82                };
83                if col.unique {
84                    attrs.push(quote! { unique });
85                }
86                let mut ts = quote! {};
87                if !attrs.is_empty() {
88                    for (i, attr) in attrs.into_iter().enumerate() {
89                        if i > 0 {
90                            ts = quote! { #ts, };
91                        }
92                        ts = quote! { #ts #attr };
93                    }
94                    ts = quote! { #[sea_orm(#ts)] };
95                }
96                let serde_attribute = col.get_serde_attribute(
97                    is_primary_key,
98                    serde_skip_deserializing_primary_key,
99                    serde_skip_hidden_column,
100                );
101                ts = quote! {
102                    #ts
103                    #serde_attribute
104                };
105                ts
106            })
107            .collect();
108        let schema_name = match Self::gen_schema_name(schema_name) {
109            Some(schema_name) => quote! {
110                schema_name = #schema_name,
111            },
112            None => quote! {},
113        };
114        let extra_derive = with_serde.extra_derive();
115
116        let mut compound_objects: Punctuated<_, Comma> = Punctuated::new();
117
118        let map_col = |a: &syn::Ident| -> String {
119            let a = a.to_string();
120            let b = a.to_snake_case();
121            if a != b.to_upper_camel_case() {
122                // if roundtrip fails, use original
123                a
124            } else {
125                b
126            }
127        };
128        let map_punctuated = |punctuated: Vec<String>| -> String {
129            let len = punctuated.len();
130            let punctuated = punctuated.join(", ");
131            match len {
132                0..=1 => punctuated,
133                _ => format!("({punctuated})"),
134            }
135        };
136
137        let via_entities = entity.get_conjunct_relations_via_snake_case();
138        for rel in entity.relations.iter() {
139            if !rel.self_referencing && rel.impl_related {
140                let (rel_type, sea_orm_attr) = match rel.rel_type {
141                    RelationType::HasOne => (format_ident!("HasOne"), quote!(#[sea_orm(has_one)])),
142                    RelationType::HasMany => {
143                        (format_ident!("HasMany"), quote!(#[sea_orm(has_many)]))
144                    }
145                    RelationType::BelongsTo => {
146                        let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated);
147                        let on_update = if let Some(action) = &rel.on_update {
148                            let action = Relation::get_foreign_key_action(action);
149                            quote!(, on_update = #action)
150                        } else {
151                            quote!()
152                        };
153                        let on_delete = if let Some(action) = &rel.on_delete {
154                            let action = Relation::get_foreign_key_action(action);
155                            quote!(, on_delete = #action)
156                        } else {
157                            quote!()
158                        };
159                        let relation_enum = if rel.num_suffix > 0 {
160                            let relation_enum = rel.get_enum_name().to_string();
161                            quote!(relation_enum = #relation_enum,)
162                        } else {
163                            quote!()
164                        };
165                        (
166                            format_ident!("HasOne"),
167                            quote!(#[sea_orm(belongs_to, #relation_enum from = #from, to = #to #on_update #on_delete)]),
168                        )
169                    }
170                };
171
172                if let Some(to_entity) = rel.get_module_name() {
173                    if !via_entities.contains(&to_entity) {
174                        // skip junctions
175                        let field = if matches!(rel.rel_type, RelationType::HasMany) {
176                            format_ident!(
177                                "{}",
178                                pluralizer::pluralize(&to_entity.to_string(), 2, false)
179                            )
180                        } else {
181                            to_entity.clone()
182                        };
183                        let field = if rel.num_suffix == 0 {
184                            field
185                        } else {
186                            format_ident!("{field}_{}", rel.num_suffix)
187                        };
188                        compound_objects.push(quote! {
189                            #sea_orm_attr
190                            pub #field: #rel_type<super::#to_entity::Entity>
191                        });
192                    }
193                }
194            } else if rel.self_referencing {
195                let (from, to) = rel.get_src_ref_columns(map_col, map_col, map_punctuated);
196                let on_update = if let Some(action) = &rel.on_update {
197                    let action = Relation::get_foreign_key_action(action);
198                    quote!(, on_update = #action)
199                } else {
200                    quote!()
201                };
202                let on_delete = if let Some(action) = &rel.on_delete {
203                    let action = Relation::get_foreign_key_action(action);
204                    quote!(, on_delete = #action)
205                } else {
206                    quote!()
207                };
208                let relation_enum = rel.get_enum_name().to_string();
209                let field = format_ident!(
210                    "{}{}",
211                    entity.get_table_name_snake_case_ident(),
212                    if rel.num_suffix > 0 {
213                        format!("_{}", rel.num_suffix)
214                    } else {
215                        "".into()
216                    }
217                );
218
219                compound_objects.push(quote! {
220                    #[sea_orm(self_ref, relation_enum = #relation_enum, from = #from, to = #to #on_update #on_delete)]
221                    pub #field: HasOne<Entity>
222                });
223            }
224        }
225        for (to_entity, via_entity) in entity
226            .get_conjunct_relations_to_snake_case()
227            .into_iter()
228            .zip(via_entities)
229        {
230            let field = format_ident!(
231                "{}",
232                pluralizer::pluralize(&to_entity.to_string(), 2, false)
233            );
234            let via_entity = via_entity.to_string();
235            compound_objects.push(quote! {
236                #[sea_orm(has_many, via = #via_entity)]
237                pub #field: HasMany<super::#to_entity::Entity>
238            });
239        }
240
241        if !compound_objects.is_empty() {
242            compound_objects.push_punct(<syn::Token![,]>::default());
243        }
244
245        quote! {
246            #[sea_orm::model]
247            #[derive(Clone, Debug, PartialEq, DeriveEntityModel #if_eq_needed #extra_derive #model_extra_derives)]
248            #[sea_orm(
249                #schema_name
250                table_name = #table_name
251            )]
252            #model_extra_attributes
253            pub struct Model {
254                #(
255                    #attrs
256                    pub #column_names_snake_case: #column_rs_types,
257                )*
258                #compound_objects
259            }
260        }
261    }
262
263    #[allow(dead_code)]
264    fn gen_dense_related_entity(entity: &Entity) -> TokenStream {
265        let via_entities = entity.get_conjunct_relations_via_snake_case();
266
267        let related_modules = entity.get_related_entity_modules();
268        let related_attrs = entity.get_related_entity_attrs();
269        let related_enum_names = entity.get_related_entity_enum_name();
270
271        let items = related_modules
272            .into_iter()
273            .zip(related_attrs)
274            .zip(related_enum_names)
275            .filter_map(|((related_module, related_attr), related_enum_name)| {
276                if !via_entities.contains(&related_module) {
277                    // skip junctions
278                    Some(quote!(#related_attr #related_enum_name))
279                } else {
280                    None
281                }
282            })
283            .collect::<Vec<_>>();
284
285        quote! {
286            #[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
287            pub enum RelatedEntity {
288                #(#items),*
289            }
290        }
291    }
292}
293
294#[cfg(test)]
295mod test {
296    #[test]
297    #[ignore]
298    fn test_name() {
299        panic!("{}", pluralizer::pluralize("filling", 2, false));
300    }
301}