summaryrefslogtreecommitdiffstats
path: root/acf/model/model.lua
blob: 7c27b50a636002b8adb9c76cd3c9f7f5a6209a32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

module(..., package.seeall)

local raise = require('acf.error').raise

local fld = require('acf.model.field')
local Field = fld.Field

local node = require('acf.model.node')

local object = require('acf.object')
local class = object.class
local super = object.super
local isinstance = object.isinstance

local util = require('acf.util')


function to_field(obj)
   if object.issubclass(obj, Model) then return fld.Model{model=obj} end
   return getmetatable(obj).class and obj or obj()
end


function new(base)
   if not base then base = Model end

   local res = class(base)
   res._fields = base == node.TreeNode and {} or util.copy(base._fields)

   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_field(v) end

      rawset(t, k, v)

      if isinstance(v, Field) then
	 v.name = k
	 if not override then table.insert(t._fields, k) end
      end
   end

   setmetatable(res, mt)

   if isinstance(base, Model) then util.setdefaults(res, base) end

   return res
end

Model = new(node.TreeNode)

function Model:init(context)
   super(self, Model):init(context)

   local mt = getmetatable(self)

   function mt.field(name)
      local res = mt.class[name]
      return isinstance(res, Field) and node.BoundField(self, res) or nil
   end

   function mt.mmeta(name) return mt.field(name):meta() end

   function mt.get(k)
      local f = mt.field(k)
      if f then
	 if f.compute then return f:compute() end
	 return f:load()
      end
      return mt.class[k]
   end

   function mt.set(k, v)
      local f = mt.field(k)
      if not f then raise(mt.path, 'Field named '..k..' does not exist') end
      f:save(v)
   end

   mt.meta = {
      type='model',
      fields=util.map(function(f) return mt.mmeta(f) end, self._fields)
   }

   function mt.members()
      return util.map(function(f) return f.name end, mt.meta.fields)
   end

   function mt.validate()
      for _, name in ipairs(mt.members()) do
	 local field = mt.field(name)
	 if not field.compute then field:validate_saved() end
      end
   end
end