How do I display One-To-Many and Many-To-Many attribute by strategy?

For example, if I have a table named “Customer”, and an other table named “Address”, and a third table named “Phone”,
so, here it means one customer may have multiple address, and one address may owned by multiple people, it’s Many-To-Many,
and, one customer may have multiple phone numbers, but one phone number will be belonged to only one customer, it’s One-To-Many for customers.
The problem is, how do I exhibit a “mainly used” phone number for a customer, and how do I exhibit a string that concats all of phone numbers of a customer, in the customer browser?
Please show me a lead to accomplish those goal.
Thank you.

1 Like

Any clues?

Do I have to create a CustomCollectionDatasource with temporarily generated column data to reach the goal?
Is that right?

This is quite a typical problem, so I’ve created a sample application solving it: GitHub - knstvk/cuba-sample-customer-phones

I’ve added non-persistent attributes to the Customer entity to return de-normalized information about Phones:


public class Customer extends StandardEntity {
    ...

    @MetaProperty(related = "phones")
    public Phone getMainPhone() {
        if (PersistenceHelper.isLoaded(this, "phones") && phones != null) {
            return phones.stream()
                    .filter(phone -> Boolean.TRUE.equals(phone.getMain()))
                    .findFirst()
                    .orElse(null);
        } else {
            return null;
        }
    }

    @MetaProperty(related = "phones")
    public String getPhonesAsString() {
        if (PersistenceHelper.isLoaded(this, "phones") && phones != null) {
            return phones.stream()
                    .map(phone -> phone.getNum())
                    .collect(Collectors.joining(", "));
        } else {
            return "";
        }
    }
}    

A view used in the Customer browser screen includes phones collection attribute. The table contains mainPhone and phonesAsString attributes corresponding to getters above.

Alternatively, you could implement all logic in a generated column of the Table component on the customer browser screen. But I think the attributes in the data model can be useful in other parts of your system too, not only on a single screen.

And one more trick that can be useful in this case: how to make sure only one phone is selected as Main. In the Customer editor screen, I’ve added AfterCommitHandler’s to Create and Edit actions of Phones:


public class CustomerEdit extends AbstractEditor<Customer> {

    @Named("phonesTable.create")
    private CreateAction phonesTableCreate;

    @Named("phonesTable.edit")
    private EditAction phonesTableEdit;

    @Inject
    private CollectionDatasource<Phone, UUID> phonesDs;

    @Override
    public void init(Map<String, Object> params) {
        phonesTableCreate.setAfterCommitHandler(entity -> makeSingleMainPhone((Phone) entity));
        phonesTableEdit.setAfterCommitHandler(entity -> makeSingleMainPhone((Phone) entity));
    }

    private void makeSingleMainPhone(Phone phone) {
        if (Boolean.TRUE.equals(phone.getMain())) {
            for (Phone p : phonesDs.getItems()) {
                if (!p.equals(phone) && Boolean.TRUE.equals(p.getMain())) {
                    p.setMain(false);
                }
            }
        }
    }
}

So after adding or editing a Phone, all other phones are checked and their Main attribute is reset if needed.

1 Like

Very impressive and completely, thank you very much!

A new problem here:

  • Seems only
String

non-persistent attributes could be resided on a view, neither

Long

or other returned type.

So if we do need the non-persistent attributes return other types, how to accomplish it?

Hi Konstantin,

this is a great example. When the requirement is to additionally filter and sort on the main phone, do we just have to make the mainPhone attribute persistent? In this case, are there any downsides besides the fact that there is another FK that gets stored?

Bye,
Mario

The following works for me:


public class Customer extends StandardEntity {
...
    @MetaProperty(related = "phones")
    public Integer getPhonesCount() {
        if (PersistenceHelper.isLoaded(this, "phones") && phones != null) {
            return phones.size();
        } else {
            return 0;
        }
    }
}

And I can add this phonesCount attribute to a view and display in a UI table.

Hi Mario,

Absolutely, creating a direct reference from Customer to one of the Phones would be better because it makes sorting on the database possible and simplifies filtering. In the current solution filtering is possible too, but you have to create a custom condition with Join = join {E}.phones p and Where = p.num = ? and p.main = true