Skip to content

Commit 85fee9f

Browse files
committed
Replace #[changeset_for] with #[derive(AsChangeset)]
This is required to make the API compatible with macros 1.1. While syntex could theoretically keep the old API, I'd like to keep it in sync with nightly. Macros 1.1 requires that we represent this as a custom derive. This does mean that the same struct can no longer be used as a changeset for more than one table (at least not with codegen) but that doesn't feel like something we need to be concerned about. The stable macro is still an option for that case.
1 parent 9ea449c commit 85fee9f

11 files changed

Lines changed: 99 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
3535
This was a source of trouble for people that had created views or are using
3636
any extension that automatically creates views (e.g. PostGIS)
3737

38+
### Changed
39+
40+
* `#[changeset_for(foo)]` should now be written as
41+
`#[derive(AsChangeset)] #[table_name="foo"]`. If you were specifying
42+
`treat_none_as_null = "true"`, you should additionally have
43+
`#[changeset_options(treat_none_as_null = "true")]`.
44+
3845
## [0.7.2] - 2016-08-20
3946

4047
* Updated nightly version and syntex support.

diesel/src/query_builder/functions.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use super::delete_statement::DeleteStatement;
44
use super::insert_statement::Insert;
55

66
/// Creates an update statement. Helpers for updating a single row can be
7-
/// generated by
8-
/// [`#[changeset_for]`](https://github.com/diesel-rs/diesel/tree/master/diesel_codegen#changeset_fortable_name).
7+
/// generated by deriving [`AsChangeset`][trait.AsChangeset.html]
98
///
109
/// # Examples
1110
///

diesel/src/query_builder/update_statement/changeset.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ use result::QueryResult;
55

66
/// Types which can be passed to
77
/// [`update.set`](struct.IncompleteUpdateStatement.html#method.set). This can
8-
/// be automatically generated for structs by
9-
/// [`#[changeset_for]`](https://github.com/diesel-rs/diesel/tree/master/diesel_codegen#changeset_fortable_name).
8+
/// be automatically generated for structs by `#[derive(AsChangeset)]`.
9+
///
10+
/// Structs which derive this trait must be annotated with `#[table_name = "something"]`. By
11+
/// default, any option fields on the struct are skipped if their value is `None`. If you would
12+
/// like to assign `NULL` to the field instead, you can annotate your struct with
13+
/// `#[changeset_options(treat_none_as_null = "true")]`
1014
pub trait AsChangeset {
1115
type Target: QuerySource;
1216
type Changeset;

diesel_codegen/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,23 @@ item, targeting the given table. Can only annotate structs and tuple structs.
100100
Enums are not supported. See [field annotations][#field-annotations] for
101101
additional configurations.
102102

103-
### `#[changeset_for(table_name)]`
103+
### `#[derive(AsChangeset)]`
104104

105105
Adds an implementation of the [`AsChangeset`][as_changeset] trait to the
106106
annotated item, targeting the given table. At this time, it only supports
107107
structs with named fields. Tuple structs and enums are not supported. See [field
108108
annotations][#field-annotations] for additional configurations.
109109

110+
Structs which derive `AsChangeset` must have a table name annotation like so:
111+
`#[table_name = "something"]`
112+
110113
Any fields which are of the type `Option` will be skipped when their value is
111114
`None`. This makes it easy to support APIs where you may not want to update all
112115
of the fields of a record on every request.
113116

114117
If you'd like `None` to change a field to `NULL`, instead of skipping it, you
115-
can pass the `treat_none_as_null` option like so: `#[changeset_for(posts,
116-
treat_none_as_null="true")]`
118+
can pass the `treat_none_as_null` option like so:
119+
`#[changeset_options(treat_none_as_null = "true")]`
117120

118121
If the struct has a field for the primary key, an additional function,
119122
`save_changes<T: Queryable<..>>(&self, connection: &Connection) ->

diesel_codegen/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ pub fn register(reg: &mut rustc_plugin::Registry) {
1515
MultiDecorator(Box::new(associations::expand_derive_associations)),
1616
);
1717
reg.register_syntax_extension(
18-
intern("derive_Queryable"),
19-
MultiDecorator(Box::new(queryable::expand_derive_queryable))
18+
intern("derive_AsChangeset"),
19+
MultiDecorator(Box::new(update::expand_derive_as_changeset)),
2020
);
2121
reg.register_syntax_extension(
2222
intern("derive_Identifiable"),
2323
MultiDecorator(Box::new(identifiable::expand_derive_identifiable))
2424
);
2525
reg.register_syntax_extension(
26-
intern("insertable_into"),
27-
MultiDecorator(Box::new(insertable::expand_insert))
26+
intern("derive_Queryable"),
27+
MultiDecorator(Box::new(queryable::expand_derive_queryable))
2828
);
2929
reg.register_syntax_extension(
30-
intern("changeset_for"),
31-
MultiDecorator(Box::new(update::expand_changeset_for)),
30+
intern("insertable_into"),
31+
MultiDecorator(Box::new(insertable::expand_insert))
3232
);
3333
reg.register_macro("embed_migrations", migrations::expand_embed_migrations);
3434
reg.register_macro("infer_table_from_schema", schema_inference::expand_load_table);

diesel_codegen_syntex/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ pub fn register(reg: &mut syntex::Registry) {
3030
reg.add_attr("feature(custom_derive)");
3131
reg.add_attr("feature(custom_attribute)");
3232

33-
reg.add_decorator("derive_Queryable", queryable::expand_derive_queryable);
34-
reg.add_decorator("derive_Identifiable", identifiable::expand_derive_identifiable);
33+
reg.add_decorator("derive_AsChangeset", update::expand_derive_as_changeset);
3534
reg.add_decorator("derive_Associations", associations::expand_derive_associations);
35+
reg.add_decorator("derive_Identifiable", identifiable::expand_derive_identifiable);
36+
reg.add_decorator("derive_Queryable", queryable::expand_derive_queryable);
3637
reg.add_decorator("insertable_into", insertable::expand_insert);
37-
reg.add_decorator("changeset_for", update::expand_changeset_for);
3838
reg.add_macro("embed_migrations", migrations::expand_embed_migrations);
3939
reg.add_macro("infer_table_from_schema", schema_inference::expand_load_table);
4040
reg.add_macro("infer_schema", schema_inference::expand_infer_schema);

diesel_codegen_syntex/src/update.rs

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,86 @@ use syntax::attr::AttrMetaMethods;
33
use syntax::codemap::Span;
44
use syntax::ext::base::{Annotatable, ExtCtxt};
55
use syntax::ptr::P;
6-
use syntax::parse::token::{InternedString, str_to_ident};
7-
use syntax::tokenstream::TokenTree;
86

97
use model::Model;
10-
use util::lifetime_list_tokens;
8+
use util::{lifetime_list_tokens, str_value_of_attr_with_name};
119

12-
pub fn expand_changeset_for(
10+
pub fn expand_derive_as_changeset(
1311
cx: &mut ExtCtxt,
1412
span: Span,
1513
meta_item: &MetaItem,
1614
annotatable: &Annotatable,
1715
push: &mut FnMut(Annotatable),
1816
) {
1917
if let Some(model) = Model::from_annotable(cx, span, annotatable) {
20-
let options = changeset_options(cx, meta_item).unwrap();
18+
let options = changeset_options(cx, span, annotatable.attrs()).unwrap();
2119
push(Annotatable::Item(changeset_impl(cx, span, &options, &model).unwrap()));
2220
} else {
2321
cx.span_err(meta_item.span,
24-
"`changeset_for` may only be applied to enums and structs");
22+
"`#[derive(AsChangeset)]` may only be applied to enums and structs");
2523
}
2624
}
2725

2826
struct ChangesetOptions {
2927
table_name: ast::Ident,
30-
treat_none_as_null: Vec<TokenTree>,
28+
treat_none_as_null: bool,
3129
}
3230

33-
fn changeset_options(cx: &mut ExtCtxt, meta_item: &MetaItem) -> Result<ChangesetOptions, ()> {
34-
match meta_item.node {
35-
MetaItemKind::List(_, ref meta_items) => {
36-
let table_name = try!(table_name(cx, &meta_items[0]));
37-
let treat_none_as_null = try!(boolean_option(cx, &meta_items[1..], "treat_none_as_null"));
38-
Ok(ChangesetOptions {
39-
table_name: str_to_ident(&table_name),
40-
treat_none_as_null: treat_none_as_null,
41-
})
31+
fn changeset_options(
32+
cx: &mut ExtCtxt,
33+
span: Span,
34+
attributes: &[ast::Attribute]
35+
) -> Result<ChangesetOptions, ()> {
36+
let changeset_options_attr = attributes.iter().find(|a| a.check_name("changeset_options"));
37+
let treat_none_as_null = try!(changeset_options_attr
38+
.map(|a| extract_treat_none_as_null(cx, a))
39+
.unwrap_or(Ok(false)));
40+
let table_name = match str_value_of_attr_with_name(cx, attributes, "table_name") {
41+
Some(name) => name,
42+
None => return missing_table_name_error(cx, span),
43+
};
44+
45+
Ok(ChangesetOptions {
46+
table_name: table_name,
47+
treat_none_as_null: treat_none_as_null,
48+
})
49+
}
50+
51+
fn extract_treat_none_as_null(cx: &mut ExtCtxt, attr: &ast::Attribute) -> Result<bool, ()>{
52+
match attr.node.value.node {
53+
MetaItemKind::List(_, ref items) if items.len() == 1 => {
54+
if items[0].check_name("treat_none_as_null") {
55+
boolean_option(cx, &*items[0])
56+
} else {
57+
options_usage_error(cx, attr.span)
58+
}
4259
}
43-
_ => usage_error(cx, meta_item),
60+
_ => options_usage_error(cx, attr.span),
4461
}
4562
}
4663

47-
fn table_name(cx: &mut ExtCtxt, meta_item: &MetaItem) -> Result<InternedString, ()> {
48-
match meta_item.node {
49-
MetaItemKind::Word(ref word) => Ok(word.clone()),
50-
_ => usage_error(cx, meta_item),
64+
fn boolean_option(cx: &mut ExtCtxt, item: &MetaItem) -> Result<bool, ()> {
65+
match item.value_str() {
66+
Some(ref s) if *s == "true" => Ok(true),
67+
Some(ref s) if *s == "false" => Ok(false),
68+
_ => options_usage_error(cx, item.span)
5169
}
5270
}
5371

54-
#[allow(unused_imports)] // quote_tokens! generates warnings
55-
fn boolean_option(cx: &mut ExtCtxt, meta_items: &[P<MetaItem>], option_name: &str)
56-
-> Result<Vec<TokenTree>, ()>
57-
{
58-
if let Some(item) = meta_items.iter().find(|item| item.name() == option_name) {
59-
match item.value_str() {
60-
Some(ref s) if *s == "true" => Ok(quote_tokens!(cx, "true")),
61-
Some(ref s) if *s == "false" => Ok(quote_tokens!(cx, "false")),
62-
_ => {
63-
cx.span_err(item.span,
64-
&format!("Expected {} to be in the form `option=\"true\"` or \
65-
option=\"false\"", option_name));
66-
Err(())
67-
}
68-
}
69-
} else {
70-
Ok(quote_tokens!(cx, "false"))
71-
}
72+
fn options_usage_error<T>(cx: &mut ExtCtxt, span: Span) -> Result<T, ()> {
73+
cx.span_err(span,
74+
r#"`changeset_options` must be used in the form \
75+
`#[changeset_options(treat_none_as_null = "true")]`"#);
76+
Err(())
7277
}
7378

74-
fn usage_error<T>(cx: &mut ExtCtxt, meta_item: &MetaItem) -> Result<T, ()> {
75-
cx.span_err(meta_item.span,
76-
"`changeset_for` must be used in the form `#[changeset_for(table1)]`");
79+
fn missing_table_name_error<T>(cx: &mut ExtCtxt, span: Span) -> Result<T, ()> {
80+
cx.span_err(span, r#"Structs annotated with `#[derive(AsChangeset)]` must \
81+
also be annotated with `#[table_name="something"]`"#);
7782
Err(())
7883
}
7984

85+
#[allow(unused_imports)] // quote_tokens! generates warnings
8086
fn changeset_impl(
8187
cx: &mut ExtCtxt,
8288
span: Span,
@@ -85,7 +91,11 @@ fn changeset_impl(
8591
) -> Option<P<ast::Item>> {
8692
let struct_name = model.name;
8793
let table_name = options.table_name;
88-
let treat_none_as_null = &options.treat_none_as_null;
94+
let treat_none_as_null = if options.treat_none_as_null {
95+
quote_tokens!(cx, "true")
96+
} else {
97+
quote_tokens!(cx, "false")
98+
};
8999
let struct_ty = &model.ty;
90100
let lifetimes = lifetime_list_tokens(&model.generics.lifetimes, span);
91101

diesel_codegen_syntex/src/util.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ pub fn ident_value_of_attr_with_name(
6969

7070
#[cfg(feature = "with-syntex")]
7171
const KNOWN_ATTRIBUTES: &'static [&'static str] = &[
72-
"table_name",
72+
"belongs_to",
73+
"changeset_for",
7374
"column_name",
7475
"has_many",
75-
"belongs_to",
76+
"table_name",
7677
];
7778

7879
#[cfg(feature = "with-syntex")]

diesel_compile_tests/tests/compile-fail/codegen_does_not_add_save_changes_method_on_structs_without_primary_key.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ table! {
1414
}
1515
}
1616

17-
#[derive(Queryable)]
18-
#[changeset_for(users)]
17+
#[derive(Queryable, AsChangeset)]
18+
#[table_name = "users"]
1919
pub struct User {
2020
name: String,
2121
hair_color: String,

diesel_tests/tests/schema.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use diesel::*;
22

33
infer_schema!(dotenv!("DATABASE_URL"));
44

5-
#[derive(PartialEq, Eq, Debug, Clone, Queryable, Identifiable, Associations)]
6-
#[changeset_for(users)]
5+
#[derive(PartialEq, Eq, Debug, Clone, Queryable, Identifiable, AsChangeset, Associations)]
76
#[insertable_into(users)]
87
#[has_many(posts)]
8+
#[table_name = "users"]
99
pub struct User {
1010
pub id: i32,
1111
pub name: String,
@@ -65,9 +65,9 @@ select_column_workaround!(comments -> users (id, post_id, text));
6565

6666
join_through!(users -> posts -> comments);
6767

68-
#[derive(Debug, PartialEq, Eq, Queryable, Clone)]
68+
#[derive(Debug, PartialEq, Eq, Queryable, Clone, AsChangeset)]
6969
#[insertable_into(users)]
70-
#[changeset_for(users)]
70+
#[table_name = "users"]
7171
pub struct NewUser {
7272
pub name: String,
7373
pub hair_color: Option<String>,

0 commit comments

Comments
 (0)