В Grails мы часто используем наши доменные объекты непосредственно в качестве вспомогательной модели для целей презентации, и только для специализированных ситуаций мы создаем объекты-держатели значений или DTO.
Начинающие разработчики Grails знают, как довольно легко отобразить отдельные свойства сущности в GSP, например, имя вымышленного класса домена…
1
2
3
|
< g:each in=”${breedingGoals}” var=”breedingGoal”> ${breedingGoal.name} </ g:each > |
или же
1
2
3
|
< g:select from = "${breedingGoals}" optionKey = "code" optionValue = "name" /> |
но иногда возникают трудности, когда появляются новые требования к представлению, и необходимо отображать немного больше или объединенную информацию . Придуманы специальные конструкции, такие как:
1
2
3
4
5
6
7
|
def list() { def breedingGoals = BreedingGoal.list() breedingGoals.each { it.name = it.name + " (" + it.code + ")" } render view: “viewName”, model: [breedingGoals: breedingGoals] } |
чтобы не только отображать имя, но и что-то еще — например, его код. И вот тогда все становится волосатым!
Это выглядит как разумный способ получить новый правильный вывод в браузере. Но это не так.
В этом случае сущности фактически обновляются и после нескольких итераций оставляют базу данных в очень испорченном состоянии. Это тот момент, когда опытные разработчики советуют начинающим разработчикам не идти по этому пути.
Если вы один из последних, сначала ознакомьтесь с некоторыми основами постоянства GORM для некоторой предыстории и вернитесь сюда для некоторых рекомендаций, которые вы могли бы использовать в порядке моего личного предпочтения.
Мое основное правило:
НЕ заменяйте данные (ре) презентационной версией.
Это не полный список, просто чтобы вы подумали о нескольких вариантах, взяв в качестве примера наш вышеупомянутый вариант использования BreedingGoal .
Если информация содержится в самом объекте
и у вас есть объект, просто отформатируйте его прямо на месте.
1
2
3
|
< g:each in=”${breedingGoals}” var=”breedingGoal”> ${breedingGoal.name} (${breedingGoal.code}) </ g:each > |
GSP особенно полезен для презентаций. Я всегда пробовал бы это сначала.
Некоторые люди все еще путаются с различными тегами Grails, которые, кажется, принимают «только 1 свойство» класса домена, который перебирается, например select .
1
2
3
|
< g:select from = "${breedingGoals}" optionKey = "code" optionValue = "name + (code)? Arh! Now what?" /> |
К счастью, иногда автор тега думал об этом! В случае выбора optionValue
может применить преобразование, используя замыкание:
1
2
3
|
< g:select from = "${breedingGoals}" optionKey = "code" optionValue = "${{ it.name + ' (' + it.code + ')' }}" /> |
Если эта новая отформатированная информация нужна в большем количестве мест
чтобы оставаться сухим, вы можете поместить его в сам объект. В своем доменном классе добавьте геттер :
1
2
3
|
String getNamePresentation() { name + " (" + code + ")" } |
который можно назвать как бы свойством самого объекта.
1
2
3
|
< g:each in=”${breedingGoals}” var=”breedingGoal”> ${breedingGoal.namePresentation} </ g:each > |
Если информация НЕ находится в самом объекте, но должна быть извлечена / рассчитана с использованием некоторой другой логики
Вы можете использовать библиотеку тегов .
1
2
3
4
5
6
7
8
|
class BreedingGoalTagLib { def translationService def formatBreedingGoal = { attrs -> out << translationService.translate(attrs.code, user.lang) } } |
В этом примере используется своего рода translationService
для получения фактической информации, которую вы хотите отобразить.
1
2
3
|
< g:each in=”${breedingGoals}” var=”breedingGoal”> < g:formatBreedingGoal code=${breedingGoal.code} /> </ g:each > |
TagLib очень легко вызывать из GSP и имеет полный доступ к взаимодействующим сервисам и среде Grails.
Если нет необходимости в дополнительных вычислениях, а требуется только специальное (HTML) форматирование
Получить материал и делегировать шаблон:
1
2
3
4
5
6
7
|
class BreedingGoalTagLib { def displayBreedingGoal = { attrs -> out << render(template: '/layouts/breedingGoal' , bean: attrs.breedingGoal) } } |
где конкретный шаблон может содержать некоторый HTML для специальной разметки:
1
|
<strong>${it.name}< /strong > (${it.code}) |
Или вам нужно один раз инициализировать кучу вещей, или вы на самом деле не стремитесь к отображению GSP
Вы можете создать (временное) свойство и заполнить его по времени инициализации.
Настройте класс домена, сделайте ваше новое свойство «презентацией» временным, чтобы оно не сохранялось в базе данных, и инициализируйте его в какой-то момент.
1
2
3
4
5
6
7
8
|
class BreedingGoal { String code String name String namePresentation static transients = [ 'namePresentation' ] } |
01
02
03
04
05
06
07
08
09
10
|
class SomeInitializationService { // in some method... def breedingGoal = findBreedingGoal(... breedingGoal.namePresentation = translationService.translate(breedingGoal.code, user.lang) return breedingGoal } |
Дружеский совет: не пытайтесь слишком замусоривать ваши доменные классы переходными полями для целей презентации. Если ваш класс домена содержит 50% свойств со странными кодами, а другая половина вам нужна со связанными полями «презентация», вам лучше использовать специализированное значение или объект DTO .
Вывод
Вы видите там различные варианты презентации, и в зависимости от того, откуда берется информация и куда она направляется, одни подходы лучше подходят, чем другие. Не бойтесь выбирать один и рефакторинг к другому подходу, когда это необходимо, но я сначала остановлюсь на простейшем варианте в GSP и постараюсь не слишком сильно манипулировать вашими основными данными для целей презентации!
Ссылка: | Классы домена Grails и специальные требования к презентации от нашего партнера по JCG Теда Винке в блоге |