FOAM supports composite primary keys through the ids property. When a model needs a unique identifier composed of multiple fields, you define an ids array listing the property names that form the composite key.
FOAM automatically generates an ID class for models with multipart keys. This generated class includes methods for creating, parsing, and comparing composite IDs.
Define a model with a composite key:
foam.CLASS({
package: 'com.example',
name: 'OrderItem',
// Composite primary key: orderId + productId
ids: ['orderId', 'productId'],
properties: [
{
class: 'Long',
name: 'orderId'
},
{
class: 'Long',
name: 'productId'
},
{
class: 'Int',
name: 'quantity'
},
{
class: 'Double',
name: 'price'
}
]
});
Creating and working with multipart key objects:
var item = com.example.OrderItem.create({
orderId: 1001,
productId: 5,
quantity: 3,
price: 29.99
});
// Access the composite ID
console.log(item.id);
// Output: com.example.OrderItemId { orderId: 1001, productId: 5 }
// String representation of the ID
console.log(item.id.toString());
// Output: {"orderId"="1001","productId"="5"}
FOAM generates an ID class named <ModelName>Id with the following features:
// The generated ID class can be used directly
var id = com.example.OrderItemId.create({
orderId: 1001,
productId: 5
});
// Convert string back to ID object using FROM_STRING
var idFromString = com.example.OrderItemId.FROM_STRING('{"orderId"=1001,"productId"=5}');
console.log(id.equals(idFromString));
// Output: true
Note: The string format uses = instead of : (e.g., {"field"="value"} not {"field":"value"}). toString() writes every value as a string and leaves out falsy values such as 0. FROM_STRING also accepts unquoted numbers.
To check if a model uses multipart keys:
var of = com.example.OrderItem;
// Check if the model's ID axiom is a MultiPartID
if ( foam.lang.MultiPartID.isInstance(of.ID) ) {
console.log('Model uses multipart keys');
console.log('Key fields:', of.ID.propNames);
// Output: Key fields: ['orderId', 'productId']
}
When using DAOs with multipart key models:
// Put works normally - FOAM handles the composite key
await orderItemDAO.put(item);
// Find requires the composite ID object (not a simple value)
var id = com.example.OrderItemId.create({
orderId: 1001,
productId: 5
});
var found = await orderItemDAO.find(id);
// Or create an object with the key fields and use its id
var lookup = com.example.OrderItem.create({
orderId: 1001,
productId: 5
});
var found = await orderItemDAO.find(lookup.id);
When you have a string representation of a multipart ID (common when passing IDs through URLs or table views):
var idString = '{"orderId"=1001,"productId"=5}';
// Use the generated ID class's FROM_STRING method
var of = com.example.OrderItem;
var id = of.ID.of.FROM_STRING(idString);
// Now you can use it with DAO.find()
var item = await orderItemDAO.find(id);
Multipart keys are useful when:
documentation to explain why these fields form the keyfoam.CLASS({
package: 'com.example',
name: 'UserPermission',
documentation: `
User permission assignments with composite key.
userId + permissionId ensures uniqueness per user
and enables upserts when syncing permissions.
`,
ids: ['userId', 'permissionId'],
properties: [
{
class: 'Long',
name: 'userId',
documentation: 'Reference to the user'
},
{
class: 'String',
name: 'permissionId',
documentation: 'Permission identifier'
}
// ... other properties like grantedAt, expiresAt
]
});