Nali.extend Model:
extension: -> if @_name isnt 'Model' @_table = @_tables[ @_name ] ?= [] @_table.index = {} @_parseRelations() @_adapt() @ cloning: -> @_views = {} _tables: {} _callStack: [] attributes: {} updated: 0 destroyed: false _adapt: -> for name, method of @ when /^__\w+/.test name do ( name, method ) => @[ name[ 2.. ] ] = @[ name ] if typeof method is 'function' @_adaptViews() @ _adaptViews: -> @_views = {} for name, view of @View.extensions when name.indexOf( @_name ) >= 0 do ( name, view ) => @_views[ short = view._shortName ] = view shortCap = short.capitalize() unless @[ viewMethod = 'view' + shortCap ]? then @[ viewMethod ] = -> @view short unless @[ showMethod = 'show' + shortCap ]? then @[ showMethod ] = ( insertTo ) -> @show short, insertTo unless @[ hideMethod = 'hide' + shortCap ]? then @[ hideMethod ] = -> @hide short @ _callStackAdd: ( params ) -> # добавляет задачу в очередь на выполнение, запускает выполнение очереди @_callStack.push params @runStack() @ runStack: -> # запускает выполнение задач у существующих моделей for item, index in @_callStack[ 0.. ] if model = @extensions[ item.model ].find item.id model[ item.method ] item.params @_callStack.splice @_callStack.indexOf( item ), 1 @ # работа с моделями _accessing: -> # устанавливает геттеры доступа к атрибутам и связям @access @attributes @_setRelations() @ save: ( success, failure ) -> # отправляет на сервер запрос на сохранение модели, вызывает success в случае успеха и failure при неудаче @beforeSave?() if @isValid() @query "#{ @_name.lower() }s.save", @attributes, ( { attributes, created, updated } ) => @update( attributes, updated, created ).write() @afterSave?() success? @ else failure? @ @ sync: ( { name, attributes, created, updated, destroyed } ) -> # синхронизирует пришедшую с сервера модель с локальной, либо создает новую if model = @extensions[ name ].find attributes.id if destroyed then model.remove() else if updated > @updated model.updated = updated model.created = created model.update attributes else model = @extensions[ name ].new attributes model.updated = updated model.created = created model.write() @ select: ( options ) -> # отправляет на сервер запрос на выборку моделей obj = {} if typeof options is 'object' obj.selector = Object.keys( options )[0] obj.params = options[ obj.selector ] else obj.selector = options obj.params = {} @query @_name.lower() + 's.select', obj @ written: -> @ in @_table write: -> # добавляет модель в локальную таблицу, генерирует событие create @_table.index[ @id ] = @ if @id and not @_table.index[ @id ] unless @written() @_table.push @ @onCreate?() @Model.trigger "create.#{ @_name.lower() }", @ @Model.runStack() @ remove: -> # удаляет модель из локальной таблицы, генерирует событие destroy if @written() @destroyed = true delete @_table.index[ @id ] @_table.splice @_table.indexOf( @ ), 1 @trigger 'destroy' @Model.trigger "destroy.#{ @_name.lower() }", @ @onDestroy?() @unsubscribeAll() @ new: ( attributes ) -> # создает модель, не сохраняя её на сервере model = @clone( attributes: @_defaultAttributes() )._accessing() model[ name ] = @_normalizeAttributeValue name, value for name, value of attributes model create: ( attributes, success, failure ) -> # создает модель, и сохраняет её на сервере, вызывает success в случае успеха и failure при неудаче @new( attributes ).save success, failure update: ( attributes, checkValidation = true ) -> # обновляет атрибуты модели, проверяя их валидность, генерирует событие update changed = [] changed.push name for name, value of attributes when @updateProperty name, value, checkValidation if changed.length @onUpdate? changed @trigger 'update', changed @Model.trigger "update.#{ @_name.lower() }", @ @ updateProperty: ( name, value, checkValidation = true ) -> # обновляет один атрибут модели, проверяя его валидность, генерирует событие update.propertyName value = @_normalizeAttributeValue name, value if @[ name ] isnt value and ( not checkValidation or @isValidAttributeValue( name, value ) ) @[ name ] = value @[ 'onUpdate' + name.capitalize() ]?() @trigger "update.#{ name }" true else false upgrade: ( attributes, success, failure ) -> # обновляет атрибуты модели и сохраняет её на сервер @update( attributes ).save success, failure @ destroy: ( success, failure ) -> # отправляет на сервер запрос на удаление модели, вызывает success в случае успеха и failure при неудаче @query @_name.lower() + 's.destroy', @attributes, success, failure @ # поиск моделей find: ( id ) -> # находит модель по её id используя индекс @_table.index[ id ] where: ( filters ) -> # возвращает коллекцию моделей соответствующих фильтру @Collection.new @, filters all: -> # возвращает коллекцию всех моделей @where id: /./ each: ( callback ) -> # применяет колбек ко всем моделям callback.call @, model for model in @_table @ # работа с аттрибутами guid: -> # генерирует случайный идентификатор date = new Date().getTime() 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, ( sub ) -> rand = ( date + Math.random() * 16 ) % 16 | 0 date = Math.floor date / 16 ( if sub is 'x' then rand else ( rand & 0x7 | 0x8 ) ).toString 16 _defaultAttributes: -> # возвращает объект аттрибутов по умолчанию attributes = id: @guid() for name, value of @attributes if value instanceof Object attributes[ name ] = if value.default? then @_normalizeAttributeValue name, value.default else null else attributes[ name ] = @_normalizeAttributeValue name, value attributes _normalizeAttributeValue: ( name, value ) -> # если формат свойства number пробует привести значение к числу if @::attributes[ name ]?.format is 'number' and value is ( ( correct = + value ) + '' ) then correct else value isCorrect: ( filters = {} ) -> # проверяет соответствие аттрибутов модели определенному набору фильтров, возвращает true либо false return filters.call @ if typeof filters is 'function' return false unless Object.keys( filters ).length for name, filter of filters result = if name is 'correct' and typeof filter is 'function' filter.call @ else @isCorrectAttribute @[ name ], filter return false unless result return true isCorrectAttribute: ( attribute, filter ) -> # проверяет соответствие аттрибута модели определенному фильтру, возвращает true либо false return false unless attribute if filter instanceof RegExp filter.test attribute else if typeof filter is 'string' '' + attribute is filter else if typeof filter is 'number' + attribute is filter else if typeof filter is 'boolean' attribute is filter else if filter instanceof Array '' + attribute in filter or + attribute in filter else false # работа со связями _parseRelations: ( type, options ) -> # производит разбор связей @relations = {} for type in [ 'belongsTo', 'hasOne', 'hasMany' ] for options in [].concat( @[ type ] ) when @[ type ]? params = @[ '_parse' + type.capitalize() ] @_parseInitialRelationParams options section = type + if params.through? then 'Through' else '' ( @relations[ section ] ?= [] ).push params @ _parseInitialRelationParams: ( options ) -> # дает начальные параметры настроек связи if typeof options is 'object' params = name: Object.keys( options )[0] params.through = through if through = options[ params.name ].through params.key = key if key = options[ params.name ].key params.model = @Model.extensions[ model.capitalize() ] if model = options[ params.name ].model else params = name: options params _parseBelongsTo: ( params ) -> # производит разбор связей belongsTo params.model ?= @Model.extensions[ params.name.capitalize() ] params.key ?= params.name.lower() + '_id' params _parseHasOne: ( params ) -> # производит разбор связей hasOne params.model ?= @Model.extensions[ params.name.capitalize() ] params.key ?= ( if params.through then params.name else @_name + '_id' ).lower() params _parseHasMany: ( params ) -> # производит разбор связей hasMany params.model ?= @Model.extensions[ params.name[ ...-1 ].capitalize() ] params.key ?= ( if params.through then params.name[ ...-1 ] else @_name + '_id' ).lower() params _setRelations: -> # запускает установку связей у модели @_setRelationsType type for type in [ 'belongsTo', 'hasOne', 'hasMany', 'hasOneThrough', 'hasManyThrough' ] @ _setRelationsType: ( type ) -> # запускает установку связей определенного типа if params = @relations[ type ] @[ '_set' + type.capitalize() ] param for param in params @ _setBelongsTo: ( { key, model, name, through } ) -> # устанавливает геттер типа belongsTo возвращающий связанную модель @getter name, => model.find @[ key ] @ _setHasOne: ( { key, model, name, through } ) -> # устанавливает геттер типа hasOne возвращающий связанную модель @getter name, => delete @[ name ] ( filters = {} )[ key ] = @id collection = model.where filters @getter name, => collection.first() @[ name ] @ _setHasMany: ( { key, model, name, through } ) -> # устанавливает геттер типа hasMany возвращающий коллекцию связанных моделей @getter name, => delete @[ name ] ( filters = {} )[ key ] = @id @[ name ] = model.where filters @ _setHasOneThrough: ( { key, model, name, through } ) -> # устанавливает геттер типа hasOne возвращающий модель, # связанную с текущей через модель through @getter name, => delete @[ name ] @getter name, => @[ through ][ key ] @[ name ] @ _setHasManyThrough: ( { key, model, name, through } ) -> # устанавливает геттер типа hasMany возвращающий коллекцию моделей, # связанных с текущей через модель through @getter name, => delete @[ name ] list = @[ through ] @[ name ] = model.where correct: -> return true for model in list when model[ key ] is @ false @[ name ].subscribeTo @[ through ], 'update.length.add', ( model ) -> @add model[ key ] @[ name ].subscribeTo @[ through ], 'update.length.remove', ( model ) -> @remove model[ key ] @[ name ] @ # работа с видами view: ( name ) -> # возвращает объект вида, либо новый, либо ранее созданный unless ( view = @_views[ name ] )? if ( view = @::_views[ name ] )? view = @_views[ name ] = view.clone model: @ else console.error 'View "%s" of model "%s" does not exist', name, @_name view show: ( name, insertTo ) -> # вставляет html-код вида в указанное место ( это может быть селектор, html-элемент или ничего - тогда # вставка произойдет в элемент указанный в самом виде либо в элемент-контейнер приложения ) # функция возвращает объект вида при успехе либо null при неудаче if ( view = @view( name ) )? then view.show insertTo else null hide: ( name, delay ) -> # удаляет html-код вида со страницы # функция возвращает объект вида при успехе либо null при неудаче if ( view = @view( name ) )? then view.hide delay else null # валидации validations: # набор валидационных проверок presence: ( value, filter ) -> if filter then value? else not value? inclusion: ( value, filter ) -> not value? or value in filter exclusion: ( value, filter ) -> not value? or value not in filter length: ( value, filter ) -> if not value? then return true else value += '' return false if filter.in? and value.length not in filter.in return false if filter.min? and value.length < filter.min return false if filter.max? and value.length > filter.max return false if filter.is? and value.length isnt filter.is true format: ( value, filter ) -> return true if not value? return true if filter instanceof RegExp and filter.test value return true if filter is 'boolean' and /^true|false$/.test value return true if filter is 'number' and /^[0-9]+$/.test value return true if filter is 'letters' and /^[A-zА-я]+$/.test value return true if filter is 'email' and /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,4})+$/.test value false isValid: -> # проверяет валидна ли модель, вызывается перед сохранением модели на сервер если модель валидна, # то вызов model.isValid() вернет true, иначе false return false for name, value of @attributes when not @isValidAttributeValue( name, value ) true isValidAttributeValue: ( name, value ) -> # проверяет валидно ли значение для определенного атрибута модели, вызывается при проверке # валидности модели, а также в методе updateProperty() перед изменением значения атрибута, если значение # валидно то вызов model.isValidAttributeValue( name, value )? вернет true, иначе false for validation, tester of @validations when ( filter = @::attributes[ name ]?[ validation ] )? unless tester.call @, value, filter console.warn 'Attribute %s of model %O has not validate %s', name, @, validation if notice = @::attributes[ name ].notice for type, params of notice if @Notice[ type ]? then @Notice[ type ] params else console.warn 'Unknown Notice type "%s"', type return false true