1
votes

I need some help with Lua metatables, specificaly with AutomagicTables (http://lua-users.org/wiki/AutomagicTables). The ability to just assign to undefined tables is very nice, and I would like to keep this functionality. My version has been placed in a single function:

require("dataentry") -- Contains my age function
function AutomagicTable()
-- Create a new data table
-- from https://lua-users.org/wiki/AutomagicTables
    local auto, assign

    function auto(tab, key)
    return setmetatable({}, {
        __index = auto,
        __newindex = assign,
        parent = tab,
        key = key
    })
    end

    local meta = {__index = auto}

    function assign(tab, key, val)
    if val ~= nil then
        local oldmt = getmetatable(tab)
        oldmt.parent[oldmt.key] = tab
        setmetatable(tab, meta)
        tab[key] = val
    else
        return nil
    end
    end

    return setmetatable({}, meta)
end

What I want is to pass a table of defaults to be used when a field is not defined - as described in PIL Chapter 13 (https://www.lua.org/pil/13.4.3.html). This would allow calculated and lookup fields in my data strucutre. The following is the syntax I want to use:

t_defaults = {
  Age = age(table["DOB"]),
  Sex = "Female",
}

t = AutomagicTable(t_defaults)

t.ID12345.DOB = "7/2/1965"
t.ID12346.DOB = "1/2/1945"

print("ID12345",t.ID12345.Sex,t.ID12345.DOB,t.ID12345.Age) 
print("ID12346",t.ID12346.Sex,t.ID12346.DOB,t.ID12346.Age) 

Note the reference to the DOB field of the current table (see below) in this code age() fails as table["DOB"] is nil. If you run this code where there are no default values Automagic returns a table for the missing values.

I can assign defaults following the example in PIL Chapter 13, but the syntax is messy and once applied I loose the AutomagicTable functionality (as I have assigned a different metatable):

-- Make a metatables
t_defaults = {}
t_defaults.__index = function (table, key)
local def = {
    Age = age(table["DOB"]),
    Sex = "Female"
    }
return def[key]
end

-- Set new metatable - but now we can't make anymore Automagic tables
setmetatable(t.ID12345, t_defaults)
setmetatable(t.ID12346, t_defaults)

-- This will work
print("ID12345",t.ID12345.Sex,t.ID12345.DOB,t.ID12345.Age) 
print("ID12346",t.ID12346.Sex,t.ID12346.DOB,t.ID12346.Age) 

-- This assignment fails
t.ID12347.DOB = "12/12/1945"

Unfortunately I don't fully understand the AutomagicTables code and am struggling to add the required functionality within the AutomagicTable code.

Any help gratefully received.

Gavin

1

1 Answers

1
votes

I think you don't need full-depth automagic here.
Only users (i.e, objects at depth level 1) should be created automatically.
So, more simple logic could be used:

local function age(DOB_str)
   local m, d, y = (DOB_str or ""):match"^(%d+)/(%d+)/(%d+)$"
   if m then
      local t = {month = tonumber(m), day = tonumber(d), year = tonumber(y)}
      local now = os.date"*t"
      local now_year = now.year
      now = os.time{year = now_year, month = now.month, day = now.day}
      local lower_bound = math.max(0, now_year - t.year - 1)
      local completed_years = -1 + lower_bound
      t.year = t.year + lower_bound
      repeat
         completed_years = completed_years + 1
         t.year = t.year + 1
      until os.difftime(now, os.time(t)) < 0
      return completed_years
   end
end

-- Class "User"
local user_default_fields = {Sex = "Female"}
local user_mt = {__index =
   function (tab, key)
      if key == "Age" then  -- this field is calculatable
         return age(tab.DOB)
      else                  -- other fields are constants
         return user_default_fields[key]
      end
   end
}

-- The table containing all users with auto-creation
local users = setmetatable({}, {__index =
   function (tab, key)
      local new_user = setmetatable({}, user_mt)
      tab[key] = new_user
      return new_user
   end
})


-- usage
users.ID12345.DOB = "12/31/1965"
users.ID12346.DOB = "1/2/1945"

print("ID12345", users.ID12345.Sex, users.ID12345.DOB, users.ID12345.Age)
print("ID12346", users.ID12346.Sex, users.ID12346.DOB, users.ID12346.Age)