web-dev-qa-db-ja.com

Grailsコマンドオブジェクトのデータバインディング

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プロパティを「シミュレート」することです。コマンドオブジェクトの関連付けを設定する簡単な方法はありますか?

24
Dónal

実際に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
    //...
}

うまくいけば、私はあなたが達成しようとしていることを誤解していないでしょう:)

7
Chris

いくつかのプロジェクトで私が見たのは、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/

14

ネストされたコマンドオブジェクトで同じ問題に直面したため、次の回避策を実行しました。

  1. 他のドメインオブジェクトをパラメーターとしてコントローラーアクションに明示的に追加しました
  2. また、ネストされたコマンドオブジェクトに対して明示的にbindData()を呼び出します(通常、他のコマンドオブジェクトをラップするコマンドオブジェクトは、明示的にバインドする必要なしにデータを正常にバインドします。これは、ビューの命名規則によって異なります)
  3. 次に、それらのコマンドオブジェクトで.Validate()を呼び出しました
  4. これらのオブジェクトを使用して、.hasErrors()でエラーをチェックします
  5. ドメインオブジェクトを保存するには、ネストされた各プロパティにも、対応するコマンドオブジェクトを明示的に割り当てます。

説明のために、以下にサンプルの擬似コードを示します。

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の将来のリリースでこの問題が修正され、開発者がオプションのparamsまたはparamsスコープを追加して各コマンドオブジェクトに割り当てることができるようになることを願っています。便利な場合があります:)
5
simsim

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
   } 

}
1
R Tiwari