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 the Role is equal to "manager"
    • Complex NSPredicate (Remote) creates a more complex Dynamic Where clause that is comprised of 3 NSPredicates & NSCompoundPredicates to request all records where the Role is "manager" and the Name begins with "J", or where the Id is either "20, 22 or 24".
  • 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 3 NSPredicates & NSCompoundPredicates, which filters the table for all records where the Role is "Guest" and the Name begins with "Je", or those records where the Id is between "19 and 23".

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 NSPredicates which are built using multiple NSPredicates and NSCompoundPredicates.

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

  1. Dynamic Select provides a means to request only the columns you need from a table row.