Search code examples
rustnearprotocol

Access the vector state and updating it using near-sdk-rs


I am trying to update the vector, this my code so far.

use near_sdk::collections::Map;
use near_sdk::collections::Vector;
#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct ProfileDetails {
    profile_tags: Map<String, ProductList>,
}

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize, Debug)]
pub struct Product {
    product_name: String,
    product_details: String,
}

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct ProductList {
    products: Vector<Product>,
}

#[near_bindgen]
impl ProfileDetails {
    pub fn set_profile(&mut self, product_name: String, product_details: String) {
        let account_id = String::from("amiyatulu.test");
        println!("{}", product_name);
        let p = Product {
            product_name,
            product_details,
        };
        let id = account_id.clone().into_bytes();
        let mut id_products = ProductList {
            products: Vector::new(id),
        };
        id_products.products.push(&p);
        self.profile_tags.insert(&account_id, &id_products);
    }

    pub fn push_product_to_profile(&mut self, product_name: String, product_details: String) {
        let account_id = String::from("amiyatulu.test");
        let p = Product {
            product_name,
            product_details,
        };
        let my_products_option = self.profile_tags.get(&account_id);
        match my_products_option {
            Some(mut my_products) => {
                my_products.products.push(&p); //It doesn't update the state
                self.profile_tags.insert(&account_id, &my_products);
                println!("Hello myproducts push");
            }
            None => println!("Can't get the profile tag"),
        }
    }

The problem is this statement doesn't update the state of the blockchain.

my_products.products.push(&p); //It doesn't update the state

So, I have inserted the vector again in this statement.

self.profile_tags.insert(&account_id, &my_products);

Is this the right way? Does it cause repeated storage of ProductList's product vector? How to get the vector state and update it?

Complete code here


Solution

  • In Rust, the result of match is frequently immutable. So in your example:

    Some(mut my_products) => {
    

    although having mut, does not mean it's mutating (changing) the value that's ultimately associated with self.

    So your approach with self…insert is correct. This is a common approach in Rust generally. There are exceptions to this rule in Rust that may highlight what's going on.

    For instance, in the standard collection HashMap there's get_mut (as opposed to get) which returns a mutable reference. You can see in the Rust docs that this returns Option<&mut V> while the HashMap's get method returns Option<&V>.

    Now we can look at the docs for the NEAR Vector get method to see that get returns Option<T> which is immutable.

    So again, your approach looks good here to me.

    In terms of modifying the Vector, I believe you're likely going to want to use the Set collection instead, but I dislike it when StackOverflow questions change the subject like that. So below I've provided a test you can add that demonstrates some of the Vector usage I believe you're looking for.

        #[test]
        pub fn modify_update_product_list() {
            let context = get_context(vec![], false);
            testing_env!(context);
    
            // first product
            let product_one = Product {
                product_name: "toothbrush".to_string(),
                product_details: "electric and sleek".to_string(),
            };
            let product_two = Product {
                product_name: "soap".to_string(),
                product_details: "smells like teen spirit".to_string(),
            };
            let product_three = Product {
                product_name: "shampoo".to_string(),
                product_details: "natural mint oils".to_string(),
            };
    
            let mut profile_detail = ProfileDetails::default();
    
            let mut product_list = ProductList {
                products: Vector::new(b"products".to_vec()),
            };
            println!("Corner Store: adding toothbrush and soap…");
            product_list.products.push(&product_one);
            product_list.products.push(&product_two);
    
            let shop_name = "Corner Store".to_string();
            profile_detail.profile_tags.insert(&shop_name, &product_list);
    
            // Say we come back later to the saved state
            // Get the product list from Corner Store
            match profile_detail.profile_tags.get(&shop_name) {
                Some(mut current_product_list) => {
                    // add shampoo product
                    println!("Corner Store: Inserting shampoo…");
                    current_product_list.products.push(&product_three);
                    profile_detail.profile_tags.insert(&shop_name, &current_product_list);
    
                },
                None => {
                    // if this were outside of tests we'd have:
                    // env::log(b"Didn't find shop name");
                    println!("Didn't find shop name")
                }
            }
    
            let product_list = profile_detail.profile_tags.get(&shop_name);
            assert!(product_list.is_some()); // can also have is_none()
    
            println!("Corner Store: Listing products");
            for product in product_list.unwrap().products.iter() {
                println!("Corner Store: Product {:?}", product);
            }
    
            // Sold out of soap, remove it
            println!("Corner Store: Removing index 1, which should be soap");
            // Consider using something like Set
            // https://docs.rs/near-sdk/0.10.0/near_sdk/collections/struct.Set.html
            let current_product_list = profile_detail.profile_tags.get(&shop_name);
            assert!(current_product_list.is_some(), "Couldn't find shop");
    
            // Remove and get object at index 1
            let soap = current_product_list.unwrap().products.swap_remove(1);
            println!("Corner Store: Removed index 1 which was {:?}", soap);
        }