7
votes

I am having an issue with trying to save many to many relationships in Ember.js using ember-data and rails. The association works fine on the ember side of things, however when I try to commit the transaction it will not include the list of new associations when submitting to rails. Any help would be much appreciated, I've been tearing out my hair trying to find an example application that does something this simple on github but I can't seem to find one.

Here is a dumbed down version of my Ember code:

App.Store = DS.Store.extend
  revision: 11
  adapter: DS.RESTAdapter.create
    url: '/api'

App.Router.map ->
  @resource 'rosters'
  @resource 'users'

App.User = DS.Model.extend
  rosters: DS.hasMany 'App.Roster'

App.Roster = DS.Model.extend
  users: DS.hasMany 'App.User'

App.RostersEditRoute = Ember.Route.extend
  setupController: (controller, model) ->
    controller.set 'users_list', App.User.find()

App.RostersEditView = Ember.View.extend
  userCheckbox: Em.Checkbox.extend
    checkedObserver: ( ->
      users = @get 'roster.users'
      if @get 'checked'
        users.addObject @get 'user'
      else
        users.removeObject @get 'user'
      @get('controller.store').commit()
    ).observes 'checked'

Edit form:

each user in users_list
  label.checkbox
    view view.userCheckbox rosterBinding="current_roster" userBinding="user" | #{user.full_name}

Rails Backend (Using Inherited Resources and Active Model Serializer gems):

class User < ActiveRecord::Base
  has_many :rosters_users
  has_many :rosters, through: :rosters_users
  accepts_nested_attributes_for :rosters
end

class RostersUser < ActiveRecord::Base
  belongs_to :user
  belongs_to :roster
end

class Roster < ActiveRecord::Base
  has_many :rosters_users
  has_many :users, through: :rosters_users
  accepts_nested_attributes_for :users
end

module Api
  class RostersController < BaseController
  end
end

module Api
  class UsersController < BaseController
  end
end

module Api
  class BaseController < ActionController::Base
    inherit_resources

    respond_to :json
    before_filter :default_json

    protected

    # Force JSON
    def default_json
      request.format = :json if params[:format].nil?
    end
  end
end

class UserSerializer < BaseSerializer
  has_many :rosters, embed: :ids
end

class RosterSerializer < BaseSerializer
  has_many :users, embed: :ids
end

So like I said, the association works fine on Ember but rails doesn't seem to be getting the new association data. When I check out the XHR tab in the web inspector, I see it only sent this:

Request Payload {"roster":{"created_at":"Thu, 21 Mar 2013 23:16:02 GMT","team_id":"1"}}

And I am returned with a 204 no content error because nothing changed.

2

2 Answers

17
votes

Despite the fact that it's possible to set up matching hasMany relationships, Ember Data doesn't actually support many to many relationships yet (see this issue). What you can do for now is decompose the relationship using a membership model.

App.User = DS.Model.extend
  rosterMemberships: DS.hasMany 'App.RosterMembership'      

App.RosterMembership = DS.Model.extend
  user: DS.belongsTo 'App.User'
  roster: DS.belongsTo 'App.Roster'

App.Roster = DS.Model.extend
  rosterMemberships: DS.hasMany 'App.RosterMembership'

Now you can use createRecord() and deleteRecord() with the membership model to add and delete relationships.

Unfortunately, in this example, it's not so easy to bind to the collection of rosters for a particular user. One work-around is as follows:

App.User = DS.Model.extend
  rosterMemberships: DS.hasMany 'App.RosterMembership'
  rosters: ( ->
    @get('rosterMemberships').getEach('user')
  ).property '[email protected]'

App.RosterMembership = DS.Model.extend
  user: DS.belongsTo 'App.User'
  roster: DS.belongsTo 'App.Roster'
  relationshipsLoaded: ( ->
    @get('user.isLoaded') and @get('roster.isLoaded')
  ).property 'user.isLoaded', 'roster.isLoaded'

If you bind to user.rosters, then your template should update when relationships are created or destroyed.

0
votes

Alternate solution:

App.Roster = DS.Model.extend
  users_map: DS.attr('array')
  users: DS.hasMany 'App.User'

  users_change: (->
    @set('users_map', @get('users').map((el) -> el.id).toArray())
  ).observes('users.@each')

to server in POST data will sending array of users ids