Search code examples
node.jscoffeescripttowerjs

How do I use One-to-Many Relationships in Tower.js?


So, I've been generating different scaffolds trying to use the One-to-Many relationship features of Tower, but I can't figure out how to actually link up the related instances to make use of the relationship. For instance, my most recent scaffolds were generated like this:

tower generate scaffold User email:string firstName:string lastName:string hasMany:posts

tower generate scaffold Post title:string body:text userId:integer belongsTo:user

Now, in rails, that userId field, on the post model, would form a connection with the user sharing that id, and then you could use that to access the relationship. But here it doesn't seem to do anything. Trying to use any of the code from here: https://github.com/viatropos/tower/wiki/1-n just gives me errors.

In the tower console, I was able to create an example user and post (I can also do this easily enough by running the server and using the forms on the web page), like so:

tower> user = new App.User

tower> user.attributes = { email: "[email protected]", firstName: "bill", lastName: "billiams" }

tower> post = new App.Post

tower> post.attributes = { title: "A Post", body: "This is a short post.", userId: "4fbf23224503fe670e000006" }

And these instances are persisted to the database, but when I try code like:

tower> user.get('posts').exists()

I get "TypeError: Cannot call method 'exists' of undefined". Similarly, calling:

tower> user.get('posts').create(title: 'Berlin never sleeps.')

produces "TypeError: Cannot call method 'create' of undefined". The same sort of thing happens if I try anything like this in the models or controllers. I'm really stuck here, and have been trying for several days to figure out how this works, but I don't know where else to look. If anyone has example code I could peek at, that would be awesome, otherwise an explanation of sorts would be helpful also. Thanks.

[Edit: My working example, along with detailed README explaining the steps, can be found here: https://github.com/edubkendo/demoApp ]


Solution

  • Right now relations are accessed through methods rather than getters:

    user.posts().all (error, posts) -> console.log(posts)
    

    the posts() method returns a Tower.Model.Relation.HasMany.Scope object, which is a subclass of Tower.Model.Scope. The "scope" is what allows you to do queries on the database:

    user.posts().where(title: /a/).asc('createdAt').all()
    

    Just for reference, here's how the relations are built. First, when you do something like:

    class App.User extends Tower.Model
      @hasMany 'posts'
    

    that @hasMany 'posts' goes to /tower/model/relations.coffee#L50, which constructs a Tower.Model.Relation.HasMany object, which extends Tower.Model.Relation. The Tower.Model.Relation class defines the association methods/getters on the owner class, the App.User: /tower/model/relation.coffee#L85.

    When you do user.posts(), it constructs a new Tower.Model.Relation.HasMany.Scope: /tower/model/relations.coffee#L85, which gives you all the finder/persistence methods, the same as doing App.User.asc('createdAt').all(). The relation subclasses Tower.Model.Scope in order to customize find/create/update/delete methods for the association, that's pretty much it.

    Last thing, the api is going to be changed within the next few weeks as we normalize it with the field methods (i.e. user.get('email')). So soon, it will look like this:

    user.get('posts').all()
    user.posts.all() # a getter, if getter/setter support is available in the browser, and it is available in node.js
    

    But for now, use this:

    user.posts().all (error, posts) ->
    

    Hope that helps.