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

[FEATURE ember-container-inject-owner]: Inject owner instead of container during lookup. #11874

Merged
merged 2 commits into from
Nov 4, 2015

Conversation

dgeb
Copy link
Member

@dgeb dgeb commented Jul 23, 2015

This change represents the final brick needed to wall off the
Container as fully private.

The Container no longer injects itself into every object that it looks
up. Instead, the owner of the Container is injected as owner by the
ContainerProxy mixin.

The current net effect is that an app instance is injected into every
looked up object instead of that app instance's container. This
provides clean, public access to methods exposed by the app
instance's ContainerProxy and RegistryProxy methods. It also guarantees
that the only supported path to get to a Container or Registry is
through a proxied method. This guarantee is important because it allows
for owner-specific logic to be placed in proxy methods.

In the future, other classes, such as Engine (coming soon), may mix in
ContainerProxy and thus have the potential to be "owners".

Note 1: This work is behind the ember-registry-container-reform flag.

Note 2: Totally open to alternatives to the name owner. I was drawn
to this name because the owner of the container "owns" the instantiated
objects to the extent that its responsible for their cleanup.

Note 3: This is still a WIP and will require changes throughout the
test suite for compatibility.


  • Move owner to __owner__ (actually, use symbol instead).
  • Create getOwner / setOwner functions.
  • Add simple container tests for accessing container in an object looked up from the container.
  • Confirm the Object.defineProperty supporting deprecated access to this.container is called a single time per factory (and not once for every created instance).
  • Add a new feature flag (because ember-container-registry-reform is already enabled). Perhaps ember-container-inject-owner?

let container = get(this, '__container__');
let result = container.lookup(...arguments);
if (result && result.owner === undefined) {
result.owner = this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may want to be careful about owner, we dont want to smash users properties on collision.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, definitely open to alternatives to owner - it's short and sweet, but that also means it might be commonly used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the very least some underscores, at the very best non-enumerable.

But why can't we just use container ? it seems like its the same thing...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. For better or worse we have already reserved this.container, and any top level "word" we take will potentially collide with something.

I'm not opposed to something better, but keeping it as container would preserve current this.container.lookup semantics and not take over another reserved word...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume from the syntax highlighting that you mean __container__, right?

My concern with using anything related to "container" publicly is that it breaks the clean encapsulation. Devs will continue to think of Container as semi-private and try to dig into container.registry for instance.

Maybe __owner__? It's probably safe from collisions, clearly private, and clearly not an instance of a Container. I guess I've been unclear how private / public we want this to appear (given that container has been available without dunderscores).

EDIT: typed as a reply to @stefanpenner above before seeing @rwjblue's comment.

@dgeb
Copy link
Member Author

dgeb commented Jul 23, 2015

The issue of container injections was raised by @krisselden.

As I replied, I didn't include this in #11440 because it wasn't part of my original RFC and so I thought it appropriate to discuss it separately.

If feedback is positive, I'll bring the tests / implementation inline.

injections.container = container;

if (isEnabled('ember-registry-container-reform')) {
Object.defineProperty(injections, 'container', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ran once for each object looked up? Does that pose a performance issue? I am unsure, I'd love others' thoughts...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it (if the code hasn't changed since i wrote it) should only run 1 per factory. If we are not doing that anymore, we should likely chat, because it would be a big perf hit to run every injection rule for every instance.

@krisselden
Copy link
Contributor

@dgeb owner still is too much of a reserved word, why not __container__ too?

@krisselden
Copy link
Contributor

We can have a public API via an accessor that knows the symbol, import getOwner from 'ember-injections' getOwner(this).lookup() and we can have Ember.injectService etc. understand this for lazy injections.

@dgeb
Copy link
Member Author

dgeb commented Jul 24, 2015

owner still is too much of a reserved word, why not container too?

My issue with using __container__ or container is that the property is not in fact an instanceof Container.

We can have a public API via an accessor that knows the symbol, import getOwner from 'ember-injections' getOwner(this).lookup() and we can have Ember.injectService etc. understand this for lazy injections.

Seems good to me.

@dgeb dgeb force-pushed the inject-owner-not-container branch from e586dab to b715907 Compare July 24, 2015 05:35
@rwjblue
Copy link
Member

rwjblue commented Aug 9, 2015

Where are we at with this?

@dgeb
Copy link
Member Author

dgeb commented Aug 12, 2015

Sorry to let this sit.

Would __owner__ work for everyone, combined with the getOwner accessor that @krisselden suggested?

^ @stefanpenner @rwjblue

@rwjblue
Copy link
Member

rwjblue commented Aug 16, 2015

@dgeb - Yeah, seems like a good path forward for now (we can always do more bikeshedding :trollface: ). I think this will need to use a different feature flag name though, as we are going to ship the container / registry reform feature before this is ready.

@rwjblue
Copy link
Member

rwjblue commented Aug 23, 2015

@dgeb - I believe we are generally in agreement with your last comment.

General checklist added to PR description.

@dgeb
Copy link
Member Author

dgeb commented Sep 3, 2015

@rwjblue - thanks - I'll try to get to this soon!

@dgeb
Copy link
Member Author

dgeb commented Oct 22, 2015

I've completed all the tasks in the checklist except for the following:

Confirm the Object.defineProperty supporting deprecated access to this.container is called a single time per factory (and not once for every created instance).

If we use Object.defineProperty in injectionsFor instead of instantiate, the defined container property is not in fact applied to the instantiated object. This looks to be because the injections are applied via factory.extend(injections), but more investigation is needed.

@dgeb dgeb force-pushed the inject-owner-not-container branch 2 times, most recently from 891779f to 9b0b7fe Compare October 22, 2015 19:10
@dgeb
Copy link
Member Author

dgeb commented Oct 22, 2015

Ok, I'm now injecting the container via defineProperty on the generated factory prototype rather than on the injections object (since it wasn't being applied via extend). This injection should only be necessary until Ember 3.0.

The checklist is now complete, but CI is apparently not happy yet.

@dgeb dgeb force-pushed the inject-owner-not-container branch 6 times, most recently from 162830a to c04429e Compare October 23, 2015 19:27
@dgeb
Copy link
Member Author

dgeb commented Oct 23, 2015

Finally green and ready for review :)

@dgeb
Copy link
Member Author

dgeb commented Oct 30, 2015

@rwjblue - let me know when you're ready to review this and I'll be glad to rebase it.

@rwjblue
Copy link
Member

rwjblue commented Oct 30, 2015

Thank you. Sorry for the delay, I've gotten a bit behind on my triaging and PR review while in London for EmberCamp London.

}
}

// TODO - remove when Ember reaches v3.0.0
function injectDeprecatedContainer(object, container) {
Object.defineProperty(object, 'container', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could likely install the container in injections via a symbol, then leave this defProp on CoreObject, that way all ember objects injections don't need to do the redefine. (only those that are not decedents of CoreObject would?

@stefanpenner
Copy link
Member

the setOwner usage in tests seems unfortunate. The entity should be initialized with its reference to the owner, it seems perfectly reasonable for one to assume in all cases the owner is present during view init.

I suspect, something like:

View.extend(ownerInject(), {

}).create()

would do the trick

@dgeb
Copy link
Member Author

dgeb commented Nov 3, 2015

@stefanpenner

I'm open to using a helper to clean up the tests. Keep in mind we need to pass owner into whatever helper we choose.

For example:

View.extend(ownerInject(owner), {
  prop: value
}).create()

Alternative names: injectOwner or ownedBy

Another possible helper:

View.extend(injectOwner({
  prop: value
}, owner)).create()

Alternative name: withOwner

@stefanpenner @rwjblue any preference?

@dgeb
Copy link
Member Author

dgeb commented Nov 3, 2015

After chatting with @rwjblue, we're looking to export the OWNER symbol for use by Ember and its tests, but we won't be exposing it publicly (on the Ember global).

This will allow us to set the owner without a helper in Ember's tests:

View.extend({
  [OWNER]: owner,
  prop: value
}).create()

@stefanpenner let us know if you have any objections.

@stefanpenner
Copy link
Member

@stefanpenner let us know if you have any objections.

nope this sounds good.

@stefanpenner
Copy link
Member

Once this lands, we will seal a private but often used feature. One for example used heavily by ember-data. Do we have strategies in-place to provide public alternatives?

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

Yes, this PR exposes the new public API.

Instead of using this (which is private):

this.container.lookup('foo:bar')

Once this feature is enabled, you would do:

Ember.getOwner(this).lookup('foo:bar')

The only things exposed on the returned owner are the functions that we have already decided are public (during he ember-container-reform feature that landed in 2.1).

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

Also, please note: this.container still fully works with a deprecation (since it is SUPER heavily used).

…helpers.

Use a symbol to obscure the owner property.
…tainer` during `lookup`.

This change represents the final brick needed to wall off the
`Container` as fully private.

The Container no longer injects itself into every object that it looks
up. Instead, it uses the new `setOwner` helper to inject the "owner",
which can then be retrieved from any object using the new `getOwner`
helper.

The current net effect is that an app instance is injected into every
looked up object instead of that app instance's container. This
provides clean, public access to methods exposed by the app
instance's ContainerProxy and RegistryProxy methods. It also guarantees
that the only supported path to get to a Container or Registry is
through a proxied method. This guarantee is important because it allows
for owner-specific logic to be placed in proxy methods.

In the future, other classes, such as Engine (coming soon), may mix in
ContainerProxy and thus have the potential to be "owners".

This work is behind the `ember-container-inject-owner` flag. Without
this flag enabled, the Container will continue to inject itself directly
into objects that it instantiates (as `container`).
@dgeb dgeb force-pushed the inject-owner-not-container branch from 98bd567 to 9845a9a Compare November 4, 2015 02:24
@dgeb
Copy link
Member Author

dgeb commented Nov 4, 2015

Ok - I've applied the changes discussed above and all is green (except for the sauce build). Let me know if there's anything else I can do to help this land.

@rwjblue rwjblue changed the title [FEATURE ember-registry-container-reform] WIP: Inject owner instead of container during lookup. [FEATURE ember-container-inject-owner]: Inject owner instead of container during lookup. Nov 4, 2015
rwjblue added a commit that referenced this pull request Nov 4, 2015
[FEATURE ember-registry-container-reform] WIP: Inject `owner` instead of `container` during `lookup`.
@rwjblue rwjblue merged commit cb93860 into emberjs:master Nov 4, 2015
@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

#12555 is a checklist of items needed to be done for enabling the new feature.

@stefanpenner
Copy link
Member

@dgeb sounds good

@workmanw
Copy link

@rwjblue @dgeb et al. -- OMG. Thank you. I just replaced all of my usage of container.XXX() and registry.XXX() with getOwner (via the polyfill) and their new respective API calls. Great to finally have a public API for these things. 👍 🍻

@rwjblue
Copy link
Member

rwjblue commented Nov 20, 2015

Awesome!

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

Successfully merging this pull request may close these issues.

6 participants