Rails serialize heir class - ruby-on-rails

How can I specify that I want to serialize additional attributes of classes that inherit from an abstract one ?
(I am using ActiveModel::Serializer)
EDIT : I have associations/references that I need to keep in my serializer, not just attributes
class AbstractClass
[attributes]
**has_one :reference** # Added after Edit
end
class Foo < AbstractClass
field :some_special_foo_field
end
class Bar < AbstractClass
field :some_other_bar_field
end
Class Baz
has_many :abstract_class
end
I want a JSON that would look like
{:baz => [
{ id => "foo_1_ID",
some_special_foo_field => "something"
}, {
id => "bar_1_ID,
some_other_bar_field => "Somewhere"
}]
Somewhere in my controller I am doing:
#bazz = Baz.all
respond_to do |format|
format.json { render json: #bazz}
end
class BazSerializer < ActiveModel::Serializer
attributes [what I need]
has_many :constraints, serializer: AbstractClassShortSerializer, embed: :objects
end
class AbstractClassSerializer
**has_one :reference, serializer: SomethingSerializer, embed: :objects** # Added after Edit
attributes :[attributes I want to keep]
end
Now Problem : depending on whether the objects in #bazz are Foo or Bar, I'd like to have their special attributes in my JSON, but I currently can't with this code
This question is somewhat linked to this one Simulating constraints and sub-constraints

Make it simple:
do one serializer for foos and one serializer for bars
dont use has_many in your baz serializer
Do this way:
class AbstractClassShortSerializer < ActiveModel::Serializer
attributes common_attributes_here
end
class BarSerializer < AbstractClassShortSerializer
end
class BazSerializer < ActiveModel::Serializer
attributes [what I need]
attributes :constraints
def constraints
object.constraints.map do |constraint|
if constraint.foo?
FooSerializer
elsif constraint.baz?
BazSerializer
else
AbstractClassShortSerializer
end.new(constraint, scope: scope).attributes
end
end
end

Related

JSON with full hierarchy in Rails

I have an hierarchical structure in my app:
Environment has Containers
Container has Items
Item has expression
so my Model code looks like:
class Environment < ActiveRecord::Base
has_many :containers, :dependent => :destroy
def as_json(options = {})
super(options.merge(include: :containers))
end
end
class Container < ActiveRecord::Base
has_many :items, :dependent => :destroy
belongs_to :environment
def as_json(options = {})
super(options.merge(include: :items))
end
end
class Item < ActiveRecord::Base
has_many :expressions, :dependent => :destroy
belongs_to :container
def as_json(options = {})
super(options.merge(include: :expressions))
end
end
class Expression < ActiveRecord::Base
belongs_to :item
def as_json(options = {})
super()
end
end
In a regular get of a record I usually need only one hierarchy below the desired record, that's why in the as_json I merge only one hierarchy down (get Environment will return a collection of containers but those containers will not have Items)
My Question:
Now what I need is to add a method to the controller that allows full hierarchy response i.e. GET /environment/getFullHierarchy/3 will return: environment with id=3 with all its containers and for every container all it's Items & for every Item all it's expressions. without breaking the current as_json
I'm kinda new to Rails, wirking with Rails 4.2.6 & don't know where to start - can anyone help?
Sure it goes something like this hopefully you get the idea.
EnvironmentSerializer.new(environment) to get the hierarchy json.
lets say environments table has columns environment_attr1 , environment_attr2
class EnvironmentSerializer < ActiveModel::Serializer
attributes :environment_attr1, :environment_attr2 , :containers
# This method is called if you have defined a
# attribute above which is not a direct value like for
# a rectancle serializer will have attributes length and width
# but you can add a attribute area as a symbol and define a method
# area which returns object.length * object.width
def containers
ActiveModel::ArraySerializer.new(object.containers,
each_serializer: ContainerSerializer)
end
end
class ContainerSerializer < ActiveModel::Serializer
attributes :container_attr1, :container_attr2 , :items
def items
ActiveModel::ArraySerializer.new(object.items,
each_serializer: ItemSerializer)
end
end
class ItemSerializer < ActiveModel::Serializer
...
end
class ExpressionSerializer < ActiveModel::Serializer
...
end

ActiveModel Serializer with has_and_belongs_to_many

I have a model called Event. An Event has_and_belongs_to_many :event_sub_categories and a EventSubCategory has_and_belongs_to_many :events. I have the following action:
def index
#events = Event.where(begins_at: DateTime.now.beginning_of_day..1.week.from_now).group_by{|e| e.begins_at.beginning_of_day}.to_a.to_json
render json: #events
end
The action returns the data exactly as needed except for one problem, it doesn't have subcategories. I need the json to contain the subcategories. I tried making the following ActiveModel Serializer:
class EventSerializer < ActiveModel::Serializer
attributes :id, :name, :event_sub_categories
end
but the serializer above doesn't change the json at all. How do I fix this?
try
class EventSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :event_sub_categories
end
Try this:
1- In your controller modify the query in a way it includes the event_sub_categories:
def index
#events = Event.includes(:event_sub_categories).where(begins_at: DateTime.now.beginning_of_day..1.week.from_now).group_by{|e| e.begins_at.beginning_of_day}.to_a.to_json
render json: #events
end
2- create a Serializer for EventSubCategory model
3- in your Event serializer create the method event_sub_categories
class EventSerializer < ActiveModel::Serializer
attributes :id, :name, :event_sub_categories
def event_sub_categories
object.event_sub_categories.map do |sub_category|
EventSubCategorySerializer.new(sub_category)
end
end
end

has_ancestry and active model serializers

I have a model Category
class Category < ActiveRecord::Base
attributes :id, :name, :order, :x, :y, :z
has_ancestry
end
In my controller, I can use the following to get the whole tree as JSON
Category.first.subtree.arrange_serializable
But this returns all DB attributes such as created_at or id
I wanted to use the active model serializer to shape my output without losing the tree structure.
class CategorySerializer < ActiveModel::Serializer
# Children is the subtree provided by ancestry
attributes :name, :x, :children
end
Controller
class CategoryController < ActionController::Base
def index
category = Category.first
render :json => category
end
end
The code above will only show the first sub level, but not the children of the children.
Any help appreciated
To use arrangement, we need to pass an additional parameter to serializer, you can do it this way:
category.subtree.arrange_serializable do |parent, children|
CategorySerializer.new(parent, scope: { children: children })
end
And here's how you can take that parameter in serializer:
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name, :order, :children
def children
scope.to_h[:children]
end
end
You may also want to take a look at this test to have a better understanding how arrange_serializable works.
In AMS 10.x (master branch) we can support external parameters in such way:
class CategorySerializer < ActiveModel::Serializer
attributes :id, :name, :order, :children
def children
instance_options[:children]
# or instance_options[:children]&.as_json
end
end
Next you can simply pass children to the serializer:
category.subtree.arrange_serializable do |parent, children|
CategorySerializer.new(parent, children: children)
end
or
category.subtree.arrange_serializable do |parent, children|
ActiveModelSerializers::SerializableResource(parent, children: children)
end

active model serializer with virtual attribute - Rails 4

I am currently making API with RoR, and I need to create an object with virtual attributes and associated object.
The problem is that serializer does not kick in when I return an object with virtual attribute.
Here is the returned object from foo_controller
{
:id=>280,
:virtual=>"y8st07ef7u"
:user_id=>280
}
:virtual is a virtual attribute and user_id is an id of associated table - User.
My goal is to make this
{
:id=>280,
:virtual=>"y8st07ef7u",
:user=>{
:id=>280,
:name=>'foo'
}
}
Foo_controller setting
class Api::V1::FoosController < ApplicationController
foos = Foo.all
foos.each do |foo|
foo.set_attribute('y8st07ef7u')
end
render json: foos.to_json(:methods => :virtual), status: 200
end
Foo_model setting
class Foo < ActiveRecord::Base
belongs_to :user
attr_accessor:virtual
def set_attribute(path)
self.virtual = path
end
end
Foo_serializer setting
class FooSerializer < ActiveModel::Serializer
attributes :id, :virtual
has_one :user
end
Foo migration setting
class CreateFoos < ActiveRecord::Migration
def change
create_table :foo do |t|
t.references :user
end
end
end
user model
class User < ActiveRecord::Base
has_many :foos
end
user serializer
class UserSerializer < ActiveModel::Serializer
attributes :id, :name
belongs_to :foo
end
When I replace "foo.to_json(:methods => :virtual)" in foo_controller with "foos", serializer kicks in and I get a user object inside the returned json instead of user_id, but :virtual is not in the json.
Are there any ways I can get an object with both virtual attributes and associated object using active model serializer.
Thank you in advance for your help!
I figured out. It was very simple.
I just had to add ":virtual" to attributes in the foo_serializer and replace "foo.to_json(:methods =>:virtual)" with just "foos"

How to add one-to-many objects to the parent object using ActiveRecord

I have the following code base for has_many relation in ActiveRecord rails:
class Foo < ActiveRecord::Base
has_many :foo_bars
end
class Bar < ActiveRecord::Base
end
class FooBar < ActiveRecord::Base
belongs_to :foo
belongs_to :bar
end
How do i add FooBar entries to Foo during creation.
This is my code as follows:
#foo = Foo.create(params[:foo])
bars = params[:bars] # bars in a array of string format
bar_ids = bars.collect{|b| b.to_i}
#foo.foo_bars << bar_ids
#foo.save
Try with
#foo = Foo.create(params[:foo])
#foo.foo_bars << params[:bars].map {|s| FooBar.new(:bar_id => s.to_i)}
#foo.save
It build a new FooBar instance of each id in the params[:bars] collection. The final save will create both the #foo and the FooBar. See doc here for help on associations.
For edition:
#foo = Foo.find(params[:id])
#foo.foo_bars = params[:bars].map {|s| #foo.foo_bars.where(:bar_id => s.to_i).first_or_initialize }

Resources