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
プロパティを「シミュレート」することです。コマンドオブジェクトの関連付けを設定する簡単な方法はありますか?
実際に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())
}
}
そして、resources.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
//...
}
うまくいけば、私はあなたが達成しようとしていることを誤解していないでしょう:)
いくつかのプロジェクトで私が見たのは、ApacheCommonsCollectionsのLazy *コレクションクラスの使用でした。次のようなコードを使用して、コマンドの関連付けを遅延初期化しました。
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" ...
LazyListに対するすべてのget(index)呼び出しは、リストにその位置に要素がすでにあるかどうかを評価し、ない場合、リストは自動的にサイズが大きくなり、指定されたファクトリから新しいオブジェクトを返すため、これは存在しないインデックスに対しても機能します。
レイジーマップで同様のコードを作成するために、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
更新:
Groovy 2.0(まだGrailsディストリビューションの一部ではありません)には、怠惰で熱心なリストのサポートが組み込まれています。私はこのトピックに関するブログ投稿を書きました:
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
更新:
Grails 2.2.0のリリースにより、Groovy2.0はディストリビューションの一部になりました。
http://blog.andresteingress.com/2012/06/29/groovy-2-0-love-for-grails-command-objects/
ネストされたコマンドオブジェクトで同じ問題に直面したため、次の回避策を実行しました。
説明のために、以下にサンプルの擬似コードを示します。
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's value using cos command object instance, and call the save on you
//command object and every thing should go smoothly from there
.
.
.
}
.
.
.
Grailsのコマンドオブジェクト
Grailsでは、コマンドオブジェクトはドメインクラスに似ていますが、データを永続化しません。 Grailsでコマンドオブジェクトを使用することは、ドメインオブジェクトを作成する必要がない場合に、データバインディングと検証を実行する簡単な方法です。
最初に行う必要があるのは、コマンドオブジェクトを説明することです。それを使用するコントローラーを含む同じファイルで行うのは問題ありません。コマンドオブジェクトが複数のコントローラーで使用される場合は、groovyソースディレクトリに記述してください。
コマンドオブジェクトの宣言
@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
}
}