Desktop
The DynamicWhere desktop sample demonstrates using Dynamic Where clauses (DADynamicWhereClause) to request a subset of the records available in a schema's DADataTable. The sample demonstrates 3 different ways to compose a Dynamic Where clause to pass to the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter which will retrieve the records. There are also two examples of filtering table data locally in the client app.
Getting Started
The sample is typically located in /Developer/RemObjects Software/Samples/Data Abstract/Desktop/DynamicWhere
, 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 see the results of running pre-defined Dynamic Where clauses against the Users
table located in the Simple
schema in an instance of Relativity Server, as well as see the results of using an NSPredicate
to filter a local copy of the Users
table held by the app.
When this sample is run you will see an empty table and a toolbar which contains two pop-ups. The first is the "Conditions" popup which contains all of the conditions or filters that you can execute. Simply click on the popup and choose the rule you wish to execute from the menu of 5, this will trigger the sample to establish a connection to the selected server from the "Servers" popup, and retrieve the data as needed before then showing the filtered results in the table. The header and footer of the table are updated based on the selected condition to show the title of the filter and a description of what the filter was doing.
The "Servers" popup 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...".
There are 5 conditions available in this sample, 3 that do the filtering remotely
-
Remote Filters
- XML to DynWhere (Remote) uses an XML based Dynamic Where clause to retrieve all table records where the
Type
field is equal to "3". - NSPredicate to DynWhere (Remote) creates a Dynamic Where clause from a
NSPredicate
that will filter all records where theRole
is equal to "manager" - Complex NSPredicate (Remote) creates a more complex Dynamic Where clause that is comprised of 3
NSPredicate
s &NSCompoundPredicate
s to request all records where theRole
is "manager" and theName
begins with "J", or where theId
is either "20, 22 or 24".
- XML to DynWhere (Remote) uses an XML based Dynamic Where clause to retrieve all table records where the
-
Local Filters
- Simple NSPredicate (Local) filters the local table data for all records where the
Name
contains ll - Complex NSPredicate (Local) creates a complex
NSPredicate
, using 3NSPredicate
s &NSCompoundPredicate
s, which filters the table for all records where theRole
is "Guest" and theName
begins with "Je", or those records where theId
is between "19 and 23".
- Simple NSPredicate (Local) filters the local table data for all records where the
Examining the Code
The code for creating the connection to the server and handling logging in is covered by other samples. In this section we shall focus solely on creating the DADynamicWhereClause objects and retrieving the records using the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter.
App Structure
The sample is built around four classes; AppDelegate
, DataAccess
, ServiceAccess
, and RegisterServerWindowController
.
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 back to the server.
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 RegisterServerWindowController
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 RegisterServiceWindowController
, DataAccess
and NSTableView
classes. The main thing of note here is the runReport:
method which is called when a condition is chosen from the popup and determines which condition was selected and calls the appropriate method in DataAccess
. The other method is reportReady:
which is called when the NOTIFICATION_REPORT_READY
notification is received and will reload the table and display the new data.
All of the code below can be found in DataAccess.m
.
Creating a DA SQL Statement and executing it
In Data Abstract for Cocoa, Dynamic Where clauses can be constructed from either XML statements or from NSPredicate
& NSCompoundPredicate
objects. Here we demonstrate composing a multi-line XML statement with fixed arguments rather than dynamically supply them, which you could do. See the Dynamic Where page to learn more about the different types of expressions available and the XML you will need to use them.
The dynamicWhereClauseWithXmlString: method will create and return a DADynamicWhereClause object for you to use. It is important to note that the class returned is merely a wrapped for the XML and that no validation of the XML is performed. If there are any typos they will be caught by the server at runtime which will generate an ROException
object.
To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select1 feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.
This request will return all rows from the table Users
where their Type
is equal to 3, and as the argument passed to select:
is nil
we indicate that we want all of the fields to be returned for that row.
NOTE If the table name, or the field names do not exist then an ROException
will be raised and should be handled appropriately.
//DataAccess.m
- (void)beginRemoteFilteringUsingPlainXML {
NSString *dynamicWhereXML =
@" \n"
@" <query \n"
@" version='5.0' \n"
@" xmlns='http://www.remobjects.com/schemas/dataabstract/queries/5.0'> \n"
@" <where xmlns=''> \n"
@" <binaryoperation operator='Equal'> \n"
@" <field>Type</field> \n"
@" <constant type='Integer' null='0'>3</constant> \n"
@" </binaryoperation> \n"
@" </where> \n"
@" </query> \n";
NSLog(@"Generating DADynamicWhereClause from XML %@", dynamicWhereXML);
DADynamicWhereClause * filterClause = [DADynamicWhereClause dynamicWhereClauseWithXmlString:dynamicWhereXML];
[self.dataAdapter beginGetDataTable:@"Users"
select:nil /* select all fields */
where:filterClause
withBlock:^(DADataTable *table) {
NSLog(@"Recieved data from the DataAbstract server.");
self.reportTable = table;
NSDictionary *userInfo =
@{REPORT_ROWS_INFO: self.reportTable.rows,
REPORT_HEADER_INFO: @"Select GUEST Users",
REPORT_DETAIL_INFO: @"XML translated into following condition \nType == 3"};
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}];
}
Using an NSPredicate for a Dynamic Where clause
As an alternative to composing a Dynamic Where clause with XML, you can instead use the native Cocoa NSPredicate
class. This is a simpler approach that requires fewer lines of code and feels more natural. There is nothing special you need to do in terms of formatting the predicate, you build it as you normally would.
The dynamicWhereClauseWithPredicate creates and returns an instance of DADynamicWhereClause
using the supplied NSPredicate
object. It is important to note that the class returned is merely wraps the NSPredicate
object and that no validation of the specified field names is performed. If there are any typos they will be caught by the server at runtime, which will generate an ROException
object.
To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.
This request will return all rows from the table Users
where their Role
contains the string "manager", and as the argument passed to select:
is nil
we indicate that we want all of the fields to be returned for that row.
//DataAccess.m
- (void)beginRemoteFilteringUsingPredicate {
NSString *userRolePattern = @"manager";
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"Role CONTAINS[cd] %@", userRolePattern];
NSLog(@"Generating DADynamicWhereClause from NSPredicate %@", filterPredicate);
DADynamicWhereClause *filterClause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:filterPredicate];
[self.dataAdapter beginGetDataTable:@"Users"
select:nil /* select all fields */
where:filterClause
withBlock:^(DADataTable *table) {
NSLog(@"Recieved data from the DataAbstract server.");
self.reportTable = table;
NSDictionary *userInfo =
@{REPORT_ROWS_INFO: self.reportTable.rows,
REPORT_HEADER_INFO: @"Select Managers",
REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", filterPredicate.description]
};
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}];
}
Using an Compound Predicate for a Dynamic Where clause
Building on the previous step, Data Abstract for Cocoa will accept much more complicated NSPredicate
s which are built using multiple NSPredicate
s and NSCompoundPredicate
s.
The dynamicWhereClauseWithPredicate creates and returns an instance of DADynamicWhereClause
using the supplied NSPredicate
object. It is important to note that the class returned is merely wraps the NSPredicate
object and that no validation of the specified field names is performed. If there are any typos they will be caught by the server at runtime, which will generate an ROException
object.
To retrieve the filtered table we use the beginGetDataTable:select:where:withBlock: method of DARemoteDataAdapter. It takes four arguments; the first is the name of the table we want to retrieve, the second could be an array field names that we want to selectively retrieve - this uses the Dynamic Select feature of Data Abstract, the next argument is the DADynamicWhereClause we created a moment ago and the last argument is the block to be executed when the request completes; here it posts a notification that contains the rows from the table and a description of what the request was.
In the example we are building a where clause, where we want people who are "managers" and whose name starts with "J", otherwise users whose Id
is 20, 22 or 24.
//DataAccess.m
- (void)beginRemoteFilteringUsingCompoundPredicate {
// users with role - manager (both sales manager and income managers)
NSString *userRolePattern = @"manager";
NSPredicate *roleCondition = [NSPredicate predicateWithFormat:@"Role CONTAINS[cd] %@", userRolePattern];
// users that name begins with "J"
NSString *userNamePattern = @"J";
NSPredicate *nameCondition = [NSPredicate predicateWithFormat:@"Name BEGINSWITH %@", userNamePattern];
// set of userID to select
NSArray *userIds = @[@20, @22, @24];
NSPredicate *idCondition = [NSPredicate predicateWithFormat:@"Id IN %@", userIds];
// Building final condition ((roleCondition AND nameCondition) OR idCondition)
NSPredicate *compoundAndCondition = [NSCompoundPredicate andPredicateWithSubpredicates:@[roleCondition, nameCondition]];
NSPredicate *finalCondition = [NSCompoundPredicate orPredicateWithSubpredicates:@[compoundAndCondition, idCondition]];
NSLog(@"Generating DADynamicWhereClause from NSPredicate %@", finalCondition);
DADynamicWhereClause *filterClause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:finalCondition];
[self.dataAdapter beginGetDataTable:@"Users"
select:nil /* select all fields */
where:filterClause
withBlock:^(DADataTable *table) {
NSLog(@"Recieved data from the DataAbstract server.");
self.reportTable = table;
NSDictionary *userInfo =
@{REPORT_ROWS_INFO: self.reportTable.rows,
REPORT_HEADER_INFO: @"Complex Compound Predicate",
REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", finalCondition.description]
};
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}];
}
Using an predicate to filter locally
This method creates an NSPredicate
that will filter records whose Name
contains "ll" . The predicate is passed to the filteredArrayUsingPredicate:
of NSArray
which evaluates each object against the given predicate.
A notification is then dispatched to indicate that the data is ready which includes the filtered rows and a description of what the filter was in an NSDictionary
. The notification will be handled by the ResultViewController
object and will cause the table to reload with the filter table.
//DataAccess.m
- (void)applyLocalFilteringUsingPredicate {
NSString *userNamePattern = @"*ll*";
NSPredicate *condition = [NSPredicate predicateWithFormat:@"Name LIKE[cd] %@", userNamePattern];
NSArray *reportRows = [[self.usersTable rows] filteredArrayUsingPredicate:condition];
NSDictionary *userInfo =
@{REPORT_ROWS_INFO: reportRows,
REPORT_HEADER_INFO: @"Simple Local Predicates",
REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", condition.description]
};
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}
Using a complex predicate to filter locally
This method creates a complex NSPredicate
which uses three separate NSPredicate
s and two NSCompoundPredicate
objects to combine them. Here the predicate will filter those records where the Role
is "Guest" and the Name
begins with "Je", or records where the Id
is between 19 and 23. The predicate is passed to the filteredArrayUsingPredicate:
of NSArray
which evaluates each object against the given predicate.
A notification is then dispatched to indicate that the data is ready which includes the filtered rows and a description of what the filter was in an NSDictionary
. The notification will be handled by the ResultViewController
object and will cause the table to reload with the filter table.
//DataAccess.m
- (void)applyLocalFilteringUsingCompoundPredicate {
NSString *rolePattern = @"Guest";
NSPredicate *roleCondition = [NSPredicate predicateWithFormat:@"Role == %@", rolePattern];
NSString *namePattern = @"Je";
NSPredicate *nameCondition = [NSPredicate predicateWithFormat:@"Name BEGINSWITH[cd] %@", namePattern];
// userID range
NSArray *userIds = @[@19, @23];
NSPredicate *idCondition = [NSPredicate predicateWithFormat:@"Id BETWEEN %@", userIds];
// Building final condition ((roleCondition AND nameCondition) OR idCondition)
NSPredicate *compoundAndCondition = [NSCompoundPredicate andPredicateWithSubpredicates:@[roleCondition, nameCondition]];
NSPredicate *finalCondition = [NSCompoundPredicate orPredicateWithSubpredicates:@[compoundAndCondition, idCondition]];
NSLog(@"Applying complex compound predicate %@", finalCondition);
NSArray *reportRows = [[self.usersTable rows] filteredArrayUsingPredicate:finalCondition];
NSDictionary *userInfo =
@{REPORT_ROWS_INFO: reportRows,
REPORT_HEADER_INFO: @"Complex Local Predicate",
REPORT_DETAIL_INFO: [NSString stringWithFormat:@"Predicate: %@", finalCondition.description]
};
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REPORT_READY object:self userInfo:userInfo];
}
-
Dynamic Select provides a means to request only the columns you need from a table row. ↩