Implement a phase 1 of solution to assignment 1
Even if you have completed a substantial amount of the assignment, download and include this projects here into your workspace:
(You must run 'play eclipsify' before importing)
Run the app and familiarize yourself with the features as implemented so far.
Introduce a a new 'component' view:
<section class="ui stacked segment">
<h4 class="ui inverted blue block header">Published Posts</h4>
#{list items:user.posts, as:'post'}
<div class="ui item">
<i class="url large basic icon"></i>
<a href="/blogpost/view/${post.id}"> ${post.title} </a>
</div>
#{/list}
</sectiion>
Change Views/Blog/index.html view to include the 'displaypostsummaries.html instead of 'displayposts.html':
...
<h1 class="ui inverted teal block header" >Blog </h1>
<section class="ui two column grid segment">
<div class="row">
<div class="ui column">
#{include "components/createpost.html" /}
</div>
<div class="column">
#{include "components/displaypostsummaries.html" /}
</div>
</div>
</section>
Introduce a new controller called 'BlogPost'.
package controllers;
import java.util.List;
import models.Message;
import models.Post;
import models.User;
import play.Logger;
import play.mvc.Controller;
public class BlogPost extends Controller
{
public static void view(Long postid)
{
Logger.info("Post ID = " + postid);
Post post = Post.findById(postid);
render (post);
}
}
With a matching view:
#{extends 'main.html' /}
#{set title:'Blog' /}
<nav class="ui inverted menu">
<header class="header item"> Spacebook </header>
<div class="right menu">
<a class="active item" href="/blog">Blog</a>
</div>
</nav>
<section class="ui stacked segment">
<h5 class="ui inverted green block header"> ${post.title}</h5>
<p>
${post.content}:
</p>
<a href="/blog/deletepost/${post.id}" class="ui button red labeled icon"><i class="icon delete"></i> Delete </a>
</section>
This needs a matching route:
# BlogPost
GET /blogpost/view/{postid} BlogPost.view
This should establish each post with its own view, and links to these views from the main blog view. Try it out now and make sure it works before proceeding.
To delete a post, we need this method in the Blog Controller:
public static void deletePost(Long postid)
{
User user = Accounts.getLoggedInUser();
Post post = Post.findById(postid);
Logger.info ("Request to delete title:" + post.title + " content:" + post.content);
user.posts.remove(post);
user.save();
post.delete();
index();
}
This route:
GET /blog/deletepost/{postid} Blog.deletePost
We then insert a button onto the end of each post:
...
<section class="ui stacked segment">
<h5 class="ui inverted green block header"> ${post.title}</h5>
<p>
${post.content}:
</p>
<a href="/blog/deletepost/${post.id}" class="ui button red labeled icon"><i class="icon delete"></i> Delete </a>
</section>
Run this now, and verify that you can safely delete posts.
Introduce a new Commment model class:
package models;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import play.db.jpa.Model;
@Entity
public class Comment extends Model
{
@Lob
public String content;
@OneToOne
public User author;
public Date postedAt;
public Comment(User author, String content)
{
this.author = author;
this.content = content;
this.postedAt = new Date();
}
}
This models the comment text, the author + the time when the comment was created.
Extend the Post class to use keep a collection of these:
...
@OneToMany
public List<Comment> comments = new ArrayList<Comment>();
...
We need a test to make sure this actually works:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import models.Comment;
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 CommentTest extends UnitTest
{
private User bob;
private Post aPost;
@BeforeClass
public static void loadDB()
{
Fixtures.deleteAllModels();
}
@Before
public void setup()
{
bob = new User("bob", "jones", "bob@jones.com", "secret",20, "irish");
aPost = new Post ("Post A", "This is the post A content");
bob.posts.add(aPost);
aPost.save();
bob.save();
}
@After
public void teardown()
{
bob.delete();
aPost.delete();
}
@Test
public void testAddDeleteComment()
{
User user = User.findByEmail("bob@jones.com");
Comment comment1 = new Comment(user, "Comment 1");
comment1.save();
Post post = user.posts.get(0);
post.comments.add(comment1);
post.save();
user.save();
User anotherUser = User.findByEmail("bob@jones.com");
assertEquals("Comment 1", anotherUser.posts.get(0).comments.get(0).content);
post.comments.clear();
post.save();
comment1.delete();
User yetAnotherUser = User.findByEmail("bob@jones.com");
assertEquals(0, yetAnotherUser.posts.get(0).comments.size());
}
}
Run the application in test mode:
play test
and then browse to:
and see if all the tests run correctly.
This time we will start with a new route:
POST /blogpost/comment/{postid} BlogPost.newComment
We need to extend the BlogPost controller to respond to this route:
public static void newComment(Long postid, String content)
{
Logger.info("Post ID = " + postid);
Post post = Post.findById(postid);
User user = Accounts.getLoggedInUser();
Comment comment = new Comment(user, content);
comment.save();
post.comments.add(comment);
post.save();
view(postid);
}
(Import comment to get this to compile correctly)
Replace the BlogPost view with the following:
#{extends 'main.html' /}
#{set title:'Blog' /}
<nav class="ui inverted menu">
<header class="header item"> Spacebook </header>
<div class="right menu">
<a class="active item" href="/blog">Blog</a>
</div>
</nav>
<section class="ui stacked segment">
<h5 class="ui inverted green block header"> ${post.title}</h5>
<p>
${post.content}:
</p>
<a href="/blog/deletepost/${post.id}" class="ui button red labeled icon"><i class="icon delete"></i> Delete </a>
<section class="ui stacked segment">
<h5 class="ui inverted blue block header">Comments:</h5>
#{list items:post.comments, as:'comment'}
<p>
${comment.content}
<hr>
</p>
#{/list}
</section>
</section>
<section class="ui form segment">
<h4 class="ui inverted blue block header">Add a comment</h4>
<form action="/blogpost/comment/${post.id}" method="POST">
<div class="field">
<textarea name="content" placeholder="Content"></textarea>
</div>
<button class="ui button green submit labeled icon"><i class="icon edit"></i> Add Comment </button>
</form>
</section>
Read the above very carefully - and note how the comments are listed after the post - and that a comment form is included at the end to allow new comments to be created. Also, make sure you can delete posts without incident.
We will make one more subtle change to make sure our delete post cleans up any attached comments correctly. Change the Post model as follows:
...
@OneToMany (cascade=CascadeType.ALL)
public List<Comment> comments = new ArrayList<Comment>();
...
The 'cascade' means that when we delete a post, all comments will be deleted as well.
Even if you have implemented the steps correctly, down, unzip and import this solution:
spacebook-assignment-part-1.zip
Compare this solution so far with your own assignment.