Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model.getX: Cannot read property 'map' of undefined. #4157

Closed
bretmattingly opened this issue Jul 20, 2015 · 7 comments
Closed

Model.getX: Cannot read property 'map' of undefined. #4157

bretmattingly opened this issue Jul 20, 2015 · 7 comments

Comments

@bretmattingly
Copy link

Sequelize Ver: 3.3.2

Model user has an N:M association with model group. Attempting to use getGroups() results in an error.

Source

router.get('/lazy', function(req, res, next){
    models.user.unscoped().findOne({
        where: {username: 'bomattin'}
    }).then(function(userresult){
        userresult.getGroups().then(function(result){
            res.json(result)
        })
    })
});

Error:

Unhandled rejection TypeError: Cannot read property 'map' of undefined
    at generateJoinQueries (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1052:42)
    at /Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1346:36
    at Array.forEach (native)
    at generateJoinQueries (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1344:27)
    at Object.<anonymous> (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1363:27)
    at Array.forEach (native)
    at Object.QueryGenerator.selectQuery (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1362:23)
    at QueryInterface.select (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/query-interface.js:682:25)
    at null.<anonymous> (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/lib/model.js:1219:32)
    at tryCatcher (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/util.js:26:23)
    at Promise._settlePromiseFromHandler (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/promise.js:503:31)
    at Promise._settlePromiseAt (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/promise.js:577:18)
    at Async._drainQueue (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/async.js:128:12)
    at Async._drainQueues (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/async.js:133:10)
    at Immediate.Async.drainQueues [as _onImmediate] (/Users/bomattin/Documents/code/buapi/sequelize-testing/node_modules/sequelize/node_modules/bluebird/js/main/async.js:15:14)
    at processImmediate [as _immediateCallback] (timers.js:358:17)

The code referenced by the first line of the stack trace is:

attributes = include.attributes.map(function(attr) {

Which suggests that Sequelize is looking for includes and is not finding them. Am I using getX() incorrectly?

@janmeier
Copy link
Member

That code should only execute if options.include is set. Can you post model definitions + assocations as well?

@bretmattingly
Copy link
Author

User.js

module.exports = function(sequelize, DataTypes) {
    var user = sequelize.define("user", {
        id: {
            type: DataTypes.INTEGER,
            field: 'emplid',
            primaryKey: true,
            autoIncrement: false
        },
        firstname: {
            type: DataTypes.STRING,
            field: 'firstname_preferred',
            defaultValue: '',
            allowNull: false
        },
        middlename: {
            type: DataTypes.STRING,
            field: 'middlename_preferred',
            defaultValue: '',
            allowNull: false
        },
        lastname: {
            type: DataTypes.STRING,
            field: 'lastname_preferred',
            defaultValue: '',
            allowNull: false
        },
        legalFirstname: {
            type: DataTypes.STRING,
            field: 'firstname_legal',
            allowNull: false
        },
        legalMiddlename: {
            type: DataTypes.STRING,
            field: 'middlename_legal',
            defaultValue: '',
            allowNull: false
        },
        legalLastname: {
            type: DataTypes.STRING,
            field: 'lastname_legal',
            allowNull: false
        },
        username: {
            type: DataTypes.STRING,
            field: 'username',
            allowNull: false,
            unique: true
        },
        email: {
            type: DataTypes.STRING,
            defaultValue: '',
            allowNull: false
        },
        phone: {
            type: DataTypes.STRING,
            defaultValue: '',
            allowNull: false
        },
        location: {
            type: DataTypes.STRING,
            field: 'location',
            allowNull: false
        },
        photo: {
            type: DataTypes.STRING,
            field: 'photo_url',
            allowNull: false
        }
    }, {
        //tableName: 'people',
        classMethods: {
            associate: function(models) {
                user.belongsToMany(models.group, {
                    foreignKey: "emplid",
                    through: 'userGroup',
                    onDelete: 'cascade'
                });
                user.belongsToMany(models.role, {
                    scope: {
                      roleid: {$ne: 11}
                    },
                    foreignKey: "emplid",
                    through: 'user_roles',
                    onDelete: 'cascade'
                });

            }
        }, defaultScope: {
            include: [
                // 'as' clauses prevent table alias errors
                { model: sequelize.models.role, as: sequelize.models.role.tableName },
                { model: sequelize.models.group, as: sequelize.models.group.tableName }
            ]
        }, scopes: {
            facstaff: {
                include: [
                    { model: sequelize.models.role, as: sequelize.models.role.tableName, where: {id: {$lte: 2}} },
                    { model: sequelize.models.group, as: sequelize.models.group.tableName }
                ]
            }
        }
});

    return user;
};

Group.js

module.exports = function(sequelize, DataTypes) {
    var group = sequelize.define("group", {
        id: {
            type: DataTypes.INTEGER,
            field: 'groupid',
            primaryKey: true,
            autoIncrement: true
        },
        name: {
            type: DataTypes.STRING,
            allowNull: false
        },
        description: {
            type: DataTypes.STRING
        },
        longDescription: {
            type: DataTypes.STRING(1000),
            field: 'long_description'
        }
    }, {
        classMethods: {
            associate: function(models) {

                group.belongsToMany(models.user, {
                    foreignKey: "groupid",
                    through: 'userGroup',
                    onDelete: 'cascade'
                });
            }
        }
    });
    return group;
};

@bretmattingly
Copy link
Author

I forgot to show the userGroup model:

UserGroup.js

module.exports = function(sequelize, DataTypes) {
    var userGroup = sequelize.define("userGroup", {
        title: {
            type: DataTypes.STRING,
            field: 'title',
            allowNull: false,
            defaultValue: ''
        }
    }, {
        tableName: 'user_groups'
    });

    return userGroup;
};

Edit: The Plot Thickens

This works in the reverse order: I.e., if I get a single Group, then call getUsers, I get exactly what I expect. I suspect scopes or the manually defined join table userGroup are at fault here, but I'm not sure which.

@janmeier
Copy link
Member

Yep, definitely related to scopes somehow - not sure why :)

But it seems we are forgetting to call conformOptions on the defaultScope https://github.com/sequelize/sequelize/blob/master/lib/model.js#L585 - doing so fixes the problem for me

@bretmattingly
Copy link
Author

Huh, my lib code there looks the same as yours.

addOptionalClassMethods.call(this);

  this.$scope = _.isPlainObject(this.options.defaultScope) ? this.options.defaultScope : {};

  _.each(this.options.scopes, function (scope) {
    if (_.isPlainObject(scope) && scope.include) {
      conformOptions(scope);
    }
  });

@janmeier
Copy link
Member

Yes, I was merely linking to the line that needs to be fixed for this to work :-)

@bretmattingly
Copy link
Author

Works like a charm! Thanks @janmeier and team!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants