Generic property compare

26 Views Asked by At

I have a domain model of user where I store original user state and current. If property from current state doesn't match original - it needs to be updated in database and included in sql query.

Creating similar compare methods for each property becomes repetitive and I wanted to know, is there a way to replace this compare methods with some generic shared one?

Note: original can be changed to non-optional if it will be simpler to implement. It is used to distinguish insert vs update sql operation for just created model

Playground

#[derive(Debug, Clone)]
struct UserProps {
    pub age: i32,
    pub name: String,
}
struct User {
    pub original: Option<UserProps>,
    pub current: UserProps,
}
impl User {
    // creating similar function for each property is burthersome
    // is there a way to create a generic one?
    pub fn age_changed(&self) -> bool {
        self.original
            .as_ref()
            .map_or(true, |val| val.age != self.current.age)
    }

    pub fn name_changed(&self) -> bool {
        self.original
            .as_ref()
            .map_or(true, |val| val.name != self.current.name)
    }
}

fn x() {
    let props = UserProps {
        age: 12,
        name: "x".to_owned(),
    };

    let mut user = User {
        original: Some(props.clone()),
        current: props.clone(),
    };

    user.current.age = 22;

    assert!(user.age_changed());
    if user.age_changed() {
        
        // add property to sql query update operation
    }
}

For exmaple in javascript I would do something like this:

propertyChanged(propName: string): boolean {
  if (this.original === undefined){
     return true
  }

  return this.original[propName] !== this.current[propName]
}
1

There are 1 best solutions below

0
Chayim Friedman On BEST ANSWER

A macro to the rescue:

macro_rules! generate_prop_changed {
    ( $( $method:ident => $prop:ident; )*  ) => {
        $(
            pub fn $method(&self) -> bool {
                self.original
                    .as_ref()
                    .map_or(true, |val| val.$prop == self.current.$prop)
            }
        )*
    };
}

impl User {
    generate_prop_changed! {
        age_changed => age;
        name_changed => name;
    }
}

If you want to play PRO-land, try:

  1. Getting rid of the need to provide the method names (hint: paste).
  2. Getting rid of the need to provide the field names too by wrapping the struct declaration with the macro.
  3. Or, the hardest: create a derive macro for it.