import CamelCase from 'camelcase' import SnakeCase from 'snake-case'
export default class Records {
constructor( args = {} ){ this._records = {} this._addNewRecord = args.addNewRecord || this.addNewRecord this._associations = args.associations || [] this._cable = args.cable this._modelName = args.modelName this._references = args.references // A subscription is just it's filter{} this._subscriptions = [] // An ugly object of { filter{}: boolean } this._dataLoading = {} } setAssociatedModels( models ){ this._associations.forEach( ( association ) => { var model = models.find( ( model ) => model.name == association.class ) if( model ){ association.model = model } else { throw `Model ${this._modelName} is set up for association with ${association.class} but ${association.class} is not available through Active Sync` } } ) } getRecord( id ){ return this._records[id] } push( record ){ this._associations.forEach( ( association ) => { switch(association.type){ case 'ActiveRecord::Associations::HasManyAssociation': case 'ActiveRecord::Associations::HasManyThroughAssociation': record[ association.name ] = [1] break case 'ActiveRecord::Associations::BelongsToAssociation': var standIn = {} Object.defineProperty(standIn, "$count", { enumerable: false, writable: true }) standIn.$count = 1 //this._addNewRecord( record, association.name, standIn ) record[ association.name ] = standIn break default: console.log('Unknown know association type ' + association.type) } } ) var newRecord = new Proxy( this.camelCaseKeys( record ), { get: ( record, property )=> this.getFromRecord( record, property, this ) } ) this._addNewRecord( this._records, newRecord.id, newRecord ) } addNewRecord( records, recordId, record ){ if( records[ recordId ] ){ Object.keys( record ).forEach( (key) => records[ recordId ][ key ] = record[ key ] ) } else { records[ recordId ] = record } } forEach( func ){ Object.keys( this._records ).forEach(( id ) => func( this._records[id] ) ) } camelCaseKeys( record ){ return Object.keys( record ).reduce( ( camelRecord, key ) => { camelRecord[ CamelCase(key) ] = record[key] return camelRecord }, {} ) } snakeCaseKeys( record ){ return Object.keys( record ).reduce( ( camelRecord, key ) => { camelRecord[ SnakeCase(key) ] = record[key] return camelRecord }, {} ) } getFromRecord( record, property, self ){ self.loadIfAssociation( record, property ) return record[ property ] } //If the records already exist it will return an instantaneously resolving promise loadRecords( filter = null ){ if( !this.isSubscribed( filter ) ){ this._subscriptions.push( filter ? filter : 'all' ) this.subscribeToRecords( filter ) } return new Promise( (resolve, reject)=> this.awaitData(resolve, reject, filter) ) } awaitData( resolve, reject, filter ){ setTimeout( ()=>{ if( this._dataLoading[ filter ] ){ this.awaitData( resolve, reject, filter ) } else { resolve() } //TODO there's something wrong with _dataLoading not working so this wait time has been cranked up. }, 200 ) } // Adds records that match properties into records forEachMatch( properties, func ){ let records = [] this.forEach( ( record ) => { var match = true Object.keys( properties ).forEach( ( property ) => { if( properties[ property ] != record[ property ] ){ match = false } }) if( match ){ func(record) } }) return records } isSubscribed( filter ){ filter = filter ? filter : 'all' if( this._subscriptions.includes( 'all' ) && !filter.IsReference ) { return true } else { return !!this._subscriptions.find( ( sub ) => JSON.stringify(sub) == JSON.stringify(filter) ) } } // Subscribing to a record is the source of all data communication. With no filter // all records are subscribed to, this is done without checking for existing subscriptions // so that needs to be done before getting here. subscribeToRecords( filter = null ){ let subscriptionParameters = { channel: 'ActiveSyncChannel', model: this._modelName } this._dataLoading[ filter ] = true if ( filter !== null ) { subscriptionParameters.filter = filter.IsReference ? filter : this.snakeCaseKeys(filter) } this._cable.subscriptions.create( subscriptionParameters ,{ received: (data) => { var records = data.IsReference ? this._references : this // Will find records and update them, if not found will add them to // _records or references. if( data.length > 0){ // data is a promise so might not have anything at this point, // adding with a forEach allows promises to be handled (is there a better way?) data.forEach((addModel) => { records.push(addModel) }) } else { records.push( data ) } this._dataLoading[ filter ] = false } }) } loadIfAssociation( record, property ){ var association = this._associations.find( ( a ) => a.name == property ) if( association ){ var referencedRecords = [] if( record[property][0] == 1 ){ record[property].pop() } else if( record[property].$count > 0 ){ record[property].$count-- } else { this.loadRecords({ IsReference: true, record_id: record.id, association_name: property }) .then( () => { var references = this._references.getRecord( record.id )[ property ] if( references.length > 0 && references.length !== record[property].length ){ record[property] = [] references.forEach( ( reference ) => { record[property].push( association.model.find( reference )) } ) } else if( typeof references.length === 'undefined' && record[property].$count == 0 ) { this._addNewRecord(record,property, association.model.find( references )) } }) } } }
}