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
bomattin opened this Issue Jul 20, 2015 · 7 comments

Comments

2 participants
@bomattin

bomattin commented Jul 20, 2015

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

This comment has been minimized.

Member

janmeier commented Jul 20, 2015

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

@bomattin

This comment has been minimized.

bomattin commented Jul 21, 2015

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;
};
@bomattin

This comment has been minimized.

bomattin commented Jul 21, 2015

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

This comment has been minimized.

Member

janmeier commented Jul 22, 2015

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

@janmeier janmeier added the bug label Jul 22, 2015

@bomattin

This comment has been minimized.

bomattin commented Jul 22, 2015

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

This comment has been minimized.

Member

janmeier commented Jul 22, 2015

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

@janmeier janmeier closed this in 1db2ee6 Jul 25, 2015

@bomattin

This comment has been minimized.

bomattin commented Jul 27, 2015

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