From 1f533177044e983d55115ca9f1cbf0261ab62f26 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 12 Jan 2016 11:12:00 -0700 Subject: [PATCH] Allow structs to be annotated with `#[table_name="foo"]` This is related to #86, where we are not properly inferring the table name for some structs. While I do want to actually fix some of the cases in that issue, it's also been pointed out that we don't handle any edge cases for pluralization. I might improve pluralization *slightly*, but we're not going to maintain an actual mapping of every word as it's brittle, difficult to maintain, and causes bug fixes to stop people's code from compiling. Regardless of how good our inference is, we should decouple the table name from the struct name. This now allows specifying the table name with an annotation. This does not affect any public API, only associations which I have not made public or documented as they're still very prototypical. It should be noted that this *only* affects other annotations on the same struct. When we're processing an annotation on `Foo`, we don't actually have a way to go look at the annotations on `Bar` (at least not as far as I can tell). At this point in time, we do not directly reference any struct in a way that should be affected by this. `#[belongs_to]` comes close, but the table name is based on the association name, not the foreign struct that we look for. --- diesel_codegen/src/attr.rs | 19 ++++--------------- diesel_codegen/src/lib.rs | 2 ++ diesel_codegen/src/model.rs | 11 +++++++++-- diesel_codegen/src/util.rs | 28 ++++++++++++++++++++++++++++ diesel_tests/tests/associations.rs | 15 +++++++++++++++ diesel_tests/tests/schema.rs | 8 ++++++++ 6 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 diesel_codegen/src/util.rs diff --git a/diesel_codegen/src/attr.rs b/diesel_codegen/src/attr.rs index e2b2a778ffa3..4c7d529650a9 100644 --- a/diesel_codegen/src/attr.rs +++ b/diesel_codegen/src/attr.rs @@ -1,8 +1,8 @@ use syntax::ast; -use syntax::attr::AttrMetaMethods; use syntax::ext::base::ExtCtxt; use syntax::ptr::P; -use syntax::parse::token::str_to_ident; + +use util::str_value_of_attr_with_name; #[derive(Debug, PartialEq, Eq)] pub struct Attr { @@ -14,19 +14,8 @@ pub struct Attr { impl Attr { pub fn from_struct_field(cx: &mut ExtCtxt, field: &ast::StructField) -> Option { let field_name = field.node.ident(); - let column_name = field.node.attrs.iter().filter_map(|attr| { - if attr.check_name("column_name") { - attr.value_str().map(|name| { - str_to_ident(&name) - }).or_else(|| { - cx.span_err(attr.span(), - r#"`column_name` must be in the form `#[column_name="something"]`"#); - None - }) - } else { - None - } - }).nth(0); + let column_name = + str_value_of_attr_with_name(cx, &field.node.attrs, "column_name"); let ty = field.node.ty.clone(); match (column_name, field_name) { diff --git a/diesel_codegen/src/lib.rs b/diesel_codegen/src/lib.rs index 00883fa67b9d..e02358c62544 100644 --- a/diesel_codegen/src/lib.rs +++ b/diesel_codegen/src/lib.rs @@ -24,6 +24,8 @@ include!(concat!(env!("OUT_DIR"), "/lib.rs")); #[cfg(not(feature = "with-syntex"))] include!("lib.in.rs"); +mod util; + #[cfg(feature = "with-syntex")] pub fn register(reg: &mut syntex::Registry) { reg.add_attr("feature(custom_derive)"); diff --git a/diesel_codegen/src/model.rs b/diesel_codegen/src/model.rs index 077ea9a34a68..2a845ee11875 100644 --- a/diesel_codegen/src/model.rs +++ b/diesel_codegen/src/model.rs @@ -5,11 +5,13 @@ use syntax::ptr::P; use syntax::parse::token::str_to_ident; use attr::Attr; +use util::str_value_of_attr_with_name; pub struct Model { pub ty: P, pub attrs: Vec, pub name: ast::Ident, + table_name_from_annotation: Option, } impl Model { @@ -19,6 +21,8 @@ impl Model { annotatable: &Annotatable, ) -> Option { if let Annotatable::Item(ref item) = *annotatable { + let table_name_from_annotation = + str_value_of_attr_with_name(cx, &item.attrs, "table_name"); Attr::from_item(cx, item).map(|(generics, attrs)| { let ty = builder.ty().path() .segment(item.ident).with_generics(generics.clone()) @@ -27,6 +31,7 @@ impl Model { ty: ty, attrs: attrs, name: item.ident, + table_name_from_annotation: table_name_from_annotation, } }) } else { @@ -39,8 +44,10 @@ impl Model { } pub fn table_name(&self) -> ast::Ident { - let pluralized = format!("{}s", self.name.name.as_str()); - str_to_ident(&pluralized.to_lowercase()) + self.table_name_from_annotation.unwrap_or_else(|| { + let pluralized = format!("{}s", self.name.name.as_str()); + str_to_ident(&pluralized.to_lowercase()) + }) } pub fn attr_named(&self, name: ast::Ident) -> &Attr { diff --git a/diesel_codegen/src/util.rs b/diesel_codegen/src/util.rs new file mode 100644 index 000000000000..cd24f6c2116b --- /dev/null +++ b/diesel_codegen/src/util.rs @@ -0,0 +1,28 @@ +use syntax::ast; +use syntax::attr::AttrMetaMethods; +use syntax::ext::base::ExtCtxt; +use syntax::parse::token::str_to_ident; + +fn str_value_of_attr( + cx: &mut ExtCtxt, + attr: &ast::Attribute, + name: &str, +) -> Option { + attr.value_str().map(|value| { + str_to_ident(&value) + }).or_else(|| { + cx.span_err(attr.span(), + &format!(r#"`{}` must be in the form `#[{}="something"]`"#, name, name)); + None + }) +} + +pub fn str_value_of_attr_with_name( + cx: &mut ExtCtxt, + attrs: &[ast::Attribute], + name: &str, +) -> Option { + attrs.iter() + .find(|a| a.check_name(name)) + .and_then(|a| str_value_of_attr(cx, &a, name)) +} diff --git a/diesel_tests/tests/associations.rs b/diesel_tests/tests/associations.rs index 572eec2ee151..86f895246896 100644 --- a/diesel_tests/tests/associations.rs +++ b/diesel_tests/tests/associations.rs @@ -24,3 +24,18 @@ fn one_to_many_returns_query_source_for_association() { let found_posts: Vec<_> = Post::belonging_to(&tess).load(&connection).unwrap().collect(); assert_eq!(tess_posts, found_posts); } + +#[test] +fn association_where_struct_name_doesnt_match_table_name() { + let connection = connection_with_sean_and_tess_in_users_table(); + + let sean = find_user_by_name("Sean", &connection); + let post: Post = insert(&sean.new_post("Hello", None)).into(posts::table) + .get_result(&connection).unwrap(); + insert(&NewComment(post.id, "comment")).into(comments::table) + .execute(&connection).unwrap(); + + let comment_text = SpecialComment::belonging_to(&post).select(comments::text) + .first::(&connection); + assert_eq!(Ok("comment".into()), comment_text); +} diff --git a/diesel_tests/tests/schema.rs b/diesel_tests/tests/schema.rs index 0cef19a7ab7e..441bc5fdcce8 100644 --- a/diesel_tests/tests/schema.rs +++ b/diesel_tests/tests/schema.rs @@ -59,6 +59,14 @@ pub struct Comment { text: String, } +#[derive(PartialEq, Eq, Debug, Clone, Queriable)] +#[belongs_to(post)] +#[table_name="comments"] +pub struct SpecialComment { + id: i32, + post_id: i32, +} + infer_schema!(dotenv!("DATABASE_URL")); numeric_expr!(users::id);