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];
}];
}