Lesson Tuesday

Over the next few lessons we will add more features to our Blog application. We'll focus particularly on the static variables and methods we introduced at the beginning of our pre-work. If you skimmed the Big Topic: POJOS, Static Variables and Methods lesson, make sure to revisit it now. And remember; static methods operate on the class as a whole, whereas non-static methods operate on an instance of an object.

In the next three lessons, we will add the following to our Blog:

  • A published property to record if a Post has been published or is still a draft.
  • A createdAt property to record when a Post was created.
  • A static variable and method to record and return all Post objects.
  • A static method to empty the list of Post objects.
  • A id property to contain a unique ID for each Post.
  • A method that locates a specific Post using this unique ID.

Recording Whether a Post Has Been Published

Blogging is all about getting posts out there! First, let's add a property that tracks whether a Post is published. That way, if we were to implement an admin backend, we could list Posts that are drafts, while only showing the published Posts on the blog itself. The published property should default to false when a Post is created. Then, when a user publishes the Post, it will be updated to true. Cool.

As always, we'll begin with a JUnit test. First, we'll test that published is set to false by default when a Post is instantiated. Move to your PostTest file and generate a new test. Type out the following:

my-epicodus-blog/src/test/java/PostTest.java
...
@Test
public void getPublished_isFalseAfterInstantiation_false() throws Exception {
  Post myPost = new Post("Day 1: Intro");
  assertEquals(false, myPost.getPublished()); //should never start as published
}
...

Now we can write the code to make this happen. Our code in Post.java should look like this at this moment:

my-epicodus-blog/src/main/java/Post.java
public class Post {

   private String content;
   private static ArrayList<Post> instances = new ArrayList<>();
   private boolean published; //i’m new

   public Post (String content){
       this.content = content;
       this.published = false; //also new
       instances.add(this);
   }

   public String getContent() {
       return content;
   }

   public static ArrayList<Post> getAll(){
       return instances;
   }

   public static void clearAllPosts(){
       instances.clear(); //clear as a method is part of the ArrayList class.
   }

   public boolean getPublished(){ //new too
       return this.published;
   }
}

As you’ve maybe already seen, defining a member variable in the constructor without taking it as an argument allows us to define a default value. That is, all new Post objects will have a published property set to false, because our constructor won't allow us to set it to anything else. And, after implementing this code, our test should pass. Make sure that’s true before you move on.

Dates and Timestamps

Next, we want to record when a new Post is created. We'll need a property to automatically record the date and time our program constructs a Post. To refresh your memory, revisit our lesson on Dates in Java if this is feeling fuzzy. As always, we'll begin with a test.

But making a new Post every time we write a new test isn’t very DRY. Let’s quickly write a function we can call that sets up a new Post for our tests. It doesn’t necessarily shorten our code, but if we later change how a Post is constructed, we'll only have to update our code in one place, so it’s still an improvement. Add the test below, refactor to use setupNewPost, and run your tests to confirm everything still passes.

my-epicodus-blog/src/test/java/PostTest.java
import java.time.LocalDateTime;
...
@Test
public void getCreatedAt_instantiatesWithCurrentTime_today() throws Exception{
  Post myPost = setupNewPost(); //see below
  assertEquals(LocalDateTime.now().getDayOfWeek(), myPost.getCreatedAt().getDayOfWeek());
}

public Post setupNewPost(){
   return new Post("Day 1: Intro");
}
...

Here’s some more information on using Date and Time in our app:

  • As you can see this test uses the LocalDateTime class, so we must import it at the top of the file.
  • LocalDateTime.now() returns a timestamp of the current time.
  • Chaining getDayOfWeek() after LocalDateTime.now() returns only the day of the week denoted by the timestamp. We did not write the method getDayOfWeek(), it is a method of the LocalDateTime class.

We'll add code to Post.java to automatically record the time a Post is created:

my-epicodus-blog/src/main/java/Post.java
import java.time.LocalDateTime;

public class Post {

   private String content;
   private static ArrayList<Post> instances = new ArrayList<>();
   private boolean published;
   private LocalDateTime createdAt; //see constructor and my method

   public Post (String content){
       this.content = content;
       this.published = false;
       this.createdAt = LocalDateTime.now();
       instances.add(this);
   }

   public String getContent() {
       return content;
   }

   public static ArrayList<Post> getAll(){
       return instances;
   }

   public static void clearAllPosts(){
       instances.clear(); //clear as a method is part of the ArrayList class.
   }

   public boolean getPublished(){
       return this.published;
   }

   public LocalDateTime getCreatedAt() {
       return createdAt;
   }

}

We've added a private member property (createdAt) to our class, set its value in the constructor using LocalDateTime.now(), and created a getter method to access it. The test should now pass. Make sure it does!

We'll work more with date objects and timestamps in the future, but if you'd like to explore these concepts now check out the Java Documentation on Date Classes.

Assigning Unique IDs

Now that we can manage a list of many Posts, let's add a unique ID to each. This will not only allow us to differentiate between similar-looking posts, but it will later assist in locating a specific Post, and routing in Spark.

Eventually we'll want users to be able to click an individual Post to view its details. That means each Post will require its own page in our app. Later on we'll also use the ID property to create unique URLs for each post's page.

First, let's implement logic to assign and access IDs; beginning with a test:

my-epicodus-blog/src/test/java/PostTest.java
...
@Test
public void getId_postsInstantiateWithAnID_1() throws Exception{
  Post.clearAllPosts();  // Remember, the test will fail without this line! We need to empty leftover Posts from previous tests!
  Post myPost = new Post("Day 1: Intro");
  assertEquals(1, myPost.getId());
}
...

Moving on, we'll add the logic to assign and retrieve unique ID's:

my-epicodus-blog/src/main/java/Post.java
import java.util.ArrayList;
import java.time.LocalDateTime;

public class Post {
  private final String content;
private static ArrayList<Post> instances = new ArrayList<>();
private boolean published;
private LocalDateTime createdAt;
private int id;

public Post (String content){
   this.content = content;
   this.published = false;
   this.createdAt = LocalDateTime.now();
   instances.add(this);
   this.id = instances.size();
}

public String getContent() {
   return content;
}

public static ArrayList<Post> getAll(){
   return instances;
}

public static void clearAllPosts(){
   instances.clear(); //clear as a method is part of the ArrayList class.
}

public boolean getPublished(){
   return this.published;
}

public LocalDateTime getCreatedAt() {
   return createdAt;
}

public int getId() {
   return id;
}

}

We want our application to assign the Post ID's automatically, instead of requiring the user to manually insert an ID number. That wouldn't be very user friendly. So, similar to the fashion we automatically set each Post's published property to false, we add code to assign a unique id to each Post in the constructor.

Our getId() method test should be passing, since we're clearing our list before running the test. The first Post created should be assigned an id of 1. We're assigning the ID in the constructor with this line:

id = instances.size(); //I’m never null of zero. How come?

After the first Post is added, instances.size() should be 1, after the second Post is created and added to the list it should be 2, and so on. By using instances.size() to assignid, we ensure that each Post always has a unique id. Pretty cool, right?

Finding Specific Objects

So, we're currently adding Posts to a list and automatically assigning them ID's. Nice work! But we'll also need a manner of locating specific Posts using this unique ID. First, more tests. We’re doing two here just to be doubly careful that our findById() method works 💯.

my-epicodus-blog/src/test/java/PostTest.java
...
@Test
public void findReturnsCorrectPost() throws Exception {
   Post post = setupNewPost();
   assertEquals(1, Post.findById(post.getId()).getId());
}

@Test
public void findReturnsCorrectPostWhenMoreThanOnePostExists() throws Exception {
   Post post = setupNewPost();
   Post otherPost = new Post("How to pair successfully");
   assertEquals(2, Post.findById(otherPost.getId()).getId());
}

...

And, the code we'll implement to make it pass:

my-epicodus-blog/src/main/java/Post.java
...
public static Post findById(int id){
   return instances.get(id-1); //why minus 1? See if you can figure it out.
}

...

But wait - why do we have to use id -1? This isn’t a typo. See if you can if you can figure it out. Check the Cheat Sheet if you are stumped.

Here we've created another static method to locate a Post using its id property. This method is static because it must sift through all Post objects to find the specific one we're seeking. And, because it is static, we will call it on the entire class, like this: Post.find().

Again, an individual Post does not "know" about all other Posts. It only knows its own properties. So, if a method's functionality must access all instances of a class, like our find() method above, it needs to be declared static.

OK! Time for a break. Nice work.


Example GitHub Repo for Epicodus Blog

Why -1? Well, the length of an array is not the same as the index, remember?. Arrays and ArrayLists are 0-based in their indexing.

Consider this:
```java String[] fruit = [“apples”, “bananas”, “pears”]; // mm fruity System.out.println(fruit.size); → “3” // sure System.out.println(fruit[3]); // this will result in a NullPointerException as index 3 does not exist.

Hence the need for `id-1`. Were you right?