Статьи

Доменные классы Grails и специальные требования к презентации

В 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 и постараюсь не слишком сильно манипулировать вашими основными данными для целей презентации!