diesel_codegen 0.16.1

Custom derive and procedural macros for Diesel
Documentation
use syn;
use quote;

use model::{Model, infer_association_name};
use util::str_value_of_meta_item;

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

    for attr in input.attrs.as_slice() {
        if attr.name() == "belongs_to" {
            let options = t!(build_association_options(attr, "belongs_to"));
            derived_associations.push(expand_belongs_to(&model, options))
        }
    }

    quote!(#(#derived_associations)*)
}

fn expand_belongs_to(model: &Model, options: AssociationOptions) -> quote::Tokens {
    let parent_struct = options.name;
    let struct_name = &model.name;

    let foreign_key_name = options.foreign_key_name.unwrap_or_else(||
        to_foreign_key(parent_struct.as_ref()));
    let child_table_name = model.table_name();
    let fields = model.attrs.as_slice();

    quote!(BelongsTo! {
        (
            struct_name = #struct_name,
            parent_struct = #parent_struct,
            foreign_key_name = #foreign_key_name,
            child_table_name = #child_table_name,
        ),
        fields = [#(#fields)*],
    })
}

struct AssociationOptions {
    name: syn::Ident,
    foreign_key_name: Option<syn::Ident>,
}

fn build_association_options(
    attr: &syn::Attribute,
    association_kind: &str,
) -> Result<AssociationOptions, String> {
    let usage_error = Err(format!(
            "`#[{}]` must be in the form `#[{}(table_name, option=value)]`",
            association_kind, association_kind));
    match attr.value {
        syn::MetaItem::List(_, ref options) if options.len() >= 1 => {
            let association_name = match options[0] {
                syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref name)) => name.clone(),
                _ => return usage_error,
            };
            let foreign_key_name = options.iter()
                .filter_map(|o| match *o {
                    syn::NestedMetaItem::MetaItem(ref mi) => Some(mi),
                    _ => None
                })
                .find(|a| a.name() == "foreign_key")
                .map(|a| str_value_of_meta_item(a, "foreign_key"))
                .map(syn::Ident::new);

            Ok(AssociationOptions {
                name: association_name,
                foreign_key_name: foreign_key_name,
            })
        }
        _ => usage_error,
    }
}

fn to_foreign_key(model_name: &str) -> syn::Ident {
    let lower_cased = infer_association_name(model_name);
    syn::Ident::new(format!("{}_id", &lower_cased))
}