Грайль связывает привязку данных объекта
Grails имеет очень хорошую поддержку для привязки параметров запроса к объекту домена и ассоциациям. Это в значительной степени зависит от обнаружения параметров запроса, заканчивающихся .id
и автоматически загружая их из базы данных.
Однако неясно, как заполнить ассоциации объекта команды. Возьмем следующий пример:
class ProductCommand {
String name
Collection<AttributeTypeCommand> attributeTypes
ProductTypeCommand productType
}
Этот объект имеет одностороннюю связь с ProductTypeCommand
и многопользовательскую связь с AttributeTypeCommand
. Список всех типов атрибутов и типов продуктов доступен из реализации этого интерфейса
interface ProductAdminService {
Collection<AttributeTypeCommand> listAttributeTypes();
Collection<ProductTypeCommand> getProductTypes();
}
Я использую этот интерфейс для заполнения списков выбора типа продукта и атрибутов в GSP. Я также зависим - вставляю этот интерфейс в объект команды и использую его для "имитации" attributeTypes
и productType
свойств объекта команды
class ProductCommand {
ProductAdminService productAdminService
String name
List<Integer> attributeTypeIds = []
Integer productTypeId
void setProductType(ProductTypeCommand productType) {
this.productTypeId = productType.id
}
ProductTypeCommand getProductType() {
productAdminService.productTypes.find {it.id == productTypeId}
}
Collection<AttributeTypeCommand> getAttributeTypes() {
attributeTypeIds.collect {id ->
productAdminService.getAttributeType(id)
}
}
void setAttributeTypes(Collection<AttributeTypeCommand> attributeTypes) {
this.attributeTypeIds = attributeTypes.collect {it.id}
}
}
Фактически происходит то, что свойства attributeTypeIds
и productTypeId
привязаны к соответствующим параметрам запроса, а свойства "имитировать" геттеры/сеттеры "имитируют" productType
и attributeTypes
. Существует ли более простой способ заполнения ассоциаций объекта команды?
Ответы
Ответ 1
Вам действительно нужны подкоманды для свойств attributeTypes и productType? По какой причине вы не используете привязку PropertyEditorSupport? Например:.
public class ProductTypeEditor extends PropertyEditorSupport
{
ProductAdminService productAdminService // inject somewhow
void setAsText(String s)
{
if (s) value = productAdminService.productTypes.find { it.id == s.toLong() }
}
public String getAsText()
{
value?.id
}
}
(и что-то подобное для объекта attributeType) и зарегистрируйте их в регистраторе редактора:
import java.beans.PropertyEditorSupport
public class CustomEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry reg) {
reg.registerCustomEditor(ProductType, new ProductTypeEditor())
reg.registerCustomEditor(AttributeType, new AttributeTypeEditor())
}
}
И зарегистрируйтесь в своих ресурсах .groovy:
beans =
{
customEditorRegistrar(CustomEditorRegistrar)
}
то в вашем Cmd у вас есть только:
class ProductCommand {
String name
List<AttributeType> attributeTypes = []
ProductType productType
}
Если вы do нуждаетесь в фактических ассоциациях подкоманд, то я сделал нечто похожее на то, что предложил @Andre Steingress в сочетании с привязкой PropertyEditorSupport:
// parent cmd
import org.apache.commons.collections.ListUtils
import org.apache.commons.collections.FactoryUtils
public class DefineItemConstraintsCmd implements Serializable
{
List allItemConstraints = ListUtils.lazyList([], FactoryUtils.instantiateFactory(ItemConstraintsCmd))
//...
}
// sub cmd
@Validateable
class ItemConstraintsCmd implements Serializable
{
Item item // this has an ItemEditor for binding
//...
}
Надеюсь, я не понял, чего вы пытаетесь достичь:)
Ответ 2
То, что я видел в некоторых проектах, было использование классов коллекции Lazy * из коллекций Apache Commons. Он использовал такой код, чтобы лениво инициализировать ассоциацию команд:
class ProductCommand {
String name
String type
List<AttributeTypeCommand> attributes = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(AttributeTypeCommand.class))
}
class AttributeTypeCommand {
// ...
}
В приведенном выше примере GSP может ссылаться на индексы ассоциации
<g:textField name="attributes[0].someProperty" ...
Это работает даже для несуществующих индексов, так как каждый вызов get (index) в LazyList оценивает, имеет ли список уже элемент в этой позиции, а если нет, список будет автоматически расти в размере и возвращать новый объект из указанного factory.
Обратите внимание, что вы также можете использовать LazyMap для создания аналогичного кода с ленивыми картами:
http://commons.apache.org/collections/apidocs/org/apache/commons/collections/map/LazyMap.html
http://commons.apache.org/collections/apidocs/org/apache/commons/collections/list/LazyList.html
Update:
Groovy 2.0 (который еще не является частью дистрибутива Grails) будет поставляться со встроенной поддержкой для ленивых и нетерпеливых списков. Я написал сообщение в блоге по этой теме:
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
Update:
С выпуском Grails 2.2.0 Groovy 2.0 является частью дистрибутива.
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
Ответ 3
У меня возникла та же проблема с вложенными командами, поэтому я сделал следующее обходное решение:
- Я явно добавил другие объекты домена в качестве параметров для моего
действия контроллера
- Также явным образом называется bindData() для вложенных объектов команды
(обычно, объект команды, который обертывает другие объекты команды
будет успешно связывать свои данные без необходимости связывать его
явно, это зависит от вашего соглашения об именовании просмотров)
- Затем я вызывал .Validate() в этих командных объектах
- Используйте эти объекты для проверки ошибок с помощью .hasErrors()
- Чтобы сохранить объект домена, укажите явно также каждый вложенный
свойство с ним соответствующий объект команды
Чтобы проиллюстрировать, вот пример псевдокода:
class CommandObjectBig{
String name
CommandObjectSmall details
static constraints = {
name (blank: false)
}
}
class CommandObjectSmall{
String address
static constraints = {
address (blank: false)
}
}
В контроллере:
.
.
.
def save = { CommandObjectBig cob, CommandObjectSmall cos ->
//assuming cob is bounded successfully by grails, and we only need to handle cos
bindData(cos, params.details)
cos.validate()
//then do you code logic depending on if cos or cob has errors
if(cob.hasErrors() || cos.hasErrors())
render(view: "create", model: [bigInstance: cob, smallInstance: cos])
}
else
{
//create the Domain object using your wrapper command object, and assign its details
//property it value using cos command object instance, and call the save on you
//command object and every thing should go smoothly from there
.
.
.
}
.
.
.
- Надежды будущих выпусков grails могут исправить эту проблему и, возможно, позволить разработчику добавлять необязательные параметры или параметры, которые должны быть назначены каждому объекту команды, также может быть полезно:)
Ответ 4
Командный объект в Grails
В Grails объекты команд похожи на классы домена, но не сохраняют данные. Использование командных объектов в Grails - это простой способ выполнить привязку и проверку данных, когда нет необходимости создавать объект домена.
Первое, что вам нужно сделать, это описать свой объект команды. Это нормально делать в том же файле, который содержит контроллер, который будет его использовать. Если командный объект будет использоваться более чем одним контроллером, опишите его в каталоге groovy source.
Объявление командных объектов
@Validateable
class UserProfileInfoCO {
String name
String addressLine1
String addressLine2
String city
String state
String zip
String contactNo
static constraints = {
name(nullable: false, blank: false)
addressLine1(nullable: true, blank: true)
addressLine2(nullable: true, blank: true)
city(nullable: true, blank: true)
state(nullable: true, blank: true)
zip(nullable: true, blank: true, maxSize: 6, matches: "[0-9]+")
contactNo(blank: true, nullable: true)
}
}
Использование командных объектов
Следующая вещь, которую вы, вероятно, захотите сделать, - это привязать данные, полученные по действию в вашем контроллере к объекту команды, и проверить его.
def updateUserProfile(UserProfileInfoCO userProfileInfo) {
// data binding and validation
if (!userProfileInfo.hasErrors()) {
//do something
}
}