diff options
-rw-r--r-- | acf/model/init.lua | 8 | ||||
-rw-r--r-- | acf/model/node.lua | 39 | ||||
-rw-r--r-- | acf/model/root.lua | 13 | ||||
-rw-r--r-- | acf/transaction/init.lua | 18 |
4 files changed, 66 insertions, 12 deletions
diff --git a/acf/model/init.lua b/acf/model/init.lua index a53cc0d..e1a4dd9 100644 --- a/acf/model/init.lua +++ b/acf/model/init.lua @@ -114,6 +114,14 @@ function Reference:_validate(context, value) return value end +function 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 + Model = fld.Model diff --git a/acf/model/node.lua b/acf/model/node.lua index 4c05d4d..0e22d63 100644 --- a/acf/model/node.lua +++ b/acf/model/node.lua @@ -6,8 +6,10 @@ See LICENSE file for license details module(..., package.seeall) local raise = require('acf.error').raise + local object = require('acf.object') local class = object.class +local isinstance = object.isinstance local super = object.super local pth = require('acf.path') @@ -103,6 +105,38 @@ function TreeNode:fetch(path, create) return TreeNode.fetch(next, path, create) end +function TreeNode:search_refs(path) + if type(path) == 'string' then path = pth.split(path) end + + if #path == 0 then return {} end + + local mt = getmetatable(self) + local name = path[1] + table.remove(path, 1) + + local function collect(name) + local next = mt.load(name) + if not next then return {} end + + local member = mt.member(name) + if member.deleted then return {member} end + + return isinstance(next, TreeNode) and TreeNode.search_refs( + next, path + ) or {} + end + + if name == pth.wildcard then + local res = {} + for _, member in ipairs(mt.members()) do + util.extend(res, collect(member)) + end + return res + end + + return collect(name) +end + Collection = class(TreeNode) @@ -111,6 +145,7 @@ function Collection:init(context, params) self.init = nil self.fetch = nil + self.search_refs = nil local mt = getmetatable(self) local field = BoundMember(self, pth.wildcard, params.field) @@ -221,7 +256,7 @@ local rawpairs = pairs local rawipairs = ipairs function pairs(tbl) - if not object.isinstance(tbl, TreeNode) then return rawpairs(tbl) end + if not isinstance(tbl, TreeNode) then return rawpairs(tbl) end local mt = getmetatable(tbl) local res = {} for _, member in rawipairs(mt.members()) do res[member] = mt.load(member) end @@ -235,6 +270,6 @@ local function _ipairs(mt, i) return i, v end function ipairs(tbl) - if not object.isinstance(tbl, TreeNode) then return rawipairs(tbl) end + if not isinstance(tbl, TreeNode) then return rawipairs(tbl) end return _ipairs, getmetatable(tbl), 0 end diff --git a/acf/model/root.lua b/acf/model/root.lua index d17f8a9..2d3b2a0 100644 --- a/acf/model/root.lua +++ b/acf/model/root.lua @@ -40,7 +40,7 @@ function topology(addr, create) else addr = pth.split(addr) end local function defaults(top) - return util.setdefaults(top, {members={}, paths={}}) + return util.setdefaults(top, {members={}, paths={}, referrers={}}) end while #addr > 0 do @@ -77,14 +77,11 @@ function register(name, field, params) table.insert(top.paths, record.path) if record.scope then - set( - 'scope', - node.addr( - root:fetch( - pth.to_absolute(record.scope, pth.parent(record.path)) - ) - ) + local scope = node.addr( + root:fetch(pth.to_absolute(record.scope, pth.parent(record.path))) ) + set('scope', scope) + table.insert(topology(scope, true).referrers, record.path) end end end diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua index a76e7bf..76a8d47 100644 --- a/acf/transaction/init.lua +++ b/acf/transaction/init.lua @@ -6,7 +6,7 @@ See LICENSE file for license details module(..., package.seeall) local ErrorDict = require('acf.error').ErrorDict -local RootModel = require('acf.model.root').RootModel +local root = require('acf.model.root') local object = require('acf.object') local pth = require('acf.path') local be_mod = require('acf.transaction.backend') @@ -46,7 +46,7 @@ function Transaction:init(backend, validate) self.validate = validate self.validable = {} - self.root = RootModel(self) + self.root = root.RootModel(self) end function Transaction:check() @@ -94,6 +94,20 @@ function Transaction:_set_multiple(mods) local delete = value == nil + if delete then + -- assume one-level refs for now + local top = root.topology(ppath) + if top then + local errors = ErrorDict() + for _, refs in ipairs(top.referrers) do + for _, ref in ipairs(self.root:search_refs(refs)) do + errors:collect(ref.deleted, ref, path) + end + end + errors:raise() + end + end + if type(old) == 'table' then if delete then for _, child in ipairs(old) do self:set(pth.join(path, child)) end |