Reference Source

js/datas/data_record.js


// NPM IMPORTS
import assert from 'assert'
import {Map} from 'immutable'

// COMMON IMPORTS
import T from '../utils/types'


/**
 * Contextual constant for this file logs.
 * @private
 */
let context = 'common/data/data_record'



/**
 * DataRecord class.
 * 
 * @author Luc BORIES
 * @license Apache-2.0
 * 
 * @example
* 
* 	API:
* 		->constructor(arg_collection, arg_id, arg_attributes)
* 
* 		->get_collection():DataCollection - get record collection.
* 
* 		->get_type():string - get record type.
* 		->get_id():string - get record id.
* 		->get_id_field_name():string - get record id.
* 		->get_attributes():Immutable.Map - get record attributes.
* 
* 		->set(arg_attribute_name, arg_attribute_value):boolean - set an attribute value.
* 		->get(arg_attribute_name):any - get an attribute value.
* 
* 		->save():Promise - save all changed attributes to the collection store.
* 		->rollback():boolean - disguard any unsaved changed attributes.
* 		->remove():Promise - remove record from collection store.
* 		->reload():Promise - restore attributes from collection store.
* 
* 		->serialize():string - transform attributes map to a string.
* 		->deserialize(string):boolean - load attributes from a string.
* 
* 		->fill(arg_datas):boolean - fill attributes with given datas.
* 		->clear():boolean - fill attributes with default datas.
* 
* 
 */
export default class DataRecord
{
	/**
	 * Data record class, contains one collection item attributes.
	 * 
	 * @param {DataCollection} arg_data_collection - data collection: contains schema.
	 * @param {string} arg_id - data id, unique in collection.
	 * @param {object|Immutable.Map} arg_attributes - initial datas values.
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_data_collection, arg_id, arg_attributes)
	{
		assert( T.isObject(arg_data_collection) && arg_data_collection.is_data_collection, context + ':constructor:bad model object')
		assert( T.isString(arg_id) && arg_id.length > 0, context + ':constructor:bad id string')
		assert( T.isObject(arg_attributes), context + ':constructor:bad attributes object')
		
		/**
		 * Class type flag.
		 * @type {boolean}
		 */
		this.is_data_record = true

		/**
		 * Datas collection instance.
		 * @type {DataCollection}
		 */
		this._collection = arg_data_collection

		/**
		 * Record is a new created record flag.
		 * @type {boolean}
		 */
		this._is_new = false

		/**
		 * Record unique identifier.
		 * @type {string}
		 */
		this._id = arg_id
		
		/**
		 * Record identifier field name.
		 * @type {string}
		 */
		this._id_field_name = this._collection.get_schema().get_id_field_name()

		/**
		 * Record attributes map.
		 * @type {object}
		 */
		this._attributes = new Map()

		this.set_attributes(arg_attributes)
		if (this._id)
		{
			this.set(this._id_field_name, this._id)
		}


		/**
		 * Record has dirty attributes values flag.
		 * @type {boolean}
		 */
		this._has_dirty_attributes = false

		/**
		 * Record dirty attributes names.
		 * @type {array}
		 */
		this._dirty_attributes = []
		
		/**
		 * Record dirty attributes map.
		 * @type {Map}
		 */
		this._previous_attributes = this._attributes
	}



	/**
	 * Emit on event.
	 * @private
	 * 
	 * @param {string} arg_event - event name.
	 * @param {any} arg_datas - event datas (optional, default:undefined).
	 * 
	 * @returns {nothing}
	 */
	/* eslint no-unused-vars: "off" */
	_emit(arg_event, arg_datas=undefined) // TODO
	{
	}



	/**
	 * Check attribute name and value against model schema.
	 * 
	 * @param {string} arg_attribute_name - attribute name.
	 * @param {any} arg_attribute_value - attribute value.
	 * 
	 * @returns {boolean}
	 */
	_check_attribute(arg_attribute_name, arg_attribute_value) // TODO
	{
		return true
	}



	/**
	 * Get record model.
	 * 
	 * @returns {DataModel}
	 */
	get_model()
	{
		return this._model
	}



	/**
	 * Get record collection.
	 * 
	 * @returns {DataCollection}
	 */
	get_collection()
	{
		return this._collection
	}



	/**
	 * Get record type.
	 * 
	 * @returns {string}
	 */
	get_type()
	{
		return this._model.get_name()
	}



	/**
	 * Get record id.
	 * 
	 * @returns {string}
	 */
	get_id()
	{
		return this._id
	}



	/**
	 * Get record id field name.
	 * 
	 * @returns {string}
	 */
	get_id_field_name()
	{
		return this.get_collection().get_schema().get_id_field_name()
	}



	/**
	 * Get record fields names.
	 * 
	 * @returns {array} - array of all schema fields names strings.
	 */
	get_fields_names()
	{
		return this.get_collection().get_schema().get_fields_names()
	}



	/**
	 * Get record attributes.
	 * 
	 * @returns {Immutable.Map}
	 */
	get_attributes()
	{
		return this._attributes
	}


	/**
	 * Get record attributes.
	 * 
	 * @returns {object}
	 */
	get_attributes_object()
	{
		return this._attributes.toJS()
	}



	/**
	 * Set record attributes and normalize with schema.
	 * 
	 * @param {object|Immutable.Map} arg_attributes - attributes map.
	 * 
	 * @returns {nothing}
	 */
	set_attributes(arg_attributes)
	{
		this._previous_attributes = this._attributes
		this._attributes = (arg_attributes.has && arg_attributes.get && arg_attributes.getIn) ? arg_attributes : new Map(arg_attributes)
		// console.log('record._attributes 1', this._attributes)

		const fields_names = this.get_fields_names()
		const defaults_values = this.get_collection().get_schema().get_defaults()

		fields_names.forEach(
			(field_name)=>{
				if(this._attributes.has(field_name) || this._id_field_name == field_name)
				{
					return
				}
				
				this._attributes.set(field_name, defaults_values[field_name])
			}
		)
		// console.log('record._attributes 2', this._attributes)

		this._has_dirty_attributes = true
		this._dirty_attributes = Object.keys( this._attributes.toJS() )
	}
	



	/**
	 * Set an attribute value.
	 * 
	 * @param {string|array} arg_attribute_path - attribute name or path.
	 * @param {any} arg_attribute_value - attribute value.
	 * 
	 * @returns {boolean}
	 */
	set(arg_attribute_path, arg_attribute_value)
	{
		const check = this._check_attribute(arg_attribute_path, arg_attribute_value)
		if (!check)
		{
			return false
		}

		this._has_dirty_attributes = true
		if ( this._dirty_attributes.indexOf(arg_attribute_path) == -1 )
		{
			this._dirty_attributes.push(arg_attribute_path)
		}
		this._attributes = this._attributes.set(arg_attribute_path, arg_attribute_value)
		this._emit('changed', {path:arg_attribute_path, value:arg_attribute_value})

		return true
	}



	/**
	 * Get an attribute value.
	 * 
	 * @param {string|array} arg_attribute_path - attribute name or path.
	 * 
	 * @returns {any|undefined}
	 */
	get(arg_attribute_path)
	{
		return this._attributes.get(arg_attribute_path, undefined)
	}



	/**
	 * Save all attributes values to the collection store.
	 * 
	 * @returns {Promise} - promise of success (boolean)
	 */
	save()
	{
		let promise = undefined
		if (this._is_new)
		{
			promise = this.get_collection().create_record(this)
		} else {
			promise = this.get_collection().update_record(this)
		}
		
		return promise.then(
			(success)=>{
				if (success)
				{
					this._is_new = false
					this._emit('saved')
					this._has_dirty_attributes = false
					this._dirty_attributes = []
					this._previous_attributes = this._attributes
				}
				console.log(context + ':save:success', success)
				return success
			}
		)
	}



	/**
	 * Disguard all dirty attributes values.
	 * 
	 * @returns {Promise} - promise of success (boolean)
	 */
	rollback()
	{
		this._emit('rollbacked')
		this._has_dirty_attributes = false
		this._dirty_attributes = []
		this._attributes = this._previous_attributes
		return Promise.resolve(true)
	}



	/**
	 * Delete record from collection store.
	 * 
	 * @returns {Promise} - promise of success (boolean)
	 */
	remove()
	{
		return this.get_collection().delete_record(this).then(
			(success)=>{
				if (success)
				{
					this._emit('removed')
					this._has_dirty_attributes = false
					this._dirty_attributes = []
					this._previous_attributes = this._attributes = new Map()
				}
			}
		)
	}



	/**
	 * Set record as removed.
	 * 
	 * @return {nothing}
	 */
	set_removed()
	{
		// TODO
	}



	/**
	 * Reload record from collection store.
	 * 
	 * @returns {Promise} - promise of success (boolean)
	 */
	reload()
	{
		return this.get_collection().reload_record(this).then(
			(success)=>{
				if (success)
				{
					this._emit('reloaded')
					this._has_dirty_attributes = false
					this._dirty_attributes = []
					this._previous_attributes = this._attributes
				}
			}
		)
	}



	/**
	 * Transform attributes to a string.
	 * 
	 * @returns {string} - serialized attributes.
	 */
	serialize() // TODO
	{
		return ''
	}



	/**
	 * Transform attributes string to an object.
	 * 
	 * @param {stirng} arg_serialized - serialized attributes.
	 *  
	 * @returns {boolean} - success.
	 */
	/* eslint no-unused-vars: "off" */
	deserialize(arg_serialized) // TODO
	{
		return false
	}



	/**
	 * Fill all attributes values with given datas.
	 * 
	 * @param {arg_datas} - new attribute values.
	 * 
	 * @returns {boolean} - success.
	 */
	fill(arg_datas)
	{
		if ( ! T.isObject(arg_datas) )
		{
			return false
		}

		const backup = this._attributes
		let result = true
		Object.keys(arg_datas).forEach(
			(attribute_name)=>{
				const value = arg_datas[attribute_name]
				if ( ! this.set(attribute_name, value) )
				{
					result = false
					return
				}
				if ( this._dirty_attributes.indexOf(attribute_name) == -1 )
				{
					this._dirty_attributes.push(attribute_name)
				}
			}
		)

		if (! result)
		{
			this._attributes = backup
			return false
		}

		this._emit('filled')
		this._has_dirty_attributes = true
		return true
	}



	/**
	 * Clear all attributes values with default values.
	 * 
	 * @returns {boolean} - success.
	 */
	clear()
	{
		const backup = this._attributes
		let result = true
		this._dirty_attributes = []
		this._model.get_fields().forEach(
			(field)=>{
				result = result && this.set(field.get_name(), field.get_default())
				this._dirty_attributes.push(field.get_name())
			}
		)

		if (!result)
		{
			this._attributes = backup
			return false
		}

		this._has_dirty_attributes = true
		this._emit('cleared')
		return true
	}
}