UNPKG

@leansdk/leanrc

Version:

LeanRC is a MVC framework for creating graceful applications

571 lines (494 loc) 19.5 kB
# This file is part of LeanRC. # # LeanRC is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # LeanRC is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with LeanRC. If not, see <https://www.gnu.org/licenses/>. ### http://edgeguides.rubyonrails.org/active_record_migrations.html http://api.rubyonrails.org/ http://guides.rubyonrails.org/v3.2/migrations.html http://rusrails.ru/rails-database-migrations http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html ### ### ```coffee module.exports = (Module)-> class BaseMigration extends Module::Migration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module return BaseMigration.initialize() ``` ```coffee module.exports = (Module)-> {MIGRATIONS} = Module:: class PrepareModelCommand extends Module::SimpleCommand @inheritProtected() @module Module @public execute: Function, default: -> #... @facade.registerProxy Module::BaseCollection.new MIGRATIONS, delegate: Module::BaseMigration #... PrepareModelCommand.initialize() ``` ```coffee module.exports = (Module)-> class CreateUsersCollectionMigration extends Module::BaseMigration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module @up -> yield @createCollection 'users' yield @addField 'users', name, 'string' yield @addField 'users', description, 'text' yield @addField 'users', createdAt, 'date' yield @addField 'users', updatedAt, 'date' yield @addField 'users', deletedAt, 'date' yield return @down -> yield @dropCollection 'users' yield return return CreateUsersCollectionMigration.initialize() ``` Это эквивалентно ```coffee module.exports = (Module)-> class CreateUsersCollectionMigration extends Module::BaseMigration @inheritProtected() @include Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных @module Module @change -> @createCollection 'users' @addField 'users', name, 'string' @addField 'users', description, 'text' @addField 'users', createdAt, 'date' @addField 'users', updatedAt, 'date' @addField 'users', deletedAt, 'date' return CreateUsersCollectionMigration.initialize() ``` ### module.exports = (Module)-> { AnyT, AsyncFunctionT, PointerT FuncG, ListG, StructG, EnumG, MaybeG, UnionG, InterfaceG, AsyncFuncG, SubsetG MigrationInterface, RecordInterface Record Utils: { _, forEach, assign, co } } = Module:: class Migration extends Record @inheritProtected() @implements MigrationInterface @module Module { UP, DOWN, SUPPORTED_TYPES } = @:: # @const UP: UP = Symbol 'UP' # @const DOWN: DOWN = Symbol 'DOWN' # @const SUPPORTED_TYPES: SUPPORTED_TYPES = { # json: 'json' # binary: 'binary' # boolean: 'boolean' # date: 'date' # datetime: 'datetime' # number: 'number' # decimal: 'decimal' # float: 'float' # integer: 'integer' # primary_key: 'primary_key' # string: 'string' # text: 'text' # time: 'time' # timestamp: 'timestamp' # array: 'array' # hash: 'hash' # } @const REVERSE_MAP: REVERSE_MAP = { createCollection: 'dropCollection' dropCollection: 'dropCollection' createEdgeCollection: 'dropEdgeCollection' dropEdgeCollection: 'dropEdgeCollection' addField: 'removeField' removeField: 'removeField' addIndex: 'removeIndex' removeIndex: 'removeIndex' addTimestamps: 'removeTimestamps' removeTimestamps: 'addTimestamps' changeCollection: 'changeCollection' changeField: 'changeField' renameField: 'renameField' renameIndex: 'renameIndex' renameCollection: 'renameCollection' } iplSteps = PointerT @private steps: MaybeG ListG StructG { args: Array method: EnumG [ 'createCollection' 'createEdgeCollection' 'addField' 'addIndex' 'addTimestamps' 'changeCollection' 'changeField' 'renameField' 'renameIndex' 'renameCollection' 'dropCollection' 'dropEdgeCollection' 'removeField' 'removeIndex' 'removeTimestamps' 'reversible' ] } @public steps: ListG(StructG { args: Array method: EnumG [ 'createCollection' 'createEdgeCollection' 'addField' 'addIndex' 'addTimestamps' 'changeCollection' 'changeField' 'renameField' 'renameIndex' 'renameCollection' 'dropCollection' 'dropEdgeCollection' 'removeField' 'removeIndex' 'removeTimestamps' 'reversible' ] }), get: -> assign [], @[iplSteps] ? [] # так же в рамках DSL нужны: # Creation #@createCollection #name, options @public @static createCollection: FuncG([String, MaybeG Object]), default: (args...)-> @::[iplSteps].push {args, method: 'createCollection'} return @public @async createCollection: FuncG([String, MaybeG Object]), default: -> throw new Error 'Not implemented specific method' yield return # @createEdgeCollection #для хранения связей М:М #collection_1, collection_2, options @public @static createEdgeCollection: FuncG([String, String, MaybeG Object]), default: (args...)-> @::[iplSteps].push {args, method: 'createEdgeCollection'} return @public @async createEdgeCollection: FuncG([String, String, MaybeG Object]), default: -> throw new Error 'Not implemented specific method' yield return #@addField #collection_name, field_name, options #{type} @public @static addField: FuncG([String, String, UnionG( EnumG SUPPORTED_TYPES InterfaceG { type: EnumG SUPPORTED_TYPES default: AnyT } )]), default: (args...)-> @::[iplSteps].push {args, method: 'addField'} return @public @async addField: FuncG([String, String, UnionG( EnumG SUPPORTED_TYPES InterfaceG { type: EnumG SUPPORTED_TYPES default: AnyT } )]), default: -> throw new Error 'Not implemented specific method' yield return #@addIndex #collection_name, field_names, options @public @static addIndex: FuncG([String, ListG(String), InterfaceG { type: EnumG 'hash', 'skiplist', 'persistent', 'geo', 'fulltext' unique: MaybeG Boolean sparse: MaybeG Boolean }]), default: (args...)-> @::[iplSteps].push {args, method: 'addIndex'} return @public @async addIndex: FuncG([String, ListG(String), InterfaceG { type: EnumG 'hash', 'skiplist', 'persistent', 'geo', 'fulltext' unique: MaybeG Boolean sparse: MaybeG Boolean }]), default: -> throw new Error 'Not implemented specific method' yield return #@addTimestamps # создание полей createdAt, updatedAt, deletedAt #collection_name, options @public @static addTimestamps: FuncG([String, MaybeG Object]), default: (args...)-> @::[iplSteps].push {args, method: 'addTimestamps'} return @public @async addTimestamps: FuncG([String, MaybeG Object]), default: -> throw new Error 'Not implemented specific method' yield return # Modification #@changeCollection #name, options @public @static changeCollection: FuncG([String, Object]), default: (args...)-> @::[iplSteps].push {args, method: 'changeCollection'} return @public @async changeCollection: FuncG([String, Object]), default: -> throw new Error 'Not implemented specific method' yield return #@changeField #collection_name, field_name, options #{type} @public @static changeField: FuncG([String, String, UnionG( EnumG SUPPORTED_TYPES InterfaceG { type: EnumG SUPPORTED_TYPES } )]), default: (args...)-> @::[iplSteps].push {args, method: 'changeField'} return @public @async changeField: FuncG([String, String, UnionG( EnumG SUPPORTED_TYPES InterfaceG { type: EnumG SUPPORTED_TYPES } )]), default: -> throw new Error 'Not implemented specific method' yield return #@renameField #collection_name, field_name, new_field_name @public @static renameField: FuncG([String, String, String]), default: (args...)-> @::[iplSteps].push {args, method: 'renameField'} return @public @async renameField: FuncG([String, String, String]), default: -> throw new Error 'Not implemented specific method' yield return #@renameIndex #collection_name, old_name, new_name @public @static renameIndex: FuncG([String, String, String]), default: (args...)-> @::[iplSteps].push {args, method: 'renameIndex'} return @public @async renameIndex: FuncG([String, String, String]), default: -> throw new Error 'Not implemented specific method' yield return #@renameCollection #collection_name, old_name, new_name @public @static renameCollection: FuncG([String, String]), default: (args...)-> @::[iplSteps].push {args, method: 'renameCollection'} return @public @async renameCollection: FuncG([String, String]), default: -> throw new Error 'Not implemented specific method' yield return #Deletion #@dropCollection #name @public @static dropCollection: FuncG(String), default: (args...)-> @::[iplSteps].push {args, method: 'dropCollection'} return @public @async dropCollection: FuncG(String), default: -> throw new Error 'Not implemented specific method' yield return # @dropEdgeCollection #collection_1, collection_2 @public @static dropEdgeCollection: FuncG([String, String]), default: (args...)-> @::[iplSteps].push {args, method: 'dropEdgeCollection'} return @public @async dropEdgeCollection: FuncG([String, String]), default: -> throw new Error 'Not implemented specific method' yield return #@removeField #collection_name, field_name @public @static removeField: FuncG([String, String]), default: (args...)-> @::[iplSteps].push {args, method: 'removeField'} return @public @async removeField: FuncG([String, String]), default: -> throw new Error 'Not implemented specific method' yield return #@removeIndex #collection_name, field_names, options @public @static removeIndex: FuncG([String, ListG(String), InterfaceG { type: EnumG 'hash', 'skiplist', 'persistent', 'geo', 'fulltext' unique: MaybeG Boolean sparse: MaybeG Boolean }]), default: (args...)-> @::[iplSteps].push {args, method: 'removeIndex'} return @public @async removeIndex: FuncG([String, ListG(String), InterfaceG { type: EnumG 'hash', 'skiplist', 'persistent', 'geo', 'fulltext' unique: MaybeG Boolean sparse: MaybeG Boolean }]), default: -> throw new Error 'Not implemented specific method' yield return #@removeTimestamps # удаление полей createdAt, updatedAt, deletedAt #collection_name, options @public @static removeTimestamps: FuncG([String, MaybeG Object]), default: (args...)-> @::[iplSteps].push {args, method: 'removeTimestamps'} return @public @async removeTimestamps: FuncG([String, MaybeG Object]), default: -> throw new Error 'Not implemented specific method' yield return # Special # нужен для того, чтобы обернуть операцию изменения множества документов в удобном виде внутри `change` чтобы не писать много кода в up и down # пример использования: ### ``` {wrap} = RC::Utils.co # без асинхронности - 'addField', 'reversible' и 'removeField' части DSL. # Они сохранят в метаданные все необходимое. # а реальный запускаемый код (автоматический или кастомынй) # будет в 'up' и 'down' @change -> @addField 'users', 'first_name', 'string' @addField 'users', 'last_name', 'string' @reversible wrap (dir)-> UsersCollection = @collection.facade.retrieveProxy USERS yield UsersCollection.forEach wrap (u)-> yield dir.up wrap -> [u.first_name, u.last_name] = u.full_name.split(' ') yield return yield dir.down wrap -> u.full_name = "#{u.first_name} #{u.last_name}" yield return yield u.save() yield return yield return @removeField 'users', 'full_name' return ``` ### @public @static reversible: FuncG(AsyncFuncG( StructG { up: AsyncFuncG AsyncFunctionT down: AsyncFuncG AsyncFunctionT } )), default: (args...)-> @::[iplSteps].push {args, method: 'reversible'} return # Custom # будет выполняться функция содержащая платформозависимый код. # пример использования ### ``` {wrap} = RC::Utils.co @up -> yield @execute wrap -> { db } = require '@arangodb' unless db._collection 'cucumbers' db._createDocumentCollection 'cucumbers', waitForSync: yes db._collection('cucumbers').ensureIndex type: 'hash' fields: ['type'] yield return yield return @down -> yield @execute wrap -> { db } = require '@arangodb' if db._collection 'cucumbers' db._drop 'cucumbers' yield return yield return ``` ### @public @async execute: FuncG(AsyncFunctionT), default: (lambda)-> yield lambda.apply @, [] yield return # управляющие методы #@migrate #direction - переопределять не надо, тут главная точка вызова снаружи. @public @async migrate: FuncG([EnumG UP, DOWN]), default: (direction)-> switch direction when UP yield @up() when DOWN yield @down() yield return # если объявлена реализация метода `change`, то `up` и `down` объявлять не нужно (будут автоматически выдавать ответ на основе методанных объявленных в `change`) # использовать показанные выше DSL-методы надо именно в `change` @public @static change: FuncG(Function), default: (lambda)-> @::[iplSteps] ?= [] lambda.apply @, [] return # с кодом Collections и Records объявленных в приложении надо работать именно в `up` и в `down` # асинхронные, потому что будут работать с базой данных возможно через I/O # здесь должна быть объявлена логика "автоматическая" - если вызов `change` создает метаданные, то заиспользовать эти метаданные для выполнения. Если метаданных нет, то скорее всего либо это пока еще пустая миграция без кода вообще, либо в унаследованном классе будут переопределны и `up` и `down` @public @async up: Function, default: -> steps = @[iplSteps]?[..] ? [] yield forEach steps, ({ method, args }) -> if method is 'reversible' [lambda] = args yield lambda.call @, up: co.wrap (f)-> return yield f() down: co.wrap -> return yield Module::Promise.resolve() else yield @[method] args... , @ yield return @public @static up: FuncG(AsyncFunctionT), default: (lambda)-> @::[iplSteps] ?= [] @public @async up: AsyncFunctionT, default: lambda return @public @async down: Function, default: -> steps = @[iplSteps]?[..] ? [] steps.reverse() yield forEach steps, ({ method, args }) -> if method is 'reversible' [lambda] = args yield lambda.call @, up: co.wrap -> return yield Module::Promise.resolve() down: co.wrap (f)-> return yield f() else if _.includes [ 'renameField' 'renameIndex' ], method [collectionName, oldName, newName] = args yield @[method] collectionName, newName, oldName else if method is 'renameCollection' [collectionName, newName] = args yield @[method] newName, collectionName else yield @[REVERSE_MAP[method]] args... , @ yield return @public @static down: FuncG(AsyncFunctionT), default: (lambda)-> @::[iplSteps] ?= [] @public @async down: AsyncFunctionT, default: lambda return @public @static @async restoreObject: FuncG([SubsetG(Module), Object], RecordInterface), default: -> throw new Error "restoreObject method not supported for #{@name}" yield return @public @static @async replicateObject: FuncG(RecordInterface, Object), default: -> throw new Error "replicateObject method not supported for #{@name}" yield return @initialize()