Lua - Use Cases for Proxy Tables



Proxy table with metatables is a very powerful combination to perform complex scenarios. In this chapter, we're covering most important ones.

Example - Create Read-Only table

We can create a proxy to read data from a table by implementing __index, while preventing any modification to the underlying table by use of __newindex making it effectively readonly.

main.lua

-- underlying real data
local actual_data = { name = "Julie", age = 30 }

-- an empty proxy table
local proxyTable = {}

-- metatable to prevent write access to actual_data table via proxy table.
local metatableReadOnly = {
  __index = actual_data,
  __newindex = function(table, key, value)
    error("Attempt to modify a read-only table", 2)
  end
}

-- set metatable for the proxy table
setmetatable(proxyTable, metatableReadOnly)

-- read name using proxy table, prints Julie
print(proxyTable.name) 
-- try to modify age, throws error
proxyTable.age = 31 

Output

When we run the above program, we will get the following output−

Julie
lua: main.lua:21: Attempt to modify a read-only table
stack traceback:
	[C]: in function 'error'
	main.lua:11: in metamethod 'newindex'
	main.lua:21: in main chunk
	[C]: in ?

Example - Lazy Loading

We often need to load data on first access to improve performance. For example, loading data from database or from network only when accessed instead of loading initially. We can use __index metamethod to handle such kind of data handling in Lua in a lazy loading way.

main.lua

-- data to cache
local cachedData = {}

-- proxy table
local proxyLazyLoad = {}

-- metatable for proxy table
local metatableLazyLoad = {
   __index = function(table, key)
      if not cachedData[key] then
         print("Loading data for key for first time:", key)
         -- once data loaded, set in cache
         cachedData[key] = "Data loaded for " .. key
      end
      return cachedData[key]
   end
}

-- set the metatable
setmetatable(proxyLazyLoad, metatableLazyLoad)

-- access item1 for the first time
-- prints Loading data for key for first time: item1
print(proxyLazyLoad.item1) 

-- access item1 again
-- prints Data loaded for item1
print(proxyLazyLoad.item1) 

-- access item1 for the first time
-- prints Loading data for key for first time: item2
print(proxyLazyLoad.item2)

Output

When we run the above program, we will get the following output−

Loading data for key for first time:	item1
Data loaded for item1
Data loaded for item1
Loading data for key for first time:	item2
Data loaded for item2

Example - Interception and Validation

Using proxy tables, we can intercepts writing attempt to log events, to validate data to be entered as shown in example below −

main.lua

-- underlying table
local actualSettings = {}
-- proxy table
local validatedSettings = {}
-- metatable for validation
local metatableValidation = {
  __index = actualSettings,
  __newindex = function(table, key, value)
     -- if value is a number 
    if type(value) == "number"  and value >= 0 and value <= 1 then
      actualSettings[key] = value
    else
      error("Invalid value for " .. key , 2)
    end
  end
}
-- set the metatable to proxy
setmetatable(validatedSettings, metatableValidation)

-- set a valid value via proxy
validatedSettings.volume = 0.5

-- get volume from real table, prints 0.5
print(actualSettings.volume) 

-- setting invalid value will cause error
validatedSettings.brightness = "high" 

Output

When we run the above program, we will get the following output−

0.5
lua: main.lua:27: Invalid value for brightness
stack traceback:
	[C]: in function 'error'
	main.lua:13: in metamethod 'newindex'
	main.lua:27: in main chunk
	[C]: in ?
Advertisements