Skip to content
This repository has been archived by the owner on Mar 5, 2023. It is now read-only.

Model: Add deleteTag(Tag) #790

Closed
wants to merge 2 commits into from

Conversation

yamgent
Copy link
Member

@yamgent yamgent commented Jan 7, 2018

Part of #784. Fixed the deleteTag(Tag) exception by overhauling the design. Also added test.

Proposed commit message:

The user may want to remove a particular tag from all persons in the
AddressBook. For example, he may want to delete the tag "colleague"
from all his contacts if he is no longer working at his former company.

No such command exist at the moment, as the model API does not expose a
way to delete a particular tag entirely from the AddressBook (i.e. 
removing the Tag from all Persons and purging the Tag in the 
AddressBook's tag list).

As a step towards allowing the user to remove a particular tag with a
single command, let's implement Model#deleteTag(Tag), so that the
developer dealing with the 'logic' component is able to implement such
a command.

Note: For demo purpose, don't merge this to master!

@CanIHasReview-bot
Copy link

v1

@yamgent submitted v1 for review.

(📚 Archive)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/1/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[1/4] I've modified the 1st 2 paragraphs of your commit message to fit our standard structure more.

AddressBook#updatePerson() does not update the list of tags in the 
address book correctly, as tags that are no longer used by anyone after
the update remains in the list.

This is a buggy behavior as the list of tags is expected to only 
contain tags that are used persons in the address book.

Let's modify AddressBook#updatePerson() to refresh the tags list after
updating the person, so that any unused tags are purged.

}

/**
* Returns whether any {@code Person} in this {@code AddressBook} is still using {@code tag}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns true whether if any {@code Person} in this {@code AddressBook} {@code persons} is still using {@code tag}.

private void removeUnusedTags() {
Set<Tag> tagsInUse = new HashSet<Tag>();

tags.forEach(tag -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use enhanced for-loop here, since you are doing so as well for the method isTagUsed, so we should standardise it (since there's no additional benefit compared to using forEach over enhanced for-loop.

Alternatively, you can update the UniquePersonList & UniqueTagList to return a Stream<Person> and Stream<Tag> respectively. That will shorten these methods here quite substantially.

@@ -112,6 +112,35 @@ public void updatePerson(ReadOnlyPerson target, ReadOnlyPerson editedReadOnlyPer
// This can cause the tags master list to have additional tags that are not tagged to any person
// in the person list.
persons.setPerson(target, editedPerson);
removeUnusedTags();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you spotted a bug here, this should actually be fixed in master right? Wanna raise an issue for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already raised in #753 actually.

This commit is a temporary fix, it is not ideal because the complexity will become O(n^2). An overhaul of the tag list is probably required.

@@ -69,6 +72,16 @@ public void getTagList_modifyList_throwsUnsupportedOperationException() {
addressBook.getTagList().remove(0);
}

@Test
public void updatePerson_detailsChanged_personsAndTagsListUpdated() throws Exception {
addressBook.addPerson(BOB);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should use AddressBookBuilder() to create an AddressBook with BOB. Then we call addressBook.updatePerson(BOB, AMY); afterwards.

This is so that we don't need to rely on AddressBook#addPerson(Person) to be working correctly, since this method only tests for updatePerson(Person, Person)

* Removes all {@code Tag}s that are not used by any {@code Person} in this {@code AddressBook}.
*/
private void removeUnusedTags() {
Set<Tag> tagsInUse = new HashSet<Tag>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the word Tag in new HashSet<Tag> is unnecessary, as reported by my trustworthy IntelliJ :P

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[2/4] Should the tests be more vigorous by including the throwing of PersonNotFoundException?

@Test
public void removeTagFromPerson_tagUsedByMultiplePersons_personUpdated() throws Exception {
addressBook.addPerson(AMY);
addressBook.addPerson(BOB);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as mentioned above.

@@ -191,6 +191,22 @@ public void addTag(Tag t) throws UniqueTagList.DuplicateTagException {
tags.add(t);
}

/**
* Removes {@code tag} from {@code person} in this {@code AddressBook}.
* @throws DuplicatePersonException if removing the tag causes the {@code person} to be equivalent to another
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation of equals doesn't allow for this exception to happen actually, since it doesn't consider equality of tags. As such, perhaps we shouldn't throw this exception, but do a try-catch followed with throw new AssertionError()?

I'm not too picky about this though, @damithc what do you think?

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[3/4]

/**
* Removes {@code tag} in this {@code AddressBook}.
*/
public void removeTag(Tag tag) throws DuplicatePersonException, PersonNotFoundException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this method shouldn't be throwing both Exceptions, we should do a try-catch followed by throw new AssertionError().


AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(AMY).withPerson(BOB).build();

assertEquals(addressBook, expectedAddressBook);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong order of assertEquals :P

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[4/4]

public void deleteTag(Tag tag) throws PersonNotFoundException, DuplicatePersonException {
for (ReadOnlyPerson person : addressBook.getPersonList()) {
addressBook.removeTagFromPerson(tag, person);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just call addressBook.removeTag(tag); right? :P That way, you can make removeTagFromPerson private instead.

ModelManager modelManager = new ModelManager(addressBook, userPrefs);
modelManager.deleteTag(new Tag(VALID_TAG_UNUSED));

assertEquals(modelManager, new ModelManager(addressBook, userPrefs));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong ordering of assertEquals :P

AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(amyWithoutFriendTag)
.withPerson(bobWithoutFriendTag).build();

assertEquals(modelManager, new ModelManager(expectedAddressBook, userPrefs));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong ordering of assertEquals :P

@@ -26,6 +35,34 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
modelManager.getFilteredPersonList().remove(0);
}

@Test
public void deleteTag_nonExistentTag_sameAddressBook() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the last segment of this test be named as modelUnchanged instead?

@yamgent yamgent force-pushed the getting-started-model branch 2 times, most recently from b313b09 to b2ad284 Compare January 12, 2018 13:13
@CanIHasReview-bot
Copy link

v2

@yamgent submitted v2 for review.

(📚 Archive) (📈 Interdiff between v1 and v2)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/2/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[1/4] commit message for 2nd paragraph, sorry I typed wrongly:

This is a buggy behavior as the list of tags is expected to only
contain tags that are used by persons in the address book.

@Test
public void updatePerson_detailsChanged_personsAndTagsListUpdated() throws Exception {
AddressBook addressBookWithBob = new AddressBookBuilder().withPerson(BOB).build();
addressBookWithBob.updatePerson(BOB, AMY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after the update, this address book is no longer addressBookWithBob cos it actually stores AMY. Not sure if this may end up being confusing. I can't think of a better naming though. Maybe addressBookUpdatedToAmy?

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[2/4]

try {
updatePerson(person, newPerson);
} catch (DuplicatePersonException dpe) {
throw new AssertionError("Modifying a person's tags only should not result in a duplicate.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add something like "See Person#equals(Object)" for clarity?

Copy link
Contributor

@Zhiyuan-Amos Zhiyuan-Amos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[3/4] as mentioned #790 (comment), we can make removeTagFromPerson(Tag, ReadOnlyPerson) private. As such, perhaps a better way is to squash commits 2 and 3 together. I think we don't test private methods in our code base right? So I think we can remove the tests in commit 2.

@@ -116,6 +116,28 @@ public void removeTagFromPerson_tagUsedByMultiplePersons_personUpdated() throws
assertEquals(expectedAddressBook, addressBookWithBobAndAmy);
}

@Test
public void removeTag_nonExistentTag_sameAddressBook() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we change sameAddressBook -> addressBookUnchanged? Sounds a bit more natural.

@yamgent yamgent force-pushed the getting-started-model branch 2 times, most recently from bf83718 to 299e83a Compare January 18, 2018 07:35
@se-edu se-edu deleted a comment Jan 18, 2018
@CanIHasReview-bot
Copy link

v3

@yamgent submitted v3 for review.

(📚 Archive) (📈 Interdiff between v2 and v3)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/3/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

@CanIHasReview-bot
Copy link

v4

@yamgent submitted v4 for review.

(📚 Archive) (📈 Interdiff between v3 and v4)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/4/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

@yamgent yamgent mentioned this pull request Aug 4, 2018
7 tasks
@yamgent yamgent force-pushed the getting-started-model branch 2 times, most recently from 1b03d80 to 20502f5 Compare August 9, 2018 04:57
@CanIHasReview-bot
Copy link

v7

@yamgent submitted v7 for review.

(📚 Archive) (📈 Interdiff between v6 and v7) (📈 Range-Diff between v6 and v7)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/7/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

@yamgent yamgent requested a review from a team August 9, 2018 05:20
@yamgent
Copy link
Member Author

yamgent commented Aug 9, 2018

Changelog:

  • [v6 1/3] was purged because the codebase finally got rid of syncWithMasterTagList(), which was what the commit was fixing (hooray 🎉!)
  • 20502f5: deleteTag_tagUsedByMultiplePersons_tagRemoved() had its test logic rewritten because we now have version address books.


try {
updatePerson(person, newPerson);
} catch (DuplicatePersonException dpe) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DuplicatePersonException will be made RuntimeException in #896 tho :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, I'll say that is a good thing, given the

            throw new AssertionError("Modifying a person's tags only should not result in a duplicate. "
                    + "See Person#equals(Object).");

below.

for (Person person : persons) {
removeTagFromPerson(tag, person);
}
} catch (PersonNotFoundException pnfe) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we catch PNFE and DPE at the same location (in removeTagFromPerson)?

Copy link
Member Author

@yamgent yamgent Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it makes sense to handle PNFE here, because callers of removeTagFromPerson(Tag, Person) might actually pass in a person that does not exist in the address book, so it is a valid exception in that sense. However, handling DPE here would be quite strange since callers are not expected to manipulate Persons themselves, so removeTagFromPerson(Tag, Person) should deal with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm I read your comments wrongly, will update it :P.

@yamgent yamgent force-pushed the getting-started-model branch 3 times, most recently from 2b094b4 to 04fa653 Compare August 16, 2018 12:12
@CanIHasReview-bot
Copy link

v8

@yamgent submitted v8 for review.

(📚 Archive) (📈 Interdiff between v7 and v8) (📈 Range-Diff between v7 and v8)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/8/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

@yamgent
Copy link
Member Author

yamgent commented Aug 16, 2018

Conflicts Resolution (Round 2):

  • Thankfully, only import conflicts.

Update:

  • Requested change made.


try {
updatePerson(person, newPerson);
} catch (DuplicatePersonException dpe) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this can be removed?

throw new AssertionError("Modifying a person's tags only should not result in a duplicate. "
+ "See Person#equals(Object).");
} catch (PersonNotFoundException pnfe) {
throw new AssertionError("Original person must be obtained from the address book.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this can be removed?

*/
public void removeTag(Tag tag) {
for (Person person : persons) {
removeTagFromPerson(tag, person);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do foreach here instead?

We want to be able to delete a particular Tag from our AddressBook
entirely.

Let's add AddressBook#removeTag(Tag).

An alternative would be to move the logic to a new method
ModelManager#deleteTag(Tag). However, it violates SRP as the
ModelManager should not be concerned with the details of how the tags
are removed from the AddressBook.
The Model API does not expose a way to delete a particular Tag entirely
from the AddressBook (i.e. removing the Tag from all Persons and purging
the Tag in the AddressBook's tag list).

Let's add Model#deleteTag(Tag).
@CanIHasReview-bot
Copy link

v9

@yamgent submitted v9 for review.

(📚 Archive) (📈 Interdiff between v8 and v9) (📈 Range-Diff between v8 and v9)

Checkout this PR version locally
git fetch https://github.com/se-edu/addressbook-level4.git refs/pr/790/9/head:BRANCHNAME

where BRANCHNAME is the name of the local branch you wish to fetch this PR to.

@yamgent yamgent requested a review from a team August 18, 2018 12:11
@yamgent yamgent requested a review from damithc August 18, 2018 12:52
@damithc damithc closed this Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
pr.Answer Pull request is answer for a tutorial in developer guide. Do not actually merge.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants