API Docs for: 0.0.3
Show:

File: src/Model.coffee

utils = require './utils'
CoreObject = require './CoreObject'
RecordStore = require './RecordStore'


Class = null

###*
  Used to manipulate records of a given type. Use {{#crossLink "Database/modelFactory:method"}}{{/crossLink}}
  to get an instance related to a {{#crossLink "Database"}}{{/crossLink}}.

  @since 0.0.2
  @class Model
  @extends CoreObject
  @constructor
###
class Model extends CoreObject
  ###*
    Holds our store, could be a {{#crossLink "MergedRecordStore"}}{{/crossLink}}
    or a simple {{#crossLink "RecordStore"}}{{/crossLink}}
    @since 0.0.2
    @private
    @property _store
    @type RecordStore|MergedRecordStore
  ###
  _store:          null
  ###*
    Name of our model, normalized
    @since 0.0.2
    @private
    @property _name
    @type String
  ###
  _name:           null
  ###*
    Our database object where we came from
    @since 0.0.2
    @private
    @property _database
    @type Database
  ###
  _database:       null
  ###*
    Store our event listener methods that have been self-bound for detaching later
    @since 0.0.2
    @private
    @property _eventListeners
    @type Object
  ###
  _eventListeners: null


  ###*
    Constructs a new instance of `Model`

    @since 0.0.2
    @method constructor
    @param {Database|null} database our database
    @param {String|null} name       our name
    @param {RecordStore} store      our store
  ###
  constructor: (database, name, store) ->
    if name?
      @_name = Class._modelName(name)
    else
      @_name = null
    if database?
      @assert(
          database instanceof Class._databaseClass(),
        "given database isn't an instance of #{ Class._databaseClass().className() }"
      )
      @_database = database
    else
      @_database = null
    if store?
      @assert store instanceof RecordStore, "given store must be an instance of #{ RecordStore.className() }"
      @_store = store
    else
      @_store = new RecordStore()
    @setMaxListeners Infinity
    @_attachEvents()


  ###*
    Creates a new record

    @since 0.0.2
    @method create
    @param {Object} [record={}] the attributes of our record, including a possible `id` if we want to force it
    @return {Object} copy of our new record
  ###
  create: (record) ->
    @_store.createRecord arguments...


  ###*
    Update a record with new given attributes

    @since 0.0.2
    @method update
    @param {String|Number} [id] id of the record to update, if not given, it must be defined in `record.id`
    @param {Object} record      attributes to update
    @return {Object}            copy of the updated record
  ###
  update: (id, record) ->
    @_store.updateRecord arguments...


  ###*
    Deletes a record

    @since 0.0.2
    @method delete
    @param {String|Number} id id of the record to delete
    @return {Object}          copy of the old record which has been deleted
  ###
  delete: (id) ->
    @_store.deleteRecord arguments...


  ###*
    Find a record given its id

    @since 0.0.2
    @method find
    @param {String|Number} id id of the record to get
    @return {Object|undefined} copy of the record, or `undefined` if no such record
  ###
  find: (id) ->
    @_store.readRecord arguments...


  ###*
    Find multiple records at once given their id

    @since 0.0.2
    @method findMany
    @param {Array|Number|String} id* id list of the records to get, or one array with all of them
    @return {Array<Object>} array of all records found
  ###
  findMany: (ids...) ->
    if ids.length is 1 and utils.isArray(ids[0])
      ids = ids[0]
    ids = utils.uniq ids
    record for id in ids when (record = @_store.readRecord id)


  ###*
    Find all records in the store for this model

    @since 0.0.2
    @method findAll
    @return {Array<Object>} array of all records
  ###
  findAll: ->
    @_store.readAllRecords()


  ###*
    Find multiple records using a filter object or function

    @since 0.0.2
    @method findQuery
    @param {Object|Function} filter the object with attributes to match, or a function used to filter records
    @param {Object} [thisArg]       the object to bind `filter` on if it's a function
    @return {Array<Object>}         array of all records which matched
  ###
  findQuery: (filter, thisArg) ->
    utils.filter @_store.readAllRecords(), filter, thisArg


  ###*
    Count all records

    @since 0.0.2
    @method count
    @return {Number} the total count of all records
  ###
  count: ->
    @_store.countRecords()


  ###*
    Destroy this instance, freeing the store

    @since 0.0.2
    @method destroy
  ###
  destroy: ->
    @_detachEvents()
    @_store = null
    @_database = null
    Object.freeze @
    super


  ###*
    Attach events on the store

    @since 0.0.2
    @private
    @method _attachEvents
    @chainable
  ###
  _attachEvents: ->
    unless @_eventListeners
      @_eventListeners = {
        created: @_recordCreated.bind @
        updated: @_recordUpdated.bind @
        deleted: @_recordDeleted.bind @
      }
      for k, v of @_eventListeners
        @_store.on "record.#{k}", v
    @


  ###*
    Detach events if previously attached to the store

    @since 0.0.2
    @private
    @method _detachEvents
    @chainable
  ###
  _detachEvents: ->
    if @_eventListeners
      for k, v of @_eventListeners
        @_store.removeListener "record.#{k}", v
      @_eventListeners = null
    @


  ###*
    Handles the `record.created` event

    @since 0.0.2
    @private
    @method _recordCreated
    @param {Object} record the created record
  ###
  _recordCreated: (record) ->
    @_emit 'created', record


  ###*
    Handles the `record.updated` event

    @since 0.0.2
    @private
    @method _recordUpdated
    @param {Object} record the updated record
  ###
  _recordUpdated: (record) ->
    @_emit "record:#{ if @_name then "#{@_name}" else '-' }##{ record.id }", record
    @_emit 'updated', record


  ###*
    Handles the `record.deleted` event

    @since 0.0.2
    @private
    @method _recordDeleted
    @param {Object} record the deleted record
  ###
  _recordDeleted: (record) ->
    @_emit "record:#{ if @_name then "#{@_name}" else '-' }##{ record.id }", null
    @_emit 'deleted', record


  ###*
    Emits an event from ourself or our database if we have one

    @since 0.0.2
    @private
    @method _emit
    @param {String} event   name of the event
    @param {mixed} [args]*  any additional args to pass to the event handlers
  ###
  _emit: (event, args...) ->
    if @_database
      @_database.emit event, args...
    else
      @emit event, args...


  ###*
    Asserts that the given model name is valid

    @since 0.0.2
    @static
    @method assertValidModelName
    @param {String} name name of the model to check
    @chainable
  ###
  @assertValidModelName: (name) ->
    @assert utils.isString(name) and name.length, "the model name must be a string of at least on char"
    @


  ###*
    Normalize a model name

    @since 0.0.2
    @static
    @private
    @method _modelName
    @param {String} name  name of the model to normalize
    @return {String}      the normalized model name
  ###
  @_modelName: (name) ->
    @assertValidModelName(name)
    utils.camelCase(utils.singularize name)


  ###*
    Return the {{#crossLink "Database"}}{{/crossLink}} class, used to avoid cross-referencing packages

    @since 0.0.2
    @static
    @private
    @method _databaseClass
    @return {Object} the `Database` class
  ###
  @_databaseClass: ->
    require './Database'


module.exports = Class = Model