Регистрация  |  Вход

Работа с объектами. Расширение стандартного функционала объектов за счет триггеров.

Операции create, read, update, delete являются основным функционалом любого объекта в salesforce. Но что делать, когда этого функционала не хватает, а нам необходимо добавить какие нибудь проверки или дополнительную логику при создании, изменении или удалении объекта?

На помощь приходят триггеры - замечательный инструмент, который позволяет отрабатывать логику в ответ на события при работе с базой данные.





О том что такое триггеры и как они работают я уже писал в статьях Apex Triggers - триггеры в Salesforce и Контекст триггера - trigger runtime context.

Сейчас давайте применим эту технологию в нашем приложении для объекта Card_Item. Сформулируем требования к объекту, которые будет контролировать триггер  исходя из логики:


  • нельзя добавить в читательский билет книгу, которая уже находится на руках, т.е. для которой аналогичная запись уже существует

  • если запись создается, задавать для нее Status In Progress несмотря на то что выбрали в  списке

  • при изменении записи можно поменять Status c In Progress (или Expired) на Returned или Missing только один раз. После этого поменять статус будет нельзя.

  • Запретим удаление записи. Будем их хранить в качестве истории.


Значение Expired в поле Status будет выставляться автоматически спустя определенное время. Для этого воспользуемся time-based workflow о чем я расскажу в следующей статье.

Но хочу отметить, что использование триггера это крайний случай и требуется только при использовании стандартного функционала salesforce для работы с объектами или если на страницу не получается добавить свою логику. Как показывает практика, лучше отказаться от стандартных страниц new, edit, list и перенести всю логику в Visualforce pages и контроллеры. Почему? Могу много рассказать на эту тему. Если интересно, прошу задавайте вопросы на форуме. Далее мы так и сделаем в нашем приложении - уберем все стандартные страницы и создадим красивый понятный интерфейс и кучей логики в контроллере.

Вот мы и добрались до программирования :). Не буду вас учить плохому - программировать в браузере, хотя salesforce предоставляет такую возможность. Будем считать это пожарным выходом, когда вы находитесь в гостях у знакомой девушки и тут посреди ночи звонит заказчик (по закону подлости у них в это время день) и просит срочно исправить баг или что-то быстро поменять. Для программирования есть Eclipse с плагином Force.com IDE.

Если вы еще не знаете как им пользоваться, читаем статьи Рабочее место Salesforce Developer - Force.com IDE и Работаем в Force.com IDE (Eclipse)

Создаем триггер для объекта Card_Item__c. Вот первая часть триггера, которая срабатывает на Before Insert и будет проверять два первых пункта.
trigger Card_Item on Card_Item__c (before delete, before insert, before update) {


if (Trigger.isInsert) {
Set<Id> BookIds = new Set<Id>();
for (Card_Item__c ci : Trigger.new) {
BookIds.add(ci.Book__c);
}
List<Card_Item__c> AvailableCardItems = [SELECT Id, Book__c FROM Card_Item__c WHERE Book__c in :BookIds AND Status__c in ('In progress', 'Expired', 'Missing')];
Set<Id> BooksInCardsIds = new Set<Id>();
for (Card_Item__c ci : AvailableCardItems) {
BooksInCardsIds.add(ci.Book__c);
}
for (Card_Item__c ci : Trigger.new) {
if (BooksInCardsIds.contains(ci.Book__c)) {
ci.Book__c.addError('This book is not available');
}
ci.Status__c = 'In progress';
}
}

}

Посмотрим на результаты работы триггера. Создадим запись для объекта Card_Item не указывая Status.

salesforce-trigger-1 salesforce-trigger-2

Видим, что в поле status установлено значение, которое мы задали в триггере. Отлично. Попробуем создать еще одну запись для той же книги.

salesforce-trigger-3

Не получилось создать аналогичную запись. На экране видна ошибка, которую мы задали в триггере.

Допишем триггер для остальных случаев.
...

if (Trigger.isUpdate) {
for (Card_Item__c ci : Trigger.new) {
if (ci.Status__c != Trigger.oldMap.get(ci.Id).Status__c && (Trigger.oldMap.get(ci.Id).Status__c == 'Returned' || Trigger.oldMap.get(ci.Id).Status__c == 'Missing')) {
ci.Status__c.addError('You can\'t change status from Returned or Missing');
}
}
}

if (Trigger.isDelete) {
for (Card_Item__c ci : Trigger.old) {
ci.addError('You can\'t delete Card Item records');
}
}
...

Теперь при попытке заменить статус для записи на неразрешенное значение будет выводиться ошибка:

salesforce-trigger-4

а при попытке удалить запись:

salesforce-trigger-5

Вот собственно и все. Хочу заметить что аналогичные проверки можно сделать с помощью Validation Rules. Validation Rules считаются предпочтительнее с той точки зрения что заказчик может в любой момент отключить их  или заменить логику или выводимые сообщения. Но они недостаточно универсальны и гибки как триггеры.

Большим минусом реализации ограничений на триггерах является, то что в триггеры записи приходят пачками. Во-первых, это необходимо учитывать и для новичков это является большой проблемой написать правильный триггер, который не вывалится по лимитам. Во-вторых, пачка данных является неизменной и если ошибка появляется на одной записи, то вся пачка не будет сохранена.

В общем идеальным решением будет вынести логику по проверке в контроллер Visualforce page, которую мы разработаем на следующем этапе.