Closed RoDmitry closed 3 years ago
That's my vision on how to make it right. I'm kind of new in Rust, if something is wrong tell me.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct User {
pub id: i32,
pub name: String,
pub password: String,
pub update: Option<UserUpdate>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct UserUpdate {
pub name: Option<String>, // maybe use pointers to String?
pub password: Option<String>,
}
impl User {
pub fn set_name(&mut self, name: String) -> &mut User {
if self.name != name {
self.name = name.clone(); // avoid clone?
if let Some(user_update) = &mut self.update {
user_update.name = Some(name); // maybe use pointer to self.name?
} else {
self.update = Some(UserUpdate { ..Default::default() });
if let Some(user_update) = &mut self.update {
user_update.name = Some(name);
}
}
}
self
}
// abstract types
pub fn update(&mut self, db: &DbType) -> ResultType {
let result = self.update.update(db);
if result.success {
self.update = None;
}
result
}
}
You call user.set_name("asdf".to_owned())
on the User you have, and it will be updated in DB once user.update(&db)
is called.
I would suggest making the update completely seperate from the actual struct. Here's my vision:
struct User {
id: i32,
name: String,
password: String,
}
let user = User::by_id(&db, 1);
let mut update: UserChangeset = user.changeset();
update.name = Some("name".to_owned());
update.update(&db);
ormx would generate UserChangeset
which, in this example, would look like this:
struct UserChangeset {
name: Option<String>,
password: Option<String>,
update: Option<Option<UserUpdate>>,
}
But user.set_name()
checks if the name was actually changed, that's easier to work with. And user struct fields will be updated also, so it's synchronized for output to the template for example.
The concept of batch-updating should be completely seperate. With regards to updating the fields of the user too, this is still possible. I suggest this API:
struct User {
id: u32,
name: String,
password: String
}
impl User {
fn changeset(&mut self) -> UserChangeset<'_> {
UserChangeset {
_ref: self,
name: None,
password: None
}
}
}
struct UserChangeset<'a> {
_ref: &'a mut User,
name: Option<String>,
password: Option<String>
}
impl <'a> UserChangeset<'a> {
fn update(self) {
if let Some(name) = self.name {
self._ref.name = name;
}
if let Some(password) = self.password {
self._ref.password = password;
}
// write to database
}
}
then, you can do updates like this:
let mut user = User {
id: 1,
name: "John".to_owned(),
password: "$2y$12$OgdSDmNerIDMqW7N5vO7e.5NjMwPGXoe2zqLFMP8nLKbkUsInQPq.".to_owned()
};
let mut update = user.changeset();
update.name = Some("hey".to_owned());
update.update();
Ok, then can we do the checking of whether User
fields were modified or not?
Maybe like that? Now it's bool
instead of Option
, and we change User
fields immediately. If you need old data on error, I would consider cloning before any change, and if update is successful then drop old User
. Or use user.reload(&db)
.
struct User {
id: u32,
name: String,
password: String
}
impl User {
pub fn changeset(&mut self) -> UserChangeset<'_> {
UserChangeset {
_ref: self,
name: false,
password: false
}
}
}
struct UserChangeset<'a> {
_ref: &'a mut User,
name: bool,
password: bool
}
impl <'a> UserChangeset<'a> {
pub fn set_name(&mut self, name: String) -> &mut Self {
if self._ref.name != name {
self._ref.name = name;
self.name = true;
}
self
}
pub fn update(self) { // must return Result
// write to database
}
}
fn main() {
let mut user = User {
id: 1,
name: "John".to_owned(),
password: "$2y$12$OgdSDmNerIDMqW7N5vO7e.5NjMwPGXoe2zqLFMP8nLKbkUsInQPq.".to_owned(),
};
let mut changeset = user.changeset();
changeset.set_name("Admin".to_owned());
changeset.update();
}
closing this due to inactivity If there is something left to say, feel free to comment here and I will reopen the issue, or create a PR.
if Dynamic updates (which can't use Patch) are really a common usecase... - NyxCode
We can use one full update query to compile check, and small generated queries for modified fields.
We must have the struct of all
Option
fields, with all by defaultNone
and changed areSome
. Nullable fields areOption<Option<T>>
.Possible use case:
And this use case might be also possible, but not necessary:
Here we use setters to make the variable of
Some<T>
. It needs to generate a lot of functions, which might be redundant? But it looks better.Discussion of it took place in the Discord.