The simplest way to handle this kind of relations is to provide an extra field on your models that will serve as a representation of the relations.
For example, from a user we need to see his companies: expose a new field user.company_ids.
This field don't have to be in the database, your API or a proxy can list the related companies ID in this field, and for write requests use it to create, change or delete relations.
Once this field is implemented, admin-on-rest is very easy to configure.
I made a fully working snippet: https://codesandbox.io/s/r04y8rn96p
First, write your top-level resources:
<Admin
authClient={authClient}
restClient={restClient}
title="Example Admin"
locale="en"
messages={messages}
>
<Resource
name="companies"
list={CompanyList}
create={CompanyCreate}
edit={CompanyEdit}
show={CompanyShow}
remove={Delete}
icon={CompanyIcon}
/>
<Resource
name="users"
list={UserList}
create={UserCreate}
edit={UserEdit}
remove={Delete}
icon={UserIcon}
show={UserShow}
/>
<Resource
name="company_user"
icon={CompanyUserIcon}
list={CompanyUserList}
create={CompanyUserCreate}
show={CompanyUserShow}
remove={Delete}
/>
</Admin>
Then, you can use ReferenceArrayInput for the user model on the extra field just like a one-to-many relation.
export const UserEdit = ({ ...props }) => (
<Edit title={<UserTitle />} {...props}>
<TabbedForm>
<FormTab label="User Infos">
<TextInput source="name" />
<DateInput source="published_at" defaultValue={() => new Date()} />
<TextInput source="company_ids" />
</FormTab>
<FormTab label="Companies">
<ReferenceArrayInput source="company_ids" reference="companies">
<SelectArrayInput optionText="title" />
</ReferenceArrayInput>
</FormTab>
</TabbedForm>
</Edit>
);
Since the idea is to mock the relations into an extra field, you can handle this field from your backend (the best choice) or the frontend via the restClient. Here is an dummy example for read-only relations:
// It's better to implement these rules on the backend
const improvedRestClient = (type, resource, params) => {
return restClient(type, resource, params).then((response) => {
// Inject company_ids into user
if (type === 'GET_ONE' && resource === 'users') {
return restClient('GET_LIST', 'company_user', defaultOptions).then(({ data: companyUsers }) => {
const user = response.data;
return {
data: {
...user,
company_ids: companyUsers
.filter(item => item.user_id === user.id)
.map(item => item.company_id),
},
}
});
}
// Inject user_ids into company
if (type === 'GET_ONE' && resource === 'companies') {
return restClient('GET_LIST', 'company_user', defaultOptions).then(({ data: companyUsers }) => {
const company = response.data;
return {
data: {
...company,
user_ids: companyUsers
.filter(item => item.company_id === company.id)
.map(item => item.user_id),
},
}
});
}
return response;
});
};