Over the past few hours I’ve been fighting against ember-data to save a many-to-many relationship with ActiveModelSerializer to my Rails 4 app. I’ve gotten it to work after browsing through a number of other peoples code and seeing what they have done. This issue has been talked about a ton it seems, but I still had to struggle a bit to get my case to work. So I thought I’d share what I ended up with to see if it’s correct and possibly to help others who’re having a difficult time.
Here is the code I ended up with:
store.js
Sis.ApplicationAdapter = DS.ActiveModelAdapter.extend({});
Sis.ApplicationSerializer = DS.ActiveModelSerializer.extend({
// This is solution for embedding entire objects into save post.
// I couldn't get this solution to save the relationships on the rails side due to strong parameters,
// so the below solution of including ids is what I went with.
// 'attrs': {
// 'students': { embedded: 'always'},
// },
// Below is solution to only include ids for saving hasMany relationships
// I adapted this from here: http://dbushell.com/2013/04/25/ember-data-and-mongodb/
serializeHasMany: function(record, json, relationship) {
var key = relationship.key,
jsonKey = Ember.String.singularize(key) + '_ids';
json[jsonKey] = [];
record.get(key).forEach(function(item) {
json[jsonKey].push(item.get('id'));
});
return json;
}
});
new_task_controller.js
Sis.NewTaskController = Ember.ObjectController.extend({
needs: ["requiredTasks", "project"],
isShowingUsers: false,
actions: {
createNewSubtask: function(requiredTask) {
var content = this.get('content'),
projectController = this.get('controllers.project'),
students = content.get('students');
// TODO: I'm not sure if this is needed or if ember-data will auto-associate the
// many-to-many relationship on save. Keeping for now.
students.forEach(function(student, index){
student.get('tasks').addObject(content);
});
// Set the associated project, projectGroup, and parentTask
content.set('project', projectController.get('model'));
content.set('projectGroup', projectController.get('projectGroup'));
content.set('parentTask', requiredTask);
// Save the new subtask
content.save();
},
}
});
new_task_view.js
Sis.NewTaskView = Ember.View.extend({
content: function() {
return this.controller.get('content');
}.property('this.controller.content'),
didInsertElement: function() {
$('.datepicker').datepicker();
},
// Overrides Ember.Checkbox to add the checked student to this content
userCheckbox: Em.Checkbox.extend({
checkedObserver: function(){
var content = this.get('controller').get('content')
if(this.get('checked')) {
content.get('students').pushObject(this.get('student'));
} else {
content.get('students').removeObject(this.get('student'));
}
}.observes('checked')
}),
actions: {
addNewTask: function() {
var content = this.get('content'),
requiredTask = this.get('parentView').get('controller').get('model');
this.get('controller').send('createNewSubtask', requiredTask);
},
toggleUserSelect: function() {
var controller = this.get('controller');
controller.toggleProperty('isShowingUsers');
if (controller.get('isShowingUsers')) {
Ember.run.next(this, function() {
// Setup the click handler to set isShowingUsers to false
$('body').one('click', function() {
controller.toggleProperty('isShowingUsers');
});
// Make sure the select-users-container doesn't trigger the above event
$(".select-users-container").click(function(e) {
e.stopPropagation();
return true;
});
});
}
}
},
})
newTask.handlebars
<form role="form" class="form-horizontal">
<div class="form-group col-lg-10">
{{view Ember.TextField valueBinding="title" placeholder="Title" required="true" class="form-control"}}
</div>
<div class="form-group col-lg-10">
<label for="subtask-due-date" class="col-lg-2 control-label">Due Date:</label>
<div id="new-task-date" class="datepicker input-append date col-lg-2" data-date-format="dd-mm-yyyy">
{{view Sis.DateField valueBinding="dueDate" class="form-cntrol" size="15"}}
<span class="add-on"><i class="icon-th"></i></span>
</div>
<label for="subtask-users" class="col-lg-3 control-label">Assigned to:</label>
<a {{action toggleUserSelect target="view"}} {{bind-attr class="isShowingUsers:hide"}}>Select</a>
{{#if isShowingUsers}}
<div class="select-users-container">
<ul id="select-users-list">
{{#each student in controllers.requiredTasks.selectableStudents}}
{{view view.userCheckbox studentBinding="student" class="user-select-checkbox"}}
{{student.fullName}}
{{/each}}
</ul>
</div>
{{/if}}
</div>
<div class="col-lg-3">
<button {{action addNewTask target="view"}} type="button" class="btn btn-default">Add Task</button>
<button {{action cancelNewTask}} type="button" class="btn btn-default">Cancel</button>
</div>
</form>
Relevant models
Sis.Student = Sis.User.extend({
projectGroups: DS.hasMany('projectGroup'),
tasks: DS.hasMany('subtask')
});
Sis.Subtask = Sis.Task.extend({
projectGroup: DS.belongsTo('projectGroup'),
parentTask: DS.belongsTo('requiredTask'),
students: DS.hasMany('student')
});
Rails 4 strong_parameters to allow for saving ‘student_ids’ inside subtasks_controller.rb
def subtask_params
params.require(:subtask).permit(:title, :type, :project_group_id,
:parent_task_id, :is_completed, {:student_ids => []})
end
Any comments or suggestions welcome. This is the first time I’ve had to dig a bit deeper into ember/ember-data to get something working so I’m interested in getting some feedback. Thanks.