On first look, the separation into years looks a bit more like a way to present it than a way to store it. You might end up with another struct for year
.
If you flattened it out a little (with the year stored with the rest of the Case attributes):
├── Client 1
│ │── Case 1
│ │── Case 2
├── Client 2
│ ├── Case 1
It still won't be especially easy to retrieve things by Case number unless you know the Client. I like to cover all bases that might be needed in the future and allow for enumeration at both levels. Instead of structs
inside structs
(perfectly valid), you might treat them as two related tables.
├── Client 1
│ │── Cases[]
│ │── Cases[]
├── Client 2
│ ├── Cases[]
├── Case 1
├── Case 2
Make Client
a property of each Case
so it's easy to explore:
struct Client {
// stuff
bool isClient;
bytes32 cases[];
}
struct Case {
// stuff
uint year;
bool isCase;
bytes32 client;
}
Support random access:
mapping(bytes32 => Client) public clients;
mapping(bytes32 => Case) public cases;
function getClient(bytes32 clientId) public view ...
function getCase(bytes32 caseId) public view ...
Support sequential access:
bytes32[] public clientIdList;
bytes32[] public caseIdList;
function getClientCount() public view returns(uint count) ...
function getCaseCount() public view returns(uint count) ...
function getClientCaseCount(bytes32 clientId) public view returns(uint count) ...
function getClientCaseAtRow(bytes32 clientId, uint row) public view returns(...
There are quite a few things working together there:
function insertClientCase(bytes32 clientId, bytes32 caseId, string attributes ... {
require(client[clientId].isClient); // client must exist
require(!cases[caseId].isCase); // no duplicates
clients[clientId].cases.push(caseId); // append to client's case list
cases[caseId].isCase = true; // this exists
cases[caseId].atttributes = attributes; // placeholder for case data
caseIdList.push(caseId); // index of mapping keys that are populated
emit LogNewClientCase(msg.sender, clientId, caseId, attributes, ...); // audit
return true;
}
You can easily add update methods that just write over the records, and even add a soft delete that just flips isClient
and isCase
to false
while keeping accumulated history, e.g. a deleted client still has related cases.
It's a lot trickier if you need to support a move of a case from one client to another. That would be implemented as a delete from one client's list and an append on the destination list, which involves row pointers and moving the last item on the list into the row to delete.
Hope it helps.
Case
changesClient
for some reason? – Rob Hitchens Oct 2 '18 at 23:14