Ruby on Rails 5 - Passing data between two serializers - ruby-on-rails

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

Related

How to use CollectionSerializer in FastJson API?

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

Apply method to all attributes using active_model_serializers

Using rails-api/active_model_serializers, could one apply a method to all attributes?
Basically, I wish to apply
object.zeroed_value(:symbol)
to each attribute without having to write a separate method for each. See example:
class NutritionalSerializer < ActiveModel::Serializer
attributes :calories,
:sodium
def calories
object.zeroed_value(:calories)
end
def sodium
object.zeroed_value(:sodium)
end
# many, many more attributes...
end
I think you can use metaprogramming to generate all methods you need with something like:
class NutritionalSerializer < ActiveModel::Serializer
attributes :calories,
:sodium
%i{attr1 attr2 attr3}.each do |attr|
define_method attr do
object.zeroed_value(attr)
end
end
end
Also you can override the attributes method of serializer and then do something like:
class NutritionalSerializer < ActiveModel::Serializer
def attributes
data = super
%i{attr1 attr2 attr3}.each do |attr|
data[attr] = object.zeroed_value(attr)
end
data
end
end

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

How to override default deserialization of params to model object?

How to override default deserialization of params to model object?
In other words, how to make Rails understand camel case JSON with a snake case database?
Example: I receive params Foo object with a field fooBar and I want my Foo model to understand fooBar is in fact database field foo_bar.
"Foo": {
"fooBar": "hello" /* fooBar is database field foo_bar */
}
class Foo < ActiveRecord::Base
attr_accessible :foo_bar
end
class FoosController < ApplicationController
def new
#foo = Foo.new(params[:foo])
end
Foo.new(params[:foo]) assumes params[:foo] contains foo_bar. Instead params[:foo] contains fooBar (in my case params contains JSON data).
I would like a clean way to handle this case, the same way a model can override as_json:
class Foo < ActiveRecord::Base
attr_accessible :foo_bar, :another_field
def as_json(options = nil)
{
fooBar: foo_bar,
anotherField: another_field
}
end
end
There is a from_json method inside ActiveModel but it is not called when Foo.new(params[:foo]) is run.
I've read several times that overriding initialize from a model object is a terrible idea.
All that Foo.new does with the params hash you give it is iterate over the keys and values in that hash. If the key is foo_bar then it tries to call foo_bar= with the value.
If you define a fooBar= method that sets self.foo_bar then you'll be able to pass a hash with the key :fooBar to Foo.new.
Less manually, you can do
class Foo < ActiveRecord::Base
alias_attribute :fooBar, :foo_bar
end
which generates all the extra accessors for you.
I wouldn't say that overriding initialize is a terrible thing but it can be tricky to do right and there's almost always a simpler way or a way that makes your intentions clearer.
I've checked active_model_serializers, RABL and JBuilder. None of them allow to customize the JSON format that is received.
For that one must deal with wrap_parameters, see http://edgeapi.rubyonrails.org/classes/ActionController/ParamsWrapper.html
It works, still the code is ugly: I get JSON stuff inside my controller + the serializer/model instead of one place.
Example of use of wrap_parameters:
class EventsController < ApplicationController
wrap_parameters :event, include: [:title, :start, :end, :allDay, :description, :location, :color]
def create
respond_with Event.create(params[:event])
end
end
and then inside my model (Frederick Cheung is right on this part):
class Event < ActiveRecord::Base
attr_accessible :title, :start, :end, :allDay, :description, :location, :color
# JSON input allDay is all_day
alias_attribute :allDay, :all_day
# JSON input start is starts_at
# +datetime+:: UNIX time
def start=(datetime)
self.starts_at = Time.at(datetime)
end
# JSON input end is starts_at
# +datetime+:: UNIX time
def end=(datetime)
self.ends_at = Time.at(datetime)
end
# Override the JSON that is returned
def as_json(options = nil)
{
id: id,
title: title,
start: starts_at, # ISO 8601, ex: "2011-10-28T01:22:00Z"
end: ends_at,
allDay: all_day,
description: description, # Not rendered by FullCalendar
location: location,
color: color
}
end
end
For info ASP.NET MVC (with Json.NET) does it using C# decorator attributes which is pretty elegant:
class Post
{
[JsonPropertyAttribute("title")]
public string Title;
}
I have created a gist that shows how to implement serialization/deserialization: https://gist.github.com/3858908

Resources