has_ancestry and active model serializers - ruby-on-rails

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

Related

how to optionally include / delete values in an Active Model Serializer 0.10 class

I have the folowing ASM 0.10 :
class UserMicroSerializer < ActiveModel::Serializer
attributes :id, :name, :is_friend
def is_friend
#instance_options[:is_friend]
end
end
but would also like to support not having the is_friend attribute.
I have tried various things like:
class UserMicroSerializer < ActiveModel::Serializer
attributes :id, :name
if #instance_options[:is_friend]
attributes :is_friend
end
def is_friend
#instance_options[:is_friend]
end
end
but get error msg:
NoMethodError: undefined method `[]' for nil:NilClass
How would I make the #instane_options conditionally include is_friend?
If you can conditionally use a different serializer in the controller then you may be able to do this
class SimpleUserMicroSerializer < ActiveModel::Serializer
attributes :id, :name
end
By subclassing the simple serializer, you don't have much code overlap
class UserMicroSerializer < SimpleUserMicroSerializer
attributes :is_friend
def is_friend
#instance_options[:is_friend]
end
end
You can also send { scope: 'is_friend' } from controller and then check it into serializer.
class UserMicroSerializer < ActiveModel::Serializer
attributes :id, :name, :is_friend
def filter(keys)
keys.delete :is_friend if scope and scope[:is_friend]
super(keys)
end
end

Passing options to ActiveModel serializer

When using the serializer from the controller I can pass extra options to it like so
render json: user, some_option: 'foobar
Then I can reference some_option within the serializer as
serialization_options[:some_option]
But, if I call the serializer directly as
MySerializer.new(user, some_option: 'foobar')
I cannot get the extra options since serialization_options is an empty object.
For v0.9
You may call the following:
MySerializer.new(user).as_json({some_option: 'foobar'})
If you are doing that inside another serializer and you need to pass the scope and the current serialization_options as well, you can do this:
class MyParentSerializer
has_one :user
def user
MySerializer.new(object.user, { scope: scope }).as_json(serialization_options.merge({ some_option: 'foobar' }))
end
end
ActiveModel::Serializer's API has not really been consistent, in v0.9, however if you upgrade to v0.10, you could use the instance_options method to access the additional params. However, I'd be curious to learn how the objects were parsed in v0.9, though
Here is how you can pass parameters (options) from the parent serializer and show or hide attributes based on these parameters in the child serializer.
Parent serializer:
class LocationSharesSerializer < ActiveModel::Serializer
attributes :id, :locations, :show_title, :show_address
def locations
ActiveModelSerializers::SerializableResource.new(object.locations, {
each_serializer: PublicLocationSerializer,
params: {
show_title: object.show_title
},
})
end
end
Child serializer
class PublicLocationSerializer < ActiveModel::Serializer
attributes :id, :latitude, :longitude, :title, :directions, :description, :address, :tags, :created_at, :updated_at, :photos
def title
object.title if #instance_options[:params][:show_title]
end
end

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

How to serialize nested Model in Rails?

I'm having an extremely difficult time figuring out how to serialize the nested attributes of a model in rails. I have a RecipeTemplate which will store an already existing Recipe in it's template_data attribute. Recipe has nested attributes two levels deep.
This is on rails 3.1.0.rc4
class RecipeTemplate < ActiveRecord::Base
serialize :template_data, Recipe
...
end
class Recipe < ActiveRecord::Base
has_many :ingredients
accepts_nested_attributes_for :ingredients
...
end
Ingredients in Recipe also has nested attributes (SubIngredients).
If I set the template_data with an object like so:
Recipe.includes(:ingredients => [:sub_ingredients]).find(1)
I'll get a TypeError "can't dump anonymous class Class" which makes sense, since it doesn't know how to serialize the Ingredients or SubIngredients.
How can you serialize the nested attributes in a model so that you can use:
serialize :template_data, Recipe
Or do I have to serialize the data in some other manner and perform the type safety checks myself?
Thanks in advance for any help
I can see why you would want the template itself to be stored inside of a serialized column, but you need a little more manipulation of the data being stored than is permitted by that type of column. Here's what I would do:
app/models/recipe_template.rb
class RecipeTemplate < ActiveRecord::Base
serialize :template_data
attr_accessible :name, :recipe
def recipe=(r)
self.template_data = r.serializable_hash_for_template
end
def recipe
Recipe.new(template_data)
end
end
app/models/recipe.rb
class Recipe < ActiveRecord::Base
has_many :ingredients, as: :parent
accepts_nested_attributes_for :ingredients
attr_accessible :name, :ingredients_attributes
def serializable_hash_for_template(options={})
options[:except] ||= [:id, :created_at, :updated_at]
serializable_hash(options).tap do |h|
h[:ingredients_attributes] = ingredients.map(&:serializable_hash_for_template)
end
end
end
app/models/ingredient.rb
class Ingredient < ActiveRecord::Base
belongs_to :parent, polymorphic: true
has_many :sub_ingredients, class_name: 'Ingredient', as: :parent
accepts_nested_attributes_for :sub_ingredients
attr_accessible :name, :sub_ingredients_attributes
def serializable_hash_for_template(options={})
options[:except] ||= [:id, :parent_id, :parent_type, :created_at, :updated_at]
serializable_hash(options).tap do |h|
h[:sub_ingredients_attributes] = sub_ingredients.map(&:serializable_hash_for_template)
end
end
end
Then to create and use a template:
# create a recipe to use as a template
taco_meat = Ingredient.create(name: "Taco Meat")
taco_seasoning = taco_meat.sub_ingredients.create(name: "Taco Seasoning")
sams_tacos = Recipe.create(name: "Sam's Tacos")
sams_tacos.ingredients << taco_meat
# create a template from the recipe
taco_recipe = RecipeTemplate.create(name: "Taco Recipe", recipe: sams_tacos)
# build a new recipe from the template
another_taco_recipe = taco_recipe.recipe
The difference is that you're using the serialized column to store a Hash to use in the Recipe contructor. If you just wanted to serialize the object, the other posters are correct–just associate an object.
Why don't you just keep a field in your RecipeTemplate model with the label recipe_id
and link that to the recipe instead of trying to serialize a recipe object?

Resources