How to have SQL and database createdAt dates match exactly? - swift

I have an app which includes a functionality which allows users to send messages to one another. The process for sending the message involves saving the sent message to the sender's internal SQL database while at the same time saving the message to the overall app's database, which is a parse-server (and then actually send the message via push notifications to the recipient). What I am concerned about is with the discrepancy that is bound to arise between the 'createdAt' parameter for the message in the database and the same parameter for that message in the SQL database. The reason for the perceived discrepancy is because I have it set up as follows:
import Parse
func sendMessage(){
let newMessage = newMessage(messageString: "Hi", createdAt: Date())
//drop the newMessage into the SQL table... will save the Date() as a String at time A
let newMessageToDB = PFObject(className: "messages")
newMessageToDB["messageString"] = newMessage.messageString
//parse-server will automatically give the object a "createdAt" parameter when saved
newMessageToDB.saveInBackground()
//Since this is created automatically, the value will be the exact moment
//the object is saved to the DB, at time B
}
Since the createdAt value for SQL is created by Date(), this value is set at time A. The createdAt for the database is created by the completion of the async function saveInBackground() and will therefore be saved at time B. With this method, I can in no way guarantee that time A = time B. This creates the problem that for the recipient the time of the message will be time B from the database (which will be put into their SQL table with time B), while, for the sender, the time of the message will be time A.
My question:
1. First and foremost, does this discrepancy even matter? The entire explanation of the problem might be irrelevant if this difference doesn't actually create any issues.
2. If it is relevant and may cause issues, how do I fix it? I know I can send it to the server, and then read the value back immediately after it is created to get the database's 'createdAt' to be saved in the SQL statement, but is that inefficient? To save an object, then immediately read it back from the DB to extract a value?

Related

Firestore idempotency for a virtual wallet

I'm coming to you guys again for help. I have an app, in which you can top up your own virtual wallet from your credit card.
My structure is as followers:
collection transactions/{transactionId} -- contains an owner UID and the top up amount.
collection users/{userId}/personal/wallet containing { funds: 0 }
I am keeping the wallet into a subcollection of personal info because I don't want other users to see it.
Anyway, I have an onCreate trigger called walletSync for new documents under transactions collection, from which I take the amount and add it to the user's current funds via runTransaction.
Problem is, the function sometime triggers three times ... for one document. Here's a screenshot. I have logged the transactionId ( which is just one document ) and as you can see, onCreate, is being processed three times, therefore, topping up my wallet with three times the actual value.
Here's my function code, in which I am marking any processed transaction with processed: true so I can ignore it next time it triggers. As you can see in the logs, that processed_already message never pops up, which means the trigger does not fetch the new data, next time it triggers.
I am out of ideas here, please advise.
EDIT: Here's my function: https://pastebin.com/PRA7CbxL
I have managed to figure it out, with the help of the mighty #Doug and the mentioned similar questions.
After all, the issue with my code was that while I was marking my transaction as processed, I was assuming that the second / third time the function triggers, snapshot.data() would be the value of the document, ( took me some time to realize ) it was actually the change data sent towards the database trigger. I know it sounds confusing, but the function is not being sent the value of the document ( for that you could get snapshot.ref and fetch the data from there, in order to get the updated value in a transaction ).
So here's my updated function on Pastebin. Here's a screenshot of the code in action: https://i.imgur.com/fbQSGT6.png

update One2many Record using Write function called from Onchange (Odoo10)

I am trying to update my One2many record when Onchange method is called. I am fetching the exact value but its not updating the field. Neither I am getting any error.
class abc_1(models.Model):
_name = 'abc.1'
field_a1 = fields.Many2one('crm.team',required=True)
field_a2 = fields.One2many('abc.2', 'field_b1')
#api.onchange('field_a1')
#api.depends('field_a1')
def get_records(self):
for res in self.field_a1.member_ids:
print res.name
self.write({'field_a2':(0,0,'field_b2':res.name})]})
class abc_2(models.Model):
_name = 'abc.2'
field_b1 = fields.Many2one("abc.1")
field_b2 = fields.Char(string='Sales Person')
I am trying to update records fetched but its not doing anything.
First #api.depends trigger onchange event so you don't need
#api.onchange.
Second don't update the database in onchange event because you will commit
changes to the database what if the user click on cancel button the changes
all ready saved in database.
Third if this code works your querying a lot the database, because any call to write will execute update in database. don't query the database inside a loop only if there is no other solution
Four self.write do not work in #api.onchange because the self is not the record is not the real record it's just a dummy record that holds the data passed from
the client side you can check the id you find is always NewID object even if the record is saved in database. usually you find the real record in self._origin (in new API, or list of ids in old API) in
this you can call write and change the values in database a danger thing to do.
#api.depends('field_a1')
def get_records(self):
# when you use depends, best thing you do is to loop so won't get
# singleton error
for rec in self
# create a list to hold the cammands to create the records
res_ids = []
for res in self.field_a1.member_ids:
# now just add the cammand to the list
res_ids.append((0,0,{
'field_b2':res.name,
})
# if you will affect only one element just
# but if more use rec.update() works like rec.write()
# but with onchange it don't save data in database.
rec.field_a2 = res_ids

How to save one value of Parse object without overwriting entire object?

I have two users accessing the same object. If userA saves without first fetching the object to refresh their version, data that userB has already successfully saved would be overwritten. Is there any way(perhaps cloud code?) to access and update one, and only one, data value of a PFObject?
I was thinking about pushing the save out to the cloud, refreshing the object once it gets there, updating the value in the cloud, and then saving it back. However that's a pain and still not without it's faults.
This seems easy enough, but to me was more difficult than it should have been. Intuitively, you should be able to filter out the fields you don't want in beforeSave. Indeed, this was the advice given in several posts on Parse.com. In my experience though, it would actually treat the filtering as deletions.
My goal was a bit different - I was trying to filter out a few fields and not only save a few fields, but translating to your context, you could try querying the existing matching record, and override the new object. You can't abort via response.failure(), and I don't know what would happen if you immediately save the existing record with the field of interest and null out the request.object property - you could experiment on your own with that:
Parse.Cloud.beforeSave("Foo", function(request, response) {
// check for master key if client is not end user etc (and option you may not need)
if (!request.master) {
var query = new Parse.Query("Foo");
query.get(request.object.id).then(function(existing) {
exiting.set("some_field", request.object.get("some_field"));
request.object = exiting; // haven't tried this, otherwise, set all fields from existing to new
response.success();
}, function(error) {
response.success();
});
}
});

Mobile Services Offline PullAsync only retrieves data where updatedAt date > the latest record

I am using offline data sync in Mobile Services and the following function only retrieves data where UpdatedAt > the largest updatedAt in the userTable:
await userTable.PullAsync(userTable.Where(a => a.UserName == userName));
The first time this function is executed, it retrieves my data correctly. The second time the function executes, whith a different username, it will only retrieve data where UpdatedAt is greater than the greatest UpdatedAt datetime that is already present in my SQLite db. If I change an UpdatedAt field in the backend (by setting it to DateTime.Now), this record is retrieved. Is this by design or am I missing something?
For anybody else having issues with this: I have started another thread here where you will find the complete answer
Basically what it comes down to is this:
This will retrieve all records from the backend where username is donna:
await itemTable.PullAsync(itemTable.Where(a => a.UserName == "donna"));
This will retrieve all records where username is "donna" the first time, and after that only updated records. (incremental)
await itemTable.PullAsync("itemtabledonna", itemTable.Where(a => a.UserName == "donna"));
The first parameter is the queryKey. This is used to track your requests to the backend. A very important thing to know is that there is a restriction on this queryKey:
^[a-zA-Z][a-zA-Z0-9]{0,24}$
Meaning: alphanumeric characters, max 25 characters long. So no hyphens either (at the time of this writing). If your queryKey does not match this regex, no recrods will be returned. There is currently no exception thrown and no documentation on this.
PullAsync() is supposed to use an incremental sync (getting only records what have a newer date than the last record it retrieved) when you pass in a query key. If not, it should execute your query and pull down all matching records.
It sounds like a bug is occurring if you are getting that behavior without passing in a query key.
Also, in the incremental sync case, it is not the latest updated at in the SQLite DB but a cached version from the last time PullAsync() was ran (that was cached under the given query key)
Your updatedAt column also by default will have a trigger that causes its timestamp to update whenever the row is modified, so you shouldn't have to take any additional actions when using incremental sync.
If the above is not what you are seeing, I recommend submitting a github bug against azure-mobile-services so it can be reviewed.

atk4.2 form submit-how to get new record id before insert to pass in arguments

I am referencing the 2 step newsletter example at http://agiletoolkit.org/codepad/newsletter. I modified the example into a 4 step process. The following page class is step 1, and it works to insert a new record and get the new record id. The problem is I don't want to insert this record into the database until the final step. I am not sure how to retrieve this id without using the save() function. Any ideas would be helpful.
class page_Ssp_Step1 extends Page {
function init(){
parent::init();
$p=$this;
$m=$p->add(Model_Publishers);
$form=$p->add('Form');
$form->setModel($m);
$form->addSubmit();
if($form->isSubmitted()){
$m->save();//inserts new record into db.
$new_id=$m->get('id');//gets id of new record
$this->api->memorize('new_id',$new_id);//carries id across pages
$this->js()->atk4_load($this->api->url('./Step2'))->execute();
}
}
}
There are several ways you could do this, either using atk4 functionality, mysql transactions or as a part of the design of your application.
1) Manage the id column yourself
I assume you are using an auto increment column in MySQL so one option would be to not make this auto increment but use a sequence and select the next value and save this in your memorize statement and add it in the model as a defaultValue using ->defaultValue($this->api->recall('new_id')
2) Turn off autocommit and create a transaction around the inserts
I'm from an oracle background rather than MySQL but MySQL also allows you to wrap several statements in a transaction which either saves everything or rollsback so this would also be an option if you can create a transaction, then you might still be able to save but only a complete transaction populating several tables would be committed if all steps complete.
In atk 4.1, the DBlite/mysql.php class contains some functions for transaction support but the documentation on agiletoolkit.org is incomplete and it's unclear how you change the dbConnect being used as currently you connect to a database in lib/Frontend.php using $this->dbConnect() but there is no option to pass a parameter.
It looks like you may be able to do the needed transaction commands using this at the start of the first page
$this->api->db->query('SET AUTOCOMMIT=0');
$this->api->db->query('START TRANSACTION');
then do inserts in various pages as needed. Note that everything done will be contained in a transaccion so if the user doesnt complete the process, nothing will be saved.
On the last insert,
$this->api->db->query('COMMIT');
Then if you want to, turn back on autocommit so each SQL statement is committed
$this->api->db->query('SET AUTOCOMMIT=1');
I havent tried this but hopefully that helps.
3) use beforeInsert or afterInsert
You can also look at overriding the beforeInsert function on your model which has an array of the data but I think if your id is an auto increment column, it won't have a value until the afterInsert function which has a parameter of the Id inserted.
4) use a status to indicate complete record
Finally you could use a status column on your record to indicate it is only at the first stage and this only gets updated to a complete status when the final stage is completed. Then you can have a housekeeping job that runs at intervals to remove records that didn't complete all stages. Any grid or crud where you display these records would be limited with AddCondition('status','C') in the model or added in the page so that incomplete ones never get shown.
5) Manage the transaction as non sql
As suggested by Romans, you could store the result of the form processing in session variables instead of directly into the database and then use a SQL to insert it once the last step is completed.