Make root node in Active Model Serializer - ruby-on-rails

I have an array of JSON in my Rails App in this format using Active Model Serializer:
[
{
"contact" : {}
},
{
"contact" : {}
}
]
How do I make it so that I remove one level of node above the contact USING active model serializer like this:
[
{
},
{
}
]
I also want to remove the node name "contact".

This was covered in RailsCast #409 Active Model Serializers.
In order to remove the root node, you add root: false in the call to render in your controller. Assuming your contacts in JSON come from a contacts#index method, your code may look something like:
def index
#contacts = Contacts.all
respond_to do |format|
format.html
format.json { render json: #contacts, root: false }
end
end
Or, if you don't want any root nodes in any of your JSON, in your ApplicationController, add the following method:
def default_serializer_options
{root: false}
end

For people using ActiveModel::Serializer v0.10.x, you will need to create an initializer and include the following:
# config/initializers/serializer.rb
ActiveModelSerializers.config.adapter = :json
ActiveModelSerializers.config.json_include_toplevel_object = true
Then, just restart your app and you should get the root objects you desire.
This works in Rails 5.1.x. YMMV. HTH.

Usually the root node has the name of your controller by default if I am not wrong.
format.json { render json: #contacts}
Of course you need to remove root false, it removes the node's name.
If you want contact as root object use this:
format.json { render json :#contacts, :root => 'contact' }

/config/initializers/serializer.rb
ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`
By default ActiveModelSerializers will use the Attributes Adapter (no
JSON root). But we strongly advise you to use JsonApi Adapter, which
follows 1.0 of the format specified in jsonapi.org/format.

Related

How to return JSON from Rails with camelcased key names

I'm building a JS app with a Rails backend and in order not to confuse snake and camel cases, I want to normalize it all by returning camelcase key names from the server. So user.last_name would return user.lastName when returned from the API.
How do I achieve this? Thanks!
Edit: Added Controller Code
class Api::V1::UsersController < API::V1::BaseController
# authorize_resource
respond_to :json, only: [:index]
def sky
#user = User.find_by_id(params[:user_id])
if #user
obj = {
sky: {
sectors: #user.sectors,
slots: #user.slots
}
}
render json: obj
else
raise "Unable to get Sky"
end
end
end
The way I do it is using ActiveModelSerializer and the json_api adapter:
In your Gemfile, add:
gem 'active_model_serializers'
Create a new file /config/initializers/ams.rb containing:
ActiveModelSerializers.config.adapter = :json_api
ActiveModelSerializers.config.key_transform = :camel_lower
Your controller action should look like this:
class ApiController < ApplicationController
def sky
#user = User.find_by_id(params[:user_id])
if #user
render json: #user, serializer: UserSkySerializer
else
raise "Unable to get Sky"
end
end
end
Now you need to create a serializer. First, create a new directory app/serializers/.
Next, create a new serializer app/serializers/user_sky_serializer.rb, containing:
class UserSkySerializer < ActiveModel::Serializer
attributes :sectors, :slots
end
The result will be similar to what you describe in your obj hash, but all attribute keys will be rendered in camelCase using the jsonapi standard.
Another option is the use the olive_branch gem. As described in this post, all you need to do is:
Add this gem
add config.middleware.use OliveBranch::Middleware in application.rb.
Then, add this header to requests from your client-side app:
'X-Key-Inflection': 'camel'
If you are using libs like axios, you can add this header to a constant along with other headers:
const HEADERS = {
...
'X-Key-Inflection': 'camel'
}
const request = axios.post(url, param, HEADERS)
This way you don't need to deep_transform keys manually on the server side. Even deeply-nested json keys will be camelized. Example response from such request:
[
{
"id": 1,
"firstName": "Foo",
"lastName": "Bar",
"createdAt": ...,
"updatedAt": ...,
...
"cabinAssignments": [
{
"id": 1,
"cabinKeeperId": 1,
"userId": 1,
"houseId": 1,
...
}
]
}
]
You don't mention your Rails version, so for others who look for this, I'll mention that in Rails 5 the answer is to use #camelize from ActiveSupport::Inflector. In my case, I had to call it in a chain on an ActiveRecord model (t below):
def index
trucks = AvailableTruck.all.map do |t|
t.as_json(only: AvailableTruck.exposed)
.deep_transform_keys(&:camelize).deep_transform_values(&:upcase)
end
render json: trucks
end
#deep_transform_keys may or may not be necessary depending on your application.
You can achieve that with ruby's method_missing method.
Create a file something like concerns/method_missing_extension.rb and put following code in it.
module MethodMissingExtension
def method_missing(method_name, args = {})
if self.class.column_names.include? method_name.to_s.underscore
send method_name.to_s.underscore.to_sym
else
super
end
end
end
Include this module in each model like include MethodMissingExtension.
Now whenever you do user.firstName then user.first_name will returned.

Serialize an array of models using active_model_serializers

I am trying to send the serialized version of a model to a view as a param, using the gem active_model_serializers
#app/serializers/admin_serializer.rb
class AdminSerializer < ActiveModel::Serializer
attributes :id, :email, :access_locked?
end
#app/controllers/dashboard/admins_controller.rb
def index
#search = Admin.search(params[:q])
#admins = #search.result(:distinct => true).page(params[:page]).per(10)
#page_entries_info = view_context.page_entries_info #admins
# render json: #admins
respond_to do |format|
format.html
format.js
format.json {render json: #admins}
end
end
#app/views/dashboard/admins/index.html.erb
<%= debug (ActiveModel::Serializer::Adapter.adapter_class(:json_api).new(ActiveModel::Serializer.serializer_for(#admins.first).new(#admins.first),{}).to_json) %>
<%= debug (#admins.all.map{|admin| AdminSerializer.new(admin).to_json}) %>
Above debugs are yielding the below response:
--- '{"data":{"id":"1","type":"admins","attributes":{"email":"tech#bluesapling.com","access_locked?":false}}}' //returned by the first debug
---
- '{"object":{"id":36,"email":"aubrey_schmitt#feeneykoch.io","created_at":"2016-03-28T05:15:17.546Z","updated_at":"2016-03-28T05:15:17.546Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":20,"email":"alysa_johnston#thompson.io","created_at":"2016-03-28T05:15:16.304Z","updated_at":"2016-03-28T05:15:16.304Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":22,"email":"kristofer.langosh#kunzeluettgen.com","created_at":"2016-03-28T05:15:16.459Z","updated_at":"2016-03-28T05:15:16.459Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":37,"email":"beryl_keler#wiza.biz","created_at":"2016-03-28T05:15:17.624Z","updated_at":"2016-03-28T05:15:17.624Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":5,"email":"wilhelmine_buckridge#crona.io","created_at":"2016-03-28T05:15:15.139Z","updated_at":"2016-03-28T05:15:15.139Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":14,"email":"edward_wisoky#corkery.net","created_at":"2016-03-28T05:15:15.838Z","updated_at":"2016-03-28T05:15:15.838Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":27,"email":"leonor#jerde.biz","created_at":"2016-03-28T05:15:16.848Z","updated_at":"2016-03-28T05:15:16.848Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":2,"email":"carley#wyman.net","created_at":"2016-03-28T05:15:14.873Z","updated_at":"2016-03-28T05:15:14.873Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":10,"email":"ervin.gleichner#cremin.org","created_at":"2016-03-28T05:15:15.527Z","updated_at":"2016-03-28T05:15:15.527Z"},"instance_options":{},"root":null,"scope":null}'
- '{"object":{"id":15,"email":"lonzo.dickens#johnscole.name","created_at":"2016-03-28T05:15:15.916Z","updated_at":"2016-03-28T05:15:15.916Z"},"instance_options":{},"root":null,"scope":null}'
In the first debug I am serializing only one object, while in the second one I am trying to do it for an array of objects.
The first debug is correctly returning the serialized version of the object(in json_api format) while second debug is not.
Tried ArraySerializer as well, with no success: ActiveModel::Serializer::ArraySerializer.new(#admins, each_serializer: AdminSerializer).as_json
how do I achieve the desired serialization. Moreover, if achieved, can I used some other simplified version of this? As this debug statement is way too verbose.
Tried all the solutions mentioned here - How do you initialize an ActiveModel::Serializer class with an ActiveRecord::Relation array?
The basic problem which I am trying to solve is, in the index method of the Admin controller, the Admin object is passed as a PORO to the index.html file. But I want the serialized json version of this object so that I can pass it to my react components as a prop
index method is rendering proper json on firing http://dashboard.localhost.com:3000/admins.json
UPDATE#1 for the index method
def index
#search = Admin.search(params[:q])
#admins_array = #search.result(:distinct => true).to_a
if params[:page]
#admins = #search.result(:distinct => true).page(params[:page][:number]).per(10)
#admins_json_array = Kaminari.paginate_array(#admins_array).page(params[:page][:number]).per(10)
else
#admins = #search.result(:distinct => true).page(1).per(10)
#admins_json_array = Kaminari.paginate_array(#admins_array).page(1).per(10)
end
#admins_json = ActiveModel::SerializableResource.new(#admins_json_array.to_a)
...
...
...
end
I have a controller that I need to specify the serializer in, due to wanting different attributes from the default serializer.
In Controller:
def index
search = User.ransack(search_params)
render json: search.result, each_serializer: MembershipRenewalSerializer::MemberSerializer
end
So, just to get things working, what happens if you specify the each_serializer option?
Edits:
Outside Controller:
ActiveModel::SerializableResource.new(
User.first(2),
each_serializer: MembershipRenewalSerializer::MemberSerializer
).to_json
Note, that without specifying each_serializer, SerializableResource would use the UserSerializer.
Edit #2,
It looks like there is something weird happening with the #admins data.
Try converting to an array:
ActiveModel::SerializableResource.new(#admins.to_a).to_json
Edit #3
To paginate your array, try the following:
#search = Admin.search(params[:q])
#results = #search.result(:distinct => true).to_a
#admins = Kaminari.paginate_array(#results).page(params[:page]).per(10)
Follow the guide: Serializing before controller render
You could use ActiveModel::SerializableResource.new(#admins, adapter: :json_api).to_json
in index.html.erb
<%= debug (ActiveModel::SerializableResource.new(#posts, adapter: :json_api).to_json) %>
below is the output(using posts)
'{"data":[{"id":"1","type":"posts","attributes":{"title":"first post","body":null}},{"id":"2","type":"posts","attributes":{"title":"second post","body":null}}],"links":{}}
I create a concern with some API helper methods and there you can check if its a collection pass find the appropiate serializer and pass it to the collection serializer.
def api_response(data)
render json: wrap_answer(data)
end
def wrap_answer(data)
if data.respond_to?(:each)
ActiveModel::Serializer::CollectionSerializer.new(data, each_serializer: ActiveModel::Serializer.serializer_for(data.first))
else
data
end
end
should have made that into string and use json.stringify to make that as a string and make your life easy

Rails Controller Display a of List of Resources/Objects?

For my app I have three models: Quotes, Images, Videos, all of them I consider content. I want to call the index action of my ContententController to display a mix of these objects in JSON format. For example, I get something like this back.
[
{ ...image json... },
{ ...image json... },
{ ...video json... },
{ ...quote json... },
{ ...image json... }
]
How would I approach this? I feel like this has something to do with an SQL UNION? Thank you!
If you consider all three "content", then you really should use a single table inheritance strategy where they all inherit from the Content model. That way you can call
#Contents = Content.all.whatever
Instead of having to call each and then mix them together, or getting more complex then you need with the query. Turning them into JSON would then be easy considering each object would have its normal to_json method called on it, when doing something like this
format.json {render :json => #Contents}
def index
#quotes = Quotes.all
#images = Image.all
#videos = Video.all
#contents = #quotes + #images + #videos
format.json {render :json => #contents}
end
I think you can just append each object, but I wont recommend you to do that.

How to create temporary attribute & use that in json reponse in rails active record

I am new to ROR.
I am having a controller where i am getting the search results in available_users variable..
availble_users //some active record result with id,name & address
available_users.each do |userstatus|
userstatus.class_eval do
attr_accessor :is_friend
end
if current_user.invitations.find_by(:friend_id => userstatus.id) //invitation is another table
userstatus.is_friend = "true"
else
userstatus.is_friend = "false"
end
end
render json: available_users
but when i am getting the response on ajax request it is serving the same array without including is_friend column.
here is my json response.
id: 2
name: abc
address:
please can anyone figure me out why it is not appending this temporary attribute.
Thanks.
what you have will work if you pass the methods option to_json
render json: available_users.to_json(:methods => :is_friend)
Or you could do this
available_users.each do |userstatus|
if current_user.invitations.find_by(:friend_id => userstatus.id) //invitation is another table
userstatus["is_friend"] = "true"
else
userstatus["is_friend"] = "false"
end
end
render json: available_users
[]= is an alias for write_attribute

Array of ActiveRecords to JSON

I am aware that ActiveRecord provides a to_json method which allows fields to be filtered out of the JSON output using :only and :except.
At present I am using the following to format an array from a find as JSON:
#customers = Customer.find(:all)
...
format.js { render :json => #customers}
How would I be able to select the fields to be output in the objects in the array? Is there a shortcut or do I need to do this by hand?
Cheers,
Adam
I think you answered your own question. With Rails 2.3.x you could use the following:
#customers = Customer.all #Shortcut for to Customer.find(:all)
respond_to do |format|
format.js { render :json => #customers.to_json(:only=>[:column_one, :column_two]}
end
You can overwrite the to_json method of the model class if you want to globally apply the change for the model.
For example, to exclude null values from the rendered JSON you could overwrite the original ActiveRecord method to_json
def to_json(options)
hash = Serializer.new(self, options).serializable_record
hash = { self.class.model_name => hash } if include_root_in_json
ActiveSupport::JSON.encode(hash)
end
with this in your model class:
def to_json(options)
hash = Serializer.new(self, options).serializable_record.reject {|key, value| value.nil? }
hash = { self.class.model_name => hash } if include_root_in_json
ActiveSupport::JSON.encode(hash)
end
If you peep into the ActionController::Base class, you'll see that it calls to_json on your collection immediately (no extra options used), so you've got to have it already prepared. So if in your action you don't use the attributes that are not rendered to json, you can replace your find with
#customers = Customer.find(:all, :select => ["id", ...])
to select only the ones that you need.

Resources