OOP Method Returning Nil?

Just to make this clear, I’m relatively new to the concept of OOP, therefore there are bound to be small mistakes or misuse of parameters.

In Short

My method is returning a nil value even though I have declared the value in the constructor.

What I’ve Tried

I’ve previously assigned a new credit int from a server script to the table, which showed up for the method, but wouldn’t work if I just assigned the variable data from inside the module.

ERROR

image

SERVER

local modulePath = game.ServerScriptService.Server_Script_Essentials

local creditSystem = require(modulePath.Shop_Credits_Controller.Credits_Module)

-- -- -- -- -- -- -- --

game.Players.PlayerAdded:Connect(function(player)
	creditSystem.new(player.UserId)
	warn(creditSystem.new(player.UserId)) - Returns 1000
end)

MODULE

local creditSystem = {}

creditSystem.__index = creditSystem

function creditSystem.new(userID)
	local self = {
		credits = 1000
	}
	setmetatable(self, creditSystem)
	return self
end

function creditSystem:DeductCredits(amount)
	self.credits += amount -- self.credits = nil, even though it's declared to have 1000 [This is line 14]
	local creditUpdateRemote = game.ReplicatedStorage.Events.CreditUpdate
	creditUpdateRemote:Fire(self.credits)
end

return creditSystem

Amount Script

local modulePath = game.ServerScriptService.Server_Script_Essentials

local creditSystem = require(modulePath.Shop_Credits_Controller.Credits_Module)

script.Parent.ClickDetector.MouseClick:Connect(function(player)
	creditSystem:DeductCredits(-200)
end)

Any advice/tips are appreciated + any resources that might be useful for further use.

Try this:

local creditSystem = {}
creditSystem.__index = creditSystem

function creditSystem.new(userId)
	local NewCreditSystem = {}
	setmetatable(NewCreditSystem , creditSystem)
	
	NewCreditSystem.credits = 1000

	return NewCreditSystem 
end

function creditSystem:DeductCredits(amount)
	self.credits += amount
	local creditUpdateRemote = game.ReplicatedStorage.Events.CreditUpdate
	creditUpdateRemote:Fire(self.credits)
end

What’s this variable supposed to represent?

NewBackPiece.credits = 1000

I only ask because I’m confused as to where it’s supposed to connect.

1 Like

Whoops, I didn’t replace all of the old copy & paste variables
It should be NewCreditSystem.credits.

image

Still seem to be getting a similar error.

This is because when you’re passing self to DeductCredits, you’re actually passing the module as self, not the CreditSystem object.

So, what I’d do is save the CreditSystem object to a table inside the CreditSystem module, or use a BindableFunction to return the active credit object for that userId, but I’ll use the first proposed solution for simplicity:

local creditSystem = {}

creditSystem.__index = creditSystem
creditSystem._activeCreditSystemObjects = {}

function creditSystem.new(userID)
    if creditSystem._activeCreditSystemObjects[userId] then
        return creditSystem._activeCreditSystemObjects[userId] -- find the existing credit object for this UserId, if it exists then return it
    end
    -- it doesn't exist so create a new one
	local self = {
		credits = 1000
	}
	setmetatable(self, creditSystem)
    creditSystem._activeCreditSystemObjects[userId] = self -- store the new credit object to be referenced if needed
	return self
end

function creditSystem:DeductCredits(amount)
	self.credits += amount
	local creditUpdateRemote = game.ReplicatedStorage.Events.CreditUpdate
	creditUpdateRemote:Fire(self.credits)
end

return creditSystem

Now, to deduct the credits, you can use the creditSystem.new function to return the existing credit object, or a new one if one doesn’t exist yet:

script.Parent.ClickDetector.MouseClick:Connect(function(player)
    local creditObject = creditSystem.new(player.UserId)
	creditObject:DeductCredits(-200)
end)
1 Like

Thanks for the explanation!

It works. Now I just have to figure out the differences between the two.

Thanks for the help.

Basically, when you call require(moduleScript), it returns the module which contains the constructor (module.new()). When you call module.new(), only then does it return an object, you have to save this object somewhere in memory (eg. as a variable, in a table etc) in order to use its methods and manipulate it.

local module = require(path.to.module)
local var = module:method()
-- ^ is the same as doing
local var = module.method(module)
-- which is how `self` gets defined when you define a function like
local tbl = {}
function tbl:method()
    -- here, the variable "self" is defined as tbl automatically, even though we never manually assigned a value to it
end
-- furthermore, the function declaration above is essentially the same as doing
function tbl.method(self)

end

So, how is this relevant?

When you’re calling object:method(), you’re passing the object as self, this object’s metatable has the __index metamethod which sort of gives the illusion that object.method exists despite being inside of the module, not the object.

local class = {}
class.__index = class
function class.new()
    local object = {}
    object.property = 1
    setmetatable(object, class) -- allows any function inside of the class module to be called to manipulate the `object` table
    return object
end
-- now, let's create a function to manipulate an object
function class.method(self) -- remember, same as doing `function class:method()`
    print(self.property)
end

Now, in another script,

local class = require(path.to.module)
class:method() -- this will print nil because we're passing the "class" table as self, which does not contain the "property" index
local newObject = class.new()
newObject:method() -- this will print 1 because we're passing the "newObject" object as self, which does contain the "property" index"
class.method(newObject) -- this will also print 1 because we're passing the "newObject" object as self
newObject.method(class) -- this will print nil because we're passing the "class" table as self, which once again does not contain the "property" index.

Thank you very much for the explanation. :grin:

It’s much appreciated!

1 Like