--[[ 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