Common patterns

Common patterns

Singleton documents

There are a variety of situations where (from the User perspective) there is only ever 1 record they deal with.

For example:

  • if the user has a personal settings page
  • a “sticky” search page (which remembers a user’s own search criteria)
  • a personal details page (for personal/private details)
  • a systems settings page (where these contain settings or controls for the system - admin.DataMaintenance is an example of this)

The Skyve edit view allows a user to interact with a single bean instance and normally, a user will navigate to the edit view from a list view which sets the context.

However, with the above examples, the usual gesture of list-then-edit makes little sense, and instead, there will almost always be a single record which is the one the user wants to interact with.

When the edit view is provided as a menu item, the user has no way of setting the context, as they do not navigate to the view from a list, and so Skyve implicitly creates a new bean instance for the view.

This implicit behaviour can be ambushed for the singleton pattern, by overriding the newInstance() Bizlet method - by retrieving the desired bean instance and returning this, instead of the instance implicitly created by Skyve.

User dashboard example of an edit menu item The User dashboard (admin module) is an example of a singleton pattern

To achieve this:

  • set the scope of the document appropriately
  • add an edit menu item to the module.xml for the document
  • override the newInstance() Bizlet method to search for the relevant record and return that, instead of the new bean

For a User scoped document permission, of the singleton pattern is followed, there will only ever be one retrievable record (if it has been created), and so the newInstance() override code is trivial, for example:

	@Override
	public PersonalDetails newInstance(PersonalDetails bean) 
		throws Exception {

			// find the only retrievable instance if it exists
			Persistence pers = CORE.getPersistence();
			DocumentQuery q = pers.newDocumentQuery(PersonalDetails.MODULE_NAME, PersonalDetails.DOCUMENT_NAME);
			q.setMaxResults(1); //defensively ensure only one result is returned

			PersonalDetails found = q.beanResult();

			if (found != null) {
				// we have found our singleton, 
				// return this instead of the new bean
				bean = found;
			} else {
				// set bean defaults in the usual way...
			}

			return super.newInstance(bean);
		}

Alternatively, if the singleton document is transient and not persisted, then the developer can rely on the implicit creation of a new bean, and override the newInstance() to set bean default values.

Identify the current user’s Contact

To identify the current user in Bizlet code, instantiate the Persistence class. The Persistence class provides the getUser() method.

public static Contact getCurrentUserContact() throws MetadataException, DomainException {
  Persistence persistence = CORE.getPersistence();
  User user = persistence.getUser();
  Customer customer = user.getCustomer();
  Module module = customer.getModule(Contact.MODULE_NAME);
  Document document = module.getDocument(customer, Contact.DOCUMENT_NAME);

  Contact contact = persistence.retrieve(document, user.getContactId(), false);

  return contact;
}

In the example above, the method first obtains the Persistence mechanism, then the current user, the customer context in which that user is logged in, and the application module and document of the Contact to be retrieved. When the bean is retrieved from the persistence layer, the bean is correctly typed.

Note the distinction here between org.skyve.metadata.user.User and modules.admin.domain.User (see more below).

The Contact is declared in the application domain - in the admin module, and modules.admin.domain.User is similarly the modules.admin.domain.User is part of the declared application, not the Skyve platform itself.

org.skyve.metadata.user.User is a different type used internally by the Skyve platform and Persistence.

An alternative to this approach is to use the convenience method provided in ModulesUtil as follows:

	Contact contact = ModulesUtil.currentAdminUser().getContact();

Identify if current User has a role

@Override
public boolean isManager() {
  return isUserInRole("time", "TimesheetManager");
}

Example of isUserInRole

The above example establishes whether the current user has the role of TimesheetManager in the time module.

Note that there is a distinction between

  • modules.admin.domain.User (or ‘admin User’) is the instance of the User document as declared in the Skyve ‘admin’ module.
  • org.skyve.metadata.user.User (or ‘MetaData User’) which relates to the current session/conversation.

User convenience methods

toMetaDataUser() (in the UserExtension class) convenience method to retrieve the ‘MetaDataUser User’ from the ‘admin User’. toMetaDataUser() will return null if the user has not yet been persisted.

isInRole() method returns if the metadata user has been assigned a module role.

ModulesUtil.currentAdminUser() returns the admin user associated with the conversation user.

For example, if a bean has an association to admin.User called myUser, to perform some steps if that user has been assigned the role ‘Manager’ in the module ‘crm’:

// check if the user has been assigned the role 'Manager' in module 'crm'
modules.admin.domain.User user = bean.getMyUser();
if(user!=null 
	&& user.toMetaDataUser()!=null 
	&& user.toMetaDataUser().isInRole("crm","Manager")){ 
	
...

}

Save a document instance

To save a document instance, you can identify the module and document of the bean, or optionally save any subclass of PersistentBean directly.

Persistence persistence = CORE.getPersistence();
Customer customer = persistence.getUser().getCustomer();
Module module = customer.getModule(Contact.MODULE_NAME);
Document document = module.getDocument(customer, Contact.DOCUMENT_NAME);

// save the bean specifying the document
bean = persistence.save(document, bean);

// or save the bean directly
bean = persistence.save(bean);

Example code to save a bean

Instantiate a new document instance

ContactInteraction interaction = ContactInteraction.newInstance();

Example code to instantiate a new document instance

Note that the developer can override the default Skyve newInstance() behaviour in the corresponding Bizlet class.

Building a variant domain list

Create a variant domain set

The above example creates a list of domain values (for a selection) where the relationship to invoices has not been modelled or is ad-hoc.

Normally, generating a result list is not required, or can be handled automatically by specifying a relationship and relying on the defaultQuery. However in some circumstances it may be useful to generate domain lists via code (as above).

Schedule an offline Job

Declare the Job within the module.xml file and the Job class (extending org.skyve.job.Job).

/**
 * Kick off the annual returns job
 */
@Override
public ServerSideActionResult<GrowerSearchCriteria> execute(GrowerSearchCriteria search, WebContext WebContext) throws Exception {
  User user = CORE.getPersistence().getUser();
  Customer customer = user.getCustomer();
  Module module = customer.getModule(Grower.MODULE_NAME);
  Job job = module.getJob("jAnnualReturns");

  EXT.runOneShotJob(job, search, user);

  search.setReturnResults("The generation job has commenced.");

  return new ServerSideActionResult<>(search);
}

Example code to schedule a oneShot Job

Note when scheduling a Job, the customer and user context must be established so that the job will run correctly within the specified security architecture.

Persist scalar values without traversing the bean structure

Usually, when saving beans, Skyve traverses the entire structure of the bean to enforce specified validation rules. However for performance reasons, this may not be required.

Use the upsertBeanTuple() method to save the values of the top-most attributes of the bean, without traversing the entire bean structure. This is useful if the task requires updates of trivial nature to beans with substantial complexity, or if bean validation needs to be bypassed for some reason.

DateOnly requestedDate = new DateOnly();
for(Subscription sub : subsToUpdate) {
  sub.setRequestedDateTime(requestedDate);
  CORE.getPersistence().upsertBeanTuple(sub);
}

Example upsertBeanTuple()

Retrieve and iterate through beans

DocumentQuery q = CORE.getPersistence().newDocumentQuery(FileCategory.MODULE_NAME, FileCategory.DOCUMENT_NAME);
q.addOrdering(FileCategory.namePropertyName);

List<FileCategory> categories = q.beanResults();
for(FileCategory cat : categories) {

}

Example code to retrieve and iterate through a list of beans

Singleton documents (user, parameter and configuration documents)

A singleton document is a document of which there should only ever be one instance within the current scope or context.

Singletons are commonly used for configuration or preference documents which contain module configuration/preference settings. For example, a Timesheet module may have a preference document specifying the expected number of hours to be completed.

First, in the module.xml file, add a menu item for the document which has an element type of edit.

<edit name="Configuration" document="Configuration">
	<role name="Administrator" />
</edit>

Example edit menu

Next, override the newInstance() method in the document Bizlet to set the bean to be the first bean returned from DocumentQuery. Using DocumentQuery will ensure that appropriate document scoping and permissions will automatically be applied, restricting the beans returned as declared.

@Override
public Configuration newInstance(Configuration bean) throws Exception {
	Persistence p = CORE.getPersistence();
	DocumentQuery q = p.newDocumentQuery(Configuration.MODULE_NAME, Configuration.DOCUMENT_NAME);
	Configuration result = q.beanResult();
	if (result == null) {
		result = bean;
	}
	return result;
}

Example newInstance method which sets the current Bean

Because the document will always show in edit mode (i.e. is not accessed from a list), the view should not offer the OK action as this implies “save and return to the list”. The developer must consider whether each action is sensible in the particular context.

User-scoped documents (personal preferences documents)

Create a singleton document (as described above), but additionally scope the document to User scope in the module.xml.

Generally for this type of document, the Delete permission is not assigned.

<document name="FinancialReports" permission="CRU_U" />

Figure 85 - Example of user scoped document permission

For example, a Timesheet module may have a User-scoped preference document allowing users to set their default task (which could be set during newInstance in the Timesheet Bizlet class).

Customise document and document attribute names

To customise document attribute names, place an override of the document.xml file into the customer package and modify the document attribute displayName and shortDescription values accordingly.

To complete the customisation, also place an override of module.xml for each module and update query, role and menu text as required.

Customer override

Example of customer override of the Contact document, Bizlet and view

Validation will ensure that both the “vanilla” and overridden artefacts are consistent with the rest of the application module.

⬆ back to top


Next Skyve persistence
Previous Utility Classes