diff options
Diffstat (limited to 'acf2/model/model.lua')
-rw-r--r-- | acf2/model/model.lua | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/acf2/model/model.lua b/acf2/model/model.lua new file mode 100644 index 0000000..dc506b3 --- /dev/null +++ b/acf2/model/model.lua @@ -0,0 +1,204 @@ +--[[ +Copyright (c) 2012-2013 Kaarle Ritvanen +See LICENSE file for license details +--]] + +local M = {} + +local raise = require('acf2.error').raise + +local fld = require('acf2.model.field') +local Field = fld.Field +local Member = fld.Member + +local node = require('acf2.model.node') +local BoundMember = node.BoundMember + +local object = require('acf2.object') +local class = object.class +local super = object.super +local isinstance = object.isinstance + +local pth = require('acf2.path') +local util = require('acf2.util') + + +local function to_member(obj, params) + if not params then params = {} end + if object.issubclass(obj, M.Model) then + params.model = obj + return fld.Model(params) + end + local res = getmetatable(obj).class and obj or obj(params) + assert(isinstance(res, Member)) + return res +end + +function M.to_field(obj, params) + local res = to_member(obj, params) + assert(isinstance(res, Field)) + return res +end + + +M.Action = class(Member) + +function M.Action:init(params) + super(self, M.Action):init(params) + + if not self.func then error('Function not defined for action') end + + if self.field then + assert(type(self.field) == 'table') + self.field = M.to_field(self.field) + self.field.addr = '/null/action' + end + + getmetatable(self).__newindex = function(t, k, v) + assert(k == 'name') + rawset(t, k, v) + if t.field then t.field.name = v end + end +end + +function M.Action:meta(context) + local res = super(self, M.Action):meta(context) + if self.field then res.arg = self.field:meta(context) end + return res +end + + +function M.new(base) + if not base then base = M.Model end + + local res = class(base) + res.members = base == node.TreeNode and {} or util.copy(base.members) + + local mt = util.copy(getmetatable(res)) + + function mt.__index(t, k) return base[k] end + + function mt.__newindex(t, k, v) + assert(v) + + local override = t[k] + if type(v) == 'table' then v = to_member(v) end + + rawset(t, k, v) + + if isinstance(v, Member) then + v.name = k + if not override then table.insert(t.members, k) end + end + end + + setmetatable(res, mt) + + if isinstance(base, M.Model) then util.setdefaults(res, base) end + + return res +end + +M.Model = M.new(node.TreeNode) + +function M.Model:init(context) + super(self, M.Model):init(context) + + local mt = getmetatable(self) + + function mt.member(name, loose, tpe) + local m = mt.class[name] + + if not tpe then tpe = Member end + if not isinstance(m, tpe) then m = nil end + + if m == nil then + if loose then return end + raise(mt.path, 'Does not exist: '..name) + end + + return BoundMember(self, name, m) + end + + local function _members(tpe) + local res = {} + for _, name in ipairs(self.members) do + local m = mt.member(name, true, tpe) + if m then table.insert(res, m) end + end + return res + end + + function mt.topology() + local res = {} + for _, f in ipairs(_members(Field)) do util.extend(res, f:topology()) end + return res + end + + function mt.load(k, options) + local v = mt.class[k] + local create = options and options.create + + if isinstance(v, Field) then + v = BoundMember(self, k, v) + if v.compute then return v:compute() end + return v:load{create=create} + end + + assert(mt.txn) + + if isinstance(v, M.Action) then + local f = v.field and BoundMember(self, k, v.field) + if create then return f and f:load{create=true} end + + return function(var) + if f then f:save(var) + elseif var ~= nil then + raise( + pth.join(mt.path, v.name), + 'Action does not accept an input argument' + ) + end + local res = v.func(self, f and f:load()) + if f then mt.txn:set(v.field.addr) end + return res + end + end + + return v + end + + if not mt.txn then return end + + + function mt.mmeta(name) return mt.member(name):meta() end + + function mt.save(k, v) return mt.member(k, false, Field):save(v) end + + local function tmeta(tpe) + return util.map(function(m) return m:meta() end, _members(tpe)) + end + + mt.meta.type = 'model' + mt.meta.fields = tmeta(Field) + mt.meta.actions = tmeta(M.Action) + + function mt.members() + return util.map(function(f) return f.name end, mt.meta.fields) + end + + function mt.validate() + for _, f in ipairs(_members(Field)) do + if not f.compute then f:validate_saved() end + end + end + + if self.has_permission then + function mt.has_permission(user, permission) + return self:has_permission(user, permission) + end + end +end + + +return M |