How to use CollectionSerializer in FastJson API? - ruby-on-rails

I'm updating the API code to FastJson (https://github.com/Netflix/fast_jsonapi) in where I work. The "old" code is using ActiveModel and has
ActiveModel::Serializer::CollectionSerializer.new. I have no idea how to "translate" this code to FastJson API.
I already searched in FastJson documentation about Collection Serialization (https://github.com/Netflix/fast_jsonapi#collection-serialization), but i didnt understand the example.
class API::Messages::MessagesSerializer < ActiveModel::Serializer
attributes :id, :name, :description
attribute :chats do
ActiveModel::Serializer::CollectionSerializer.new(
object.user_chats, serializer: API:Messages::ChatUserSerializer
)
end
end

When collection will be passed in serializer it'll deal that collection perfectly no need to configure anything extra. Following are the serializers
class API::Messages::MessagesSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name, :description
attributes :chats do |message|
API::Messages::ChatsSerializer.new(message. user_chats)
end
end
class API::Messages::ChatsSerializer
include FastJsonapi::ObjectSerializer
attributes ... # add attribute/logic as you want for single chat object
end
And your controller will be something like this
def show
render json: API::Messages::MessagesSerializer.new(#message).serialized_json, status: :ok
end

Related

Rails - How get first record of associated table?

i am trying to create an api for my mobile app.
I have posts and images tables. For my api, i can send all posts with:
#posts = Post.all
render json: #posts
Output: [{"id":20,"title":"Title 1", "body":" first post ", "user_id":1 }]
But it does not contain images at all. In order to show a showcase image in homepage of my app, i just need the first image of associated images.
The output which i need is (the name of showcase_image attribute does not matter) :
Output: [{"id":20, "title":"Title 1", "body":" first post ", "showcase_image": 'first_image.jpg' , "user_id":1 }]
I need to include first image from associated images table to my json response..
Thanks in advance !
I would suggest using a serializer. Active Model Serializer is pretty standard and easy to use, but is not receiving any updates and has a bad performance. You can choose any other serializer (I recommend Blueprinter) or use the AMS.
Through the AMS you coudl define the relation you want to serialize and it would build the json you're expecting
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :showcase_image, :user_id
def showcase_image
object.images.first.name # don't know what is the attribute you're looking for
end
end
And on your controller:
#posts = Post.includes(:images).all # Use includes to avoid N+1 problems
render json: #posts, serialize_collection: PostSerializer
You can include associations with the :include option when calling as_json.
render json: #posts.as_json(include: :images)
You could limit this to one image by adding a new association to Post.
class Post < ApplicationRecord
has_many :images
has_one :showcase_image, class_name: 'Image'
end
This would allow you to use the :showcase_image instead.
render json: #posts.as_json(include: :showcase_image)
You could also use Jbuilder to solve the issue at hand without adding an additional association.
# app/views/posts/index.json.jbuilder
# Get images that belong to posts, group them by post_id and
# return the minimum image id for each post_id.
images = Images.where(post_id: #posts.select(:id)).group(:post_id).minimum(:id)
# Request the full image data for all image ids returned above.
images = images.keys.zip(Image.find(images.values)).to_h
json.array! #posts do |post|
json.extract! post, :id, :title, :body, :...
json.showcase_image do
image = images[post.id]
if image
json.extract! image, :id, :name, :location, :...
else
json.null!
end
end
end
Without calling a specific render, Rails will default to the app/views/posts/index file, and select the file matching the request. (If you request HTML it will look for an HTML file, if you request JSON it looks for JSON, etc.)
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
end
Now when you request /posts.json or /posts with the header Accept: application/json your application should return the JSON response build by Jbuilder.

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 do I run methods on respond_with json data in Rails?

I had a model which returns some parameters and includes parameters from other models as follows:
def as_json(options = {})
camelize_keys(super(options.merge(:only => [:id, :userId], include:{
comments: { only: [:test, :id] },
valediction: { only: [:name, :text, :hidden, :order] }
})))
end
def camelize_keys(hash)
values = hash.map do |key, value|
[key.camelize(:lower), value]
end
Hash[values]
end
Now I have moved the code to my controller because different controller actions need to return different parts of the model. (index should just return valediction, but show should return comments and valediction)
The new controller:
def index
respond_with(displayed_user.microposts.all, include: {
valediction: { only: [:name, :text] }
})
end
def show
respond_with(displayed_user.microposts.find(params[:id]), include: {
comments: { only: [:test, :id] },
valediction: { only: [:name, :text, :hidden, :order] }
})
end
But I'm very new to rails and I don't know how to put the camelize_keys function in so that it works.
Doing complex JSON formatting in your controllers / and or models usually leads to bloat and is a pain to test.
A good solution for this is using the ActiveModel::Serializer (AMS) gem. Its included in Rails 5 but you can easily add it to a Rails 4 project by adding it to the gemfile:
# See rubygems.org for latest verstion!
gem 'active_model_serializers', '~> 0.9.3'
Then run bundle install and restart your rails server.
With AMS you create serializer classes which define how your model data should be represented in JSON, XML etc. A serializer is basically a class that takes a model instance (or an array of models) and returns a hash (or an array of hashes) when you call .serializable_hash.
But Rails will take care of that part automatically for you.
class MicropostSerializer < ActiveModel::Serializer
attributes :id, :user_id
has_many :comments
has_many :valedictions
end
class CommentSerializer < ActiveModel::Serializer
attributes :test, :id
end
class ValedictionSerializer < ActiveModel::Serializer
attributes :name, :text, :hidden, :order
end
In your controller you can simply call:
def index
render json: displayed_user.microposts.all
end
But wait, what about camelize_keys?
Unless you have to support some weird legacy client that needs camelized keys there are very few reasons to do this. Most large API's use snakecase (Facebook, Google etc.) and Rails 5 is moving towards the JSONAPI spec which uses snakecase.
From your code sample it seems that some of your rails model attributes (and the db columns backing them) use camelcase. You should change the DB column with a migration as soon as possible.
If you HAVE to support a legacy database you can use alias_attribute:
class Pet < ActiveRecord::Base
alias_attribute :ownerId, :owner_id
end
You could move the method to a class method in the model, eg
#class methods
class << self
def camelize_keys(hash)
values = hash.map do |key, value|
[key.camelize(:lower), value]
end
Hash[values]
end
end
Now you can call this from anywhere like
MyModel.camelize_keys(some_hash)

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

Present subset of an object with ActiveModel Serializer

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

Resources