question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

defaultScope mutates after applying .scope(['defaultScope', 'otherScope'])

See original GitHub issue

What are you doing?

With the code below, the default scope of the model seems to be overridden after doing Model.scope(['defaultScope', 'withBar']), leading to unexpected results.

const config = require('./config/config.json')
const Sequelize = require('sequelize')
const sequelize = new Sequelize(config['development'])

  const User = sequelize.define('User', {
    name: Sequelize.STRING,
    foo: Sequelize.BOOLEAN,
    bar: Sequelize.BOOLEAN,
    baz: Sequelize.BOOLEAN
  }, {});

  User.addScope('defaultScope', {
    where: { foo: true }
  }, { override: true })

  User.addScope('withBar', {
    where: { bar: true }
  })

  User.addScope('noBaz', {
    where: { baz: false }
  })

const users = [
  {
    name: 'Alice',
    foo: true,
    bar: true,
    baz: true
  },
  {
    name: 'Bob',
    foo: true,
    bar: true,
    baz: false,
  },
  {
    name: 'Charlie',
    foo: true,
    bar: false,
    baz: false,
  },
  {
    name: 'Danika',
    foo: false,
    bar: false,
    baz: false
  }
]

async function doStuff() {
  await sequelize.sync({force: true})
  await User.bulkCreate(users)

  const defaultscope = await User
    .findAll()
    .then(result => result.map(u => u.name))

  const withBar = await User
    .scope('withBar')
    .findAll()
    .then(result => result.map(u => u.name))

  const withBarAndDefault = await User
    .scope(['defaultScope', 'withBar'])
    .findAll()
    .then(result => result.map(u => u.name))

  const defaultscope2 = await User
    .findAll()
    .then(result => result.map(u => u.name))

  const noBaz = await User
    .scope('noBaz')
    .findAll()
    .then(result => result.map(u => u.name))

  const unscoped = await User
    .unscoped()
    .findAll()
    .then(result => result.map(u => u.name))

  const defaultscope3 = await User
    .findAll()
    .then(result => result.map(u => u.name))

  console.log('defaultscope: ', defaultscope)
  console.log('withBar: ', withBar)
  console.log('withBarAndDefault: ', withBarAndDefault)
  console.log('defaultscope2: ', defaultscope2)
  console.log('noBaz: ', noBaz)
  console.log('unscoped: ', unscoped)
  console.log('defaultscope3: ', defaultscope3)

  process.exit(0)
}

doStuff()

What do you expect to happen?

According to the docs .scope() should return a new model with the scope applied, so multiple invocations of User.scope().findAll() should have no bearing on each other.

Expected results:

defaultscope:  [ 'Alice', 'Bob', 'Charlie' ]
withBar:  [ 'Alice', 'Bob' ]
withBarAndDefault:  [ 'Alice', 'Bob' ]
defaultscope2:  [ 'Alice', 'Bob', 'Charlie' ]
noBaz:  [ 'Bob', 'Charlie', 'Danika' ]
unscoped:  [ 'Alice', 'Bob', 'Charlie', 'Danika' ]
defaultscope3:  [ 'Alice', 'Bob', 'Charlie' ]

What is actually happening?

Instead, later invocations of User.findAll() were affected.

Actual results:

// correct
defaultscope:  [ 'Alice', 'Bob', 'Charlie' ] 

// correct
withBar:  [ 'Alice', 'Bob' ]

// correct
withBarAndDefault:  [ 'Alice', 'Bob' ] 

// should include 'Charlie'
defaultscope2:  [ 'Alice', 'Bob' ]

 // correct
noBaz:  [ 'Bob', 'Charlie', 'Danika' ]

// correct
unscoped:  [ 'Alice', 'Bob', 'Charlie', 'Danika' ]

// should include 'Charlie'
defaultscope3:  [ 'Alice', 'Bob' ] 

Dialect: mysql Dialect version: XXX (where do I find this?) Database version: mysql Ver 15.1 Distrib 10.3.13-MariaDB, for Linux (x86_64) using readline 5.1 Sequelize version: 5.2.11 Tested with latest release: Yes, 5.2.11 (updated today)

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:15 (10 by maintainers)

github_iconTop GitHub Comments

2reactions
jorgelfcommented, Apr 8, 2019

Just a quick note to point out that both Utils.cloneDeep and Lodash’s _.cloneDeep raise JavaScript heap out of memory errors when running the tests. My guess is there are some instances where the scopes can contain complex objects that should not be cloned. Avoided this problem by calling Lodash’s _.cloneDeepWith specifying a function that results in cloning only plain objects.

Glad that snip was useful @jorgelf ! And thanks for showing me the ._scope property. That would have made initially tracking this down a lot nicer for me…

After digging a bit more into the code I should make clear that the ._scope property isn’t exactly the defaultScope. As far as I understand that class, it contains the scope to apply in the next query, which should reference the defaultScope (if we defined one) under normal circumstances. If you want to make sure you are checking the defaultScope, it’s usually better to check .options.defaultScope. In the provided example, both attributes work the same. You can also check other scopes through .options.scopes[<scope_name>] (the defaultScope is not included inside that object).

1reaction
jorgelfcommented, Apr 9, 2019

You are right, @WaylonOKC, that problem is still happening and though its cause is probably located in the same functions, looks unrelated to the scope overwriting. I’d say it’s happening again because of Model._mergeFunction. If we look around line 804:

    } else if (key === 'attributes' && _.isPlainObject(objValue) && _.isPlainObject(srcValue)) {
      return _.assignWith(objValue, srcValue, (objValue, srcValue) => {
        if (Array.isArray(objValue) && Array.isArray(srcValue)) {
          return _.union(objValue, srcValue);
        }
      });

It’s clearly mixing the attributes objects by merging its inner arrays, which is the problem you are having (non-array properties are actually overwritten as you expect). Maybe it’s a bug, though it looks like a very explicit block of code to me. Probable better to wait for a core developer to shed some light onto this.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Scopes - Sequelize
The default scope is always applied. This means, that with the model definition above, Project.findAll() will create the following query:.
Read more >
Scope transition mode - Documentation for BMC Discovery 22.1
All existing discovered devices are considered to be in the default scope. When you scan an endpoint, all scanned devices appear in the ......
Read more >
about Scopes - PowerShell | Microsoft Learn
The default scope for scripts is the script scope. The default scope for functions and aliases is the local scope, even if they...
Read more >
Sequelize attributes on include are overwriting defaultScope
findAll({ include: [{ model: Bar, // defaultScope does its work }], }) /* { . ... that defaultScope applies when no other scope...
Read more >
Scope | SAP Help Portal
After selecting another scope and loading the Solution Documentation again, the selected scope is displayed. The default scope Show All is always available...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found