【RAP】Unmanaged BOの実装 (3) Update処理

##はじめに
この記事では、RAP(ABAP RESTful Application Programming Model)で作成したUnmanaged BOにUpdate処理を追加します。

シリーズの先頭はこちら☞ [【RAP】Unmanaged BOの実装 (1)](https://qiita.com/tami/items/7d19052b1866f7aaf69f)

もともとドラフトありで作成していたのですが、[Editボタンを押したときにエラーになる](https://qiita.com/tami/items/d9f6cda276c0be3b7ca9#%E5%95%8F%E9%A1%8C)という問題があり、ここからはドラフトなしで進めます。ドラフトなしに変える方法は以下の通りです。

**・Behavior Projectionでドラフトに関連する箇所をコメントアウト**

```abap
projection;
//use draft;

define behavior for ZC_PERSON_U alias Person

{
  use create;
  use update;
  use delete;

//  use action Edit;
//  use action Activate;
//  use action Discard;
//  use action Resume;
//  use action Prepare;

//  use association _FamilyMember { create; with draft; }
  use association _FamilyMember { create; }
}

define behavior for ZC_FAMILYMEMBER_U alias FamilyMember

{
  use update;
  use delete;

//  use association _Person { with draft; }
  use association _Person { }
}
```

**OData V2のService Bindingを作成**
OData V4に対応したList Reportでは、ドラフトありでないと登録、編集ボタンが表示されないため、V2版を作成します。簡単にODataバージョンを切り替えられるのがRAPの良いところです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8ed99d54-dec7-e678-0a56-40eb1bdadfb6.png)

##処理概要
現在の構成は以下のようになっています。Behavior Implementationクラスから、実際の処理を行うAdapterクラスのメソッドが呼ばれます。Adapterクラスのローカルクラスではトランザクションバッファとのやり取りを行い、SAVEのタイミングでバッファから取り出したレコードをDBに格納します。

今回、UPDATEの処理を追加するとともに、SAVEの処理を変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6a240b56-2193-8b2b-3c9c-a6567d9299b7.png)


##実装ステップ
1. Adapterクラスの実装
1. Behavior Implementationクラスの実装

###1. Adapterクラスの実装
####1.1. インターフェースに型を追加
更新処理で使用する型を追加します。ts_person_controlおよびts_familymember_controlは、更新された項目を識別するためのフラグを持った型です。

```abap:zif_person_adapter
  TYPES tt_person TYPE SORTED TABLE OF zperson_u WITH UNIQUE KEY person_id.
  TYPES tt_familymember TYPE SORTED TABLE OF zfamilymember_u WITH UNIQUE KEY person_id member_id.

  TYPES BEGIN OF ts_person_control.
  TYPES  person_id TYPE ze_person_id.
  INCLUDE TYPE zsperson_x_u.
  TYPES END OF ts_person_control.

  TYPES BEGIN OF ts_familymember_control.
  TYPES person_id TYPE ze_person_id.
  TYPES member_id TYPE ze_member_id.
  INCLUDE TYPE zsfamilymember_x_u.
  TYPES END OF ts_familymember_control.
```

zsperson_x_u, zsfamilymember_x_uはRAP Generatorで自動生成された構造です。テーブルの項目名と同じ名称で、xsdbooleanの型を持つ項目を持っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/54441c79-8b2b-2eaa-4ded-66dd5f332a1e.png)

####1.2. ローカルクラスの実装
バッファ更新用のメソッドを追加します。また、`save`メソッドに更新処理を追加します。

メソッド定義

```abap:lcl_person_buffer
CLASS lcl_person_buffer DEFINITION FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    " Buffer Tables
    ...
    "更新用のバッファテーブル
    data mt_update_buffer_person type zif_person_adapter=>tt_person.
    data mt_update_buffer_familymember type zif_person_adapter=>tt_familymember.

    METHODS buffer_person_for_update IMPORTING is_person type zperson_u.
    METHODS buffer_familymember_for_update IMPORTING is_familymember type zfamilymember_u.
```

メソッド実装

```abap
  METHOD buffer_person_for_update.
    CHECK is_person is not initial.
    insert is_person into table mt_update_buffer_person.

  ENDMETHOD.

  METHOD buffer_familymember_for_update.
    CHECK is_familymember is not initial.
    insert is_familymember into table mt_update_buffer_familymember.
  ENDMETHOD.

  METHOD save.
    data(lt_perton) = get_person_create( ).
    data(lt_familymember) = get_familymember_create(  ).

    INSERT zperson_u FROM TABLE @lt_perton.
    UPDATE zperson_u FROM TABLE @mt_update_buffer_person. "追加

    INSERT zfamilymember_u FROM TABLE @lt_familymember.
    UPDATE zfamilymember_u FROM TABLE @mt_update_buffer_familymember.  "追加
  ENDMETHOD.
```

####1.3. グローバルクラスの実装
Behavior Implementationクラスから呼び出されるメソッドを実装します。Behavior Implementationクラスの`update`メソッドには、更新された項目しか渡されません。更新された項目にはフラグが設定されるので、フラグをもとにどの項目が更新されたか判断し、更新されていない項目はDBから取得して埋める必要があります。また、ドラフトなしにしたことに伴って更新日、更新ユーザ等もここで設定します。

メソッド定義

```abap:zcl_person_adapter
    METHODS update_person IMPORTING is_person         TYPE zperson_u
                                    is_person_control TYPE zif_person_adapter=>ts_person_control.
    METHODS update_familymember IMPORTING is_familymember         TYPE zfamilymember_u
                                          is_familymember_control TYPE zif_person_adapter=>ts_familymember_control.
```

メソッド実装

```abap
  METHOD update_person.
    "TODO: clientの扱い
    "データベースから全項目を取得
    SELECT SINGLE * FROM zperson_u
    WHERE person_id = @is_person-person_id
    INTO @DATA(ls_person).

    "変更された項目で上書き
    DATA index TYPE i.
    index = 2. "キーを飛ばして2番目の項目から
    DO.
      ASSIGN COMPONENT index OF STRUCTURE is_person_control TO FIELD-SYMBOL(<is_changed>).
      IF sy-subrc <> 0.
        EXIT.
      ENDIF.

      IF <is_changed> = abap_true.
        "clientつきの構造のため、indexを1つ進める
        ASSIGN COMPONENT index + 1 OF STRUCTURE is_person TO FIELD-SYMBOL(<field_upd>).
        ASSERT sy-subrc = 0.
        ASSIGN COMPONENT index + 1 OF STRUCTURE ls_person TO FIELD-SYMBOL(<field_current>).
        ASSERT sy-subrc = 0.
        <field_current> = <field_upd>.
      ENDIF.

      index = index + 1.
    ENDDO.

    "タイムスタンプ等の設定
    ls_person-last_changed_by = sy-uname.
    GET TIME STAMP FIELD ls_person-last_changed_at.
    GET TIME STAMP FIELD ls_person-local_last_changed_at.

    lcl_person_buffer=>get_instance( )->buffer_person_for_update( ls_person ).

  ENDMETHOD.

  METHOD update_familymember.

    "データベースから全項目を取得
    SELECT SINGLE * FROM zfamilymember_u
    WHERE person_id = @is_familymember-person_id
      AND member_id = @is_familymember-member_id
    INTO @DATA(ls_familymember).

    DATA index TYPE i.
    index = 3. "キーを飛ばして3番目の項目から
    DO.
      ASSIGN COMPONENT index OF STRUCTURE is_familymember_control TO FIELD-SYMBOL(<is_changed>).
      IF sy-subrc <> 0.
        EXIT.
      ENDIF.

      IF <is_changed> = abap_true.
        "clientつきの構造のため、indexを1つ進める
        ASSIGN COMPONENT index + 1 OF STRUCTURE is_familymember TO FIELD-SYMBOL(<field_upd>).
        ASSERT sy-subrc = 0.
        ASSIGN COMPONENT index + 1 OF STRUCTURE ls_familymember TO FIELD-SYMBOL(<field_current>).
        ASSERT sy-subrc = 0.
        <field_current> = <field_upd>.
      ENDIF.

      index = index + 1.
    ENDDO.

    "タイムスタンプの設定
    GET TIME STAMP FIELD ls_familymember-last_changed_at.
    lcl_person_buffer=>get_instance( )->buffer_familymember_for_update( ls_familymember ).

  ENDMETHOD.
```

###2. Behavior Implementationクラスの実装
Behavior Implentationクラスでは、受け取ったEntityおよびコントロール構造（更新された項目にフラグがセットされたもの）をAdapterクラスのメソッドに渡します。`MAPPING FROM ENTITY`というステートメントは、Behavior Definitionで定義したマッピングの通りにエンティティの項目をDBの項目にマッピングしてくれます。

Behavior Definitionでのマッピングは以下のようになっています。`control ZSPerson_X_u`を指定することにより、コントロール構造のフラグ（00 or 01）をbooleanに変換することができます。

```abap
  mapping for ZPERSON_U control ZSPerson_X_u
  {
    PersonID = PERSON_ID;
    FirstName = FIRST_NAME;
    LastName = LAST_NAME;
    Email = EMAIL;
    Birthday = BIRTHDAY;
    Status = STATUS;
    CreatedBy = CREATED_BY;
    CreatedAt = CREATED_AT;
    LastChangedBy = LAST_CHANGED_BY;
    LocalLastChangedAt = LOCAL_LAST_CHANGED_AT;
    LastChangedAt = LAST_CHANGED_AT;
  }
```

マッピングの結果は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/70953436-7602-0281-0f9d-41bf159cba04.png)

Person

```abap:zbp_i_person_u>lhc_person
  METHOD update.
    DATA person_u TYPE zperson_u.
    DATA person_ux TYPE ZSPerson_X_u.
    data person_control type zif_person_adapter=>ts_person_control.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<person>).
      person_u = CORRESPONDING #( <person> MAPPING FROM ENTITY ).
      person_ux = CORRESPONDING #( <person> MAPPING FROM ENTITY ).
      person_control = CORRESPONDING #( person_ux ).
      person_control-person_id = <person>-PersonID.
      zcl_person_adapter=>get_instance(  )->update_person(
        EXPORTING
          is_person         = person_u
          is_person_control = person_control
      ).

    ENDLOOP.

  ENDMETHOD.
```

Family Member

```abap:zbp_i_familymember_u>lhc_familymember
  METHOD update.
    DATA familymember_u TYPE zfamilymember_u.
    DATA familymember_ux TYPE ZSFamilyMember_X_u.
    data familymember_control type zif_person_adapter=>ts_familymember_control.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<member>).
      familymember_u = CORRESPONDING #( <member> MAPPING FROM ENTITY ).
      familymember_ux = CORRESPONDING #( <member> MAPPING FROM ENTITY ).
      familymember_control = CORRESPONDING #( familymember_ux ).
      familymember_control-person_id = <member>-PersonID.
      familymember_control-member_id = <member>-MemberID.
      zcl_person_adapter=>get_instance(  )->update_familymember(
        EXPORTING
          is_familymember         = familymember_u
          is_familymember_control = familymember_control
      ).
    ENDLOOP.

  ENDMETHOD.
```

##動作確認
変更前の状態は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ad447dcf-9978-c829-0bff-b887252c8268.png)
以下の項目を更新して保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/3425403d-e3e7-cf20-ec47-cd280def5972.png)
更新されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6b973f52-0ff8-1d87-56bf-f5d8b809d3be.png)