I'm trying to learn Salesforce.com's Apex programming language and I have an example of some code here that comes from the book "Development with the Force.com Platform" by Jason Ouellette. I'm still learning the basics so please bear with me. To put this code in context, there's a Services Manager sample application that goes throughout the book and I'm examining an Apex trigger device they have written that is suppose to make sure timecards have a valid assignment. An assignment is a record indicating that a resource is staffed on a project for a certain time period. A consultant (aka resource) can enter a timecard only for a project and time period he or she is authorized to work. Resource_c is a parent to both Assignment_c and Timecard_c objects.
So here 开发者_如何学运维is the code they give me for the trigger and the corresponding apex class. I have been attempting to break it down and comment/question on it line by line to make sense of its logic. However, I am still missing some fundamentals here, feel free to help me decipher this.
5-57 The Trigger
trigger validateTimecard on Timecard__c (before insert, before update) {
TimecardManager.handleTimecardChange(Trigger.old, Trigger.new);
// TheApexClass.methodThatDoesWork(variable, variable)
// So there are 2 parameters which are 2 lists, Trigger.old and Trigger.new.
// Which means, when this method is called it needs these 2 lists
// to process it's block of code, right?
// Why are they called Trigger.old, Trigger.new? Does the order of variables matter?
}
5-58 - The Apex class - which does the work of validating the timecard on behalf of the trigger.
public class TimecardManager {
public class TimecardException extends Exception {}
public static void handleTimecardChange(List<Timecard__c> oldTimecards, List<Timecard__c> newTimecards) {
// Identifying 2 lists of Timecards as parameters, oldTimecards and newTimecards
// within the class. How is this associated with the trigger parameters
// that were seen in the trigger above. Are they the same parameters with
// different names? Why are they named differently here? Is it better to
// write the trigger first, or the apex class first?
Set<ID> resourceIds = new Set<ID>(); // making a new set of primitive data type ID called resourceIds
for (Timecard__c timecard : newTimecards) {
// This for loop assigns the timecard variable record to the list of newTimecards
// and then executes the block of code below for each.
// The purpose of this is to identify all the resources that have timecards.
resourceIds.add(timecard.Resource__c);
// It does this by adding the Timecard_c's relationship ID from each parent record Resource_c to the resourceIds set.
// For clarification, Resource_c is a parent to both
// Assignment_c and Timecard_c objects. Within the Timecard_c object, Resource_c
// is a Master-Detail data type. Is there a relationship ID that is created
// for the relationship between Resource_c and Timecard_c?
}
List<Assignment__c> assignments = [ SELECT Id, Start_Date__c, End_Date__c, Resource__c FROM Assignment__c WHERE Resource__c IN :resourceIds ];
// The purpose of this is to make a list of selected information from Assignments_c that have resources with timecards.
if (assignments.size() == 0) {
// If there isn't a Resource_c from Assignments_c that matches a Resource_c that has a Timecard_c,
throw new TimecardException('No assignments'); // then an exception is thrown.
}
Boolean hasAssignment; // creation of a new Boolean variable
for (Timecard__c timecard : newTimecards) { // so for every newTimecards records,
hasAssignment = false; // set Boolean to false as default,
for (Assignment__c assignment : assignments) { // check through the assignments list
if (assignment.Resource__c == timecard.Resource__c && // to make sure the Resources match,
timecard.Week_Ending__c - 6 >= assignment.Start_Date__c && // the end of the timecard is greater than the assignment's start date,
timecard.Week_Ending__c <= assignment.End_Date__c) { // and the end of the timecard is before the assignment's end date.
hasAssignment = true; // if these all 3 are correct, than the Timecard does in fact have an assignment.
break; // exits the loop
}
}
if (!hasAssignment) { // if hasAssignment is false then,
timecard.addError('No assignment for resource ' + // display an error message
timecard.Resource__c + ', week ending ' +
timecard.Week_Ending__c);
}
}
}
}
Thank you for your help.
1. What is Trigger.old/Trigger.new : ?
Trigger.new/Trigger.old are static collections available for any Apex code running under Trigger context i.e. firing directly in trigger or in any class called by Trigger.
Apex even gives you Trigger.newMap and Trigger.oldMap, that returns a Map instead of sobject list.
The sole purpose of these collections depends in which event the trigger is fired, for example if event is "before insert" or "after insert" Trigger.old will be of no significance and thus not available. Trigger.old is always used to compare changes done during record updates.
2. Does order matters : It only depends on your logic, in this Timecard manager case, as the method "handleTimecardChange" expects old timecards before new, so you need to pass Trigger.old as first argument.
3. Does it needs to be list ? : Again it depends on your implementation, Trigger.new/old returns a list where sobject is the one on which trigger is written. Its also not mandantory to pass Trigger.new/old as argument, but its good practice to keep your Apex class decoupled from the Trigger context. It makes unit testing easier.
Hope this helps, read Apex language reference first for deeper understanding about Apex language in general. Jason O. book is awesome, but you need to understand the basics first. Here is the link to Apex Language Reference : http://www.salesforce.com/us/developer/docs/apexcode/index.htm
Trigger's new
, newMap
, old
and oldMap
are only available depending on the type of DML you are tracking.
DELETE = only old is available
INSERT = only new is available
UPDATE = both old and new are available and order of entries in the lists is matching (e.g. new[1] replaces old[1]). new contains new values, old contains old values (but ID is always the same) so you can compare and check if a certain filed is changed.
You should ALWAYS treat new/old as multi-entry lists (or maps for *Map). Never assume there will only be ONE entry, as with regular SQL a batch update operation will only invoke a trigger once giving you a list of old and new rows. You have to iterate through all changed rows and apply whatever logic you have.
In this example, oldTimecards parameter is not being used at all in the static method, therefore it didn't need to be referenced at all either. In all likelihood it also wasted resources since SF had to build a whole old list without you even using it (though, not sure if they optimize it at all). Since you control oth the trigger code and the supporting class code pass only what you need.
精彩评论