spine / spine

Lightweight MVC library for building JavaScript applications

Home Page:http://spine.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Assignment of Spine.Stack controllers to Spine.Manager instance leads to conflicts

vail130 opened this issue · comments

If an application has a controller with key "manager" in its Stack, then the instantiation of the Stack overwrites the Stack instance's "manager" property with an instance of the controller named "manager" managed by the Stack.

Stack example with conflict:

class App.Stack extends Spine.Stack
  controllers:
    manager: App.Manager

Here's where it happens (manager.coffee lines 64 - 68):

    @manager = new Spine.Manager

    for key, value of @controllers
      @[key] = new value(stack: @)
      @add(@[key])

Here's a link to the code in the repo:
https://github.com/maccman/spine/blob/master/src/manager.coffee#L67

Looking for opinions, do we document this as a GOTCHA or add some code to dodge it?

A couple options I can think of:

  • Find/replace @manager to @_manager or something.
  • Throw an error to make "manager" a reserved key.

What about keeping all controller instances in an object in the context object rather than directly in the context object? For example:

    for key, value of @controllers
      @instances[key] = new value(stack: @)
      @add(@instances[key])

...

    @instances[@default].active() if @default

Good call @vail130

The basic usage of Stacks allows for people to use the stack instance as a way to access the created controllers. I think adding another variable for the collection would potentially break code for a lot of users out there. Here's the example from the spine docs...

class PostsShow extends Spine.Controller
class PostsEdit extends Spine.Controller

class Posts extends Spine.Stack
  controllers:
    show: PostsShow
    edit: PostsEdit

posts = new Posts
posts.show # <PostsShow>

posts.show.active()
posts.show.isActive() # true
posts.edit.isActive() # false

This would change it to..

posts.instances.show.active()
posts.instances.show.isActive()
posts.instances.edit.isActive()

I think changing the @manager variable to another name name like @_manager might be the easier approach. I'm not sure if there are actual use cases of using the manager property of a stack instance?

Good point @cengebretson :)

I've only been using stacks with the built-in routing so I didn't think of this use case...

I'm not sure if there are actual use cases of using the manager property of a stack instance?

I do this in one of my stacks to bind to the 'change' event:

constructor: ->
  super
  @manager.bind 'change', @changed

So far it looks like making @manager a reserved key might be the most backward-compatible solution...?

Yeah, @cengebretson definitely makes a good point. In that case, there are a bunch of other instance variables and methods in Spine.Stack that need to be reserved:

manager, controllers, add, default, append, routes, route

New link for convenience: https://github.com/spine/spine/blob/master/src/manager.coffee#L67

Instead of hard-coding reserved keys, how about something like this?:

    @manager = new Spine.Manager

    for key, value of @controllers
      throw Error "'@#{ key }' already assigned - choose a different name" if @[key]?
      @[key] = new value(stack: @)
      @add(@[key])

seems like we could solve #232 with this as well

@adambiggs That looks good; it certainly would have saved me a headache the first time I ran into the issue.

I'll send a pull request for this tomorrow.

I think throwing an error is fairly easy/good solution. I can see where it would be a tough error to catch for somebody new to spine and they accidentally use one of the properties of a class as a controller name.

I think a similar situation comes up with models and defining attributes. When the model is converted to json the @attributes method is called which will loop over values in the constructor.attributes array and call @[key]. If the user has attribute names like url or save I could see it causing some headaches or interesting side effects.

  attributes: ->
    result = {}
    for key in @constructor.attributes when key of this
      if typeof @[key] is 'function'
        result[key] = @[key]()
      else
        result[key] = @[key]
    result.id = @id if @id
    result

I see there is one issue #324 about this type of behavior coming up, not sure how often this has come up for others. Anyone have any opinions if this is something worthwhile to catch and at the lest, throw an Error message.