• Jump To … +
    Nodulator.ls ClientDB.ls index.ls Resource.ls Bus.ls Cache.ls ChangeWatcher.ls Debug.ls Wrappers.ls Nodulator.ls Resource.ls Schema.ls NModule.ls AccountResource.ls User.ls index.ls Nodulator-Angular.ls TaskDirective.ls index.ls TaskService.ls main.ls index.ls index.ls Task.ls index.ls index.ls index.ls index.ls index.ls DOM.ls View.ls index.ls Nodulator.ls Mongo.ls Mysql.ls SqlMem.ls index.ls Resource.ls Request.ls Route.ls index.ls
  • Schema.ls

  • ¶
    require! {
      async
      underscore: _
      validator: Validator
      \./Helpers/Debug
  • ¶

    ../../ : N

    }
    
    validationError = (field, value, message) ->
      field: field
      value: value
      message: "The '#field' field #{if value? => "with value '#value'"} #message"
    
    typeCheck =
      bool: (value) -> typeof(value) isnt 'string' and '' + value is 'true' or '' + value is 'false'
      int: is-type \Number
      float: is-type \Number
      string: is-type \String
      date: Validator.isDate
      email: Validator.isEmail
      array: (value) -> Array.isArray value
      arrayOf: (type) -> (value) ~> not _(map (@[type]), value).contains (item) -> item is false
    
    class SchemaProperty
    
      name: null
      default: null
      unique: false
      optional: true
      internal: false
      unique: false
      type: null
      validation: null
    
      (@name, @type, @optional) ->
        throw new Error 'SchemaProperty must have a name' if not @name?
    
        if is-type \Array @type
          @validation = typeCheck.arrayOf @type.0
        else
          @validation = typeCheck[@type]
    
      Default: (@default) ->  @
      Unique: (@unique = true) -> @
      Optional: (@optional = true) -> @
      Required: (required = true) -> @optional = !required; @
      Virtual: (@virtual = null) -> @
      Internal: (@internal = true) -> @
      Unique: (@unique = true) -> @
    
    class Schema
    
      mode: null
    
      (@name, @mode = 'free') ->
        @properties = []
        @assocs = []
        @habtm = []
        @debug = new Debug "N::Resource::#{@name}::Schema", Debug.colors.cyan
    
      Populate: (instance, blob) ->
    
        res = obj-to-pairs blob |> filter (.0.0 isnt \_) |> pairs-to-obj
        instance <<< res
    
        @properties
          |> each (-> instance[it.name] =
            | blob[it.name]?  => that
            | it.default?     => switch
              | is-type \Function it.default  => it.default!
              | _                             => it.default
            | _               => void)
    
        @assocs |> each -> instance[it.name] = that if blob[it.name]?
    
        @properties
          |> filter (.virtual?)
          |> each ~>
            try
              result = it.virtual.call instance, instance, (val) ~>
                instance[it.name] = val
    
              if result?
                instance[it.name] = result
            catch e
              instance[it.name] = undefined
    
        instance
    
      Filter: (instance) ->
    
        res = {}
    
        if @mode is \strict
          res.id = instance.id
          @properties
            |> filter (-> not it.virtual?)
            |> each -> res[it.name] = instance[it.name]
        else
          each (~>
            if it[0] isnt \_ and typeof! instance[it] isnt 'Function' and
               it not in map (.name), @assocs and
               not _(@properties).findWhere(name: it)?.virtual? and
               (typeof! instance[it]) isnt 'Object' and
               (typeof! instance[it]) isnt 'Array'
    
              res[it] = instance[it]
          ), keys instance
    
        res
    
      RemoveInternals: (blob) ->
        @properties
          |> filter (.internal)
          |> each -> delete blob[it.name]
        blob
    
      Field: (name, type) ->
        return that if _(@properties).findWhere name: name
    
        @properties.push new SchemaProperty name, type, @mode is 'free'
        @properties[*-1]
  • ¶

    Check for schema validity

      Validate: (blob, done) ->
    
        errors = []
    
        @properties
          |> each ~>
            errors := errors.concat @_CheckPresence blob, it
            errors := errors.concat @_CheckValid blob, it
    
        errors = errors.concat @_CheckNotInSchema blob
        @_CheckUnique blob, @Resource, (err, results) ->
          if err?
            errors := errors.concat results
          done(if errors.length => {errors} else null)
    
    
      GetVirtuals: (instance, blob) ->
        res = {}
        (@properties or [])
          |> filter (.virtual?)
          |> each (-> res[it.name] = it.virtual.call instance, instance, ->)
    
        res
    
      _CheckPresence: (blob, property) ->
        if !property.optional and not property.default? and not blob[property.name]? and not property.virtual?
          [validationError property.name, blob[property.name], ' was not present.']
        else
          []
    
      _CheckValid: (blob, property) ->
        if blob[property.name]? and not (property.validation)(blob[property.name])
          [validationError property.name,
                                     blob[property.name],
                                     ' was not a valid ' + property.type]
        else
          []
      _CheckNotInSchema: (blob) ->
        return [] if @mode is \free
    
        for field, value of blob when not _(@properties).findWhere name: field and field isnt \id
          validationError field, blob[field], ' is not in schema'
    
      _CheckUnique: (blob, Resource, done) ->
        res = []
        async.eachSeries filter((.unique), @properties), (property, done) ->
          Resource.Fetch (property.name): blob[property.name]
            .Then ->
              res.push validationError property.name, blob[property.name], ' must be unique'
              done {err: 'not unique'}
            .Catch -> done!
        , (err, results) -> done err, res
    
      PrepareRelationship: (isArray, field, description) ->
        type = null
        foreign = null
        get = (blob, done) ->
          done new Error 'No local or distant key given'
  • ¶

    debug-res.Log “Preparing Relationships with #{description.type.name}”

        if description.localKey?
          keyType = \local
          foreign = description.localKey
          get = (blob, done, _depth) ->
            if _depth < 0
              return done()
    
            if !isArray
              if not typeCheck.int blob[description.localKey]
                return done new Error 'Model association needs integer as id and key'
            else
              if not typeCheck.array blob[description.localKey]
                return done new Error 'Model association needs array of integer as ids and localKeys'
    
            description.type._FetchUnwrapped blob[description.localKey], done, _depth
    
        else if description.distantKey?
          foreign = description.distantKey
          keyType = \distant
          get = (blob, done, _depth) ->
            if _depth < 0 or not blob.id?
              return done()
    
            if !isArray
              description.type._FetchUnwrapped {"#{description.distantKey}": blob.id} , done, _depth
            else
              description.type._ListUnwrapped {"#{description.distantKey}": blob.id}, done, _depth
    
        toPush  =
          keyType: keyType
          type: description.type
          name: field
          Get: get
          foreign: foreign
        toPush.default = description.default if description.default?
        @assocs.push toPush
  • ¶

    Get each associated Resource

      FetchAssoc: (blob, done, _depth) ->
        assocs = {}
  • ¶

    @debug.Log “Fetching #{@assocs.length} assocs with Depth #{_depth}”

        async.eachSeries @assocs, (resource, _done) ~>
          done = (err, data)->
            _done err, data
  • ¶

    @debug.Log “Assoc: Fetching #{resource.name}”

          resource.Get blob, (err, instance) ->
            assocs[resource.name] = resource.default if resource.default?
    
            if err? and resource.type is \distant => done!
            else
              assocs[resource.name] = instance if instance?
              done!
          , _depth
        , (err) ->
          return done err if err?
    
          done null, _.extend blob, assocs
    
      HasOneThrough: (res, through) ->
        get = (blob, done, _depth) ~>
          return done! if not _depth or not blob.id?
    
          assoc = _(@assocs).findWhere name: capitalize through.name
          assoc.Get blob, (err, instance) ->
            return done err if err?
    
            done null, instance[capitalize res._type]
          , _depth + 1
    
        toPush  =
          keyType: 'distant'
          type: res
          name: capitalize res._type
          Get: get
        @assocs.push toPush
    
      HasManyThrough: (res, through) ->
        get = (blob, done, _depth) ~>
          return done! if not _depth or not blob.id?
    
          assoc = _(@assocs).findWhere name: capitalize through.name
          assoc.Get blob, (err, instance) ->
            return done err if err?
    
            res._ListUnwrapped instance[capitalize res._type], (err, instances) ->
              return done err if err?
    
              done null, instances
            , depth
    
          , _depth
    
        toPush  =
          keyType: 'distant'
          type: res
          name: capitalize res._type
          Get: get
        @assocs.push toPush
    
      HasAndBelongsToMany: (res, through) ->
        get = (blob, done, _depth) ~>
          return done! if _depth < 0 or not blob.id?
    
          through._ListUnwrapped {(@name + \Id): blob.id}, (err, instances) ~>
            return done err if err?
    
            async.mapSeries instances, (instance, done) ~>
              res._FetchUnwrapped instance[res._type + \Id ], done, _depth - 1
            , (err, results) ~>
              return done err if err?
    
              done null, results
    
          , _depth
    
        toPush  =
          keyType: 'distant'
          type: res
          name: capitalize res._type + \s
          Get: get
        @assocs.push toPush
        @habtm.push through
    
      Inherit: ->
        properties: @properties
                      |> map ->
                        sp = new SchemaProperty it.name, it.type, it.optional
                        sp <<< it
        assocs: map (-> _ {} .extend it), @assocs
        habtm: map (-> _ {} .extend it), @habtm
    
    
    module.exports = Schema