3.3 Refining and Expanding a Query
Occassionally you may need to progresively refine an instancesOf: collection, without actually requiring the member objects of that collection to be in memory. This may be due to the structure of your user interface, or possibly to avoid one large enumeration block containing many separate conditions &'d together. Refining a query in this way can be done easily and efficiently using the method satisfying:.
satisfying: performs in a similar way to select:. However, instead of returning a Collection of objects matching the discriminator block (which would entail fetching all those objects from the database), satisfying: returns another instancesOf: collection. You may then select:, reject: etc. against this block, or alternatively refine the collection further with a second (third, fourth…) satisfying: message. Example:
allSmiths := (aReStore instancesOf: Person) satisfying: [ :each | each surname = 'Smith'].
allJohnSmiths := allSmiths satisfying: [ :each | each firstName = 'John'].
specificJohnSmith := allJohnSmiths detect: [ :each | each dateOfBirth = aDate]
The important point to grasp is that no querying against the database is done by satisfying: - this makes it very efficient. In the above example, if select: were used instead of satisfying:, then possibly several thousand Person instances would have been fetched from the database by the surname part of the query. Most of these would be discarded as the query was refined – this would make the whole process very inefficient. By using satisfying:, however, there is no interaction with the database until the final detect:, upon which only one Person object is fetched from the database (assuming a single match is found).
For a good example of the use of satisfying: within a user interface, see FindCustomerShell>>findCustomer within the Entertainment Shop example application.
It was noted earlier that hierarchies of classes which share a single table allow queries against a superclass to also return instances of subclasses. Sometimes this is not the desired effect; in this case a new instancesOf: collection, specifically excluding the subclasses, can be obtained by using the message withoutInheritance. As an example, in the Entertainment Shop application, class DVDVideo inherits from VHSVideo.
(aReStore instancesOf: VHSVideo) select: [ :each | ...]
...would query for instances of VHSVideo and DVDVideo. To restrict this to just instances of VHSVideo, you could say:
(aReStore instancesOf: VHSVideo) withoutInheritance select: [ :each | ...]
Sometimes, it is convenient or necessary to expand an instancesOf: collection to encompass more elements. Again, this can be due to the structure of a user interface, or to avoid a single enumeration block with multiple conditions |'d (or'd) together.
This can be accomplished easily by 'adding' together two or more instancesOf: collections using the standard addition method addAll:. For example, to find all Persons named 'Smith' or 'Jones':
persons := (aReStore instancesOf: Person) satisfying: [ :each | each surname = 'Smith'].
((aReStore instancesOf: Person) satisfying: [ :each | each surname = 'Jones']).
There are a couple of points to note with the use of addAll:. Firstly, addAll: behaves like a regular Collection addition method in that it returns the message argument, not the receiver. Secondly, you may only add together instancesOf: collections based on the same model class (or an inherited class sharing the same table).
An example of the use of addAll: can be seen in the Entertainment Shop example application - see StockItemFinder>>findItems.
3.3 Refining and Expanding a Query