Present subset of an object with ActiveModel Serializer - ruby-on-rails

I am using ActiveModel Serializers in a Rails project.
The default serializer for the object is fairly large, and nesting an object in API responses result in rather large JSON objects.
Sometimes, I want to embed an object, but only need a small subset of the object's attributes to be present in the JSON.
Obviously, I could do something like this:
render json: #user, serializer: SmallerUserSerializer
but that would lead to a lot of duplication.
Is there an option that I can pass to the serializer so that it will only include a subset of the serializers attributes? Eg:
class BlogSerializer
# This is pseudocode. Does not actually work.
has_one :user, only_show: [:user_id, :profile_url]
end

Create a method and call to_json on the user object. Then add that method name to your list of attributes. The method can be called user also.
class BlogSerializer
attributes :id, :user
def user
object.user.to_json( only: [ :id, :profile_url ] )
end
end

Use the active model serialzers gem.
Your pseudo code will become the following simple modularized code:
class BlogSerializer < ActiveModel::Serializer
attributes :user_id, :profile_url
end
Guide: http://railscasts.com/episodes/409-active-model-serializers

Create a method and call to_json on the user object. Then add that method name to your list of attributes. The method can be called user also.
class BlogSerializer
require 'json'
attributes :id, :user
def user
JSON.parse "#{object.user.to_json( only: [ :id, :profile_url ] )}"
end
end

Related

API Rails: How to render a model's custom attribute in a controller?

In a #index action in my controller, I'm rendering the model as below. Though, I would like to include a custom attribute but it's never rendered, how can I do that?
class MyModel < ApplicationRecord
def custom_attr
attr1 + attr2
end
end
class MyModelsController < ApplicationController
def index
# 'custom-attr' is not rendered.
render json: MyModel.all, status: :ok, only: %i[attr1, attr2, custom_attr]
end
end
Thats not an attribute. Its just an instance method. attributes are really a Rails specific feature of models that is really a setter/getter coupled with metadata.
class Person
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name
attribute :age
end
irb(main):009:0> Person.new.attributes
=> {"name"=>nil, "age"=>nil, "birthplace"=>nil}
When you render an model as JSON rails use #as_json which calls #serializeable_hash on the model. This serializes the attributes based on the attributes method. This is where the options are actually passed.
As #custom_attr is not actually an attribute its of course not included in the serialization.
You can solve this by:
Override #as_json on the model to customize its serialization.
Use a serializer layer such as ActiveModel::Serializers or JBuilder to customize the JSON representation of the model. (recommended)

Ruby on Rails 5 - Passing data between two serializers

I would like to know if it is possible to send data from a serializer to another, not from a controller to a serializer. Here is what I am doing :
class Serializer1 < ActiveModel::Serializer
attributes \
:id,
:past_teachings
def past_teachings
p_teachings = Teaching.all
p_teachings = ActiveModel::ArraySerializer.new(p_teachings,
each_serializer: Serializer2)
#### I would like to send data to serializer2 from the current serializer ####
end
end
I know it is possible to send data from a controller to a serializer. But it is possible to send data from a serializer to another?
Yes. Using AMS 0.10.x, you could change your example in this way:
serializer_1.rb
class Serializer1 < ActiveModel::Serializer
attributes :id, :past_teachings
def past_teachings
ActiveModelSerializers::SerializableResource.new(PastTeaching.all, each_serializer: TeachingSerializer)
end
end
teaching_serializer.rb:
class TeachingSerializer < ActiveModel::Serializer
attributes :id, :name
end
If you want to access the current object being serialized, you can refer to 'object.' You can access the objects functions as object.function and its attributes as object['attribute'].
So, technically, you could do something like this (though in reality you would probably use AMS has_many relationship instead):
class Serializer1 < ActiveModel::Serializer
attributes :id, :past_teachings
def past_teachings
ActiveModelSerializers::SerializableResource.new(object.past_teachings.where(...), each_serializer: TeachingSerializer)
end
end

How can I make an ActiveModel::Serializer attribute optional at runtime?

I am trying to allow an API request to specify what fields to return on an object. I can retrieve the object with only the fields specified, but when it is serialized, it throws an error:
ActiveModel::MissingAttributeError (missing attribute: x)
How can I achieve this functionality with ActiveModel::Serializer and is it possible?
I've found this question while searching for a good alternative to remove optional fields from the json response.
The gem active_model_serializers does have a solution for this. You just need to pass a conditional to the attribute method in the serializer declaration.
class MySelectiveSerializer < ActiveModel::Serializer
attributes :id, :anything
attribute :something, if: -> { object.something.present? }
end
Perhaps 3 years ago a solution like this didn't exist, but it is available now. :)
Cheers.
This happens because the Serializer.attributes method call each field using the ActiveModel.read_attribute method. This method will apply some validations, like validates_presence_of at the model's definition, that will raise the exception. To avoid it I give three bad solutions and after a better and simple one:
Change the model definition, but you will miss your validation.
Overwrite the method ActiveModel.read_attribute to handle this behavior, you will get new challenges.
Overwrite the Serializer.attributes and instead of call super, call object.attributes.
But the best option will be create a new serialize class, to avoid besides effects, with the only fields that you want. Then specify this at the controller class:
render json: People.all.reduced, each_serializer: SimplePersonSerializer
Edit 1
The right answer should be the one from MaurĂ­cio Linhares.
render json: result.to_json( only: array_of_fields )
You can remove attributes from serializer, but they should exist.
class SomeSerializer < ActiveModel::Serializer
attributes :something
def attributes
super.except(:something) if something
end
end
You can customize attributes by implementing filter method in your serializer. Note, that I describe latest stable (for the time of the writing this post) 0.9.x branch.
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :author
def filter(keys)
if scope.admin?
keys
else
keys - [:author]
end
end
end

Use attribute with condition in activemodel serializer

class ProjectSerializer < ActiveModel::Serializer
attributes :id, :title
end
I use activemodel serializer to return title attribute with some conditions. Normally I can override title method but what I want is determine whether title attribute is returned or not with condition.
I'm not sure exactly what your use case is, but maybe you can use the awesomely magical include_ methods! They are the coolest!
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :title
def include_title?
object.title.present?
end
end
If object.title.present? is true, then the title attribute will be returned by the serializer. If it is false, the title attribute will be left off altogether. Keep in mind that the include_ method comes with it's own specific functionality and does things automatically. It can't be called elsewhere within the serializer.
If you need to be able to call the method, you can create your own "local" method that you can use within the serializer.
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :title
def title?
object.title.present?
end
end
Again, not exactly sure what functionality you are looking for, but hopefully this gets you going in the right direction.

How to return all attributes of an object with Rails Serializer?

I have a simple question. I have a seriaizer that looks like this:
class GroupSerializer < ActiveModel::Serializer
attributes :id, :name, :about, :city
end
The problem is that, whenever I change my model, I have to add/remove attributes from this serializer. I just want to get the whole object by default as in the default rails json respond:
render json: #group
How can I do that?
At least on 0.8.2 of ActiveModelSerializers you can use the following:
class GroupSerializer < ActiveModel::Serializer
def attributes
object.attributes.symbolize_keys
end
end
Be carful with this though as it will add every attribute that your object has attached to it. You probably will want to put in some filtering logic on your serializer to prevent sensitive information from being shown (i.e., encrypted passwords, etc...)
This does not address associations, although with a little digging around you could probably implement something similar.
============================================================
UPDATE: 01/12/2016
On 0.10.x version of ActiveModelSerializers, attributes receives two arguments by default. I added *args to avoid exception:
class GroupSerializer < ActiveModel::Serializer
def attributes(*args)
object.attributes.symbolize_keys
end
end
Just to add to #kevin's answer. I was looking also to how to add filters on the returned attributes. I looked to the the documentation active_model_serializers 0.9 and it does support filters that looks like this:
def attributes
object.attributes.symbolize_keys
end
def filter(keys)
keys - [:author, :id]
end
I tried it, but it did not work. I assumed that's because the attributes are not specified explicitly. I had to do it the same way specified in the rails cast to work:
##except=[:author, :id]
def attributes
data = object.attributes.symbolize_keys
##except.each { |e| data.delete e }
data
end
Try the following to get all the attribute keys for the Group class:
Group.new.attributes.keys
For example, I get the following for users on one app:
> User.new.attributes.keys
=> ["id", "password_digest", "auth_token", "password_reset_token", "password_reset_requested_at", "created_at", "updated_at"]
On 0.10.x version of ActiveModelSerializers, attributes receives two arguments by default. I added *args to avoid exception:
class GroupSerializer < ActiveModel::Serializer
def attributes(*args)
object.attributes.symbolize_keys
end
end
I want get all attributes + few more.
base on answer above, this work:
class NotificationSerializer < ActiveModel::Serializer
def actor
'asdasd'
end
def attributes(*args)
keys = object.attributes
keys[:actor] = actor() # add attribute here
keys.symbolize_keys
end
end

Resources