Skip to content

Re fetching in producers

jhou-pro edited this page Jun 11, 2020 · 10 revisions

Introduction

There are several utilities in default producer to facilitate fetching / re-fetching of entity-typed values. They serve to following purposes:

  • Abstract out the need to get specific reader.
  • Abstract out the need to get fetch model.
  • Automate handling of not-found entity instances (throw error result).
  • Employ filtering.

All these aspects can be handled as part of end-application code. This however leads to greater code base and less intuitive / comprehensible code. Also, since https://github.com/fieldenms/tg/issues/1107 every entity with known ID can be opened in entity master using URI. This requires end-application developer to carefully turn on domain-driven filtering inside producers.

It is recommended to use these utilities to abstract out these important aspects and thus simplify the code.

Why re-fetching is even important?

Lets say producer context contains some entity inside. Imagine situation where this entity is not [yet] persisted. In this case re-fetching is not needed and subproperties of this entity [or the entity itself] can be used for constructing new entity backed by the producer. This case is rare.

But what happens when context contains persisted entity? This is where things get complicated. It is not easy to know fetched properties' graph for this entity (all other properties are proxied). In this case we can re-fetch the instance to know for sure that all necessary information is present.

Someone would argue that if fetched properties' graph is known then it is easier not to re-fetch instance if the graph is sufficient. Or even graph can be altered to make it sufficient. But that is not very reliable approach. The problem is that actions are added all over the place inside end-application and upper fetch models differ in each place. Also these models evolve and that could break producer logic.

Standartisation is very reasonable here and our answer to that is: re-fetch everywhere.

Utilities

Fetching of producer entity's properties

Lets define entity as the entity being produced. If there is a need to initialise "document" property in it from masterEntity(Document.class) then it should be made like this:

entity.setDocument(refetch(masterEntity(Document.class), "document"));

refetch(instance, property) method will take care of getting the reader, taking appropriate fetch model for "document" property, and will correctly apply filtering. If for some reason the entity does not exist (filtered out or deleted previously) it will throw an error and the toast will appear to the user also preventing the entity to show in entity master.

There are cases where we know only ID. In these cases we use fetchById(id, property) method with similar signature:

if (currentEntityInstanceOf(HierarchyEntry.class)) {
    entity.setKey(fetchById(currentEntity(HierarchyEntry.class).getId(), KEY));
...

Other cases require fetching by known keyValues (fetchByKey(property, ...keyValues)):

entity.setKey(fetchByKey(KEY, currentEntity(SynDocumentSheetModification.class).getDocument().getKey()));

Please note that keyValues argument is last (swapped with property parameter) -- that's because it supports variable number of values.

Fetching of producer entity itself (one-to-one association)

refetchInstrumentedEntityById(id) fetches the producer entity itself. This is useful for one-to-one associations under compound master's secondary menu item:

protected WaCostDetails provideDefaultValues(final WaCostDetails entityIn) {
    if (keyOfMasterEntityInstanceOf(WorkActivity.class)) {
        return refetchInstrumentedEntityById(keyOfMasterEntity(WorkActivity.class).getId());
    } else {
    ...

Fetching of custom data for validation or other purposes

There is utility method with custom fetch model for that (fetchById(id, fetchModel)):

final WorkActivityExpendable workActivityExpendable = fetchById(masterEntity.getNextExpendableId(), fetch(WorkActivityExpendable.class).with("inventoryPart", "quantityRequired", "quantityExpended").fetchModel());
... // workActivityExpendable to be used in terms of the above very specific fetch model

Properties fetching for new entity that is carrying in producer entity

Frequently we use producer entity as a carrier for new instance we produce. For example OpenWorkActivityMasterAction carries new WorkActivity instance in its "key" property. In that case we cannot use refetch(instance, property) -- OpenWorkActivityMasterAction.property fetch model will be used, but we need WorkActivity.property fetch model instead. For that reason refetch(instance, property, entityType) was added:

final WorkActivity newWa = new_(WorkActivity.class);
newWa.setWaType(refetch(masterEntity.getWaType(), "waType", WorkActivity.class));
...
entity.setKey(newWa);

fetchByKey(property, entityType, ...keyValues) variant also exists and proven to be useful:

final WorkActivity newWa = new_(WorkActivity.class);
...
newWa.setLocationSubsystem(fetchByKey("locationSubsystem", WorkActivity.class, masterEntity.getLocation(), masterEntity.getSubsystem()));
entity.setKey(newWa);

Even greater attempt was made to simplify major cases for creating new instance and setting one / two properties into it. Consider following example:

...
        } else if (keyOfMasterEntityInstanceOf(Equipment.class)) {
            // '+' action on Equipment embedded centre on Equipment compound master
            entity.setKey(create(new_(Equipment.class)).apply("major", keyOfMasterEntity(Equipment.class)));
            return entity;
        } else if (keyOfMasterEntityInstanceOf(LocationSubsystem.class)) {
            // '+' action on Equipment embedded centre on LocationSubsystem compound master
            entity.setKey(create(new_(Equipment.class)).apply("locationSubsystem", keyOfMasterEntity(LocationSubsystem.class)));
...

Here new_(Equipment.class) is the same as co(Equipment.class).new_() (just a little bit shorter). And create(newEntity) method gives ability to "apply" some raw (not re-fetched) value from the context. The method does three things:

  • re-fetches the value internally and sets it into newEntity;
  • throws validation error on property (if any);
  • makes property not-editable.

There is also create2(newEntity) variant to be able to chain two .apply(..., ...) invocations.

Clone this wiki locally