Skip to content

Commit e0d0196

Browse files
committed
Port #[derive(Associations)] to entirely use a custom derive
* Deprecated the `BelongsTo` macro * Add a small check if all options passed to the custom derive are valid
1 parent b07c24b commit e0d0196

2 files changed

Lines changed: 63 additions & 29 deletions

File tree

diesel/src/macros/associations/belongs_to.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#[macro_export]
22
#[doc(hidden)]
3+
#[cfg(feature = "with-deprecated")]
4+
#[deprecated(since = "1.1.0", note = "Use `#[derive(Associations)]` instead")]
35
macro_rules! BelongsTo {
46
// Format arguments
57
(

diesel_derives/src/associations.rs

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,84 @@ use syn;
22
use quote;
33

44
use model::{infer_association_name, Model};
5-
use util::str_value_of_meta_item;
5+
use util::{str_value_of_meta_item, wrap_item_in_const};
66

77
pub fn derive_associations(input: syn::DeriveInput) -> quote::Tokens {
8-
let mut derived_associations = Vec::new();
98
let model = t!(Model::from_item(&input, "Associations"));
109

11-
for attr in input.attrs.as_slice() {
10+
let derived_associations = input.attrs.as_slice().iter().filter_map(|attr| {
1211
if attr.name() == "belongs_to" {
13-
let options = t!(build_association_options(attr, "belongs_to"));
14-
derived_associations.push(expand_belongs_to(&model, options))
12+
Some(
13+
build_association_options(attr)
14+
.map(|options| expand_belongs_to(&model, options))
15+
.unwrap_or_else(|e| e),
16+
)
17+
} else {
18+
None
1519
}
16-
}
20+
});
1721

18-
quote!(#(#derived_associations)*)
22+
wrap_item_in_const(
23+
model.dummy_const_name("ASSOCIATIONS"),
24+
quote!(#(#derived_associations)*),
25+
)
1926
}
2027

2128
fn expand_belongs_to(model: &Model, options: AssociationOptions) -> quote::Tokens {
2229
let parent_struct = options.name;
2330
let struct_name = &model.name;
31+
let fields = model.attrs.as_slice();
2432

2533
let foreign_key_name = options
2634
.foreign_key_name
2735
.unwrap_or_else(|| to_foreign_key(parent_struct.as_ref()));
36+
let foreign_key_name = &foreign_key_name;
2837
let child_table_name = model.table_name();
29-
let fields = model.attrs.as_slice();
38+
let child_table_name = &child_table_name;
39+
let foreign_key_attr = fields
40+
.iter()
41+
.find(|attr| attr.column_name().as_ref() == foreign_key_name.as_ref())
42+
.unwrap();
43+
let foreign_key_ty = &foreign_key_attr.ty;
44+
45+
// we need to special case foreign keys on with an Option type
46+
// to allow self referencing joins
47+
let (foreign_key, foreign_key_ty) = match *foreign_key_ty {
48+
syn::Ty::Path(None, ref p) if p.segments[0].ident == "Option" => {
49+
let segment = &p.segments[0];
50+
let t = match segment.parameters {
51+
syn::PathParameters::AngleBracketed(ref p) => p.types[0].clone(),
52+
syn::PathParameters::Parenthesized(_) => unreachable!(),
53+
};
54+
(quote!(self.#foreign_key_name.as_ref()), t)
55+
}
56+
ref t => (quote!(Some(&self.#foreign_key_name)), t.clone()),
57+
};
3058

31-
quote!(BelongsTo! {
32-
(
33-
struct_name = #struct_name,
34-
parent_struct = #parent_struct,
35-
foreign_key_name = #foreign_key_name,
36-
child_table_name = #child_table_name,
37-
),
38-
fields = [#(#fields)*],
39-
})
59+
quote!(
60+
impl diesel::associations::BelongsTo<#parent_struct> for #struct_name {
61+
type ForeignKey = #foreign_key_ty;
62+
type ForeignKeyColumn = #child_table_name::#foreign_key_name;
63+
64+
fn foreign_key(&self) -> Option<&Self::ForeignKey> {
65+
#foreign_key
66+
}
67+
68+
fn foreign_key_column() -> Self::ForeignKeyColumn {
69+
#child_table_name::#foreign_key_name
70+
}
71+
}
72+
)
4073
}
4174

4275
struct AssociationOptions {
4376
name: syn::Ident,
4477
foreign_key_name: Option<syn::Ident>,
4578
}
4679

47-
fn build_association_options(
48-
attr: &syn::Attribute,
49-
association_kind: &str,
50-
) -> Result<AssociationOptions, String> {
51-
let usage_error = Err(format!(
52-
"`#[{}]` must be in the form `#[{}(table_name, option=value)]`",
53-
association_kind, association_kind
80+
fn build_association_options(attr: &syn::Attribute) -> Result<AssociationOptions, quote::Tokens> {
81+
let usage_error = Err(quote!(
82+
compile_error!("`#[belongs_to]` must be in the form `#[belongs_to(table_name, foreign_key=\"column_name\")]`")
5483
));
5584
match attr.value {
5685
syn::MetaItem::List(_, ref options) if options.len() >= 1 => {
@@ -67,11 +96,14 @@ fn build_association_options(
6796
.find(|a| a.name() == "foreign_key")
6897
.map(|a| str_value_of_meta_item(a, "foreign_key"))
6998
.map(syn::Ident::new);
70-
71-
Ok(AssociationOptions {
72-
name: association_name,
73-
foreign_key_name: foreign_key_name,
74-
})
99+
if options.len() == 1 || (options.len() == 2 && foreign_key_name.is_some()) {
100+
Ok(AssociationOptions {
101+
name: association_name,
102+
foreign_key_name: foreign_key_name,
103+
})
104+
} else {
105+
usage_error
106+
}
75107
}
76108
_ => usage_error,
77109
}

0 commit comments

Comments
 (0)