Refactor the the Donation Play Service to capture a OneToMany relationship from User->Donation. Then, reflect this relationship in the API.
We will start with the routes. In the previous version the User and Donations APIs were independent. In the revised routes below, not carefully how the Donations routes are structured.
# API - Users
GET /api/users UsersAPI.users
GET /api/users/{id} UsersAPI.user
POST /api/users UsersAPI.createUser
DELETE /api/users/{id} UsersAPI.deleteUser
# API - Donations
GET /api/users/{userId}/donations DonationsAPI.donations
GET /api/users/{userId}/donations/{id} DonationsAPI.donation
POST /api/users/{userId}/donations DonationsAPI.createDonation
DELETE /api/users/{userId}/donations/{id} DonationsAPI.deleteDonation
As you can see, the users ID is required in order to reach the donations. The returned (or created) donations will be associated with the given user id.
The User Model can now incorporate the relationship:
@OneToMany(cascade = CascadeType.ALL)
public List<Donation> donations = new ArrayList<Donation>();
We can keep it unidirectional for the moment.
We can get rid of the current controller, and introduce these two new controllers instead.
This one looks after the User model:
package controllers;
import play.*;
import play.mvc.*;
import utils.JsonParsers;
import java.util.*;
import com.google.gson.JsonElement;
import models.*;
public class UsersAPI extends Controller
{
public static void users()
{
List<User> users = User.findAll();
renderJSON(JsonParsers.user2Json(users));
}
public static void user(Long id)
{
User user = User.findById(id);
if (user == null)
{
notFound();
}
else
{
renderJSON(JsonParsers.user2Json(user));
}
}
public static void createUser(JsonElement body)
{
User user = JsonParsers.json2User(body.toString());
user.save();
renderJSON(JsonParsers.user2Json(user));
}
public static void deleteUser(Long id)
{
User user = User.findById(id);
if (user == null)
{
notFound();
}
else
{
user.delete();
renderText("success");
}
}
public static void deleteAllUsers()
{
User.deleteAll();
renderText("success");
}
}
.. and this is where we manage the Donations model:
package controllers;
import play.*;
import play.mvc.*;
import utils.JsonParsers;
import java.util.*;
import com.google.gson.JsonElement;
import models.*;
public class DonationsAPI extends Controller
{
public static void donations(Long userId)
{
User user = User.findById(userId);
List<Donation> donations = user.donations;
renderText(JsonParsers.donation2Json(donations));
}
public static void donation (Long userId, Long id)
{
User user = User.findById(userId);
Donation donation = Donation.findById(id);
if (user.donations.contains(donation))
{
renderJSON (JsonParsers.donation2Json(donation));
}
else
{
badRequest();
}
}
public static void createDonation(Long userId, JsonElement body)
{
User user = User.findById(userId);
Donation donation = JsonParsers.json2Donation(body.toString());
Donation newDonation = new Donation (donation.amount, donation.method);
user.donations.add(newDonation);
user.save();
renderJSON (JsonParsers.donation2Json(newDonation));
}
public static void deleteDonation(Long userId, Long id)
{
User user = User.findById(userId);
Donation donation = Donation.findById(id);
if (!user.donations.contains(donation))
{
notFound();
}
else
{
user.donations.remove(donation);
user.save();
donation.delete();
ok();
}
}
}
In the above you will see we are always using the user ID.
We have just completed a significant overhaul to the service API - so the tests will need to change to reflect this.
Open the donation-service-test project - and we can extend the API to accommodate the donations:
public static List<Donation> getDonations(User user) throws Exception
{
String response = Rest.get("/api/users/" + user.id + "/donations");
List<Donation> donationList = JsonParsers.json2Donations(response);
return donationList;
}
public static Donation createDonation(User user, Donation donation) throws Exception
{
String response = Rest.post ("/api/users/" + user.id + "/donations", JsonParsers.donation2Json(donation));
return JsonParsers.json2Donation(response);
}
public static void deleteDonation(User user, Donation donation) throws Exception
{
Rest.delete ("/api/users/" + user.id + "/donations/" + donation.id);
}
The existing user tests should still work.
However, we can introduce this new DonationTest class to exhaustively test the revised Donations API:
package app.test;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import app.api.DonationServiceAPI;
import app.model.Donation;
import app.model.User;
public class DonationTest
{
User marge = new User ("marge", "simpson", "homer@simpson.com", "secret");
@Before
public void setup() throws Exception
{
marge = DonationServiceAPI.createUser(marge);
}
@After
public void teardown() throws Exception
{
DonationServiceAPI.deleteUser(marge);
}
@Test
public void testCreateDonation () throws Exception
{
Donation donation = new Donation (123, "cash");
Donation returnedDonation = DonationServiceAPI.createDonation(marge, donation);
assertEquals (donation, returnedDonation);
DonationServiceAPI.deleteDonation(marge, returnedDonation);
}
@Test
public void testCreateDonations () throws Exception
{
Donation donation1 = new Donation (123, "cash");
Donation donation2 = new Donation (450, "cash");
Donation donation3 = new Donation (43, "paypal");
Donation returnedDonation1 = DonationServiceAPI.createDonation(marge, donation1);
Donation returnedDonation2 = DonationServiceAPI.createDonation(marge, donation2);
Donation returnedDonation3 = DonationServiceAPI.createDonation(marge, donation3);
assertEquals(donation1, returnedDonation1);
assertEquals(donation2, returnedDonation2);
assertEquals(donation3, returnedDonation3);
DonationServiceAPI.deleteDonation(marge, returnedDonation1);
DonationServiceAPI.deleteDonation(marge, returnedDonation2);
DonationServiceAPI.deleteDonation(marge, returnedDonation3);
}
@Test
public void testListDonations () throws Exception
{
Donation donation1 = new Donation (123, "cash");
Donation donation2 = new Donation (450, "cash");
Donation donation3 = new Donation (43, "paypal");
DonationServiceAPI.createDonation(marge, donation1);
DonationServiceAPI.createDonation(marge, donation2);
DonationServiceAPI.createDonation(marge, donation3);
List<Donation> donations = DonationServiceAPI.getDonations(marge);
assertEquals (3, donations.size());
assertTrue(donations.contains(donation1));
assertTrue(donations.contains(donation2));
assertTrue(donations.contains(donation3));
DonationServiceAPI.deleteDonation(marge, donations.get(0));
DonationServiceAPI.deleteDonation(marge, donations.get(1));
DonationServiceAPI.deleteDonation(marge, donations.get(2));
}
}
All tests should pass.
This is an archive of the two projects so far:
Deploy the play project to heroku. Verify that the tests work against the deployed version.
Are there any other API features we could implement?
How about: