If I have the following models in Rails, how would I represent this in Ember/Ember Data?
class Attachment < ActiveRecord::Base
belongs_to :user
belongs_to :attachable, polymorphic: true
end
class Profile < ActiveRecord::Base
belongs_to :user
has_one :photo, class_name: 'Attachment', as: :attachable
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :attachments, as: :attachable
end
References I've found are the relevant ember-data pull request, the ember-data tests for polymorphic relationships, and this related question but it's difficult to work out a canonical example from them.
I now use two different ways of working with rails-style "polymorphic" models. I have updated the code below to show both uses.
Attachment
model: This is "polymorphic" on the Rails side but is always "embedded" on the Ember side. The reason for this is that I currently only need to save/update attachments along with their associated model.
Comment
model: This is polymorphic on both the Rails side and the Ember side.
I have also included code for the Post
model as it can have multiple attachments and multiple comments.
Rails code:
class Attachment < ActiveRecord::Base
belongs_to :user
belongs_to :attachable, polymorphic: true
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :attachments, as: :attachable
has_many :comments, as: :commentable
end
class ApplicationSerializer < ActiveModel::Serializer
embed :ids, include: true
end
class AttachmentSerializer < ApplicationSerializer
attributes :id, :url, :errors
has_many :comments
end
class CommentSerializer < ApplicationSerializer
attributes :id, :body, :created_at, :commentable_id, :commentable_type
has_one :user
end
class PostSerializer < ApplicationSerializer
attributes :id, :title, :body, :posted_at, :errors
has_one :user
has_many :attachments, embed: :objects, include: true
has_many :comments
end
class Api::V1::PostsController < Api::V1::BaseController
before_filter :auth_only!, only: :create
def create
# clean / capture ember-data supplied arguments
params[:post].delete(:user_id)
attachments_params = params[:post].delete(:attachments)
@post = current_user.posts.new(params[:post])
process_attachments(attachments_params)
if @post.save
render json: @post, status: 201
else
warden.custom_failure!
render json: @post, status: 422
end
end
protected
def process_attachments(attachments_params)
return unless attachments_params.present?
attachments_params.each do |attachment_params|
# ignore ember-data's additional keys
attachment_params.delete(:created_at)
attachment_params.delete(:user_id)
attachment = @post.attachments.new(attachment_params)
attachment.user = current_user
end
end
end
Ember code:
DS.RESTAdapter.configure 'App.Post',
alias: 'Post'
DS.RESTAdapter.map 'App.Post',
attachments: { embedded: 'always' }
App.Store = DS.Store.extend
adapter: DS.RESTAdapter.create
namespace: 'api/v1'
App.Comment = App.Model.extend
user: DS.belongsTo('App.User')
commentable: DS.belongsTo('App.Commentable', { polymorphic: true })
body: DS.attr('string')
createdAt: DS.attr('date')
App.Commentable = App.Model.extend
comments: DS.hasMany('App.Comment')
App.Attachment = App.Commentable.extend
user: DS.belongsTo('App.User')
url: DS.attr('string')
App.Post = App.Commentable.extend
user: DS.belongsTo('App.User')
attachments: DS.hasMany('App.Attachment')
title: DS.attr('string')
body: DS.attr('string')
postedAt: DS.attr('date')
App.PostFormOverlayController = App.OverlayController.extend
# 'files' attribute is set by a widget that wraps the filepicker.io JS
updateAttachments: (->
attachments = @get('attachments')
attachments.clear()
@get('files').forEach (file) =>
attachment = App.Attachment.createRecord({fpFile: file})
attachments.addObject(attachment)
).observes('files')
App.CommentNewController = App.ObjectController.extend
# this should be instantiated with it's model set to the commentable
# item. eg. `{{render 'comment/new' content}}`
save: ->
transaction = @get('store').transaction()
comment = transaction.createRecord App.Comment,
body: @get('body')
commentable: @get('model')
comment.one 'didCreate', @, ->
@set('body', null)
transaction.commit()
Unfortunately my Rails code has become a little polluted with ember-data specific oddities as it tries to send back all the attributes that are defined on the models. (Note: There is an open proposal for read-only attributes that would solve the params pollution issue)
If anyone knows a better way to approach any of the above please let me know!
NB: I'm a little concerned that having models extend from App.Commentable
will prevent me from having multiple polymorphic attachments on a model, I may need to look for a different way of handling that.
The Comment part of Kevin Ansfield's answer (didn't use the Post part) works as of Ember 1.3.0-beta / Ember Data 1.0.0-beta.4 except for the rails serializer, which should now be:
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body, :created_at, :commentable_id, :commentable_type
has_one :user
def attributes
data = super
data[:commentable] = {id: data[:commentable_id], type: data[:commentable_type]}
data.delete(:commentable_type)
data.delete(:commentable_id)
data
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With