Skip to content

Comments

feat: Extend DeriveIntoActiveModel#2961

Merged
tyt2y3 merged 13 commits intoSeaQL:masterfrom
sinder38:master
Feb 23, 2026
Merged

feat: Extend DeriveIntoActiveModel#2961
tyt2y3 merged 13 commits intoSeaQL:masterfrom
sinder38:master

Conversation

@sinder38
Copy link
Contributor

@sinder38 sinder38 commented Feb 19, 2026

PR Info

New Features

  • Add simple documentation for DeriveIntoActiveModel
  • Add field-level attributes to:
    • Skip mapping a field to the ActiveModel.
    • Define a custom default value or default function for a field.
  • Introduce container-level attributes to:
    • Define fallback values for omitted fields.
    • Enforce exhaustive coverage of all ActiveModel fields.
  • test spam

Look at the cool table below:

Before this PR

Input Value Struct Field Result in ActiveModel
Not provided Field not declared NotSet
Provided Field declared Set(value)

After this PR

Input Value Required Action Result in ActiveModel
Not provided but must be set Add #[sea_orm(set(field = "..."))] Set(expr)
Sometimes provided Use Option<T> + #[sea_orm(default(expr))] Some(v) -> Set(v)
None -> Set(expr)
Provided directly Declare field normally Set(value)
Not provided and not required Omit field NotSet
Provided but not needed Add `#[sea_orm(ignore)] NotSet/ no effect

Alternative solution:

These same features could be implemented as a separate derive macro with its own trait to indicate that the struct is safe to insert.

Showcase:

Computed defaults, constants, and expressions

const SYSTEM_USER_ID: i32 = 0;
#[derive(DeriveIntoActiveModel)]
#[sea_orm(
    active_model = "post::ActiveModel",
    set(updated_at = "chrono::Utc::now()"),
    set(version = "1")
)]
struct CreatePost {
    title: String,
    content: String,
    #[sea_orm(default = "chrono::Utc::now()")]
    published_at: Option<chrono::DateTime<chrono::Utc>>,
    #[sea_orm(default = "SYSTEM_USER_ID")]
    author_id: Option<i32>,
}
  • set(...) supports arbitrary Rust expressions (functions, constants, literals).
  • #[sea_orm(default = "...")] provides fallback expressions for Option<T> fields.
  • Multiple set(...) attributes are supported and merged.

Exhaustive mode with field control

#[derive(DeriveIntoActiveModel)]
#[sea_orm(
    active_model = "account::ActiveModel",
    exhaustive,
    set(updated_at = "chrono::Utc::now()")
)]
struct UpdateAccount {
    id: i32,
    #[sea_orm(default = "false")]
    disabled: Option<bool>,
    #[sea_orm(ignore)]
    audit_log: String,
}
  • exhaustive removes ..Default::default(), forcing explicit coverage of all ActiveModel fields.
  • ignore cleanly excludes DTO-only fields.
  • default and set compose cleanly under exhaustive mode.

Breaking Changes

Should be fine, because it's backward compatible and changed code is under pub(super).

Changes

  • Add support for field-level #[sea_orm(default = "...")] to provide fallback values for Option<T> fields when None.
  • Add support for field-level #[sea_orm(skip)] / #[sea_orm(ignore)] to exclude fields from IntoActiveModel generation.
  • Add container-level #[sea_orm(set(...))] to explicitly set ActiveModel fields with custom expressions.
  • Add container-level #[sea_orm(active_model = "...")] parsing improvements.
  • Introduce IntoActiveModelField enum to support normal and default-backed fields.
  • Extend meta parsing utilities with get_as_kv_with_ident to support identifier–expression pairs.
  • Add basic documentation improvements for DeriveIntoActiveModel.
  • Some tests for good measure

@sinder38 sinder38 marked this pull request as draft February 19, 2026 01:11
@sinder38
Copy link
Contributor Author

Set as draft until the maintainers decide on the desired design.

@Huliiiiii
Copy link
Member

Huliiiiii commented Feb 19, 2026

I prefer MetaList (default(expr)) over MetaNameValue (default = "expr").

But sometimes it makes the semantics ambiguous, e.g. default(false).

@Huliiiiii
Copy link
Member

By the way, for such a complex case, wouldn’t directly implementing IntoActiveModel be simpler and clearer?”

@sinder38
Copy link
Contributor Author

sinder38 commented Feb 19, 2026

I prefer MetaList ...

Yee, I also think MetaNameValue is clearer, but I’m not opposed to a having MetaList implementation option.

By the way, for such a complex case, wouldn’t directly implementing IntoActiveModel be simpler and clearer?”

Regarding the more complex cases: honestly, I’m stuck between a rock and a hard place. Either I write custom implementations for each of the like 50 structs I have, which would roughly double the amount of code or I implement these macros.

In the end, macros it is because, in my experience, refactoring many manual implementations is significantly more painful than maintaining macro-based solution.

@Huliiiiii Huliiiiii requested a review from tyt2y3 February 19, 2026 04:58
@Huliiiiii
Copy link
Member

Because all macros share #[sea_orm(...)], I’d recommend using #[sea_orm(into_active_model(...))] or #[into_active_model(...)] to avoid conflicts. (Although it’s an uncommon usage, it can also make the semantics a bit clearer.)

@tyt2y3, thoughts?

Copy link
Member

@tyt2y3 tyt2y3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks good and well thought out!

@sinder38
Copy link
Contributor Author

should I mark as ready or do you want changes like MetaList, renaming to into_active_model , or more tests or smt else

Move field attribute parsing into a function
@sinder38
Copy link
Contributor Author

sinder38 commented Feb 19, 2026

I think it would be better to change variable names and comments once the design and features are finalised.
code before me was undocumented :v . The comments in the code were mostly for me to stop getting lost and forgetful

@tyt2y3
Copy link
Member

tyt2y3 commented Feb 19, 2026

I think the design is good, only consideration is the name for the set attribute. it's short and can mean many things, but there may not be a better name

@sinder38 sinder38 marked this pull request as ready for review February 20, 2026 00:05
@sinder38 sinder38 requested a review from tyt2y3 February 20, 2026 00:05
@Huliiiiii
Copy link
Member

Does set accept multiple values?
For example:

set(updated_at = chrono::Utc::now(), version = "1")

I think we could support a serde-like #[sea_orm(default)] attribute to let fields automatically use Set(Default::default()) as the default.

@sinder38
Copy link
Contributor Author

sinder38 commented Feb 20, 2026

Yes, it supports both ways of adding multiple set.
Bare #[sea_orm(default)] makes sense, here it is added.

Copy link

@kvpavel64 kvpavel64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a ton for your awesome contribution to the community!

@tyt2y3 tyt2y3 merged commit dc7f97b into SeaQL:master Feb 23, 2026
38 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend DeriveIntoActiveModel to support settable and default-able values

4 participants