← Back to tutorials

RAP from scratch · Lesson 4 of 6

Validations in RAP: failed, reported and messages on the right field

Add behavior to the library (create, update, delete) and validate data on save, with error messages Fiori shows next to the field.

También disponible en español →

So far the library app is read-only. In this lesson we give it behavior: creating, updating and deleting books. And since we do not want books with 0 pages or 9-star ratings, we add validations.

1. Behavior Definition for the root entity

Right-click ZR_LibBook → New Behavior Definition, type Managed. Adjust the result so it looks like this:

managed implementation in class zbp_r_libbook unique;
strict ( 2 );

define behavior for ZR_LibBook alias Book
persistent table zlib_book
lock master
authorization master ( instance )
etag master LocalLastChangedAt
{
  create;
  update;
  delete;

  field ( readonly, numbering : managed ) BookUuid;
  field ( readonly ) LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy,
                     LocalLastChangedAt, LastChangedAt;
  field ( mandatory ) BookId, Title, AuthorId;

  validation validatePages  on save { create; field Pages; }
  validation validateRating on save { create; field Rating; }

  mapping for zlib_book corresponding
  {
    BookUuid           = book_uuid;
    BookId             = book_id;
    AuthorId           = author_id;
    CurrencyCode       = currency_code;
    LocalCreatedBy     = local_created_by;
    LocalCreatedAt     = local_created_at;
    LocalLastChangedBy = local_last_changed_by;
    LocalLastChangedAt = local_last_changed_at;
    LastChangedAt      = last_changed_at;
  }
}

Line by line, what matters:

Line What it does
managed The framework handles reading, saving and locking; you only write business logic
strict ( 2 ) Turns on the strictest syntax and best-practice checks
persistent table zlib_book Where the framework stores the data
lock master Pessimistic lock while someone is editing
etag master LocalLastChangedAt Optimistic lock: if another user changed the book in the meantime, your change is rejected
numbering : managed RAP generates the UUID on create
mapping … corresponding Maps CDS fields to table columns. Fields with the same name (Title/title) do not need to be listed
validation … on save { create; field Pages; } Runs on save if the book is new or Pages changed

Put the cursor on zbp_r_libbook and use the quick fix (Ctrl+1) so ADT generates the implementation class with empty methods.

2. Behavior Definition for the projection

The projection decides which business object operations this app offers. Right-click ZC_LibBook → New Behavior Definition:

projection;
strict ( 2 );

define behavior for ZC_LibBook alias Book
use etag
{
  use create;
  use update;
  use delete;
}

For an update-only app, you would just drop use create and use delete. The business object stays the same.

3. The validations

In class zbp_r_libbook, Local Types tab, the handler looks like this:

CLASS lhc_book DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR Book RESULT result.

    METHODS validatePages FOR VALIDATE ON SAVE
      IMPORTING keys FOR Book~validatePages.

    METHODS validateRating FOR VALIDATE ON SAVE
      IMPORTING keys FOR Book~validateRating.
ENDCLASS.

CLASS lhc_book IMPLEMENTATION.

  METHOD get_instance_authorizations.
    " No restrictions for now: covered in the security lesson
  ENDMETHOD.

  METHOD validatePages.
    READ ENTITIES OF ZR_LibBook IN LOCAL MODE
      ENTITY Book
        FIELDS ( Pages )
        WITH CORRESPONDING #( keys )
      RESULT DATA(books).

    LOOP AT books INTO DATA(book).
      " 1. Clear previous messages from this validation
      APPEND VALUE #( %tky        = book-%tky
                      %state_area = 'VALIDATE_PAGES' ) TO reported-book.

      IF book-Pages <= 0.
        " 2. Mark the instance as failed: it will not be saved
        APPEND VALUE #( %tky = book-%tky ) TO failed-book.

        " 3. Explain why, and on which field
        APPEND VALUE #( %tky           = book-%tky
                        %state_area    = 'VALIDATE_PAGES'
                        %msg           = new_message_with_text(
                                           severity = if_abap_behv_message=>severity-error
                                           text     = 'The number of pages must be greater than zero' )
                        %element-Pages = if_abap_behv=>mk-on ) TO reported-book.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

  METHOD validateRating.
    READ ENTITIES OF ZR_LibBook IN LOCAL MODE
      ENTITY Book
        FIELDS ( Rating )
        WITH CORRESPONDING #( keys )
      RESULT DATA(books).

    LOOP AT books INTO DATA(book).
      APPEND VALUE #( %tky        = book-%tky
                      %state_area = 'VALIDATE_RATING' ) TO reported-book.

      IF book-Rating > 5.
        APPEND VALUE #( %tky = book-%tky ) TO failed-book.
        APPEND VALUE #( %tky            = book-%tky
                        %state_area     = 'VALIDATE_RATING'
                        %msg            = new_message_with_text(
                                            severity = if_abap_behv_message=>severity-error
                                            text     = |Rating goes from 0 to 5; got { book-Rating }| )
                        %element-Rating = if_abap_behv=>mk-on ) TO reported-book.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

ENDCLASS.

The pattern, step by step

  1. READ ENTITIES … IN LOCAL MODE: a validation only receives the keys (keys) of the affected books, so you read the data first. IN LOCAL MODE skips authorization and feature control checks, because the read is done by the business object itself. WITH CORRESPONDING #( keys ) is lesson 1’s CORRESPONDING.
  2. %tky: the instance’s “transactional” key. Always use it to refer to a book in failed and reported.
  3. failed: says which instances failed. If a validation puts anything here, the whole save is rejected.
  4. reported: says why. It carries the message (%msg) and the affected field (%element-Pages), which Fiori highlights in red.
  5. %state_area: groups the messages of one validation. At the start you add an empty entry for that area to clear the messages from the previous run: once the user fixes the value, the error goes away. It really pays off with draft mode, but it is a good habit from the start.

Rule of thumb: every APPEND … TO failed-… comes with an APPEND … TO reported-…. A failure without a message leaves the user guessing; a message without a failure is just a warning and the save goes through.

Messages from a message class

new_message_with_text is handy while learning. In a real project, texts usually live in a message class so they can be translated:

%msg = new_message( id       = 'ZLIB_MESSAGES'
                    number   = '001'
                    v1       = book-BookId
                    severity = if_abap_behv_message=>severity-error )

4. Try it

Activate everything and go back to the Service Binding preview (lesson 3). The Create and Delete buttons now show up.

  1. Create a book with 0 pages and click Save: the save is rejected and the Pages field turns red with your message.
  2. Enter 350 pages and a rating of 9: now the error is on Rating.
  3. Fix the rating to 4 and save: the book is created and the messages disappear.

Summary

  • The Behavior Definition declares what can be done (create, update, delete) and which rules apply (validation).
  • The projection decides which part of that behavior each app offers.
  • A validation reads the data, marks the wrong instances in failed and explains why in reported, on the right field.

This closes the basic block of the series: you have a complete RAP business object with data, service, UI and rules. The next level moves into business logic: custom numbering, determinations and actions.