Using modifyClass for objects which are initialized early in boot

When using modifyClass in an initializer, you may see this warning in the console:

(type) has already been initialized and registered as a singleton. Move the modifyClass call earlier in the boot process (e.g. to a pre-initializer) for changes to take effect.

(Prior to April 2023, the error text “(type) was already cached in the container. Changes won’t be applied.”)

This commonly happens when overriding methods on services (e.g. topicTrackingState), and on models which are initialized early in the app boot process (e.g. a model:user is initialized for service:current-user).

To resolve this warning, you need to move the modifyClass call earlier in the boot process. In a theme/plugin, that normally means moving the call to a pre-initializer, and configuring it to run before Discourse’s ‘inject-discourse-objects’ initializer. For example:

// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js
// or
// (theme)/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js

import { withPluginApi } from "discourse/lib/plugin-api";

export default {
  name: "extend-user-for-my-plugin",
  before: "inject-discourse-objects",

  initializeWithApi(api){
    api.modifyClass("model:user", {
      myNewUserFunction() {
        return "hello world";
      }
    });
  },

  initialize() {
    withPluginApi("0.12.1", this.initializeWithApi);
  },
};

This modification of the user model should now work without printing a warning, and the new method will be available on the currentUser object.

13 Likes

How would one go about this for a theme component? I’m trying to configure a modified version of this theme component (here), and this is the only error I cannot figure out how to resolve on my own.

2 Likes

@Firepup650 did you follow this?

Using that throws this slew of errors:

DEPRECATION: Function prototype extensions have been deprecated, please migrate from function(){}.property('bar') to computed('bar', function() {}). [deprecation id: function-prototype-extensions.property] See https://deprecations.emberjs.com/v3.x#toc_function-prototype-extensions-property for more details.
(anonymous) @ deprecate-shim.js:33
plugin-api.js:71 [THEME 61 'Profile Link - Test'] "model:user" has already been initialized and registered as a singleton. Move the modifyClass call earlier in the boot process for changes to take effect. https://meta.discourse.org/t/262064
_resolveClass @ plugin-api.js:71
plugin-api.js:21 [THEME 61 'Profile Link - Test'] To prevent errors in tests, add a `pluginId` key to your `modifyClass` call. This will ensure the modification is only applied once.
ke @ plugin-api.js:21
theme-field-397-common-html-script-1.js:29 Uncaught TypeError: t.currentProp is not a function
    at e.<anonymous> (theme-field-397-common-html-script-1.js:29:41)
    at index.js:1742:1
    at e.untrack (validator.js:681:1)
    at je.get (index.js:1741:1)
    at e.r [as externalSiteLink] (index.js:921:1)
    at Ce (index.js:1251:1)
    at xe (index.js:1239:1)
    at e.get (observable.js:114:1)
    at e.setupComponent (theme-field-397-common-html-script-1.js:24:57)
    at e.init (plugin-connector.js:47:1)
    at e.r [as init] (index.js:388:1)
    at g (core_object.js:122:1)
    at e.create (core_object.js:626:1)
    at g.create (index.js:447:1)
    at fe.create (index.js:939:1)
    at Object.evaluate (runtime.js:2679:1)
    at Object.evaluate (runtime.js:1052:1)
    at Mt.evaluateSyscall (runtime.js:4263:1)
    at Mt.evaluateInner (runtime.js:4234:1)
    at Mt.evaluateOuter (runtime.js:4227:1)
    at Wt.next (runtime.js:5058:1)
    at Wt._execute (runtime.js:5045:1)
    at Wt.execute (runtime.js:5038:1)
    at zt.handleException (runtime.js:4372:1)
    at Ut.handleException (runtime.js:4580:1)
    at Ft.throw (runtime.js:4319:1)
    at qe.evaluate (runtime.js:2091:1)
    at Ft._execute (runtime.js:4306:1)
    at Ft.execute (runtime.js:4291:1)
    at Kt.rerender (runtime.js:4606:1)
    at wr.render (index.js:6751:1)
    at index.js:7013:1
    at It (runtime.js:4139:1)
    at Tr._renderRoots (index.js:6996:1)
    at Tr._renderRootsTransaction (index.js:7039:1)
    at Tr._revalidate (index.js:7072:1)
    at p.invoke (queue.ts:201:14)
    at p.flush (queue.ts:98:13)
    at h.flush (deferred-action-queues.ts:75:19)
    at q._end (index.ts:616:32)
    at q.end (index.ts:298:10)
    at q._run (index.ts:667:14)
    at q.run (index.ts:339:17)
    at d (index.js:109:1)
    at u.success (ajax.js:105:1)
    at l (jquery.js:3213:1)
    at Object.fireWith [as resolveWith] (jquery.js:3343:1)
    at E (jquery.js:9617:1)
    at XMLHttpRequest.<anonymous> (jquery.js:9878:1)

Using my modified one only throws:

[THEME 24 'Replit profile - FPE'] "model:user" has already been initialized and registered as a singleton. Move the modifyClass call earlier in the boot process for changes to take effect. https://meta.discourse.org/t/262064
_resolveClass @ plugin-api.js:71
modifyClass @ plugin-api.js:102
(anonymous) @ theme-field-127-common-html-script-1.js:27
e.withPluginApi @ plugin-api.js:2283
initialize @ theme-field-127-common-html-script-1.js:15
i.initialize @ app.js:176
(anonymous) @ index.js:126
e.each @ dag-map.js:192
e.walk @ dag-map.js:121
e.each @ dag-map.js:66
e.topsort @ dag-map.js:72
_runInitializer @ index.js:138
runInstanceInitializers @ index.js:124
_bootSync @ instance.js:101
didBecomeReady @ application.js:656
p.invoke @ queue.ts:201
p.flush @ queue.ts:98
h.flush @ deferred-action-queues.ts:75
q._end @ index.ts:616
_boundAutorunEnd @ index.ts:257
Promise.then (async)
n @ platform.ts:28
flush @ index.js:41
q._scheduleAutorun @ index.ts:803
q._ensureInstance @ index.ts:791
q.schedule @ index.ts:384
g @ index.js:351
waitForDOMReady @ application.js:409
init @ application.js:323
r @ index.js:388
g @ core_object.js:122
create @ core_object.js:626
(anonymous) @ start-app.js:4
(anonymous) @ discourse-boot.js:20
(anonymous) @ discourse-boot.js:1

Additionally, that topic is where I got the component I’m currently tweaking:

2 Likes

@Firepup650 - looking at your repository, looks like you still need to create this pre-initializer js file for your api.modifyClass call similar to the OP example in this topic.

2 Likes

Would I put it in that exact location? Partially asking, because I only just moved it to a repository, and I don’t think I could do that via the Theme Component editor.

1 Like

you likely need to create a structure and js file setup similar to this (sorry i couldn’t find a pre-initializer example)

1 Like

i really only use this for testing code tweaks. i always work off my repositories or forks when it comes to theme components.

Trying to follow that, but I think I made it worse:

[THEME 62 'Profile link'] Compile error: SyntaxError: /discourse/theme-62/initializers/theme-field-399-common-html-script-1: Unexpected token, expected "," (30:5)

I should check and make sure I’ve properly cleaned up stuff like an extra }); in the future.

Now I no longer get errors, but it doesn’t work either.

i use vs code with prettier extension to do this sort of cleanup and formatting.

keep trying, you’ll get it. you’re probably not far off now. :slight_smile:

1 Like

I now just have a depreciation warning, and I can’t figure out what’s triggering it:

DEPRECATION: Passing the `@class` argument to <LinkTo> is deprecated. Instead, please pass the attribute directly, i.e. `<LinkTo class={{...}} />` instead of `<LinkTo @class={{...}} />` or `{{link-to class=...}}`. [deprecation id: ember.built-in-components.legacy-attribute-arguments] See https://deprecations.emberjs.com/v3.x#toc_ember-built-in-components-legacy-attribute-arguments for more details.

(Component still doesn’t work, so I’m chasing this now)

2 Likes

Got too annoyed with the pre-initializer, and switched the component to pull the user_fields and then just extract the one with the correct ID. As far as I could tell, the pre-initializer never got fired by the component, and at this point I’m just glad it works now.

2 Likes

For someone trying to get this to work and hitting the same problem, I only managed to get the initializer to work by placing is it in a different folder than indicated.

This didn’t work for me:

but using this path instead worked for me

// javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js

Kudos to @Lilly for providing the hint by linking to a sample code of a pre-initializer.

Aha, thanks @mentalstring and @Lilly for finding this. The example in the OP was for a plugin, which has a subtly different directory structure to a theme. I’ve now updated it to include example file paths for both themes and plugins :ok_hand:

4 Likes