Appearance
Deliverable 2 โ
Due Friday 16 October, 18:00 ยท individual ยท submit on GitHub and PrairieLearn
The registrar is getting tired of writing complex queries of the form { "OR": [ {"IS": { "dept": "math" }}, {"IS": {"dept": "hist"}}] }. As a solution, we want to allow users to query using a new IN operator which accepts an sfield and a list of strings that records must match exactly (no wildcard matching). For example, { "IN": { "dept": ["math", "hist"] }} selects all sections whose dept is exactly "math" or "hist".
As you will see, adding this feature directly to the current code would require editing duplicated logic across filterSections and filterRooms. Instead of adding to the duplication, you are first going to refactor the two functions so you can implement the IN operator in exactly one location.
Note: This deliverable is meant to be completed in order. The PrairieLearn assessment for this deliverable (DELIV2) has one part for each section below, all due at the deadline above. First complete Part 1 (Pre-Analysis), since it will help guide your refactor. Then strengthen the test suite, do the refactor, and complete Part 2. Finally, implement IN and complete Part 3.
1. Pre-Analysis โ
Trace Model.searchV2 and how it calls filterSections and filterRooms.
Submit in DELIV2, Part 1:
- How similar are these methods? Generate a diff file that shows the differences in code between
filterSectionsandfilterRooms, then describe: of the lines marked as different, what exactly in each line is different? To generate the diff:- Copy the whole
filterSectionsfunction (fromfunction filterSections(to its closing}) into a new filesections.ts, and the wholefilterRoomsfunction intorooms.ts. - From the directory containing those files, run
git diff --no-index --output=filter-refactor.diff sections.ts rooms.ts. Lines starting with-are only infilterSections, and lines starting with+are only infilterRooms. - Commit
filter-refactor.diffto the root of your repo (you can deletesections.tsandrooms.ts).
- Copy the whole
- How would you implement
IN? Briefly describe the changes you would need to make to implementINas is (without refactoring). - What type of coupling is this? Describe the coupling between
filterSectionsandfilterRoomsin terms of Degree, Locality, Connascence, and type (implicit/explicit) with a brief justification. - Should you refactor first? Compare and contrast two options: implementing
INdirectly (as in 1.2) versus refactoring first. What risks and difficulties does each option carry, both now and for future changes?
2. Refactor โ
Now that you have a good idea of the problem, you want to apply a refactoring so you don't have to duplicate the implementation work of IN. Using the diff file from your pre-analysis (1.1), you should have specifics on what makes the methods different (and what is the same between them). Your job here is to actually carry out the refactor. You may choose how you will centralize the logic, as long as it meets these requirements:
- One filter function.
filterSectionsandfilterRoomsare replaced by a single function that evaluates aWHEREclause for both rooms and sections. Every search (including the v1 search endpoint) uses this one function. - A common interface. Declare an interface (or abstract class) that both
RoomandSectionexplicitlyimplements(orextends). It must have at least 2 methods, chosen from your analysis in 1.1: the things that differ betweenfilterSectionsandfilterRoomsshould become methods that each class implements in its own way. - The filter function depends on the interface, not the classes (DIP). The filter function should work with records only through your interface; it should not refer to
RoomorSectiondirectly.
Keep the class names Room and Section (you may move them to other files), and keep createApp exported from src/App.ts and callable as createApp({ datadir }).
Submit in DELIV2, Part 2:
- Strengthened tests. The existing tests are your safety net for this refactor. However, only one existing test (
Test 25v) checks that a comparison operator (LT,GT,EQ, orIS) inWHERErejects a key from the other kind of dataset. Before you start refactoring:- Add tests similar to
Test 25vthat cover each comparison operator for both kinds of dataset. Commit them tomain(directly or through a pull request) so that your refactoring pull request itself does not touchtest/. Submit a link to the commit or pull request that added them. - Briefly explain what your refactor could have broken without these tests failing.
- Add tests similar to
- A link to a merged pull request that contains your refactoring and nothing else. If you choose to move methods or files around first (e.g. to help readability), do so in separate commits within this pull request, before the commits that change the filter logic. This pull request must not modify anything in
test/, and must not contain any code for theINoperator. - Justification: why did you choose the common interface that you did?
- Implications: Your refactored filter function is now used by both the v1 and v2 search endpoints. What will this mean for the v1 search once you implement
IN? Briefly explain whether you think this is a problem, and why.
3. Implement IN โ
The syntax is { "IN": { <sfield>: string[] } }, and IN behaves as follows:
- A record matches if its value for
<sfield>is exactly equal (case-sensitive) to at least one string in the array. Duplicate strings in the array are allowed. - Unlike
IS,INdoes no wildcard matching:*is treated as an ordinary character. INworks on every sfield of bothcourse_offeringsandfacilities, and can be nested insideAND,OR, andNOTlike any other filter.- The search responds with
400 Bad Request, error"Invalid query", and message"IN must be an object with one sfield and a non-empty array of strings"when:- the value of
INis not an object, or does not have exactly one key; - the key is not a valid sfield (e.g., an unknown key or an mfield);
- the value for the key is not an array, is an empty array, or contains a non-string element.
- the value of
- If the key is a valid sfield of the other kind of dataset (e.g.,
namein acourse_offeringsquery), the search responds with400 Bad Requestand the existing message"Cannot mix course_offerings and facilities fields in one query", exactly as for the other filters.
Submit in DELIV2, Part 3:
- A link to a merged pull request that contains your implementation, tests, and documentation for
INand nothing else. Merge this pull request only after your refactoring pull request (2.2).- Tests: include test cases for each behaviour above. Your tests must exercise the HTTP API (
POST /api/v2/searchusingsupertest, as in the existing tests) rather than calling internal functions directly (usecreateApp({ datadir }), as the existing tests do): we also run your tests against our own implementation to check how many incorrect implementations they catch. - Documentation: update the
POST /api/v2/searchdescription inopenapi.yml(the v1 search does not need to change):- add
INto the query grammar (EBNF); - briefly document how
INoperates, including that it does not support wildcards; - add the new
INerror message to the400response's list of messages.
- add
- Tests: include test cases for each behaviour above. Your tests must exercise the HTTP API (
- Reflection: Looking back at your predictions in 1.2 and 1.4, was the refactor worth it? Compare how much work implementing
INtook with how much work the refactor took. Did anything turn out easier or harder than you expected? Would your answer change depending on how often the query language is likely to change in the future?
Grading โ
This deliverable has both autograded and manually graded components: 40% autograded and 60% manually graded.
Autograded (40%). Every commit you push to the
mainbranch of your repo is automatically graded, and your highest-scoring commit before the deadline is used as your final grade. To request feedback on a commit, enter the following in the commit comment:@310-bot #d2. Be sure to read the full details of the autograder. In particular, note that the number of times you can request feedback is limited each day.The autograder checks that
INbehaves as specified in Section 3, that your refactor meets Section 2 requirements 2 and 3, and thatopenapi.ymldocumentsINin the query grammar and error messages. It also runs your tests from 2.1 and 3.1 against our own implementation, with a series of small bugs introduced, to check how well your tests catch them. Finally, it deducts marks if your changes break existing search behaviour. For the refactor and documentation checks, the feedback tells you specifically what is missing.Manually graded (60%). Your answers on PrairieLearn (DELIV2) and the other deliverables above will be graded by the TAs after the deadline.