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

Реализация бизнес логики и ее валидация в контроллере

Всем привет,

как вы знаете, в СФ есть стандартные средства для реализации бизнес логики и ее валидации. Я говорю о Validation Rules, Dependable Pick-lists, привязка опций Pick-list к Record type и обязательные для определенных layout поля. И все это прекрасно работает со стандартными лейаутами.

Но что если стандартный лейаут не единственный способ создания/апдатирования записей? например есть ВФ страница, позволяющая пользователю делать массовую загрузку записи. При этом в контроллере относительно легко организовать валидацию данных по их типу и формату, то что делать Validation Rules? пользователь пытается загрузить запись и тут Validation Rule выбрасывает исключение, из которого можно извлечь сообщение и с какой то другой полезной информации показать пользователю, чтобы он понял, что пошло не так и как это исправить.

Но это долгий и изнурительный для пользователя процесс, когда он бьется то с одним Validation Rule, то с другим, а потом вдруг ошибка о неверной опции для пиклиста, которая не работает с текущим Record type...

в-общем вопрос в том, чтобы изначально отвалидировать пользовательские данные, показать пользователю результат и проблемы, если они есть, и только потом спокойно делать ДМЛ.

но как реализовать в коде ту же логику, что есть в Validation Rules (и зависимых пик-листах), но без хард-кода? не сомневайтесь, как только я "захардкодю" в контроллере 20 Validation Rules, что есть на том объекте, и задвину код в прод, как заказчики тут же внесут изменения в Validation Rules.

есть какие то best practices на этот счет?

Всем привет,

как вы знаете, в СФ есть стандартные средства для реализации бизнес логики и ее валидации. Я говорю о Validation Rules, Dependable Pick-lists, привязка опций Pick-list к Record type и обязательные для определенных layout поля. И все это прекрасно работает со стандартными лейаутами.

Но что если стандартный лейаут не единственный способ создания/апдатирования записей? например есть ВФ страница, позволяющая пользователю делать массовую загрузку записи. При этом в контроллере относительно легко организовать валидацию данных по их типу и формату, то что делать Validation Rules? пользователь пытается загрузить запись и тут Validation Rule выбрасывает исключение, из которого можно извлечь сообщение и с какой то другой полезной информации показать пользователю, чтобы он понял, что пошло не так и как это исправить.

Но это долгий и изнурительный для пользователя процесс, когда он бьется то с одним Validation Rule, то с другим, а потом вдруг ошибка о неверной опции для пиклиста, которая не работает с текущим Record type...

в-общем вопрос в том, чтобы изначально отвалидировать пользовательские данные, показать пользователю результат и проблемы, если они есть, и только потом спокойно делать ДМЛ.

но как реализовать в коде ту же логику, что есть в Validation Rules (и зависимых пик-листах), но без хард-кода? не сомневайтесь, как только я "захардкодю" в контроллере 20 Validation Rules, что есть на том объекте, и задвину код в прод, как заказчики тут же внесут изменения в  Validation Rules.

есть какие то best practices на этот счет?

Единственное решение - отказаться от Validation Rules в пользу кастомных валидаторов на базе кода. Либо хардкодить, либо сделать что-то посерьезнее на базе программируемых rules в Custom Settings. Validation rules и прочая лабуда срабатывает только в DML и по порядку - тут уже никуда не деться

Единственное решение - отказаться от Validation Rules в пользу кастомных валидаторов на базе кода. Либо хардкодить, либо сделать что-то посерьезнее на базе программируемых rules в Custom Settings. Validation rules и прочая лабуда срабатывает только в DML и по порядку - тут уже никуда не деться

А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?

А если забирать Validation Rules errorConditionFormula через Metadata API  и прогонять объекты через эти conditions?

akr0bat
А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?

Это уже тянет на целый пакет на котором можно бабло рубить.

[quote="akr0bat"]А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?[/quote]
Это уже тянет на целый пакет на котором можно бабло рубить.

Дима, надо плюсики/лайки делать на сообщения ;-)

Дима, надо плюсики/лайки делать на сообщения ;-)

Форум уже много лет работает по принципе "Работает, не трогай"
Я уже не видел Ruby on Rails в глаза больше 3-х лет.

Была попытка переплить движек с нуля на Django. НО ... опять приходится зарабатывать презренные деньги

Форум уже много лет работает по принципе "Работает, не трогай" :(  
Я уже не видел Ruby on Rails в глаза больше 3-х лет.

Была попытка переплить движек с нуля на Django. НО ... опять приходится зарабатывать презренные деньги :( 

akr0bat
А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?

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

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

[quote="akr0bat"]А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?[/quote]

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

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

akr0bat
А если забирать Validation Rules errorConditionFormula через Metadata API и прогонять объекты через эти conditions?

Если я правильно понял, то errorConditionFormula это строка и придется ее парсить и самому реализовывать функции из формулы?

[quote="akr0bat"]А если забирать Validation Rules errorConditionFormula через Metadata API  и прогонять объекты через эти conditions?[/quote]
Если я правильно понял, то errorConditionFormula это строка и придется ее парсить и самому реализовывать функции из формулы?

Все это не выгодно по количеству трудозатрат и профита.

Все это не выгодно по количеству трудозатрат и профита.

camamber
Если я правильно понял, то errorConditionFormula это строка и придется ее парсить и самому реализовывать функции из формулы?

ну да, в этом то вся и сложность

Gres
Все это не выгодно по количеству трудозатрат и профита.

можно попробовать запилить это под какой-то конкретный случай: изучить какие формулы использованы, договорится с БА о том какие формулы можно использовать в будущем. Фактически это будет частичная реализация идеи, но под какой конкретный проект может и сработает.

и вообще надо бы поискать, может уже подобный пакет существует...

[quote="camamber"]Если я правильно понял, то errorConditionFormula это строка и придется ее парсить и самому реализовывать функции из формулы?[/quote]

ну да, в этом то вся и сложность

[quote="Gres"]Все это не выгодно по количеству трудозатрат и профита.[/quote]

можно попробовать запилить это под какой-то конкретный случай: изучить какие формулы использованы, договорится с БА о том какие формулы можно использовать в будущем. Фактически это будет частичная реализация идеи, но под какой конкретный проект может и сработает.

и вообще надо бы поискать, может уже подобный пакет существует...

А если использовать для ДМЛ методы Database и потом смотреть в соответствующий result обьект? Валидейшены можно там увидеть

А если использовать для ДМЛ методы Database и потом смотреть в соответствующий result обьект? Валидейшены можно там увидеть

Проблема в том, что увидишь только один. А хотелось бы сразу все возможные.

Проблема в том, что увидишь только один. А хотелось бы сразу все возможные.

Может я чего-то не понял...
Я создал 2 валидейшена на аккаунте.
1) AnnualRevenue < 10000
2) LEN(Name) > 5
Пример кода:

List<Account> accs = new List<Account>();
Account acc = new Account(Name='test long', AnnualRevenue = 100);
accs.add(acc);
Database.SaveResult[] srList = Database.insert(accs, false);

for (Database.SaveResult sr : srList) {
if (sr.isSuccess()) {
// Operation was successful, so get the ID of the record that was processed
System.debug('Successfully inserted account. Account ID: ' + sr.getId());
}
else {
// Operation failed, so get all errors
for(Database.Error err : sr.getErrors()) {
System.debug('The following error has occurred.');
System.debug(err.getStatusCode() + ': ' + err.getMessage());
System.debug('Account fields that affected this error: ' + err.getFields());
}
}
}

Вот что я увидел в дебаге
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Length is too long
|DEBUG|Account fields that affected this error: (Name)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Revenue too low
|DEBUG|Account fields that affected this error: (AnnualRevenue)

Может я чего-то не понял...
Я создал 2 валидейшена на аккаунте. 
1) AnnualRevenue < 10000
2) LEN(Name) > 5
Пример кода:
[code]
List<Account> accs = new List<Account>();
Account acc = new Account(Name='test long', AnnualRevenue = 100);
accs.add(acc);
Database.SaveResult[] srList = Database.insert(accs, false);

for (Database.SaveResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful, so get the ID of the record that was processed
        System.debug('Successfully inserted account. Account ID: ' + sr.getId());
    }
    else {
        // Operation failed, so get all errors                
        for(Database.Error err : sr.getErrors()) {
            System.debug('The following error has occurred.');                    
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
            System.debug('Account fields that affected this error: ' + err.getFields());
        }
    }
}
[/code]

Вот что я увидел в дебаге
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Length is too long
|DEBUG|Account fields that affected this error: (Name)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Revenue too low
|DEBUG|Account fields that affected this error: (AnnualRevenue)

О! А я думал, он по одной ошибке будет выдавать, как если б в браузере сохранял запись.
Круто! Ну, это похоже на правильное решение.

О! А я думал, он по одной ошибке будет выдавать, как если б в браузере сохранял запись.
Круто! Ну, это похоже на правильное решение.

А если на одно поле 2 валидейшен рула сработает также? (любопытно)

А если на одно поле 2 валидейшен рула сработает также? (любопытно)

Dmitry Shnyrev
А если на одно поле 2 валидейшен рула сработает также? (любопытно)

Дмитрий, специально добавил еще один валидейшн
LEN( Name ) > 4
И теперь три ошибки
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Length is too long
|DEBUG|Account fields that affected this error: (Name)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Revenue too low
|DEBUG|Account fields that affected this error: (AnnualRevenue)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: More than 4
|DEBUG|Account fields that affected this error: (Name)

[quote="Dmitry Shnyrev"]А если на одно поле 2 валидейшен рула сработает также? (любопытно)[/quote]
Дмитрий, специально добавил еще один валидейшн
LEN( Name ) > 4
И теперь три ошибки
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Length is too long
|DEBUG|Account fields that affected this error: (Name)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: Revenue too low
|DEBUG|Account fields that affected this error: (AnnualRevenue)
|DEBUG|The following error has occurred.
|DEBUG|FIELD_CUSTOM_VALIDATION_EXCEPTION: More than 4
|DEBUG|Account fields that affected this error: (Name)

Круто! Спасибо за инфу!!!
Будет крайне полезно иметь это в голове.

Круто! Спасибо за инфу!!! 
Будет крайне полезно иметь это в голове.

camamber
Database.SaveResult[] srList = Database.insert(accs, false);

вот это хорошая идея, я не знал, что таким образом валидируются сразу все записи

camamber, проверь что происходит с ошибками как:

(1) неверная опция для зависимого пик-листа;
(2) неверная опция пик-листа для данного Record type;

если это все работает одновременно, то мы просто счастливые люди

[quote="camamber"]Database.SaveResult[] srList = Database.insert(accs, false);[/quote]

вот это хорошая идея, я не знал, что таким образом валидируются сразу все записи

[b]camamber[/b], проверь что происходит с ошибками как:

(1) неверная опция для зависимого пик-листа;
(2) неверная опция пик-листа для данного Record type;

если это все работает одновременно, то мы просто счастливые люди :)


camamber
Вот что я увидел в дебаге

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

сейчас буду все проверять...

[quote="camamber"]Вот что я увидел в дебаге [/quote]

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

сейчас буду все проверять...

кстати говоря, вероятно, что весь трюк в том, что вот этот метод

camamber
Database.SaveResult[] srList = Database.insert(accs, false);

запускается в режиме AllOrNothing = false, и мы можем работать с ошибками. в обычном AllOrNothing режиме метод выбрасывает исключение, и вероятно, исключение на первой попавшейся ошибке...

но так как ситуция с частично загруженными записями (одна загрузилась, другая нет) обычно не годится, то придется использвать setSavepoint() и делать ролбек ЕСЛИ в результате операции вернулись какие то ошибки

кстати говоря, вероятно, что весь трюк в том, что вот этот метод

[quote="camamber"]Database.SaveResult[] srList = Database.insert(accs, false);[/quote]

запускается в режиме AllOrNothing = false, и мы можем работать с ошибками. в обычном AllOrNothing режиме метод выбрасывает исключение, и вероятно, исключение на первой попавшейся ошибке...

но так как ситуция с частично загруженными записями (одна загрузилась, другая нет) обычно не годится, то придется использвать setSavepoint() и делать ролбек ЕСЛИ в результате операции вернулись какие то ошибки


да, подход предложенный camamber работает!

там, конечно, еще нужно "причесать" и дополнить сообщение об ошибке для пользователя, в том числе найти запись, вызывающую ошибку,

но в целом, предложенный подход работает для получение всех-и-сразу вал рул и пик-лист ошибок

(но предполагается, что все ошибки по типу и форматированию данных, общей проверки на соответствие значений пиклистов и проверки на обязательные поля выполняются заранее и до этой бизнес-логик валидации, так как такие проверки все же относительно легко организовать без необходимости выполнения ДБ оперций и проверки их результатов)

да, подход предложенный [b]camamber[/b] работает!

там, конечно, еще нужно "причесать" и дополнить сообщение об ошибке для пользователя, в том числе найти запись, вызывающую ошибку,

но в целом, предложенный подход работает для получение [b]всех-и-сразу[/b] вал рул и пик-лист ошибок

(но предполагается, что все ошибки по типу и форматированию данных, общей проверки на соответствие значений пиклистов и проверки на обязательные поля выполняются [b]заранее и до[/b] этой бизнес-логик валидации, так как такие проверки все же относительно легко организовать без необходимости выполнения ДБ оперций и проверки их результатов)

Отлично сработали парни!!!!
Сразу видно профессиональный подход
Поставили вопрос, обсудили, нашли решение!!!
Однозначно в копилку знаний!!!

Отлично сработали парни!!!!
Сразу видно профессиональный подход :)
Поставили вопрос, обсудили, нашли решение!!!
Однозначно в копилку знаний!!!