Objectives

  • Complete the model from lab 07, implementing
    • OneToMany
    • ManyToOne
    • ManyToMany

Model

This is the model we wish to finalise:

Final Model

Division + Sponsor

These are the new classes:

Division.java

package models;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;

import play.db.jpa.Model;

@Entity
public class Division extends Model
{
  public String name;

  @OneToMany(cascade=CascadeType.ALL)
  public List<Club> members = new ArrayList<Club>();

  public Division(String name)
  {
    this.name = name;
  } 

  public void addClub(Club club)
  {
    members.add(club);
  }

  public String toString()
  {
    return name;
  }

  public static Division findByName(String name)
  {
    return find("name", name).first();
  }
}

Sponsor.java

package models;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

import play.db.jpa.Model;

@Entity
public class Sponsor extends Model
{
  public String name;

  @ManyToMany (mappedBy="sponsors")
  public List<Club> support = new ArrayList<Club>();;

  public Sponsor(String name)
  {
    this.name = name;
  }

  public void addSuport(Club club)
  {
    support.add(club);
  }

  public String toString()
  {
    return name;
  }
}
  • some modifications to Club:

Club.java


  //...
  @ManyToMany
  public List<Sponsor> sponsors = new ArrayList<Sponsor>();
  //...

  public void addSponsor(Sponsor company)
  {
    sponsors.add(company);  
  }

Tests

We can significantly simplify aspects of the testing if we invest some time in setting up test objects in yaml first.

Open 'test/data.yaml' and replace its contents with the following:

Club(dunmore):
    name: dunmore

Club(tramore):
    name: tramore

Club(fenor):
    name: fenor

Player(jim):
    name: jim
    club: dunmore

Player(mary):
    name: mary
    club: dunmore

Player(sam):
    name: sam
    club: tramore

Player(john):
    name: john
    club: tramore

Player(mike):
    name: mike
    club: fenor

Player(linda):
    name: linda
    club: fenor    

Division(senior):
    name: senior
    members:
            - tramore
            - dunmore

Division(junior):
    name: junior
    members:
            - fenor

Sponsor(newsagent):
    name: newsagent

Sponsor(pub):
    name: pub 

Now bring in a new test class that will exercise this model:

import org.junit.*;

import java.util.*;

import play.Logger;
import play.test.*;
import models.*;

public class ComprehensiveTest extends UnitTest
{
  @BeforeClass
  public static void loadDB()
  {
    Fixtures.deleteAllModels();
  }

  public static void loadSponsorships()
  {
    Club    tramore   = Club.find("byName", "tramore").first();
    Club    dunmore   = Club.find("byName", "dunmore").first();
    Sponsor newsagent = Sponsor.find("byName", "newsagent").first();

    tramore.addSponsor(newsagent);
    dunmore.addSponsor(newsagent);

    newsagent.addSuport(tramore);
    newsagent.addSuport(dunmore);

    tramore.save();
    dunmore.save();
    newsagent.save();
  }

  @Before
  public void setup()
  {
    Fixtures.loadModels("data.yml");
    loadSponsorships();
  }

  @After
  public void teardown()
  {
    Fixtures.deleteAllModels();
  }

  @Test
  public void testPlayerClubLong()
  {
    Player jim;
    Club   dunmore;

    jim = Player.find("byName", "jim").first();
    assertNotNull(jim);
    assertEquals(jim.name, "jim");

    dunmore = jim.club;
    assertEquals("dunmore", dunmore.name);

    dunmore = Club.find("byName", "dunmore").first();
    assertNotNull(dunmore);
    assertEquals("dunmore", dunmore.name);    
    assertEquals(2, dunmore.players.size());

    Player p1 = dunmore.players.get(0);
    assertTrue (p1.name.equals("jim") || p1.name.equals("mary"));
    Player p2 = dunmore.players.get(1);
    assertTrue (p2.name.equals("jim") || p2.name.equals("mary"));  
  }

  @Test
  public void testDivisionClubLong()
  {
    Division senior = Division.find("byName", "senior").first();
    assertNotNull(senior);
    assertEquals(2, senior.members.size());

    Club c1 = senior.members.get(0);
    Club c2  = senior.members.get(1);

    assertTrue (c1.name.equals("tramore") || c1.name.equals("dunmore"));      
    assertTrue (c2.name.equals("tramore") || c2.name.equals("dunmore"));          
  }


  //----------------------------------------------------------------------
  @Test
  public void testPlayerClub()
  {
    Club   dunmore = Club.find("byName", "dunmore").first();
    Player jim     = Player.find("byName", "jim").first(); 
    Player mary    = Player.find("byName", "mary").first(); 
    assertNotNull(mary);

    assertTrue (dunmore.players.contains(jim));
    assertTrue (dunmore.players.contains(mary)); 
  }

  @Test
  public void testDivisionClub()
  {
    Division senior  = Division.find("byName", "senior").first();
    Club     dunmore = Club.find("byName", "dunmore").first();
    Club     tramore = Club.find("byName", "tramore").first();

    assertTrue (senior.members.contains(dunmore));
    assertTrue (senior.members.contains(tramore));
  }

  @Test
  public void testClubSponsorShort()
  {
    Sponsor  newsagent = Sponsor.find("byName", "newsagent").first();
    Club     dunmore   = Club.find("byName", "dunmore").first();
    Club     tramore   = Club.find("byName", "tramore").first();

    assertTrue(newsagent.support.contains(dunmore));
    assertTrue(newsagent.support.contains(tramore));

    assertTrue(dunmore.sponsors.contains(newsagent));
    assertTrue(tramore.sponsors.contains(newsagent));
  }

  @Test
  public void testEditPlayerClub()
  {
    Club   dunmore = Club.find("byName", "dunmore").first();
    Player jim     = Player.find("byName", "jim").first(); 
    Player mary    = Player.find("byName", "mary").first();

    dunmore.players.remove(mary);
    mary.delete();
    dunmore.save();

    assertEquals (dunmore.players.size(), 1);
    assertTrue (dunmore.players.contains(jim));

    assertEquals(0, Player.find("byName", "mary").fetch().size());

    Player sara     = new Player("sara");
    dunmore.addPlayer(sara);
    dunmore.save();
    assertEquals (dunmore.players.size(), 2);    
  }

  @Test
  public void testEditClubSponsor()
  {
    Sponsor  newsagent = Sponsor.find("byName", "newsagent").first();
    Club     dunmore   = Club.find("byName", "dunmore").first();

    assertEquals(2, newsagent.support.size());  

    newsagent.support.remove(dunmore);
    dunmore.sponsors.remove(newsagent);

    newsagent.save();
    dunmore.save();

    assertEquals(1, newsagent.support.size());  
  }
}

Run the app in test mode, and run this new test suite. All of these tests should run.

Controllers/View

Incorporate all of these new artifacts:

Players

package controllers;

import java.util.List;

import models.Player;
import play.mvc.Controller;

public class Players extends Controller
{      
  public static void index()
  {
    List<Player> players = Player.findAll();
    render (players);
  }
}

Clubs

package controllers;

import java.util.List;

import models.Club;
import play.mvc.Controller;

public class Clubs extends Controller
{      
  public static void index()
  {
    List<Club> clubs = Club.findAll();
    render (clubs);
  }
}

Sponsors

package controllers;

import java.util.List;

import models.Sponsor;
import play.mvc.Controller;

public class Sponsors extends Controller
{      
  public static void index()
  {
    List<Sponsor> sponsors = Sponsor.findAll();
    render (sponsors);
  }
}

Divisions

package controllers;

import java.util.List;

import models.Division;
import play.mvc.Controller;

public class Division extends Controller
{      
  public static void index()
  {
    List<Division> divisions = Division.findAll();

    render (divisions);
  }
}

You will also need the corresponding views - each one called index.html:

views/Players

#{extends 'main.html' /}
#{set title:'Players' /}

<nav>
  <a class="item"         href="/divisions"> Divisions </a>
  <a class="item"         href="/clubs">     Clubs     </a>
  <a class="active item"  href="/players">   Players   </a>            
  <a class="item"         href="/sponsors">  Sponsors  </a>
</nav>

<section>        
 <h1>Players</h1>
  <table>
    <thead>
      <tr>
        <th>Player</th>
        <th>
        </th>
        <th>
        </th>
      </tr>
    </thead>
    <tbody>
      #{list items:players, as:'player'}
        <tr>
          <td>${player.name}</td>
          <td></td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

views/Clubs

#{extends 'main.html' /}
#{set title:'Clubs' /}

<nav>
  <a class="item"         href="/divisions"> Divisions </a>
  <a class="active item"  href="/clubs">     Clubs     </a>
  <a class="item"         href="/players">   Players   </a>            
  <a class="item"         href="/sponsors">  Sponsors  </a>
</nav>

<section>        
 <h1>Clubs</h1>
  <table>
    <thead>
      <tr>
        <th>Club</th>
        <th>
        </th>
        <th>
        </th>
      </tr>
    </thead>
    <tbody>
      #{list items:clubs, as:'club'}
        <tr>
          <td>${club.name}</td>
          <td></td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>


views/Sponsors

#{extends 'main.html' /}
#{set title:'Sponsors'/}

<nav>
  <a class="item"         href="/divisions"> Divisions </a>
  <a class="item"         href="/clubs">     Clubs     </a>
  <a class="item"         href="/players">   Players   </a>            
  <a class="active item"  href="/sponsors">  Sponsors  </a>
</nav>

<section>        
 <h1>Sponsors</h1>
  <table>
    <thead>
      <tr>
        <th>Sponsor</th>
        <th>
        </th>
        <th>
        </th>
      </tr>
    </thead>
    <tbody>
      #{list items:sponsors, as:'sponsor'}
        <tr>
          <td>${sponsor.name}</td>
          <td></td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

views/Divisions

#{extends 'main.html' /}
#{set title:'Divisions' /}

<nav>
  <a class="active item"  href="/divisions"> Divisions </a>
  <a class="item"         href="/clubs">     Clubs     </a>
  <a class="item"         href="/players">   Players   </a>            
  <a class="item"         href="/sponsors">  Sponsors  </a>
</nav>

<section>        
 <h1>Divisions</h1>
  <table>
    <thead>
      <tr>
        <th>Division</th>
        <th>
        </th>
        <th>
        </th>
      </tr>
    </thead>
    <tbody>
      #{list items:divisions, as:'division'}
        <tr>
          <td>${division.name}</td>
          <td></td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

Routes + Test Data

Now incorporate the following routes:

# Home page
GET     /                                       Divisions.index
GET     /divisions                              Divisions.index
GET     /clubs                                  Clubs.index
GET     /players                                Players.index
GET     /sponsors                               Sponsors.index

Save everything and run (not in test mode). Browse to

It views will be largely bank and unformatted.

There is a data.yaml file in your test folder. Copy this to the conf folder (there will now me two files of this name).

Create a class called 'BootStrap.java' in you 'app' folder, and replace its content with this source here:

BootStrap.java

import java.util.List;

import play.*;
import play.jobs.*;
import play.test.*;
import models.*;

@OnApplicationStart
public class Bootstrap extends Job 
{ 
  public void doJob()
  {
    Fixtures.deleteDatabase(); 
    Fixtures.loadModels("data.yml");
  }
}

Run the app again, and you should start to see some data.

Showing Division/Club/Player Relationships

Currently UI for the model if read only - and we can only see each model in isolation (we cant see relationships).

First, we tackle the relationships. Update the <table> element of the following views with this version:

views/Players/index.html

<section>        
 <h1>Players</h1>
  <table>
    <thead>
      <tr>
        <th> Player</th>
        <th> Club </th>
        <th> </th>
      </tr>
    </thead>
    <tbody>
      #{list items:players, as:'player'}
        <tr>
          <td>${player.name}</td>
          <td>${player.club.name}</td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

views/ClubsController/index.html

<section>        
  <h1>Clubs</h1>
  <table>
    <thead>
      <tr>
        <th>Club</th>
        <th></th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      #{list items:clubs, as:'club'}
        <tr>
          <td>${club.name}</td>
          <td>
             <table>
               <tr>
               #{list items:club.players, as:'player'}
                 <td>${player.name}</td> </tr>
               #{/list}
               </tr>
             </table>         
          </td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

views/DivisionsController/index.html

<section>        
  <h1>Divisions</h1>
  <table>
    <thead>
      <tr>
        <th>Division</th>
        <th>
        </th>
        <th>
        </th>
      </tr>
    </thead>
    <tbody>
      #{list items:divisions, as:'division'}
        <tr>
          <td>${division.name}</td>
          <td>
            <table class="table">
              <tr>
              #{list items:division.members, as:'club'}
                <td>${club.name}</td> </tr>
              #{/list}
              </tr>
            </table>
          </td>
          <td></td>
        </tr>
      #{/list}
    </tbody>
  </table>
</section>

See if you can make sense of the above templates. Save everything and reload. You should be able to see what players belong to which clubs, and also what divisions the clubs are in.

Delete Player Feature

Introduce the following method into the controllers/Players:

  public static void delete(Long id)
  {
    Player player = Player.findById(id);
    if (player != null)
    {
      if (player.club != null)
      {
        player.club.removePlayer(player);
        player.club.save();
      }
      player.delete();
    }
    index();
  }

Which requires this method in models/Club.java

  public void removePlayer(Player player)
  {
    players.remove(player);
  }

Now change the loop in the 'views/Players/index.html' file:

      #{list items:players, as:'player'}
        <tr>
          <td>${player.name}</td>
          <td>${player.club.name}</td>
          <td> <a href="/players/delete/${player.id}">Delete</a> </td>
          <td></td>
        </tr>
      #{/list}

(just one line changed in the above).

Run the app - players now have a delete button, when you press it, you get an error. This is because we need to establish a route for the delete link. Introduce this in to conf/routes now:

GET    /players/delete/{id}                     Players.delete

And try again. You should be able to delete players.

Exercises

Archive sp far

Exercise 1

Try to incorporate the latest version of the semantic ui css framework into your application, and restyle all of the views to take advantage of the framework. This is to include making the current <nav> work as expected.

Exercise 2

We have a 'delete' link for players, introduce a similar delete link for all of the other model elements.

Exercise 3

On the player view, introduce a new button at the end of the view (not in the menu) called 'New Player'. This should present a simple form to register a new player and add to the database.

Challenge: How will you allow a player to be associated with a club?