Reputation: 167822
I have 3 models: type
, restriction
and item
.
A type
is simple and just has an id
:
app/models/type.js
:
import Model from 'ember-data/model';
export default Model.extend({});
A restriction
can have many type
describing the allowable types for an item with this restriction:
app/models/restriction.js
:
import Model from 'ember-data/model';
import { hasMany } from 'ember-data/relationships';
export default Model.extend({
allowedTypes: hasMany( "type" )
});
An item
can have many type
but also can have many restriction
and the type
must only be a subset of the intersection of the allowed types for all the restrictions (and if there is at least one restriction then it must have at least one type).
I've implemented a validation for this using a computed property:
app/models/item.js
:
import Model from 'ember-data/model';
import { computed } from '@ember/object';
import { hasMany } from 'ember-data/relationships';
import { isEmpty } from '@ember/utils';
const peekHasMany = attr => ( item => item.hasMany( attr ).ids() );
const hasItems = array => !isEmpty( array );
const includedIn = array => ( item => array.indexOf( item ) >= 0 );
const intersectionOf = ( array1, array2, index ) => index >= 0 ? array1.filter( includedIn( array2 ) ) : array2;
export default Model.extend({
types: hasMany( "type" ),
restrictions: hasMany( "restriction" ),
isValidTypes: computed(
"types.[]",
"[email protected]",
function(){
let restrictions = this.hasMany( "restrictions" ).value();
if ( isEmpty( restrictions ) )
{
return true;
}
let allowed = restrictions
.map( peekHasMany( "allowedTypes" ) )
.filter( hasItems );
if ( isEmpty( allowed ) )
{
return true;
}
let types = this.hasMany( "types" ).ids();
if ( isEmpty( types ) )
{
return false;
}
let allowedTypes = allowed.reduce( intersectionOf );
return types.every( includedIn( allowedTypes ) );
}
)
});
This uses the DS.Model.hasMany( attributeName )
to synchronously get the HasManyReference
for the relationships which relies on the referenced models being loaded.
How can I change the computed property to use this.get()
to asynchronously get both attributes (and the child attributes) rather than using this.hasMany()
synchronously?
Upvotes: 0
Views: 149
Reputation: 167822
let value = this.hasMany( attributeName ).value();
/* following code */
can be replaced with
this.get( attributeName ).then( value => { /* following code */ } );
The complication comes with the lines:
const peekHasMany = attr => ( item => item.hasMany( attr ).ids() );
let allowed = restrictions.map( peekHasMany( "allowedTypes" ) )
/* following code */
Which, when changed will result in an array of promises. This can be wrapped in a single promise using Promise.all( arrayOfPromises )
const getAll = attr => ( item => item.get( attr ) );
Promise.all( restrictions.map( getAll( "allowedTypes" ) ) )
.then( allowed => {
/* following code */
} );
The code then becomes:
import Model from 'ember-data/model';
import { computed } from '@ember/object';
import { hasMany } from 'ember-data/relationships';
import { isEmpty } from '@ember/utils';
const getAll = attr => ( item => item.get( attr ) );
const hasItems = array => !isEmpty( array );
const includedIn = array => ( item => array.indexOf( item ) >= 0 );
const intersectionOf = ( array1, array2, index ) => index >= 0 ? array1.filter( includedIn( array2 ) ) : array2;
export default Model.extend({
types: hasMany( "type" ),
restrictions: hasMany( "restriction" ),
isValidTypes: computed(
"types.[]",
"[email protected]",
function(){
return this.get( "restrictions" )
.then( restrictions => {
if ( isEmpty( restrictions ) )
{
return true;
}
return Promise.all( restrictions.map( getAll( "allowedTypes" ) ) )
.then( allowed => {
allowed = allowed.filter( hasItems );
if ( isEmpty( allowed ) )
{
return true;
}
return this.get( "types" )
.then( types => {
if ( isEmpty( types ) )
{
return false;
}
let allowedTypes = allowed.reduce( intersectionOf );
return types.every( includedIn( allowedTypes ) );
} );
} );
} );
}
)
});
Or using the async
and await
syntax:
import Model from 'ember-data/model';
import { computed } from '@ember/object';
import { hasMany } from 'ember-data/relationships';
import { isEmpty } from '@ember/utils';
const getAll = attr => ( item => item.get( attr ) );
const hasItems = array => !isEmpty( array );
const includedIn = array => ( item => array.indexOf( item ) >= 0 );
const intersectionOf = ( array1, array2, index ) => index >= 0 ? array1.filter( includedIn( array2 ) ) : array2;
export default Model.extend({
types: hasMany( "type" ),
restrictions: hasMany( "restriction" ),
isValidTypes: computed(
"types.[]",
"[email protected]",
async function(){
let restrictions = await this.get( "restrictions" );
if ( isEmpty( restrictions ) )
{
return true;
}
let allowed = ( await Promise.all( restrictions.map( getAll( "allowedTypes" ) ) ) )
.filter( hasItems );
if ( isEmpty( allowed ) )
{
return true;
}
let types = await this.get( "types" );
if ( isEmpty( types ) )
{
return false;
}
let allowedTypes = allowed.reduce( intersectionOf );
return types.every( includedIn( allowedTypes ) );
}
)
});
Upvotes: 1