Active Model Serializers belongs_to - ruby-on-rails

This question pertains to AMS 0.8
I've got two models:
class Subject < ActiveRecord::Base
has_many :user_combinations
has_ancestry
end
class UserCombination < ActiveRecord::Base
belongs_to :stage
belongs_to :subject
belongs_to :user
end
And two serializers:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id
belongs_to :stage
belongs_to :subject
end
class SubjectSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :subjects
def include_subjects?
object.is_root?
end
def subjects
object.subtree
end
end
When a UserCombination is serialized, I want to embed the whole subtree of subjects.
When I try to use this setup I get this error:
undefined method `belongs_to' for UserCombinationSerializer:Class
I tried changing the UserCombinationSerializer to this:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id, :subject, :stage
end
In this case I get no errors, but the subject is serialized in the wrong way - not using the SubjectSerializer.
My questions:
Shouldn't I be able to use a belongs_to relation in the serializer?
If not - how can I get the wanted behaviour - embedding the subject tree using the SubjectSerializer?

This is not really elegant but it seems to be working :
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id, :stage_id, :subject_id
has_one :subject
end
I don't really like calling has_one whereas it's actually a belongs_to association :/
EDIT: Disregard my comment about has_one/belongs_to ambiguity, the doc is actually pretty clear about it: http://www.rubydoc.info/github/rails-api/active_model_serializers/frames

In Active Model Serializer 0-10-stable, belongs_to is now available.
belongs_to :author, serializer: AuthorPreviewSerializer
belongs_to :author, key: :writer
belongs_to :post
belongs_to :blog
def blog
Blog.new(id: 999, name: 'Custom blog')
end
https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#belongs_to
So you could do:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id
belongs_to :stage, serializer: StageSerializer
belongs_to :subject, serializer: SubjectSerializer
end

What if you try with something like this:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :subject,
:stage,
:id
def subject
SubjectSerializer.new(object.subject, { root: false } )
end
def stage
StageSerializer.new(object.stage, { root: false } )
end
end

Related

Rails fires N+1 queries for polymorphic associations

I am using Rails 5.0.1, and am really confused about the following problem. I have few models with polymorphic associations.
class Container < ApplicationRecord
has_many :steps, as: 'parent', dependent: :destroy
end
class Step < ApplicationRecord
belongs_to :parent, polymorphic: true
belongs_to :implementation, polymorphic: true
end
class FirstStep < ApplicationRecord
has_one :step, as: 'implementation'
has_many :params, dependent: :destroy
end
class SecondStep < ApplicationRecord
has_one :step, as: 'implementation'
has_many :headers, dependent: :destroy
end
class Param < ApplicationRecord
belongs_to :first_step
end
class Header < ApplicationRecord
belongs_to :second_step
end
A step associates to an implementation (FirstStep, SecondStep). In addition to it, a container can also be a step's implementation. I'm using Active Model Serializers to serialize the model info to JSON. Following is the related code to serializers.
class StepSerializer < ActiveModel::Serializer
attributes :id, :implementation_type, :implementation_id, :active, :position
belongs_to :implementation
end
class FirstStepSerializer < ActiveModel::Serializer
attributes :name, :params_attributes
def params_attributes
object.params.map { |p| ParamSerializer.new(p).attributes }
end
end
class SecondStepSerializer < ActiveModel::Serializer
attributes :id, :title, :headers_attributes
def headers_attributes
object.headers.map { |p| HeaderSerializer.new(p).attributes }
end
end
class ParamSerializer < ActiveModel::Serializer
attributes :id
end
class HeaderSerializer < ActiveModel::Serializer
attributes :id
end
The implementations of step model can have different attributes, as specified in the model. The problem is, when I write
render json: container.steps
it fires N+1 queries to get the results. How do I optimize it?
Edit 1
Inspired by this answer, I tried to separate objects by their implementation_type, and it worked. What I did was:
# my controller action
def index
steps = []
steps += container.steps.where(implementation_type: 'FirstStep').includes(implementation: [:params])
steps += container.steps.where(implementation_type: 'SecondStep').includes(implementation: [:headers])
render json: steps
end
This prevented the N+1 queries for fetching params and headers, but it doesn't work if a step is a container.
Change your FirstStepSerializer and SecondStepSerializer serializer like following
class FirstStepSerializer < ActiveModel::Serializer
attributes :name
has_many :params, :serializer => ParamSerializer
end
class SecondStepSerializer < ActiveModel::Serializer
attributes :id, :title
has_many :headers, :serializer => HeaderSerializer
end
This might help

Active model serializer 0.9.4 stack too deep error

Hi the serailizer in my application looks like this
class ProgressSerializer < ActiveModel::Serializer
attributes :id
has_one :race
end
class RaceSerializer < ActiveModel::Serializer
attributes :id
has_many :progresses
end
The has_one and has_many together gives me error stack level too deep.
Things I tried.
config/initializers/active_model_intializer.rb
ActiveModel::Serializer.setup do |config|
config.embed = :ids
config.include = true
end
Second thing I tried
class RaceSerializer < ActiveModel::Serializer
attributes :id
has_many :progresses , :serializer => ProgressSerializer
end
class ProgressSerializer < ActiveModel::Serializer
attributes :id
has_one :race , :serializer => RaceSerializer
end
Models
class Progress < ActiveRecord::Base
belongs_to :race
end
class Race < ActiveRecord::Base
has_many :progresses
end
Can you share your Model.rb files?
If Progress is main then Races underneath, this one should work.
class ProgressSerializer < ActiveModel::Serializer
attributes :id, :races
has_many :races
end
class RaceSerializer < ActiveModel::Serializer
attributes :id
end
then try to delete has_many :progresses and add has_one :race to Race with attributes :id, :race

using ActiveModelSerializers and having children rendered with specific Seriazlier

I have the following relationships (using RoR 3.2.13 and ancestry 2.0.0) and REALLY need some help in configuring how the serializer renders with the MenuHeaderSerializer:
class Menu < ActiveRecord::Base
has_many :menu_headers
end
class MenuHeader < ActiveRecord::Base
has_ancestry # the nested relationship
has_many :items
belongs_to :menu
end
class Item < ActiveRecord::Base
belongs_to :menu_header
end
My serializers are pretty explanatory and look like this:
class MenuSerializer < ActiveModel::Serializer
attributes :id, :name, :menu_headers
has_many :menu_headers
end
class MenuHeaderSerializer < ActiveModel::Serializer
attributes :id, :name, :children # <- this needs to be called and wrapped in a MenuHeaderSerializer; it basically just dumps eveything like to_json
#has_many :items
end
So my call to children should return children using MenuHeaderSerializer. Does that make sense?
I have tried what I think are all of the variations like the following:
class MenuHeaderSerializer < ActiveModel::Serializer
attributes :id, :name, :sub # :children #, :sub
def sub
MenuHeaderSerializer.new(children)
#object.children
end
or trying to force children into using the MenuHeaderSerializer but am just at a loss. Any help would be appreciated.
Perhaps something like:
attributes :id, :name, :children serializer: MenuHeaderSerializer # doesn't work
thx
not sure about Ancestery but you can do
has_many :children, each_serializer: MenuHeaderSerializer

Inheritance with Rails

So I have a class Category:
class Category < ActiveRecord::Base
attr_accessible :category_id, :name
end
and a class UserCategory.
class UserCategory < ActiveRecord::Base
attr_accessible :user_id, :category_id, usercategory_id
self.table_name = 'contractor_categories'
self.primary_key = :nid
belongs_to :user, class_name: "User", foreign_key: "user_id",
:inverse_of => :categories
end
So when I do User.last.categories.first.name
I would like to get the name of the first associated category.
How should I do that without doing something like: Category.find(User.last.categories.first.category_id).name
Edit: I'm currently doing:
def name
Category.find(self.category_id).name
end
But I'm pretty sure there is a better way to do it.
In your model you can add an association
belongs_to :category
and then your name method would look like this
def name
category.name
end
or you could do User.last.categories.first.category.name which would save you creating a name method, but I'm not sure why you would prefer that.

Rails 3 - Custom Validation

I'm somewhat confused by my options for custom validations in Rails 3, and i'm hoping that someone can point me in the direction of a resource that can help with my current issue.
I currently have 3 models, vehicle, trim and model_year. They look as follows:
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
end
class ModelYear < ActiveRecord::Base
attr_accessible :value
has_many :model_year_trims
has_many :trims, :through => :model_year_trims
end
class Trim < ActiveRecord::Base
attr_accessible :value, :model_id
has_many :vehicles
has_many :model_year_trims
has_many :model_years, :through => :model_year_trims
end
My query is this - when I am creating a vehicle, how can I ensure that the model_year that is selected is valid for the trim (and vice versa)?
you can use custom validation method, as described here:
class Vehicle < ActiveRecord::Base
validate :model_year_valid_for_trim
def model_year_valid_for_trim
if #some validation code for model year and trim
errors.add(:model_years, "some error")
end
end
end
You can use the ActiveModel::Validator class like so:
class VehicleValidator < ActiveModel::Validator
def validate(record)
return true if # custom model_year and trip logic
record.errors[:base] << # error message
end
end
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
include ActiveModel::Validations
validates_with VehicleValidator
end

Resources