Extend lua-resty-* modules and call parent function - lua

I am trying to write for each lua-resty-redis, lua-resty-memcached and lua-resty-mysql modules a small class that extends the default module. In my child class, I want to call a function from the parent class but couldn't find a proper way no matter what inheritance documentation for Lua I've read.
For example, I want to overwrite the connect() function, do some stuff and call the parent's connect() function at some point. But how?
local redis = require "resty.redis"
function redis.connect(self, ...)
-- Do some stuff here
local ok, err = parent:connect(...)
-- Do some other stuff here
return ok, err
end
How can this be achieved?
As a note, all the above mentioned modules are structured like this:
local _M = { _VERSION = "0.1" }
local mt = { __index = _M }
function _M.new(self)
return setmetatable({ foo = "bar" }, mt)
end
function _M.connect(self, ...)
-- Connect
end
return _M
Thank you in advance!

local redis = require "resty.redis"
local original_connect = redis.connect
function redis.connect(self, ...)
-- Do some stuff here
local ok, err = original_connect(self, ...)
-- Do some other stuff here
return ok, err
end

Related

Why doesn't the __call metamethod work in this Lua class?

in the code below, i set up a __call metafunction which, in theory, should allow me to call the table as a function and invoke the constructor, instead of using test.new()
test = {}
function test:new()
self = {}
setmetatable(self, self)
--- private properties
local str = "hello world"
-- public properties
self.__index = self
self.__call = function (cls, ...) print("Constructor called!") return cls.new(...) end
self.__tostring = function() return("__tostring: "..str) end
self.tostring = function() return("self:tstring(): "..str) end
return self
end
local t = test:new()
print(t) -- __tostring overload works
print(tostring(t)) -- again, __tostring working as expected
print(t:tostring()) -- a public call, which works
t = test() -- the __call metamethod should invoke the constructor test:new()
output:
> __tostring: hello world
> __tostring: hello world
> self.tostring(): hello world
> error: attempt to call global `test` (a table value) (x1)
(i'm using metatable(self, self) because i read somewhere it produces less overhead when creating new instances of the class. also it's quite clean-looking. it may also be where i'm getting unstuck).
You're setting the __call metamethod on the wrong table - the self table - rather than the test table. The fix is trivial:
test.__call = function(cls, ...) print("Constructor called!") return cls.new(...) end
setmetatable(test, test)
After this, test(...) will be equivalent to test.new(...).
That said, your current code needs a refactoring / rewrite; you overwrite the implicit self parameter in test:new, build the metatable on each constructor call, and don't even use test as a metatable! I suggest moving methods like tostring to test and setting the metatable of self to a metatable that has __index = test. I'd also suggest separating metatables and tables in general. I'd get rid of upvalue-based private variables for now as they require you to use closures, which practically gets rid of the metatable benefit of not having to duplicate the functions per object. This is how I'd simplify your code:
local test = setmetatable({}, {__call = function (cls, ...) return cls.new(...) end})
local test_metatable = {__index = test}
function test.new()
local self = setmetatable({}, test_metatable)
self._str = "hello world" -- private by convention
return self
end
function test_metatable:__tostring()
return "__tostring: " .. self._str
end
function test:tostring()
return "self:tstring(): " .. self._str
end
If you like, you can merge test and test_metatable; I prefer to keep them separated however.

Simpler way to call a function in a same table?

I wonder if there's a simpler way to call a function or set variable of the same table instead of writing the table name.
For example, Is there a simpler way to call MyClass.test() function from MyClass.setup() in my below example code?
local MyClass = {}
function MyClass.test()
print("Hello")
end
function MyClass.setup()
MyClass.test()
end
MyClass.setup()
If you use : to call the functions instead of ., Lua implicitly inserts a reference to the table itself as the first argument (similar to the this pointer is some object-oriented languages). Then you can say self:test() to get rid of the name dependence.
local MyClass = {
test = function(self)
print("Hello")
end,
setup = function(self)
self:test()
end
}
MyClass:setup()
You can set the module table to be the environment:
local print = print
local _ENV = {}
function test()
print("Hello")
end
function setup()
test()
end
setup()
return _ENV
local MyClass = {}
function MyClass:Setup()
print('hello...Setup')
end
function MyClass:Test()
self:Setup() -- self -> MyClass
print('hello...Test')
end
MyClass:Test()
-- or inherit
local newClass = MyClass
newClass:Test()
local MyClass = {}
function MyClass.test()
print("Hello")
end
function MyClass.setup(self)
self.test()
end
MyClass.setup(MyClass)
the key is self. In the state, MyClass is a obj and create by {}, so you can use MyClass.Test(MyClass) to incoming parameters MyClass to use the func test() of MyClass obj.
ps:
Syntax sugar provided by Lua:
MyClass.setup(MyClass) ==> MyClass:setup()

How do I invoke a class method using pcall in lua?

How do I invoke a class method using pcall in Lua?
I tried pcall(instance:method, arg) but it doesn't work.
I also tried pcall(instance.method, instance, arg) but that doesn't work either.
I googled for a solution but I couldn't get one.
An example:
local ValueOwnerMap = {}
ValueOwnerMap.__index = ValueOwnerMap
function ValueOwnerMap:create(key_prefix)
local instance = {}
setmetatable(instance, ValueOwnerMap)
instance.key = key_prefix .. ':value-owner-map'
return instance
end
function ValueOwnerMap:get(value)
return redis.call('HGET', self.key, value)
end
function ValueOwnerMap:put(value, owner_id)
return redis.call('HSETNX', self.key, value, owner_id)
end
function ValueOwnerMap:del(value)
return redis.call('HDEL', self.key, value)
end
local value_owner_map = ValueOwnerMap:create('owner:key')
local success, data = pcall(value_owner_map:put, 'a_value', 'a_owner_id')
instance:method(arg) is sugar for instance.method(instance,arg). So try
pcall(value_owner_map.put, value_owner_map, 'a_value', 'a_owner_id')
The following line replaces the last line of the block in the question. It works.
local success, data = pcall(function () value_owner_map:put('a_value', 'a_owner_id') end)
Thank you all for sharing
pcall (f, arg1, ···)
Calls function f with the given arguments in protected mode. This means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. lua ref
But Calls for functions in protected mode have some limitations especially when you are using ':' operator 'so-called synthetic sugar' of lua.
one way to WAR this limitation is to put inside a function
pcall(function () value_owner_map:put('a_value', 'a_owner_id') end)
This approach also catches the errors as usual:
local ok, msg = pcall(function () error('Phony Error') end)
if ok then
print("No error")
else
print("Got error".. tostring(msg))
end
-- Result:
-- Got error test.lua:53: Phony Error

attempt to call method 'func' (a nil value)

No matter how I approach Lua, I run into this error all the time, so I must not understand something inherit to the language:
attempt to call method 'func' (a nil value)
I've seen the error here a few times as well but the problem doesn't seem clear to me.
Here's my module:
actor.lua
Actor = {
x = 0,
mt = {},
new = function()
local new_actor = {}
new_actor.x = Actor.x
new_actor.mt = Actor.mt
return new_actor
end,
test = function(self, a, b)
print(a, b)
end
}
I'm using Löve.
main.lua
require "game/actor"
local a = Actor:new() --works fine
function love.load()
a.x = 10
print(a.x) --output: 10
a:test(11, 12) --error: attempt to call method 'test' (a nil value)
end
I'm also not sure when it's appropriate to use the previous styling over this in a module.
Actor = {
x = 0
}
Actor.mt = {}
function Actor.new()
print(42)
end
I'm honestly not sure what is more correct than the other but considering I run into a simple error either way, there's probably something I'm missing entirely?
It looks like you're trying to instance a kind of class made of metatables. You basically need to assign new_actor's metatable with Actor.mt. (Resuming the problem: when you're indexing new_actor you're not indexing Actor in this case)
setmetatable(new_actor, Actor.mt);
Even if the metatable is being added, it won't work until you put the meta "__index" event to index a table containing your class methods/values, in this case:
Actor.mt = {
__index = Actor
};
I'd suggest moving your class methods/values into a new table, like Actor.prototype, Actor.fn, etc... avoiding conflicts:
Actor.fn = {
test = function(self, a, b)
print(a, b)
end
};
Actor.mt = {
__index = Actor.fn
};
More about metatables in Lua 5.3 manual.

Not sure why extending/injecting an instance method in to a lua object isn't working

In this example, I'm using lunit and am attempting to inject an instance method in to an instance of LuaSocket and am failing to see why the following isn't working.
-- Using lunit for unit testing
local lunit = require('lunitx')
_ENV = lunit.module('enhanced', 'seeall')
local socket = require('socket')
-- connect(2) to the service tcp/echo
local conn, connErr = socket.connect('127.0.0.1', '7')
function conn:receiveLine(...)
local line, err = self:receive('*l')
assert_string(line, err)
return line
end
function conn:sendLine(...)
local bytesSent, err = self:send(... .. '\n')
assert_number(bytesSent, err)
return bytesSent
end
The error message I'm getting is:
attempt to call method 'sendLine' (a nil value)
?? This seems like there is something obvious happening here, but I'm missing the required detail.
Ass-u-me'ing getmetatable(conn).__index == getmetatable(conn) source of confusion.
In this case, conn's metatable's __index metamethod is pointing to a different table than expected, so method resolution isn't happening against the table at getmetatable(conn).
function setup()
-- Update the table at __index, not conn's metatable
local mt = getmetatable(conn).__index
function mt:receiveLine(...)
local line, err = self:receive('*l')
assert_string(line, err)
return line
end
function mt:sendLine(...)
local bytesSent, err = self:send(... .. '\n')
assert_number(bytesSent, err)
return bytesSent
end
end
I was able to tease this out by testing to see if __index was pointing to conn's metatable, which it wasn't:
assert_equal(getmetatable(conn), getmetatable(conn).__index)
Generically, if there is a __newindex handler on getmetatable(conn) that is intercepting new table entries, use rawset() on __index's table.

Resources