@leansdk/leanrc
Version:
LeanRC is a MVC framework for creating graceful applications
571 lines (494 loc) • 19.5 kB
text/coffeescript
# 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
Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных
Module
return BaseMigration.initialize()
```
```coffee
module.exports = (Module)->
{MIGRATIONS} = Module::
class PrepareModelCommand extends Module::SimpleCommand
Module
execute: Function,
default: ->
#...
.registerProxy Module::BaseCollection.new MIGRATIONS,
delegate: Module::BaseMigration
#...
PrepareModelCommand.initialize()
```
```coffee
module.exports = (Module)->
class CreateUsersCollectionMigration extends Module::BaseMigration
Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных
Module
->
yield 'users'
yield 'users', name, 'string'
yield 'users', description, 'text'
yield 'users', createdAt, 'date'
yield 'users', updatedAt, 'date'
yield 'users', deletedAt, 'date'
yield return
->
yield 'users'
yield return
return CreateUsersCollectionMigration.initialize()
```
Это эквивалентно
```coffee
module.exports = (Module)->
class CreateUsersCollectionMigration extends Module::BaseMigration
Module::ArangoMigrationMixin # в этом миксине должны быть реализованы платформозависимые методы, которые будут посылать нативные запросы к реальной базе данных
Module
->
'users'
'users', name, 'string'
'users', description, 'text'
'users', createdAt, 'date'
'users', updatedAt, 'date'
'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
MigrationInterface
Module
{ UP, DOWN, SUPPORTED_TYPES } = @::
# UP: UP = Symbol 'UP'
# DOWN: DOWN = Symbol 'DOWN'
# 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'
# }
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 steps: MaybeG ListG StructG {
args: Array
method: EnumG [
'createCollection'
'createEdgeCollection'
'addField'
'addIndex'
'addTimestamps'
'changeCollection'
'changeField'
'renameField'
'renameIndex'
'renameCollection'
'dropCollection'
'dropEdgeCollection'
'removeField'
'removeIndex'
'removeTimestamps'
'reversible'
]
}
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
# #name, options
createCollection: FuncG([String, MaybeG Object]),
default: (args...)->
@::[iplSteps].push {args, method: 'createCollection'}
return
createCollection: FuncG([String, MaybeG Object]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #для хранения связей М:М #collection_1, collection_2, options
createEdgeCollection: FuncG([String, String, MaybeG Object]),
default: (args...)->
@::[iplSteps].push {args, method: 'createEdgeCollection'}
return
createEdgeCollection: FuncG([String, String, MaybeG Object]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, field_name, options #{type}
addField: FuncG([String, String, UnionG(
EnumG SUPPORTED_TYPES
InterfaceG {
type: EnumG SUPPORTED_TYPES
default: AnyT
}
)]),
default: (args...)->
@::[iplSteps].push {args, method: 'addField'}
return
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
# #collection_name, field_names, options
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
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
# # создание полей createdAt, updatedAt, deletedAt #collection_name, options
addTimestamps: FuncG([String, MaybeG Object]),
default: (args...)->
@::[iplSteps].push {args, method: 'addTimestamps'}
return
addTimestamps: FuncG([String, MaybeG Object]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# Modification
# #name, options
changeCollection: FuncG([String, Object]),
default: (args...)->
@::[iplSteps].push {args, method: 'changeCollection'}
return
changeCollection: FuncG([String, Object]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, field_name, options #{type}
changeField: FuncG([String, String, UnionG(
EnumG SUPPORTED_TYPES
InterfaceG {
type: EnumG SUPPORTED_TYPES
}
)]),
default: (args...)->
@::[iplSteps].push {args, method: 'changeField'}
return
changeField: FuncG([String, String, UnionG(
EnumG SUPPORTED_TYPES
InterfaceG {
type: EnumG SUPPORTED_TYPES
}
)]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, field_name, new_field_name
renameField: FuncG([String, String, String]),
default: (args...)->
@::[iplSteps].push {args, method: 'renameField'}
return
renameField: FuncG([String, String, String]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, old_name, new_name
renameIndex: FuncG([String, String, String]),
default: (args...)->
@::[iplSteps].push {args, method: 'renameIndex'}
return
renameIndex: FuncG([String, String, String]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, old_name, new_name
renameCollection: FuncG([String, String]),
default: (args...)->
@::[iplSteps].push {args, method: 'renameCollection'}
return
renameCollection: FuncG([String, String]),
default: ->
throw new Error 'Not implemented specific method'
yield return
#Deletion
# #name
dropCollection: FuncG(String),
default: (args...)->
@::[iplSteps].push {args, method: 'dropCollection'}
return
dropCollection: FuncG(String),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_1, collection_2
dropEdgeCollection: FuncG([String, String]),
default: (args...)->
@::[iplSteps].push {args, method: 'dropEdgeCollection'}
return
dropEdgeCollection: FuncG([String, String]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, field_name
removeField: FuncG([String, String]),
default: (args...)->
@::[iplSteps].push {args, method: 'removeField'}
return
removeField: FuncG([String, String]),
default: ->
throw new Error 'Not implemented specific method'
yield return
# #collection_name, field_names, options
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
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
# # удаление полей createdAt, updatedAt, deletedAt #collection_name, options
removeTimestamps: FuncG([String, MaybeG Object]),
default: (args...)->
@::[iplSteps].push {args, method: 'removeTimestamps'}
return
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'
->
'users', 'first_name', 'string'
'users', 'last_name', 'string'
wrap (dir)->
UsersCollection = .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
'users', 'full_name'
return
```
###
reversible: FuncG(AsyncFuncG(
StructG {
up: AsyncFuncG AsyncFunctionT
down: AsyncFuncG AsyncFunctionT
}
)),
default: (args...)->
@::[iplSteps].push {args, method: 'reversible'}
return
# Custom
# будет выполняться функция содержащая платформозависимый код.
# пример использования
###
```
{wrap} = RC::Utils.co
->
yield 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
->
yield wrap ->
{ db } = require '@arangodb'
if db._collection 'cucumbers'
db._drop 'cucumbers'
yield return
yield return
```
###
execute: FuncG(AsyncFunctionT),
default: (lambda)->
yield lambda.apply @, []
yield return
# управляющие методы
# #direction - переопределять не надо, тут главная точка вызова снаружи.
migrate: FuncG([EnumG UP, DOWN]),
default: (direction)->
switch direction
when UP
yield
when DOWN
yield
yield return
# если объявлена реализация метода `change`, то `up` и `down` объявлять не нужно (будут автоматически выдавать ответ на основе методанных объявленных в `change`)
# использовать показанные выше DSL-методы надо именно в `change`
change: FuncG(Function),
default: (lambda)->
@::[iplSteps] ?= []
lambda.apply @, []
return
# с кодом Collections и Records объявленных в приложении надо работать именно в `up` и в `down`
# асинхронные, потому что будут работать с базой данных возможно через I/O
# здесь должна быть объявлена логика "автоматическая" - если вызов `change` создает метаданные, то заиспользовать эти метаданные для выполнения. Если метаданных нет, то скорее всего либо это пока еще пустая миграция без кода вообще, либо в унаследованном классе будут переопределны и `up` и `down`
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
up: FuncG(AsyncFunctionT),
default: (lambda)->
@::[iplSteps] ?= []
up: AsyncFunctionT,
default: lambda
return
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
down: FuncG(AsyncFunctionT),
default: (lambda)->
@::[iplSteps] ?= []
down: AsyncFunctionT,
default: lambda
return
restoreObject: FuncG([SubsetG(Module), Object], RecordInterface),
default: ->
throw new Error "restoreObject method not supported for #{@name}"
yield return
replicateObject: FuncG(RecordInterface, Object),
default: ->
throw new Error "replicateObject method not supported for #{@name}"
yield return