Skip to content
This repository has been archived by the owner on Aug 28, 2020. It is now read-only.

key does not exist error on settings.update for a folder key with a value of null #509

Closed
2 tasks
Lovinity opened this issue Nov 23, 2018 · 17 comments
Closed
2 tasks
Labels
Type: Question Issues that do not belong to the Issue Tracker and should be asked in the official Discord Server.

Comments

@Lovinity
Copy link

Describe the issue

I have a guild.settings "levelRoles" which is a folder for 99 settings, each with a numeric key from 2 to 100, a default value of null, and an expected type of role. Upon trying to update any of these keys, an error is thrown: The key levelRoles.n does not seem to exist (where n is the numeric key I'm trying to update). However, upon evaluating message.guild.settings.levelRoles, it returns the created keys all with a value of null, indicating they do in fact exist.

Code or steps to reproduce

index.js

Client.defaultGuildSchema
        .add('levelRoles', folder => {
            for (var i = 2; i <= 100; i++)
            {
                folder.add(i, 'role');
            }
        });

In Discord:
!conf set levelRoles.2 role - Returns "The key levelRoles.2 does not seem to exist."

Also,
!eval message.guild.settings.update('levelRoles.2', '515063679834783744', message.guild, { avoidUnconfigurable: true, action: 'add' }) .then(result => message.send(result.errors)); - Returns a KlasaMessage with the content "The key levelRoles.2 does not seem to exist."

However,
!eval message.guild.settings.levelRoles; - Returns

{ '2': null,
  '3': null,
  // AND SO ON
}

...which indicates the keys do exist.

Expected and actual behavior

I expect to be able to set a role to each of the levelRoles.n settings, but instead I get an error saying the key does not seem to exist.

Further details

  • discord.js version: 12-dev commit #f3cad81
  • node.js version: 8.11.4
  • Klasa version: 0.5-dev commit #037d868
  • I have modified core files.
  • I have tested the issue on latest master. Commit hash:
@bdistin
Copy link
Contributor

bdistin commented Nov 23, 2018

You are passing a number to what you need to pass a string... the add method expects a string key.

@Lovinity
Copy link
Author

That doesn't explain why !eval message.guild.settings.levelRoles is returning all of the keys as if they were created. Could you elaborate more so I can better understand?

@kyranet
Copy link
Contributor

kyranet commented Nov 23, 2018

Because the key is stored as you pass it, and it expects a string, not a number.

Schema#get (which is used in all operations to check if a key exists, does this operation, and returns the next value:

'levelRoles.2'.split('.');
// ['levelRoles', '2']

schemaFolder.get(2) exists, schemaFolder.get('2') doesn't. Therefore if you stringify your number, it'll work normally.

Also, I'd like to suggest you to use an array instead of a full object, you're doing an array-like object and it's very overcomplicated.

@Lovinity
Copy link
Author

Thank you @kyranet . I thought about using an array. However, I run into an issue. To use array, I need to use level as key, role as value. But not every level will have a value. For example, I may have one at 5, and one at 10, but not 6-9.

Is there a way one can easily set the array key for which the value is going into when it comes to an array: true setting?

@tech6hutch
Copy link
Contributor

Can't you store holey arrays?

@Lovinity
Copy link
Author

How do you store holey arrays in the Settings gateway and the conf command?

@kyranet
Copy link
Contributor

kyranet commented Nov 23, 2018

In the object you have null at 6-9, I don't see what stops you from using an array with 100 nulls, besides the large memory usage it'll come with (which is fixed in the settings branch). Either way, I'd do a dynamic array and push objects with the level and the id, keeping the level sorted so you can do something like a binary search, if you're very worried about performance in this last (and possibly best) solution to your problem.

@Lovinity
Copy link
Author

I can do that, but I lose out on role validation unless I configurable: false it and create a custom command. I was hoping to avoid that, but if it's the best solution, I'll go for it.

@tech6hutch
Copy link
Contributor

What validation?

@Lovinity
Copy link
Author

type validation... ensuring only role resolvables can be added to that setting.

@tech6hutch
Copy link
Contributor

You'd still get that, with type 'role' and array: true

@Lovinity
Copy link
Author

I thought in order to pass objects (as per the suggestion of kyranet), it has to be type any?

@tech6hutch
Copy link
Contributor

Oh, I think he was talking some performance thing. You can use the role type instead.

@Tylertron1998
Copy link
Contributor

Also, we have an official discord server which might be a little more suited for questions/issues like this - you'd probably get a much faster answer!

@kyranet kyranet added the Type: Question Issues that do not belong to the Issue Tracker and should be asked in the official Discord Server. label Nov 23, 2018
@Lovinity
Copy link
Author

Lovinity commented Nov 23, 2018

Thank you Tyler. I originally thought this was a bug, thus why I posted it on github. But it turned into a question about best practices for schemas. I'm okay with the time it takes to get a response on here because this isn't something that I need to fix right away.

I'm having more issues (not sure if this is a bug or not either). I was originally going to take on @kyranet 's suggestion of an array of objects. However, that led to extremely complicated code, and I ended up hitting a road block anyway so the code was ultimately useless.

Now, I'm trying a custom serializer "objectofroles" which looks like this:

const {Serializer} = require('klasa');
const { Role } = require('discord.js');

module.exports = class extends Serializer {

    constructor(...args) {
        super(...args, {aliases: []});
    }

    async deserialize(data, piece, language, guild) {
        if (!guild)
            throw this.client.languages.default.get('RESOLVER_INVALID_GUILD', piece.key);
        if (typeof data !== 'object' || data.constructor !== Object)
            throw new Error(`The provided data for ${piece.key} must be type Object.`);
        var checkGood = true;
        for (var key in data)
        {
            if (!data.hasOwnProperty(key))
            {
                checkGood = false;
                continue;
            }

            if (data[key] instanceof Role)
                continue;

            const role = this.constructor.regex.role.test(data[key]) ? guild.roles.get(this.constructor.regex.role.exec(data[key])[1]) : guild.roles.find(rol => rol.name === data[key]) || null;
            if (role) {
                data[key] = role;
                continue;
            }

            checkGood = false;
        }

        if (!checkGood)
            throw new Error(`All values in the provided data object for ${piece.key} must be a roleResolvable.`);

        return data;
    }

    serialize(value) {
        var temp = value;
        for (var key in temp)
        {
            if (temp.hasOwnProperty(key))
            {
                temp[key] = temp[key].id;
            }
        }

        return temp;
    }

    stringify(value, message) {
        var temp = value;
        for (var key in temp)
        {
            if (temp.hasOwnProperty(key))
            {
                temp[key] = (message.guild.roles.get(temp[key]) || {name: (temp[key] && temp[key].name) || temp[key]}).name;
            }
        }

        return JSON.stringify(temp);
    }

};

Essentially, it's a single object, but it validates all values in the object to ensure they are a role.

When pushing updates via guild.settings.update, this seems to work. For example, Let's say in that key I currently have an object {3: Role}. I merge into it a key 2 with another role. Upon checking guild.settings after the update, it correctly contains {2: Role, 3: Role}. However, upon restarting the bot, the change is lost, and the guild goes back to having {3: Role} in that key. It should persist {2: Role, 3: Role}. Doing a guild.settings.sync(true) did no good.

EDIT: My database engine is rethinkdb.

Am I doing something wrong? Is this a bug? I've had this same issue before on built-in settings type "any" when storing a single object in it.

@Lovinity
Copy link
Author

Lovinity commented Nov 24, 2018

NVM, after countless hours of scratching my head and trying to figure out why Klasa was doing these weird things with objects in the Settings gateway (and unexpectedly changing types with 'any')... I ended up proceeding with my original idea of creating 99 folders for each guild, but instead naming them level${n} so as to satisfy the string requirement.

To be blunt honest, I'm very upset and frustrated by the instability of the Settings gateway. It is extremely unpredictable, especially with objects and array of objects.

@Tylertron1998
Copy link
Contributor

If you have any recommendations or anything you'd like changed, feel free to comment on #425 - I'm glad you sorted it out.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Type: Question Issues that do not belong to the Issue Tracker and should be asked in the official Discord Server.
Projects
None yet
Development

No branches or pull requests

5 participants