How to fill the related model on a many 2 many association? - ember.js

Using ember-data, I have this two models:
App.Post = DS.Model.extend
title: DS.attr "string"
body: DS.attr "string"
categories: DS.hasMany "App.Category"
App.Category = DS.Model.extend
name: DS.attr "string"
posts: DS.hasMany 'App.Post'
and this serialization:
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :categories
embed :ids, include: true
end
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name
end
When I ask for posts, I get the expected JSON and I can access a post's categories without problem, but if I request the categories (that I think that they are cached) I get the categories without any relation to posts. It doesn't even try to make a get request (that wouldn't work either).
So, shouldn't categories have their posts relations filled up?
Not sure if I miss something in ember or AMS (which I think that the category serializer should know that has many posts)

Well, after struggling with some guys at IRC I ended with this solution, which I hope it will be helpful for others and maybe improved.
The problem was that the categories doesn't had any post reference, So if you ask for Posts, you get the posts with categories, but the categories themselves knows nothing about posts.
If I try to do something like:
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :posts
embed :ids, include: true
end
it will explode because they are referencing each other and you get a "Too deep level" or something like that.
You can do something like:
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :posts, embed: :objects
end
and it will work, but the result JSON will be huge because when you request posts, you get every post + every comment and inside them, every post that have that category... No love
So what's the idea? Having something like:
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :categories
embed :ids, include: true
end
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :posts, embed: :ids
end
For every post you get the categories_ids and for every category you reference, you only get its attributes and the ids (not the entire objects) of the posts that belongs to that category.
But what happens when you go to '/#/categories' and you didn't loaded the posts yet? Well, since your CategorySerializer doesn't serialize any post, you won't get anything.
So since you can't do cross references between serializers, I ended with 4 serializers. 2 for posts and their categories and 2 for categories and their posts (so, doesn't matter if you load the posts first or the categories):
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :categories, serializer: CategoriesForPostSerializer
embed :ids, include: true
end
class CategoriesForPostSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :posts, embed: :ids
end
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :posts, serializer: PostsForCategorySerializer
embed :ids, include: true
end
class PostsForCategorySerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :categories, embed: :ids
end
This does the trick. But since I'm new with Ember and I'm not a crack of JSON design. If someones knows a simple way or maybe doing some embedded (always or load in the adapter, which I don't understand yet), please comment :)

Related

Polymorphic Relationships in ActiveAdmin Forms in Rails 4

I have an Active Admin form where I need to be able to update/add a polymorphic relationship to an object. I can get the form to display it, but it won't update the table with the polymorphic relationship. The models are Category and TargetArea and they both have Tags. Here is the model setup:
#category.rb
class Category < ActiveRecord::Base
has_many :tags, as: :taggable
accepts_nested_attributes_for :tags
end
#tag.rb
class Tag < ActiveRecord::Base
belongs_to :category
belongs_to :target_area
belongs_to :taggable, polymorphic: true
accepts_nested_attributes_for :taggable
end
#Active Admin Form for Categories
permit_params :name, tags: []
form do |f|
f.actions
f.inputs 'Categories' do
f.input :name
f.inputs do
f.has_many :tags do |t|
t.input :name
end
end
end
f.actions
end
I want to be able to update and create new categories and add tags to the category in the form. I can't seem to find an example that does the same thing and this just doesn't seem to work.

Getting a count value between many different models and associations

I've been wondering what is the easiest way of getting a count value between several different models and associations.
I want to have something like this in my view
shop.receipts.articles.complaints.complaint_reviews.count
Here are my models and associations between them:
class Shop < ActiveRecord::Base
has_many :receipts
end
class Receipt < ActiveRecord::Base
has_many :articles, dependent: :destroy
belongs_to :shop
accepts_nested_attributes_for :articles, allow_destroy:true, :reject_if => :all_blank
end
class Article < ActiveRecord::Base
belongs_to :receipt
has_one :complaint
end
class Complaint < ActiveRecord::Base
belongs_to :article
has_many :complaint_reviews
end
class ComplaintReview < ActiveRecord::Base
belongs_to :complaint
end
I'm inferring that you want the count of all complaint_reviews that are associated with a particular shop.
In that case, the following is what you need:
shop = # get shop according to your criteria
ComplaintReview.
joins(complaint: {article: {receipt: :shop}}).
where(shops: {id: shop.id}).
count
I suppose you could save the shop joins, by applying the condition on the shop_id column of receipts; like so:
ComplaintReview.
joins(complaint: {article: :receipt}).
where(receipts: {shop_id: shop.id}).
count
Result should be the same for both if all receipts have a shop associated. But I'd opt for the first method.
The thing to keep in mind here is to 'start' with the model of which you ultimately want the count of.
Also, had there been any one-to-many relationships, you would have grouped the results by "complain_reviews.id" and then performed the count.
Ok so thanks to the code above I managed to come up with a working solution:
#shops_controller.rb:
def show
#count = ComplaintReview.
joins(complaint: {article: {receipt: :shop}}).
where(shops: {id: #shop.id}).
count
respond_with(#shop)
end
#shops/show.html.erb:
<%= #count %>
Thanks a lot for the help.

Overriding the embed_key in Active Model Serializer

I have a serializer with some has_many associations where I've modified the embed_key.
I'm trying to have the IDs for my model be like path-name rather than 12. The code below is making that happen:
class SymbolSerializer < ActiveModel::Serializer
attributes :id,
:name,
:parent_id,
:rails_id
embed :ids, include: true
has_many :ancestors, embed_key: :symbol_path, serializer: SimplifiedSymbolSerializer
has_many :children, embed_key: :symbol_path, serializer: SimplifiedSymbolSerializer
has_one :parent, embed_key: :symbol_path, serializer: SimplifiedSymbolSerializer
has_many :siblings, embed_key: :symbol_path, serializer: SimplifiedSymbolSerializer
def id
object.symbol_path
end
def parent_id
object.id
end
def rails_id
object.id
end
end
The problem is that I'd like to still have the actual id of the parent object (Which I've tried to do in my parent_id method ^^).
Right now the overridden method still returns the :symbol_path key rather than the id.
Anyone know how to address this?
The simplest way for me to address this was to just use a different naming convention for the key I wanted to pass.
So all I had to do was add this method/attribute in there too:
def parent_rails_id
object.parent.id
end

Rails collection_select passing id

I've read a bunch of questions but none of them are helping me with this problem. I am trying to create a form to make new forums but cannot get them to use the right category id.
<%= f.collection_select :category_id, Category.all, :id, :name %>
This creates a new forum but the id is not the category id from the drop down list. Here is the forums model
def new
#forum = Forum.new
end
def create
#forum = Forum.new(forum_params)
if #forum.save
redirect_to root_url
else
render 'new'
end
end
private
def forum_params
params.require(:forum).permit(:category_id, :name, :description )
end
end
Not quite sure what I am doing wrong here. Is it something to do with the foreign key? Any help would really be appreciated.
UPDATE
Forum Model
class Forum < ActiveRecord::Base
belongs_to :category
has_many :topics, dependent: :destroy
end
Category Model
class Category < ActiveRecord::Base
has_many :forums, dependent: :destroy
end
There is basically no category_id in the Forum model.
Here is a couple of things you can do to troubleshoot this. Run:
rails dbconsole
.schema
Check to see if you have category_id or not. If not, create a new migration for this.
Your initial code is correct. For some reason, I misread that your collection_select was already bound to the model.

Rails-style polymorphic model in ember-data

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