Desktop

The Commands desktop sample demonstrates executing remote schema Commands from a client application to insert, update and delete records from a data table in an instance of Relativity Server.

Getting Started

The Desktop sample is typically located in /Developer/RemObjects Software/Samples/Data Abstract/Desktop/Commands, though you may have installed the Data Abstract for Cocoa and it's samples in another location.

To build it, you will of course need Xcode, and like all the samples provided with Data Abstract for Cocoa you will need to be running the Relativity Server with the DASamples Domain and the Simple Schema available.

Running the Sample

This sample allows you to add, edit and delete records from the Clients table in the Simple schema, that is part of the DASamples domain. The UI is split into 2 sections, a toolbar with buttons for all of the actions and a table for displaying all of the records in the Clients table.

There are 4 buttons on the toolbar and a popup. The "Load" button retrieves the current state of the Clients table, initially the table is empty and won't be populated till the button is pressed. The "Add" button will drop down a panel which allows you to fill in the details for a new record, when you press the "Save" button the new record is inserted using the Client_INSERT command. The "Edit" button opens a panel populated with the details of the currently selected record, when the "Save" button is pressed it updates the record on the server using the Clients_UPDATE command; you can also edit a record by double clicking on the row. The "Delete" button uses the Clients_DELETE command to delete the currently selected record from the table. The "Edit" & "Delete" buttons are only available when a row is selected.

The last item on the toolbar is the popup which contains a list of available servers discovered by the Zeroconf discovery system. When the sample is run, it starts a Zeroconf discovery service that looks for instances of Relativity Server running on the local network. Any discovered servers will be added to the popup, if no servers are discovered then the dropbox on the toolbar will be empty. You can specify a server address yourself by clicking on the popup and then clicking on "Specify custom URL...".

Examining the Code

In this section we are focusing specifically on executing the schema command, the code for creating a connection to the server is covered by other samples.

App Structure

The sample is built around five classes; AppDelegate, DataAccess, ServiceAccess, EditorWindowController and RegisterServiceWindowController.

The DataAccess class handles everything related to interacting with the Data Abstract SDK; including retrieving data from an instance of Relativity Server and applying changes to the server. The code to execute schema commands is located in this class and will be discussed further below.

The ServiceAccess class handles the discovery of any instances of Relativity Server that are available on the local network using ROZeroConf which is a feature available with the Remoting SDK that Data Abstract is built upon. ServiceAccess sets up a ROZeroConfBrowser Class object which searches for any servers that broadcast a value matching the value defined for RELATIVITY_SERVICE_NAME. When a service is found, or indeed disappears, the server list is updated and the popup with the list of available servers is also updated. It also handles the registration of custom server addresses. To explore further the ServiceAccess class and Zeroconf discovery see the article: The ServiceAccess Class and Zeroconf discovery.

The EditorWindowController handles the presentation and dismissal of the edit sheet, as well as populating the fields with data from the selected row. The only thing of note here is that when the "Save" button is pressed it calls okAction: which in turn calls the editorDoneWithOk method in the delegate class; which in this case is AppDelegate. The editorDoneWithOk method will trigger whether the insert or update command is used.

The RegisterServiceWindowController class is a subclass of NSWindowController which handles the UI aspects of a user manually adding a server url.

Lastly the AppDelegate class handles the primary setup of the application, registers that it will listen for notifications broadcast by the ServiceAccess and DataAccess classes, and acts as a delegate to the EditorWindowController, RegisterServiceWindowController, DataAccess and NSTableView classes. The main method of note in this class is editorDoneWithOk, we will explore that command below.

Looking at the Commands

Before stepping through the code, we shall take a detour and use Server Explorer to examine that commands that are used in the next sections. If you open Server Explorer, navigate to the Simple schema and double click it to open the schema editor. There you can see a folder titled "Commands" which contains all of the commands that are available in this schema.

Each command's name describes the table it affects and what the action is to ensure that is no confusion. Clicking the disclosure triangle beside the command name allows you to drill down into the details of the command; including the parameters that the command takes and the statement object that defines the mapping between the schema table and the database table.

In the figure below you can see that the Clients_DELETE command takes a single parameter OLD_ClientId and uses a custom SQL statement that uses that parameter to try and match against the table's Id field and delete that table from the Agents table (The Clients schema table is mapped to the Agents database table)

Next we shall look at using the commands.

General

As noted above the editorDoneWithOk (AppDelegate) decides which method to call in the DataAccess class based on the rowState of the DADataTableRow class that is retrieved from the EditorWindowController class. If the row is newly added (rowState equal to rsAdded, declared in DADataTableRow.h) then beginInsertClientWithName:andPhone:andEmail:andBirthdate:andAddress:andPostalCode: is used, otherwise if the row is modified (rowState equal to rsModified, declared in DADataTableRow.h) then beginUpdateClient:withName:andPhone:andEmail:andBirthdate:andAddress:andPostalCode: is called.

Unlike the Console sample, this sample uses an asynchronous version of the executeCommand. This is the recommended way of interacting with the server so that user interface remains interactive & responsive.

Inserting a record

Before we can execute the Clients_INSERT command, we need to build up a dictionary of parameters (params) that match those defined for the command in the schema. Each of the FIELD_* keys are defined at the top of DataAccess.h, their string values match those defined in the Schema. If you fail to provide one of the parameters then a null value will instead be inserted into the database table for that parameter.

We pass the params dictionary to the beginExecuteCommand method of the DARemoteAdapter class, along with the name of the command we want to execute as a string; here Clients_INSERT. When beginExecuteCommand completes it executes the block which here simply prints out the result, which is an integer value that indicates if the command was successfully executed (It returns a 1) or if it failed (It returns a 0). The block then calls beginLoadClients which causes the Clients table to be retrieved again from the server so you can see the change has actually occurred.

The DAObjSafe is an inline function that checks if the object isn't null, and if it is returns a safe DANull object.

Note that if you supply an invalid command name or parameter name then an ROException will be thrown and should be handled.

- (void)beginInsertClientWithName:(NSString *)name
                         andPhone:(NSString *)phone
                         andEmail:(NSString *)email
                     andBirthdate:(NSDate *)birthdate
                       andAddress:(NSString *)address
                    andPostalCode:(NSString *)code {


    NSDictionary *params = @{
                             FIELD_NAME:        DAObjSafe(name),
                             FIELD_DISCOUNT:    @0.01,
                             FIELD_EMAIL:       DAObjSafe(email),
                             FIELD_PASSWORD:    @"temp",
                             FIELD_BIRTHDATE:   DAObjSafe(birthdate),
                             FIELD_ADDRESS:     DAObjSafe(address),
                             FIELD_POSTALCODE:  DAObjSafe(code),
                             FIELD_PHONE:       DAObjSafe(phone),
                             FIELD_NOTES:       @"Added by Clients_INSERT schema command" };

    [self.dataAdapter beginExecuteCommand:@"Clients_INSERT"
                           withParameters:params
                                withBlock:^(int result, NSDictionary *outParams) {
                                    NSLog(@"Result is: %d", result);
                                    [self beginLoadClients];
                                }];

}

Updating a record

As with the previous step, we create a dictionary that contains all of the parameters defined for the command in the schema. There is an additional parameter which is the record id (cid) from the Client table. Note that all of the parameters must be supplied otherwise the previous value will be replaced with a null value.

We pass the params dictionary to the beginExecuteCommand method of the DARemoteAdapter class, along with the name of the command we want to execute as a string; this time Clients_UPDATE. When beginExecuteCommand completes, it executes the block which here simply prints out the result, which is an integer value that indicates if the command was successfully executed (It returns a 1) or if it failed (It returns a 0). The block then calls beginLoadClients which causes the Clients table to be retrieved again from the server so you can see the change has actually occurred.

The DAObjSafe is an inline function that checks if the object isn't null, and if it is returns a safe DANull object.

Note that if you supply an invalid command name or parameter name then an ROException will be thrown and should be handled.

- (void)beginUpdateClient:(NSNumber *)cid
                 withName:(NSString *)name
                 andPhone:(NSString *)phone
                 andEmail:(NSString *)email
             andBirthdate:(NSDate *)birthdate
               andAddress:(NSString *)address
            andPostalCode:(NSString *)code {

    NSDictionary *params = @{
                             FIELD_ID_OLD:      cid,
                             FIELD_NAME:        DAObjSafe(name),
                             FIELD_DISCOUNT:    @0.01,
                             FIELD_EMAIL:       DAObjSafe(email),
                             FIELD_PASSWORD:    @"123",
                             FIELD_BIRTHDATE:   DAObjSafe(birthdate),
                             FIELD_ADDRESS:     DAObjSafe(address),
                             FIELD_POSTALCODE:  DAObjSafe(code),
                             FIELD_PHONE:       DAObjSafe(phone),
                             FIELD_NOTES:       @"Updated by Clients_UPDATE schema command" };

    [self.dataAdapter beginExecuteCommand:@"Clients_UPDATE"
                           withParameters:params
                                withBlock:^(int result, NSDictionary *outParams) {
                                    NSLog(@"Result is: %d", result);
                                    [self beginLoadClients];
                                }];

}

Deleting a record

The beginDeleteClient take an NSNumber which is the record id (cid) of the record that we want to delete. To delete a record from the Clients table, we execute the Clients_DELETE command, passing a dictionary that only contains the cid value. The value of FIELD_ID_OLD is defined at the top of DataAccess.h and is the string OLD_ClientId which as you saw above was defined as a parameter for the Clients_DELETE command.

When beginExecuteCommand completes, it executes the block which here simply prints out the result, which is an integer value that indicates if the command was successfully executed (It returns a 1) or if it failed (It returns a 0). The block then calls beginLoadClients which causes the Clients table to be retrieved again from the server so you can see the change has actually occurred.

- (void)beginDeleteClient:(NSNumber *)cid {

    //int result =
    [self.dataAdapter beginExecuteCommand:@"Clients_DELETE"
                           withParameters:@{FIELD_ID_OLD: cid}
                                withBlock:^(int result, NSDictionary *outParams) {
                                    NSLog(@"Result is: %d", result);
                                    [self beginLoadClients];
                                }];

}