After completing this lesson, you will be able to:After completing this lesson, you will be able to:
- Implement the behavior of a RAP Business Object
Validations
Checking the Semantic Key
As shown in the figure, in an RAP data model, the key of a database table is often made up of the client field and a UUID field, whose value is assigned automatically by the RAP runtime when you create a new instance of the business object. This field combination is sufficient to ensure that the system can identify each record in the table uniquely. However, as well as this technical key, our object also has a semantic key - in this case the combination of airline and flight number, which must also be unique according to the business logic. In order to ensure the uniqueness of this field combination, you must implement your own check in the form of a validation.
You declare validations in the behavior definition of the CDS view entity, and implement them in the behavior implementation class.
Input Checks in the App
As well as checking the semantic key, there are other checks that you need to perform. For example, although the generated app allows you to create, read, update and delete data, it does not yet contain any consistency checks. Consequently, you can create flight connections for airlines that don't exist, or where the departure and destination airports are the same.
To prevent this from happening, you define further validations in the behavior definition, and implement them in the behavior implementation class.
Creating Message Texts
Before you create the validation, you must create the texts that you want to display. You do this using a message class. A message class is a collection of up to 1000 messages that belong to a particular application area. As shown in the figure, each text has a number which identifies the message uniquely within the message class.
To create a new message class, proceed as follows:
Choose File → New → Other… and type message into the filter field.
Double-click the Message Class entry in the hit list, then enter a package, name, and description for the new message class. Choose Next.
Assign the message class to a transport request and choose Finish.
Messages can also contain placeholders, which are replaced with concrete values when the message is displayed. Placeholders are denoted by the ampersand symbol followed by a number. You can use up to four placeholders in each message.
Defining the Validation
To define a validation, you add a validation declaration to the behavior definition of your business object. In this example, the validation should be performed whenever the user saves a data record, and this could be either when they create the record or if they subsequently change it.
When you define the validation in the behavior definition, a warning tells you that the corresponding method does not exist. Use a quick fix (key combination CTRL + 1
) to add the method to the behavior implementation. The behavior implementation is a local class within your behavior pool. The method definition contains the addition FOR VALIDATE ON SAVE
, which identifies it as the implementation of the validation. It has an importing parameter KEYS. This is an internal table containing the keys of the created or changed objects. You use these to read the actual data that the user has entered.
The addition FOR Connection~CheckSemanticKey
links the method with the validation CheckSemanticKey from the behavior definition. Here, Connection is the alias name of the view entity Z_R_CONNECTION.
The Validation Process
NoteSome code examples in this section use SELECT statements inside of loops. This has been done to keep the examples simple. Note that SELECTs in loops can cause performance problems and should be avoided.
How to Validate the Semantic Key
Validate the Semantic Key
Steps
Define a validation CheckSemanticKey to check that a particular flight number has not already been used.
In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and add the following line: validation CheckSemanticKey on save { create; update; }
.
Activate the behavior definition.
Create the implementation of the validation using a Quick Fix.
Position the cursor on the name of the validation and choose Ctrl + 1.
Double-click the entry Add validation....
Use the EML READ ENTITIES statement to read the data that the user entered. Ensure that the fields CarrierID, and ConnectionID are read. Use an inline declaration for the result set.
Enter the following code in the method implementation. Replace ## with your group number:
READ ENTITIES OF z##_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS ( CarrierID ConnectionID )
WITH CORRESPONDING #( keys )
RESULT DATA(connections).
Copy code
In a loop over the data that you just read, select the UUIDs of all other data sets with the same combination of airline ID and flight number. Use a union to address the active and the draft tables at the same time.
NoteMake sure you only read other data sets, that is, exclude data sets with the same UUID from the selection.
Enter the following code in the method implementation. Replace ## with your group number:
SELECT FROM z##aconn
FIELDS uuid
WHERE carrier_id = @connection-CarrierID
AND connection_id = @connection-ConnectionID
AND uuid <> @connection-uuid
UNION
SELECT FROM z##dconn
FIELDS uuid
WHERE carrierid = @connection-CarrierID
AND connectionid = @connection-ConnectionID
and uuid <> @connection-uuid
INTO TABLE @DATA(check_result).
Copy code
If the internal table check_result contains any records create a new message with message classZS4D400, message number 001 and severity ms-error. Pass connection-CarrierID to parameter v1 and connection-ConnectionID to parameter v2.
Enter the following code in the method implementation:
IF check_result IS NOT INITIAL.
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '001'
severity = ms-error
v1 = connection-CarrierID
v2 = connection-ConnectionID
).
Copy code
Declare the structure reported_record with the type LIKE LINE OF reported-connection
. Add the record to the reported structure.
Enter the following code in the method implementation:
DATA reported_record LIKE LINE OF reported-connection.
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-CarrierID = if_abap_behv=>mk-on.
reported_record-%element-ConnectionID = if_abap_behv=>mk-on.
APPEND reported_record TO reported-connection.
Copy code
Declare the structure failed_record with the type LIKE LINE OF failed-connection
. Add the record to the failed structure and close the open IF and LOOP control structures.
Enter the following code in the method implementation:
DATA failed_record like line of failed-connection.
failed_record-%tky = connection-%tky.
APPEND failed_record TO failed-connection.
ENDIF.
ENDLOOP.
Copy code
Validate the Airline
Steps
Define a validation CheckCarrierID to check that the airline that the user entered exists.
In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and add the following line: validation CheckCerrierID on save { create; field CarrierID; }
.
Activate the behavior definition.
Create the implementation of the validation using a Quick Fix.
Position the cursor on the name of the validation and choose Ctrl + 1.
Double-click the entry Add validation....
Use the EML READ ENTITIES statement to read the data that the user entered. Ensure that the field CARRID is read. Use an inline declaration for the result set.
Enter the following code in the method implementation. Replace ## with your group number:
READ ENTITIES OF z##_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS ( CarrierID )
WITH CORRESPONDING #( keys )
RESULT DATA(connections).
Copy code
In a loop over the data that you just read, check that the airline exists. Use a SELECT statement that returns the literal abap_true if the airline exists. Use the CDS View /dmo/i_carrier as the data source of the statement.
Enter the following code in the method implementation. Replace ## with your group number:
LOOP AT connections INTO DATA(connection).
SELECT SINGLE
FROM /DMO/I_Carrier
FIELDS @abap_true
WHERE airlineid = @connection-CarrierID
INTO @DATA(exists).
Copy code
If the value of exists is abap_false, create a message object with message ID ZS4D400, number 002, severity ms-error and parameter v1 connection-CarrierID.
Enter the following code in the method implementation:
IF exists = abap_false.
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '002'
severity = ms-error
v1 = connection-CarrierID
) .
Copy code
Declare the structure reported_record with the type LIKE LINE OF reported-connection
. Fill the fields in fields group %tky with the corresponding values of the current data set, store the reference to the message object in field %msg, and link the message to view element CarrierID. Finally, add the record to the reported structure.
Enter the following code in the method implementation:
DATA reported_record LIKE LINE OF reported-connection.
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-carrierid = if_abap_behv=>mk-on.
APPEND reported_record TO reported-connection.
Copy code
Declare the structure failed_record with the type LIKE LINE OF failed-connection
. Fill the fields in fields group %tky with the corresponding values of the current data set. Add the record to the failed structure and close the open IF and LOOP control structures.
Enter the following code in the method implementation:
DATA failed_record LIKE LINE OF failed-connection.
failed_record-%tky = connection-%tky.
APPEND failed_Record TO failed-connection.
ENDIF.
ENDLOOP.
Copy code
Validate the Airport Codes
Steps
Define a validation CheckOriginDestination to check that the departure and arrival airports of the flight connection are different.
In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and add the following line: validation CheckOriginDestination on save { create; field AirportFromID, AirportToID; }
.
Activate the behavior definition.
Create the implementation of the validation using a Quick Fix.
Position the cursor on the name of the validation and choose Ctrl + 1.
Double-click the entry Add validation....
Use the EML READ ENTITIES statement to read the data that the user entered. Ensure that the fields AirportFromID and AirportToID are read. Use an inline declaration for the result set.
Enter the following code in the method implementation. Replace ## with your group number:
READ ENTITIES OF z##_r_Connection IN LOCAL MODE
ENTITY Connection
FIELDS ( AirportFromID AirportToID )
WITH CORRESPONDING #( keys )
RESULT DATA(connections).
Copy code
In a loop over the data that you just read, check whether AirportFromID and AirportToID are the same. If they are, create a message with id ZSD4D00, number 003, and severity ms-error.
Enter the following code in the method implementation.
LOOP AT connections INTO DATA(connection).
IF connection-AirportFromID = connection-AirportToID.
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '003'
severity = ms-error
).
Copy code
Declare the structure reported_record with the type LIKE LINE OF reported-connection
. Fill the fields in fields group %tky with the corresponding values of the current data set, store the reference to the message object in field %msg, and link the message to both airport fields. Finally, add the record to the reported structure.
Enter the following code in the method implementation:
DATA reported_record LIKE LINE OF reported-connection.
reported_record-%tky = connection-%tky.
reported_record-%msg = message.
reported_record-%element-AirportFromID = if_abap_behv=>mk-on.
reported_record-%element-AirportToID = if_abap_behv=>mk-on.
APPEND reported_record TO reported-connection.
Copy code
Declare the structure failed_record with the type LIKE LINE OF failed-connection
. Fill the fields in fields group %tky with the corresponding values of the current data set. Add the record to the failed structure and close the open IF and LOOP control structures.
Enter the following code in the method implementation:
DATA failed_record LIKE LINE OF failed-connection.
failed_record-%tky = connection-%tky.
APPEND failed_record TO failed-connection.
ENDIF.
ENDLOOP.
Copy code
Determinations
In this section, you will learn how to complete the data using determinations.
Determine Cities Based on Airport Codes
In the example app, the flight connection entity contains a departure airport, city, and country, and an arrival airport, city, and country. While it would be possible to force the user to enter all of this information, it is better in terms of user experience and data consistency to have the user enter only the airport codes and for the app to read the corresponding city and country information from the database. You can perform this kind of task in a RAP application using a determination.
You will first implement the determination. Afterwards, you will learn how to deactivate input for the fields that will be filled automatically.
Defining the Determination
You define a determination in the behavior definition of a business object. The determination here is called getCities, it will be called whenever the business object is saved and at least one of the AirportFromID and AirportToID fields has changed. You can use a quick fix in the behavior definition to create the corresponding method in the behavior implementation.
The Determination Process
Let's explore each step of the determination process.
How to Determine the Cities and Countries
Determine the Cities and Countries
Steps
Define a determination getCities to fill the city and country fields automatically based on the airport code that the user entered.
In the Project Explorer, navigate to the behavior definition Z##_R_CONNECTION and add the following line: determination GetCities on save { field airportFromID, AirportToID; }
.
Activate the behavior definition.
Create the implementation of the determination using a Quick Fix.
Position the cursor on the name of the determination and choose Ctrl + 1.
Double-click the entry Add method for determination....
Read the user input using an EML READ ENTITIES statement. Read the fields AirportFromID and AirportToID. Use an inline declaration for the result set.
Enter the following coding in the method implementation. Replace ## with your group number.
READ ENTITIES OF z##_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS ( AirportFromID AirportToID )
WITH CORRESPONDING #( keys )
RESULT DATA(connections).
Copy code
In a loop over the data, use two SELECT statements to read the city and country data for the two airports the user entered. Use the CDS view /DMO/I_Airport as the data source and read the fields City and CountryCode. For AirportFromID, fill the fields CityFrom and CountryFrom. For AirportToID, fill the fields CityTo and CountryTo. Remember that you need the MODIFY statement to write the changes back to the internal table.
Enter the following coding in the method implementation:
LOOP AT connections INTO DATA(connection).
SELECT SINGLE
FROM /DMO/I_Airport
FIELDS city, CountryCode
WHERE AirportID = @connection-AirportFromID
INTO ( @connection-CityFrom, @connection-CountryTo ).
SELECT SINGLE
FROM /DMO/I_Airport
FIELDS city, CountryCode
WHERE AirportID = @connection-AirportToID
INTO ( @connection-CityTo, @connection-CountryTo ).
MODIFY connections FROM connection.
ENDLOOP.
Copy code
Declare an internal table connections_upd with the type TABLE FOR UPDATE z##_r_connections
where ## is your group number. Copy the data from the internal table connections to the new table connections_upd.
Enter the following coding in the method implementation:
DATA connections_upd TYPE TABLE FOR UPDATE z##_r_connection.
connections_upd = CORRESPONDING #( connections ).
Copy code
Use an EML MODIFY ENTITIES statement to update the data in the transactional buffer. Restrict the update to the fields that you modified (CityFrom CityTo CountryFrom CountryTo). Use the REPORTED addition to receive any messages from the statement. Transfer any messages to the reported structure of your method.
Enter the following coding in the method implementation:
MODIFY ENTITIES OF z##_r_connection IN LOCAL MODE
ENTITY Connection
UPDATE
FIELDS ( CityFrom CountryFrom CityTo CountryTo )
WITH connections_upd
REPORTED DATA(reported_records).
reported-connection = CORRESPONDING #( reported_records-connection ).
Copy code