Objectives

Simplify the model by removing the association from post to user and simplfy the tests by introducing setup and teardown for objects used in each test. Evolve an approach for deleting a blog post

Blog Feature

Use the completed version of the app from last week or your own project if you completed it as the basis for this weeks lab.

Remember, when to import the project, on the command line (from within the project folder), i.e. type:

play eclipsify

In eclipse, File->Import->General->Existing Project into Eclipse....

Run the project, and verify that the blog feature operates as expected.

@Lob

Currently the model class Post may look like this:

@Entity
public class Post extends Model
{
  public String title;
  public String content;

  public Post(String title, String content)
  {
    this.title = title;
    this.content = content;
  }

  public String toString()
  {
    return title;
  } 
}

If you try to insert a very large post, however, the application will fail (try this). In order to cope with large text fields we need to insert the @Lob annotation:

Insert this now like this:

@Entity
public class Post extends Model
{
  public String title;
  @Lob
  public String content;

  public Post(String title, String content)
  {
    this.title = title;
    this.content = content;
  }

  public String toString()
  {
    return title;
  } 
}

And verify that you an insert larger posts.

For a more general introduction to the field of Java Database access, see:

in particular, briefly review this article:

Simplifying UserTest and MessageTest

The tests we have written so far as overly complex. Specifically, we are creating many of the same objects in each test. This can be simplified considerably. Here is a replacement for the UserTest class:

import models.User;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import play.test.Fixtures;
import play.test.UnitTest;


public class UserTest extends UnitTest
{
  private User bob;

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  @Before
  public void setup()
  {
    bob = new User("bob", "jones", "bob@jones.com", "secret",  20, "irish");
    bob.save();
  }

  @After
  public void teardown()
  {
    bob.delete();
  }

  @Test
  public void testCreateUser()
  {
    User testUser = User.findByEmail("bob@jones.com");
    assertNotNull (testUser);
  }

  @Test
  public void testFindUser()
  {
    User alice = User.findByEmail("alice@jones.com");
    assertNull (alice);
  }  
}

Compare this with the existing UserTest class. Note the new methods 'setUp' and 'tearDown'. These are run between each test, and can be used to create objects we might need for those tests. Replace your Test with the above and verify that is passes.

Here is a revision of MessageTest:

import java.util.List;
import models.Message;
import models.User;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import play.test.Fixtures;
import play.test.UnitTest;

public class MessageTest extends UnitTest
{
  private User mary, joan;

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  @Before
  public void setup()
  {
    mary = new User("mary", "colllins", "mary@collins.com", "secret", 20, "irish");
    mary.save();

    joan = new User("joan", "colllins", "joan@collins.com", "secret", 20, "irish");
    joan.save();
  }

  @After
  public void teardown()
  {
    mary.delete();
    joan.delete();
  }

  @Test
  public void testCreateMessage()
  {
    Message msg = new Message(mary, joan, "Hi there - how are you");
    msg.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals(joansMessages.size(), 1);

    msg.delete();
  }

  @Test
  public void testNoMessagese()
  {
    List<Message> joansMessages = Message.find("byTo", mary).fetch();
    assertEquals(joansMessages.size(), 0);
  }

  @Test
  public void testMultipleMessages()
  {
    Message msg1 = new Message(mary, joan, "Hi there - how are you");
    msg1.save();
    Message msg2 = new Message(mary, joan, "Where are you now?");
    msg2.save();

    List<Message> joansMessages = Message.find("byTo", joan).fetch();
    assertEquals(joansMessages.size(), 2);
    Message message1 = joansMessages.get(0);
    assertEquals(message1.messageText, "Hi there - how are you");
    Message message2 = joansMessages.get(1);
    assertEquals(message2.messageText, "Where are you now?");

    msg1.delete();
    msg2.delete();
  }
}

Again notice the simplification - we are creating the test users in the setUp method, this allows the tests to be more focused.

Run both of these tests now and see if they pass. Remember, to run the tests first run the app in 'test' mode:

play test

and then browse to:

Select the tests you want to run and run them.

As an experiment, comment out the very last line of the last test:

   // msg2.delete();

Run this test again. What happens? Remove the comments and run the test again. Can you explain why the test failed?

Simplifying BlogTest

Applying the same process, here is a revised version of the BlogTest class:

import java.util.ArrayList;
import java.util.List;

import models.Message;
import models.Post;
import models.User;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import play.test.Fixtures;
import play.test.UnitTest;

public class BlogTest extends UnitTest
{
  private User bob;
  private Post post1, post2;

  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  @Before
  public void setup()
  {
    bob   = new User("bob", "jones", "bob@jones.com", "secret", 20, "irish");
    post1 = new Post("Post Title 1", "This is the first post content");
    post2 = new Post("Post Title 2", "This is the second post content");
    bob.save();
    post1.save();
    post2.save();
  }

  @After
  public void teardown()
  {
    bob.delete();
    post1.delete();
    post2.delete();
  }

  @Test
  public void testCreatePost()
  {
    bob.posts.add(post1);
    bob.save();

    User user = User.findByEmail("bob@jones.com");
    List<Post> posts = user.posts;
    assertEquals(1, posts.size());
    Post post = posts.get(0);
    assertEquals(post.title, "Post Title 1");
    assertEquals(post.content, "This is the first post content");
  }

  @Test
  public void testCreateMultiplePosts()
  {
    bob.posts.add(post1);
    bob.posts.add(post2);
    bob.save();

    User user = User.findByEmail("bob@jones.com");
    List<Post> posts = user.posts;
    assertEquals(2, posts.size());
    Post posta = posts.get(0);
    assertEquals(posta.title, "Post Title 1");
    assertEquals(posta.content, "This is the first post content");

    Post postb = posts.get(1);
    assertEquals(postb.title, "Post Title 2");
    assertEquals(postb.content, "This is the second post content");
  }
}

These tests should pass.

Deleting Blog Posts - Test

It might be useful for a user to be able to delete a blog post. We could do this my placing a button beside each post and, if it is pressed, then the post will be removed.

To verify that we can do this - we should experiment with removing blog posts in our tests first.

Write the following new test in BlogTest

  @Test
  public void testDeletePost()
  {
    Post post3 = new Post("Post Title 3", "This is the third post content");
    post3.save();
    bob.posts.add(post3);
    bob.save();

    User user = User.findByEmail("bob@jones.com");
    assertEquals(1, user.posts.size());  

    user.posts.remove(0);
    user.save();

    User anotherUser = User.findByEmail("bob@jones.com");
    assertEquals(0, anotherUser.posts.size());   
   }

Run the BlogPost test. It should succeed. However, is it in fact deleted?

With the app still running under test, open the database interface in another tab:

Look at the list if Posts objects. Our post (the one we thought we deleted) is still there. However, the user bob is gone, and the other posts we created are all gone. How can this be?

The reason is that we removed the post form the collection owned by the user bob, but we never explicitly deleted the post. Here is another version:

  @Test
  public void testDeletePost()
  {
    Post post3 = new Post("Post Title 3", "This is the third post content");
    post3.save();
    bob.posts.add(post3);
    bob.save();

    User user = User.findByEmail("bob@jones.com");
    assertEquals(1, user.posts.size());  
    Post post = user.posts.get(0);

    user.posts.remove(0);
    user.save();
    post.delete();

    User anotherUser = User.findByEmail("bob@jones.com");
    assertEquals(0, anotherUser.posts.size());   
   }

Look carefully at this test. We first recover the post we want to delete (the first one), then we remove it from the collection maintained by user, and then we delete the object from the database.

The steps to remove a post were:

  • locate the post object we wish to remove
  • remove it from the collection
  • save the user object
  • delete the post object

Run the test again now - and verify (via the admin interface) that the post is in fact deleted.

Deleting Blog Posts - UI and Controller

Bearing in mind the test we have just completed, we can now implement a new method in our Blog controller:

  public static void deletePost(Long postid)
  {    
    User user = Accounts.getLoggedInUser(); 

    Post post = Post.findById(postid);
    user.posts.remove(post);

    user.save();
    post.delete();

    index();
  }

The steps are clear:

  • locate the user
  • locate the post
  • remove the post from the users collection of posts
  • save the user (with the post removed)
  • delete the post from the database

The next step will be to place a button after each post which will trigger a call this action, passing the id of the post to delete. Here is views/components/displayposts.html:

<section class="ui stacked segment">
  <h4 class="ui inverted blue block header">Published Posts</h4>
  #{list items:user.posts, as:'post'}
    <h5 class="ui inverted green block header"> ${post.title}</h5>
    <p> 
      ${post.content}:
    </p>
  #{/list}
</section>

We could place a button in at the end of the section:

<section class="ui stacked segment">
  <h4 class="ui inverted blue block header">Published Posts</h4>
  #{list items:user.posts, as:'post'}
    <h5 class="ui inverted green block header"> ${post.title}</h5>
    <p> 
      ${post.content}:
    </p>
    <a href="?" class="ui button red labeled icon"><i class="icon delete"></i> Delete </a>
  #{/list}
</section> 

Try this now and verify that you can see the delete button.

What should the href be? First, we need a route on the routes file:

GET   /blog/deletepost/{postid}                 Blog.deletePost

Now we can insert the correct url for the button:

    <a href="/blog/deletepost/${post.id}" class="ui button red labeled icon"><i class="icon delete"></i> Delete </a>

Try this now. You should be able to create a number of posts, and delete them. Keep an eye on the database:

and make sure the posts table reflect the delete actions you perform. To test this, comment out the post delete:

//    post.delete();

and create and delete posts. Examine the posts table. Are the posts still there?

Exercise

BlogPost view

Currently when we add a post, then the full post appears on our Blog page.

Consider the following changes:

  • On the blog page, instead of a list containing each blog title + post, just list the blog titles
  • Turn this title into a link.
  • Clicking on this link, takes the user to a new page containing just that title (again) + post.
  • You can only delete a post from the post page (not from the blog page as currently implemented)