Conditional associated record deletion in afterDelete() - cakephp

I have the following setup:
Models:
Team
Task
Change
TasksTeam
TasksTeam is a hasManyThrough, that associates teams to tasks. Change is used to record changes in the details of tasks, including when teams are attached/detached (i.e. through records in TasksTeam).
TasksTeam also cascades deletes of Task. If a task is deleted, all related team associations should also be deleted.
When a TasksTeam is deleted, it means a team has left a task, and I'd like to record a Change for that. I'm using the TasksTeam afterDelete() to record teams leaving. In the TasksTeam beforeDelete I save the data to $this->predelete so it'll be available in the afterDelete().
Here is the non-working code in TasksTeam:
public function afterDelete(){
$team_id = $this->predelete['TasksTeam']['team_id'];
$task_role_id = $this->predelete['TasksTeam']['task_role_id'];
$task_id = $this->predelete['TasksTeam']['task_id'];
// Wanted: only record a change if the task isn't deleted
if($this->Task->exists($task_id)){
$this->Task->Change->removeTeamFromTask($task_id, $team_id, $task_role_id);
}
return true;
}
Problem:
When a task is deleted, the delete cascades to TasksTeam correctly. However, a change will be recorded even if the Task is deleted. From another answer to something similar on SO, I think the reason is that the callbacks are called before Model:del(), meaning the task hasn't yet been deleted when it hits TasksTeam afterDelete()
Question
How can I successfully save a Change only if the task isn't deleted?
Thanks in advance.

If the callbacks are getting called before the actual delete, I see maintaining an assoc. array of flags with task IDs as keys, or a set of task IDs, which are added when afterDelete is called on Task. Then you could create a method in Task, such as isDeleting or similar, which queries the array, to tell you if the task is in the process of being deleted.

Using the suggestion from #James Dunne I ended up adding a tinyint field to the Task model called is_deleted and simply set this boolean true in the Task beforeDelete(). I then check for this flag and only save a Change if the flag is boolean false. It seems wasteful to add a field for something that is only affected just before the record is deleted, but for my purposes it works fine. I think a "real solution" would involve the Cake Events System , avoiding the need for chained callbacks.

Related

execute logic beforedelete event trigger

Before deleting the record (Ex: Account obj record), I want to update the field on the account record and send it to content management, hold it for a few seconds, and then delete it.
For this scenario, I used beforedelete event and updated the fields in the record, and called the content management with updated record data. The record is updated with new values (i verified after restoring it from recycle bin), But it is not calling the content management before deleting the record. Is there any option that we can wait for a few seconds until the record is updated on content management and delete the record? Please share your suggestions. Thank you.
You can't make a callout straight from a trigger (SF database table/row can't be locked and held hostage until 3rd party system finishes, up to 2 minutes), it has to be asynchronous. So you probably call from #future but by then the main trigger finished, the record is deleted, if you passed an Id - probably the query inside #future returns 0 rows.
Forget the bit about "holding it for few seconds". You need to make some architecture decisions. Is it important that delete succeeds no matter what? or do you want to delete only after the external system acknowledged the message?
You could query your record in the trigger (or take whole trigger.old) and pass to the future method? It's supposed to take only primitives, not objects/collections but you could always JSON.serialize it before passing as string.
You could hide the standard delete button and introduce custom one. There you'd have a controller which can make the callout, wait till success response comes back and then delete?
You could rethink the request-response thing. What if you make the callout (or raise platform event?) and it's the content management system that then reaches to salesforce and deletes (via REST API for example).
What if you just delete right away, hope they stay in recycle bin and then external system can query the bin / make special getDeleted call and pull the data.
See Salesforce - Pull all deleted cases in Salesforce for some more bin-related api calls.

Salesforce : How to prevent 'upsert' (executed from an apex Job) of a record, if the record has a checkbox field which is marked false?

Salesforce question :
We have to update the accounts from a schedulable Job. New records inserting are no problem, but existing records should only be updated if a particular checkbox field is set to true on that (otherwise not to be updated). Also since we know that the apex code runs from a system context.
I am looking for a way which DOES NOT involve pulling the record from the code by searching using the Id and then checking that field value before upserting.
Thank you for helping.
Code
List<Account> accountList = new List<Account>(accountsToUpdate);
upsert accountList MY_COMPOSITE_KEY__c;
Make a validation rule simply has Your_Checkbox__c as error condition. Or make a before insert, before update trigger on Account (if you don't have one already) that would inspect all records in trigger.new and call addError() on them. Validation rule is slightly preferred because it's just config, no code.
The problem with either is that it will cause your whole transaction to die. If your batch updates 200 accounts and one of them has this checkbox - this fail will block updating them all. This is done to ensure system's state is stable (read up about "atomic operations" or "ACID"), you wouldn't want data that's halfway updated...
So probably you'll have to mitigate that by calling Database.upsert(accountsToUpdate, External_ID__c, false); so it saves what it can and doesn't throw exceptions...

Does DynamoDB has support for Tombstone Record handling?

How do we handle the case in dynamoDB when there is a older put request for a key for which there has been a newer delete operation already performed.
Since the newer delete operation has already deleted the record, the older put request can simply write the record again which is not correct.
Does DynamoDB save any metadata for recently deleted records?
Is there any way to handle this case in DynamoDB? Or anyone has any suggestion how to handle this case.
I am using high level DynamoDBMapper instead of low level API.
Dynamo doesn't keep any 'metadata' about what has been previously deleted. Considering you can't create a new attribute to keep track of deleted status, the only two ways I can think for you to handle this are:
Option 1: create your own 'metadata' table
Create a separate table to keep track of everything you deleted. You'd have a main table where you store your regular data, and a main_deleted table, where you store only primary_keys that have been deleted from the main table.
Before inserting any item in the main table, check for the primary_key in the main_deleted table. If it's there, do not proceed with the insert.
Option 2: use the range_key
If your items have a sort key, you could use it to flag items as deleted without creating a new attribute. Suppose you have this item where the range_key is a UNIX timestamp:
{
"primary_key": "example",
"timestamp": 1234567890,
"other": "stuff"
}
Instead of deleting the item primary_key=example, remove all its attributes and set the timestamp to a value that you'd never use for regular items, such as 0. When inserting or updating items in the table, use a condition expression or query the database previously to check if the item was not deleted before (in other words, timestamp=0).
I'm sure there will be plenty of other ways and maybe (or probably) those two above aren't the best ones. Use your creativity! ;)
I believe you can use ConditionExpression to check whether the data is already exists before updating (i.e. putting) the item. Both UpdateItem and PutItem have ConditionExpression to check some condition before performing the action.
Refer this conditional writes
In your case, you need to check whether the hash key exists before performing the update operation.

Cakephp - Model Transactions for insert / update / delete

I have a table that has several fields, 2 of these fields are "startdate" and "enddate", which mark the validity of the record. If i insert 1 new record, the new record cannot overlap with other records in terms of start date and end date.
Hence on insertion of new record i may need to adjust the value of "startdate" and "enddate" of pre-existing records so they don't overlap with the new record. Similarly, any preexisting records that have 100% overlap with the new record, will need to be deleted.
My table is an InnoDB table, which i know supports such transactions.
Are there any examples which show use of insert / update / delete using transactions (all must succeed in order for any one of them to succeed and be commited) ?
I don't know how to do this. Most examples only show the use of saveAssociated() which i'm not sure is capable of catering for delete operations?
Thanks
Kevin
Perhaps you could use the beforeSave callback to search for the preexisting records and delete them before saving your new record.
from the docs:
Place any pre-save logic in this function. This function executes immediately after model data has been successfully validated, but just before the data is saved. This function should also return true if you want the save operation to continue.
I think you're looking to do Transactions: http://book.cakephp.org/2.0/en/models/transactions.html
That should allow you to run your queries - you start a transaction, perform any required actions, and then commit or rollback based on the outcome. Although, given your description I'd think doing some reads and adjusting your data before committing anything might be a better approach. Either way, transactions aren't a bad idea though!

Safely deleting a Django model from the database using a transaction

In my Django application, I have code that deletes a single instance of a model from the database. There is a possibility that two concurrent requests could both try to delete the same model at the same time. In this case, I want one request to succeed and the other to fail. How can I do this?
The problem is that when deleting a instance with delete(), Django doesn't return any information about whether the command was successful or not. This code illustrates the problem:
b0 = Book.objects.get(id=1)
b1 = Book.objects.get(id=1)
b0.delete()
b1.delete()
Only one of these two delete() commands actually deleted the object, but I don't know which one. No exceptions are thrown and nothing is returned to indicate the success of the command. In pure SQL, the command would return the number of rows deleted and if the value was 0, I would know my delete failed.
I am using PostgreSQL with the default Read Commited isolation level. My understanding of this level is that each command (SELECT, DELETE, etc.) sees a snapshot of the database, but that the next command could see a different snapshot of the database. I believe this means I can't do something like this:
# I believe this wont work
#commit_on_success
def view(request):
try:
book = Book.objects.get(id=1)
# Possibility that the instance is deleted by the other request
# before we get to the next delete()
book.delete()
except ObjectDoesntExist:
# Already been deleted
Any ideas?
You can put the constraint right into the SQL DELETE statement by using QuerySet.delete instead of Model.delete:
Book.objects.filter(pk=1).delete()
This will never issue the SELECT query at all, just something along the lines of:
DELETE FROM Book WHERE id=1;
That handles the race condition of two concurrent requests deleting the same record at the same time, but it doesn't let you know whether your delete got there first. For that you would have to get the raw cursor (which django lets you do), .execute() the above DELETE yourself, and then pick up the cursor's rowcount attribute, which will be 0 if you didn't wind up deleting anything.

Resources