--[[ Copyright (c) 2012-2013 Kaarle Ritvanen See LICENSE file for license details --]] local M = {} M.error = require('acf.error') local raise = M.error.raise local relabel = M.error.relabel local combination = require('acf.model.combination') M.Union = combination.Union M.Range = combination.Range local fld = require('acf.model.field') local Field = fld.Field M.Boolean = fld.Boolean M.Integer = fld.Integer M.Number = fld.Number M.String = fld.String local model = require('acf.model.model') M.Action = model.Action M.new = model.new local to_field = model.to_field M.net = require('acf.model.net') local node = require('acf.model.node') M.node = {} for _, m in ipairs{ 'TreeNode', 'has_permission', 'insert', 'meta', 'mmeta', 'path', 'pairs', 'ipairs' } do M.node[m] = node[m] end M.permission = require('acf.model.permission') M.register = require('acf.model.root').register M.node.Set = require('acf.model.set').Set local object = require('acf.object') local class = object.class local super = object.super local pth = require('acf.path') local map = require('acf.util').map local stringy = require('stringy') M.Reference = class(Field) function M.Reference:init(params) if not params.widget then params.widget = 'reference' end super(self, M.Reference):init(params) self.dtype = 'reference' if not self.scope then self.scope = '/' end end function M.Reference:topology(context) local res = super(self, M.Reference):topology(context) res[1].scope = self.scope return res end function M.Reference:abs_scope(context) return pth.to_absolute(self.scope, node.path(context.parent)) end function M.Reference:meta(context) local res = super(self, M.Reference):meta(context) res.scope = self:abs_scope(context) local txn = context.txn local objs = txn:get( node.addr(relabel('system', txn.fetch, txn, res.scope)) ) or {} res.choice = map(function(p) return pth.join(res.scope, p) end, objs) res['ui-choice'] = objs return res end function M.Reference:follow(context, value) return context.txn:fetch(pth.rawjoin(self:abs_scope(context), value)) end function M.Reference:load(context) local ref = super(self, M.Reference):load(context) return (context.txn and ref) and self:follow(context, ref) or ref end function M.Reference:_validate(context, value) super(self, M.Reference):_validate(context, value) if value == nil then return end if object.isinstance(value, node.TreeNode) then value = node.path(value) end local path = context.path if pth.is_absolute(value) then local scope = self:abs_scope(context) local prefix = scope..'/' if not stringy.startswith(value, prefix) then raise(path, 'Reference out of scope ('..scope..')') end value = value:sub(prefix:len() + 1, -1) end -- assume one-level ref for now if #pth.split(value) > 1 then raise(path, 'Subtree references not yet supported') end -- TODO check instance type relabel(path, self.follow, self, context, value) return value end function M.Reference:deleted(context, addr) local target = self:load(context) if target and node.addr(target) == addr then -- TODO raise error for the target object raise(context.path, 'Refers to '..addr) end end M.Model = fld.Model M.Collection = class(fld.TreeNode) function M.Collection:init(params, itype) if params.create == nil then params.create = true end super(self, M.Collection):init(params) assert(self.type) self.itype = itype or node.Collection self.iparams = { destroy=self.destroy, required=self.required, ui_member=self.ui_member } self.dtype = 'collection' self.widget = self.dtype end function M.Collection:auto_ui_name(name) if not name then return end if name:sub(-1, -1) ~= 's' then name = name..'s' end return super(self, M.Collection):auto_ui_name(name) end function M.Collection:load(context, create) if not self.iparams.field then self.iparams.field = to_field(self.type) end return super(self, M.Collection):load(context, create) end M.List = class(M.Collection) function M.List:init(params) super(self, M.List):init(params, node.List) end M.Set = class(M.Collection) function M.Set:init(params) super(self, M.Set):init(params, M.node.Set) end function M.Set.save_member(tn, k, v) node.insert(tn, v) end -- experimental M.Mixed = class(M.Collection) function M.Mixed:init(params) params.type = M.Mixed super(self, M.Mixed):init(params, node.Mixed) self.pfield = Field() end function M.Mixed:topology(context) return {} end function M.Mixed:load(context) local value = self.pfield:load(context) if type(value) == 'table' then return super(self, M.Mixed):load(context) end return value end function M.Mixed:save(context, value) if type(value) == 'table' then super(self, M.Mixed):save(context, value) else self.pfield:save(context, value) end end return M