Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to update dynamicly from json value #346

Closed
qyihua opened this issue Dec 1, 2021 · 14 comments · Fixed by #492
Closed

how to update dynamicly from json value #346

qyihua opened this issue Dec 1, 2021 · 14 comments · Fixed by #492
Assignees
Milestone

Comments

@qyihua
Copy link

qyihua commented Dec 1, 2021

Hello,
Is there any way to support dynamic update from json value?
like update from user input , and only allow update special fields
if the input json field is null, need set None value for activemodel,
so it need get None value from column:

struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub addr: Option<String>,
    pub other: Option<String>,
    ...
}
...
let allow_change_fields = vec![Column::Name, Column::Addr];

let mut actives = table::ActiveModel::new();
actives.set(Column::Id, 1.into());

// json is from user input, dynamic
let mut json = json!{addr: "new address"}; 

for col in allow_change_fileds{
   if let Some(json_val) =  json.get(col.to_string()) {
     let new_val = if json_val.is_null() {
       col.null_value() // Value::from(None::<i32>) or Value::from(None::<String>)
     } else {
       json_value_to_sea_value(json_val)
     };
     actives.set(col, new_val);
   }
}

actives.update(db).await?;
...

Any better suggestion?

qyihua@0fa0f33

@qyihua qyihua changed the title dynamic update from json value how to update dynamicly from json value Dec 1, 2021
@billy1624
Copy link
Member

billy1624 commented Dec 1, 2021

Hey @qyihua, thanks for the suggestions!

Are you integrating sea-orm with other web API? I'm interested in your use case, can you tell me more?

I think it's common to take a Json / key-value pair from user and updating the corresponding rows in the database.

@qyihua
Copy link
Author

qyihua commented Dec 1, 2021

Hey @qyihua, thanks for the suggestions!

Are you integrating sea-orm with other web API? I'm interested in your use case, can you tell me more?

I think it's common to take a Json / key-value pair from user and updating the corresponding rows in the database.

Hi, @billy1624
I am integrating sea-orm with axum,
demo web page like this:
Screenshot_20211201_193024

it's very common to update dynamicly from json value in web api, so i need a None SeaValue.

@billy1624
Copy link
Member

Wow, you got some serious use case here!!

I'm thinking a mass assignment API similar to Laravel Eloquent, where you need to explicitly define what column is allowed to be mass assigned. Then, passing in a PHP array as input data.

We could did similar things in sea-orm

  • explicitly define what column is allowed to be mass assigned
  • taking in serde_json::Value as input
  • returning ActiveModel fill with mass assigned values

@tyt2y3 any thoughts?

@qyihua
Copy link
Author

qyihua commented Dec 2, 2021

  • explicitly define what column is allowed to be mass assigned
    I think the columns can be changed at runtime, special in RBAC , diffirent role has diffirent permission, it need some variable vecs

@billy1624
Copy link
Member

billy1624 commented Dec 2, 2021

We could provide two methods for mass assignment

  • set_attributes(input: Json) it will mass assign all keys defined in fillable vec
  • set_attributes_with_keys(input: Json, keys: &[Column]) it will mass assign all keys in keys slice

(I just name the method randomly, we can rethink it later)

@qyihua
Copy link
Author

qyihua commented Dec 2, 2021

Hello, @billy1624 @tyt2y3
I had make a commit for set_from_json.
Can you give me some suggestion ? Thank you.
qyihua@6d3f693

struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub addr: Option<String>,
    pub other: Option<String>,
    ...
}
...
let allow_change_fields = vec![Column::Name, Column::Addr];

let mut actives = table::ActiveModel::new();
actives.set(Column::Id, 1.into());

// json is from user input, dynamic
let mut json = json!{addr: "new address"}; 
for col in allow_change_fileds{
   if let Some(json_val) =  json.get(col.to_string()) {
    // before
    /*
         let new_val = if json_val.is_null() {
           col.null_value() // Value::from(None::<i32>) or Value::from(None::<String>)
         } else {
           json_value_to_sea_value(json_val)
         };
         actives.set(col, new_val);
    */
    // now just one line
    actives.set_from_json(col, json_val)?;
   }
}
actives.update(db).await?;
...

@billy1624
Copy link
Member

I'm checking the changes you made with following link

It looks reasonable to me but I think we need some helper methods in ActiveModel as well. So, we don't need to manually iterate the attributes in it and update it one by one like you did here.

// json is from user input, dynamic
let mut json = json!{addr: "new address"}; 
for col in allow_change_fileds{
   if let Some(json_val) =  json.get(col.to_string()) {
    // before
    /*
         let new_val = if json_val.is_null() {
           col.null_value() // Value::from(None::<i32>) or Value::from(None::<String>)
         } else {
           json_value_to_sea_value(json_val)
         };
         actives.set(col, new_val);
    */
    // now just one line
    actives.set_from_json(col, json_val)?;
   }
}
actives.update(db).await?;
...

All in all, this is a good starting point & demo :))

@qyihua
Copy link
Author

qyihua commented Dec 3, 2021

Thank you in advance for your attention to this issue.
I think so. waiting for your good news :))

@tyt2y3
Copy link
Member

tyt2y3 commented Dec 4, 2021

Thank you for the contribution! I think this is pretty close.
I believe there could be a way to avoid adding the impl inside the DeriveActiveModel.
Instead, it should be possible to default implement it inside ActiveModelTrait by relying on set.
And perhaps the method could be set_json_value.
Because in my opinion I thought set_from_json is a massive assignment that would iterate over the json keys.

@billy1624
Copy link
Member

Hey @qyihua, please check PR #492

@qyihua
Copy link
Author

qyihua commented Jan 28, 2022

Thank

Hey @qyihua, please check PR #492

Hi, @billy1624 Thank you for your attention . The PR work for me

But in my use case, a web crawler, and the primary keys come from json. I think we also need to set primary keys come from json.

       // Restore primary key values
        for (col, active_value) in primary_key_values {
            match active_value {
                ActiveValue::Unchanged(v) | ActiveValue::Set(v) => self.set(col, v),
                // NotSet => self.not_set(col),
                NotSet => (), // do nothing
            }
        }

@billy1624
Copy link
Member

But in my use case, a web crawler, and the primary keys come from json. I think we also need to set primary keys come from json.

       // Restore primary key values
        for (col, active_value) in primary_key_values {
            match active_value {
                ActiveValue::Unchanged(v) | ActiveValue::Set(v) => self.set(col, v),
                // NotSet => self.not_set(col),
                NotSet => (), // do nothing
            }
        }

If it's implemented in this way the set_from_json method might be confusing. Consider the following cases...

  1. Given the original ActiveModel with primary key which is NotSet. After calling set_from_json method, primary key value will be Set(JSON_primary_key_value)

  2. Given the original ActiveModel with primary key which is Set(some_primary_key_value) or Unchanged(some_primary_key_value). After calling set_from_json method, primary key value will stay unchanged as Set(some_primary_key_value) or Unchanged(some_primary_key_value)

Thoughts? @qyihua @tyt2y3

@qyihua
Copy link
Author

qyihua commented Jan 29, 2022

To case 1, we can implement an associated function named from_json, this function will not backup or restore primary key values.
What do you think about it?

@billy1624
Copy link
Member

Hey @qyihua, yeah, this make sense. Check 50aeb1c

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 a pull request may close this issue.

3 participants