This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch CAUSEWAY-2873 in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 0a98055c9069a691eafc5b0f766ea2e5acfdddc6 Author: Dan Haywood <[email protected]> AuthorDate: Thu May 23 17:39:12 2024 +0100 CAUSEWAY-2873: exercises 3-1, 3-2, 3-3 --- .../petclinic/pages/030-petowner-entity.adoc | 236 +++++++++++---------- .../modules/petclinic/partials/domain.adoc | 8 +- 2 files changed, 125 insertions(+), 119 deletions(-) diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc index 6b6e6b9bac..a7b0d399fb 100644 --- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc +++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc @@ -5,12 +5,11 @@ In this set of exercises we'll just focus on the `PetOwner` entity. -[#exercise-3-1-rename-petowners-name-property] -== Ex 3.1: Rename PetOwner's name property +[#exercise-3-1-rename-petowners-knownAs-property] +== Ex 3.1: Add PetOwner's knownAs property -In the domain we are working on, `PetOwner` has a `firstName` and a `lastName` property, not a single `name` property. - -In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`, and change the fixture script that sets up data to something more realistic. +In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner``'s preferred name. +We'll also update the fixture script that sets up data to something more realistic. @@ -18,181 +17,184 @@ In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`, [source,bash] ---- -git checkout tags/03-01-renames-PetOwner-name-property +git checkout tags/03-01-adds-PetOwner-knownAs-property mvn clean install mvn -pl spring-boot:run ---- -Remember you can use the menu:Prototyping[Fixture Scripts] menu to setup some example data. +Use the menu:Prototyping[Fixture Scripts] menu to setup some example data. === Tasks -Checkout the solution above and review the git history to see the changes that have already been made. -These include: - -* property `PetOwner#name` -> `PetOwner#lastName` renamed -* JPA mappings updated: -** the corresponding JPQL named queries -** the method names of `PetOwnerRepository` +* add a `knownAs` property: + -This is a Spring Data repository, which uses a link:https://www.baeldung.com/spring-data-derived-queries[naming convention] to infer the queries - -** uniqueness constraint for `PetOwner` - -* the action method names of `PetOwners` domain service renamed +[source,java] +.PetOwner.java +---- +@Column(length = 40, nullable = true, name = "knownAs") // <.> +@Getter @Setter +@Property(editing = Editing.ENABLED) // <.> +@PropertyLayout( + fieldSetId = LayoutConstants.FieldSetId.IDENTITY, // <.> + sequence = "1.1" // <.> +) +private String knownAs; +---- +<.> JPA annotation, declared to be `nullable` because we don't necessarily have a value +<.> The xref:refguide:applib:index/annotation/Property.adoc[@Property] annotation provides domain semantics. +(In comparison, the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation defines UI hints/semantics. +Directly editable (similar to the "notes" property) +<.> Renders this new property in the same field set as `name` ... +<.> ... and after the `name`, not before it + -This also requires updating the `menubars.layout.xml`, which references these action names. +It's also possible to define UI semantics through the associated `.layout.xml` file; we'll look at that alternative in a later exercise. -* updating the xref:refguide:applib:index/annotation/ActionLayout.adoc#associateWith[@ActionLayout] of the `updateName` and `delete` action methods in `PetOwner` +* update `PetOwner_persona` to specify an `knownAs` for some of the owners: + -In the UI, the buttons for these actions are located close to the renamed "lastName" property +[source,java] +.PetOwner_persona.java +---- +public enum PetOwner_persona +implements Persona<PetOwner, PetOwner_persona.Builder> { + + JAMAL("Jamal Washington", "jamal.pdf", "J"), + CAMILA("Camila González", "camila.pdf", null), + ARJUN("Arjun Patel", "arjun.pdf", null), + NIA("Nia Robinson", "nia.pdf", null), + OLIVIA("Olivia Hartman", "olivia.pdf", null), + LEILA("Leila Hassan", "leila.pdf", null), + MATT("Matthew Miller", "matt.pdf", "Matt"), + BENJAMIN("Benjamin Thatcher", "benjamin.pdf", "Ben"), + JESSICA("Jessica Raynor", "jessica.pdf", "Jess"), + DANIEL("Daniel Keating", "daniel.pdf", "Dan"); + + private final String name; + private final String contentFileName; + private final String knownAs; + + ... +} +---- -* renames `@Name` meta-annotation to `@LastName`. +* for the builder, use the `knownAs` value of the entity once created: + -Meta-annotations are a useful way of eliminating duplication where the same value type appears in multiple locations, for example as both an entity property and in action parameters. +[source,java] +.PetOwner_persona.java +---- +@Override +protected PetOwner buildResult(final ExecutionContext ec) { -Build and run the application to make sure it still runs fine. + val petOwner = wrap(PetOwners).create(persona.name); + // ... + if (persona.knownAs != null) { + petOwner.setKnownAs(persona.knownAs); + } + + // ... +} +---- -[#exercise-3-2-add-petowners-firstname-property] -== Ex 3.2: Add PetOwner's firstName property -Now that `PetOwner` has a `lastName` property, let's also add a `firstName` property. -We'll also update our fixture script (which sets up ``PetOwner``s) so that it is more descriptive. + +[#exercise-3-2-define-PetOwners-title-imperatively] +== Ex 3.2: Define PetOwner's title imperatively + +Every domain object has a title, allowing end-users to distinguish one object instance from another. +There's no requirement for this title to be unique, but it does need to be unique "enough". + +Our app currently declares the title of `PetOwner` declaratively using the xref:refguide:applib:index/annotation/Title.adoc[@Title] annotation on the `name` property. +In this exercise, let's change that to also include the `knownAs` property as defined. +Our rule will be: + +* if there is a `knownAs`, then the title should be "<name> (<knownAs>)" +* but if `knownAs` is empty, then the title should be simply the `name`, as it is now. + +To do that, we need to define the title imperatively, using the xref:refguide:applib-methods:ui-hints.adoc#title[title()] method. === Solution [source,bash] ---- -git checkout tags/03-02-adds-PetOwner-firstName-property +git checkout tags/03-02-define-PetOwner-title-imperatively mvn clean install mvn -pl spring-boot:run ---- === Tasks -* copy `@LastName` meta-annotation to create `@FirstName`: -+ -[source,java] -.FirstName.java ----- -@Property(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL) -@Parameter(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL) -@ParameterLayout(named = "First Name") -@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface FirstName { - - int MAX_LEN = 40; -} ----- -+ -Note that this property/parameter is optional. -Its parameter name has also been updated. - +* locate the `name` property of `PetOwner`, and remove the `@Title` annotation -* add a new (JPA nullable) property `firstName` to `PetOwner`: -+ -[source,java] ----- -@FirstName -@Column(length = FirstName.MAX_LEN, nullable = true) -@Getter @Setter @ToString.Include -@PropertyLayout(fieldSetId = "name", sequence = "2") -private String firstName; ----- - -* add a new factory method to accept a `firstName`, and refactor the existing one: +* add a new `title()` method: + [source,java] .PetOwner.java ---- -public static PetOwner withName(String name) { - return withName(name, null); -} - -public static PetOwner withName(String lastName, String firstName) { - val simpleObject = new PetOwner(); - simpleObject.setLastName(lastName); - simpleObject.setFirstName(firstName); - return simpleObject; +@ObjectSupport // <.> +public String title() { + return getName() + (getKnownAs() != null ? " (" + getKnownAs() + ")" : ""); } ---- +<.> The xref:refguide:applib:index/annotation/ObjectSupport.adoc[@ObjectSupport] annotation tells the framework to include this method in the metamodel. -* remove `@Title` annotation from `lastName` property, and add a `title()` method to derive from both properties: -+ -[source,java] -.PetOwner.java ----- -public String title() { - return getLastName() + (getFirstName() != null ? ", " + getFirstName() : ""); -} ----- +[#exercise-3-3-remaining-PetOwner-properties] +== Ex 3.3: Remaining `PetOwner` properties -* Update the `PetOwner_persona` enum with more realistically last names (family names). -+ -Learn more about fixture scripts xref:testing:fixtures:about.adoc[here]. +Comparing `PetOwner` as it is currently defined with our domain model, there are a couple of changes still to make: +* we need to rename `lastCheckedIn` property to `lastVisit` property +* we still need to add in a `telephoneNumber` property, and an `emailAddress` property -[#exercise-3-3-modify-petowners-updatename-action] -== Ex 3.3: Modify PetOwner's updateName action -Although we've added a `firstName` property, currently it can't be edited. -In this exercise we'll modify the `updateName` action to also allow the `firstName` to be changed. +We'll make these changes in this exercise. === Solution [source,bash] ---- -git checkout tags/03-03-modifies-PetOwner-updateName-action +git checkout tags/03-03-remaining-PetOwner-properties mvn clean install mvn -pl spring-boot:run ---- === Tasks -* update `PetOwner#updateName` to also accept a new `firstName` parameter: -+ -image::03-03/refactor-updateName.png[width=800px] -+ -[source,java] -.PetOwner.java ----- -@Action(semantics = IDEMPOTENT, commandPublishing = Publishing.ENABLED, executionPublishing = Publishing.ENABLED) -@ActionLayout(associateWith = "lastName", promptStyle = PromptStyle.INLINE) -public PetOwner updateName( - @LastName final String lastName, - @FirstName String firstName) { - setLastName(lastName); - setFirstName(firstName); - return this; -} -public String default0UpdateName() { - return getLastName(); -} -public String default1UpdateName() { - return getFirstName(); -} ----- +Rename the `lastCheckedIn` property to `lastVisit`: -* add in a "default" supporting method for the new parameter. +* rename the field in `PetOwner` +* update `PetOwner_persona` also (but your IDE probably refactored this already). +* to make it more realistic, let's change the fixture script so that the value of `lastVisit` is some number of days in the past: + [source,java] -.PetOwner.java +.PetOwner_persona.java ---- -public String default1UpdateName() { - return getFirstName(); -} +final var numDaysAgo = fakeDataService.ints().between(100, 2); // <.> +final var lastVisit = clockService.getClock().nowAsLocalDate().minusDays(numDaysAgo); // <.> +petOwner.setLastVisit(lastVisit); ---- -+ -The "default" supporting methods are called when the action prompt is rendered, providing the default for the "Nth" parameter of the corresponding action. +<.> The xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] provides an easy way to create fake data for testing and prototyping +<.> It's good practice to use xref:refguide:applib:index/services/clock/ClockService.adoc[ClockService], so it can be easily mocked in tests + +Add the `telephoneNumber` property: + +* ... +* ... + +Add the `emailAddress` property: + +* ... +* ... + +TODO: ideas for future steps: +- introduce a meta-annotation +- introduce a custom type for these diff --git a/antora/components/tutorials/modules/petclinic/partials/domain.adoc b/antora/components/tutorials/modules/petclinic/partials/domain.adoc index 87641da592..e699f8cd23 100644 --- a/antora/components/tutorials/modules/petclinic/partials/domain.adoc +++ b/antora/components/tutorials/modules/petclinic/partials/domain.adoc @@ -28,11 +28,15 @@ package pets { class PetOwner <<role>> { +id .. - #lastName - #firstName + #name + #knownAs .. -phoneNumber -emailAddress + .. + -lastVisit + .. + -notes } }
