Add method to json output with include in rails - ruby-on-rails

I am getting info from my database
#teams = Team.select(:id,:name)
Then, I am rendering this info with an assosiation
render json: #teams, include: :players
This produce me the following input:
[
{
"id": 1,
"name": "Lyon Gaming",
"players": [
{
"name": "Jirall",
"position": "Superior",
},
{
"name": "Oddie",
"position": "Jungla",
}
]
}
]
How ever I need to sort the players in an specific order (not alphabetical)
I already have the sql statement to do it; how ever I dont know how to apply this method to each association.
I am looking something like:
render json: #teams, (include: :players, method: custom_order)
But this raise me an error.
Any ideas?

This should work:
render json: #teams, include: :players, methods: :custom_order
I'd like to add that if you will always want to return the object this way, you can add this to your model instead:
def as_json(options = nil)
super(include: :players, methods: :custom_order)
end
As Adam has mentioned, active_model_serializers is good to have if your render json's start getting ugly. If the complexity doesn't grow too much, I always recommend keeping the dependencies down.
https://github.com/rails-api/active_model_serializers

You need to read more about serializers in rails(This is good practice)
Here you have a nice article about this https://www.sitepoint.com/active-model-serializers-rails-and-json-oh-my/
Then you can make something like this
app/serializers/team_serializer.rb
class TeamSerializer < ActiveModel::Serializer
attributes :id, :name, :ordered_players
end
app/controllers/teams_controller.rb
def index
respond_with(Team.all.map(&TeamSerializer.method(:new)))
end

Related

Rails 4 render json with multiple objects and includes

I have a Rails 4 API. When a user search in the view for boats, this method is executed getting all the boats matching the search filters and return an array of boat models as json using render ActiveModel and the :include and :only like this:
render :json => #boats, :include => { :mainPhoto => {:only => [:name, :mime]},
:year => {:only => :name},
# other includes...}
This is working great.
But, additional to this information, in the view, I would like to show the total count of boats like "showing 1 - 20 of 80 boats" because there is a pagination funcionality. So, the point is I need to provide the 80 boats. I would like to avoid send two requests that execute almost the same logic, so the idea is to run the searchBoats method just once and in the result provide the list of boats and the total number of boats in a variable numTotalBoats. I understand numTotalBoats is not a boat model attribute. So, I think it should go in an independent variable in the render result.
Something like:
render :json => {boats: #boats with all the includes, numTotalBoats: #NumTotalBoats}
I tried thousands of combinations, but or I´m getting syntax errors or none of them is returning the expected result, something like
{boats: [boat1 with all the includes, boat2 with all the includes, ... boatN with all the includes], numTotalBoats: N}
Without adding any gems:
def index
boats = Boat.includes(:year)
render json: {
boats: boats.as_json(include: { year: { only: :name } }),
numTotalBoats: boats.count
}
end
At some point though, I believe you should use stand-alone serializers:
Note: Depending on whether you're using pagination gem or not, you might need to change .count calls below to .total_count (for Kaminari) or something else that will read the count correctly from paginated collection.
I recommend using ActiveModel Serializers and this is how it would be accomplished for your case.
Start by adding the gem to Gemfile:
gem 'active_model_serializers', '~-> 0.10'
Overwrite the adapter in config/initializers/active_model_serializer.rb:
ActiveModelSerializers.config.adapter = :json
Define serializers for your models,
# app/serializers/boat_serializer.rb
class BoatSerializer < ActiveModel::Serializer
attributes :name
has_one :year
end
# app/serializers/year_serializer.rb
class YearSerializer < ActiveModel::Serializer
attributes :name
end
And finally, in your controller:
boats = Boat.includes(:year)
render json: boats, meta: boats.count, meta_key: "numTotalBoats"
And you will achieve:
{
"boats": [
{
"name": "Boaty McBoatFace",
"year": {
"name": "2018"
}
},
{
"name": "Titanic",
"year": {
"name": "1911"
}
}
],
"numTotalBoats": 2
}
Adding that count in each index controller is a bit tedious, so I usually end up defining my own adapters or collection serializers in order to take care of that automatically (Tested with Rails 5, not 4).
# lib/active_model_serializers/adapter/json_extended.rb
module ActiveModelSerializers
module Adapter
class JsonExtended < Json
def meta
if serializer.object.is_a?(ActiveRecord::Relation)
{ total_count: serializer.object.count }
end.to_h.merge(instance_options.fetch(:meta, {})).presence
end
end
end
end
# config/initializers/active_model_serializer.rb
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonExtended
# make sure to add lib to eager load paths
# config/application.rb
config.eager_load_paths << Rails.root.join("lib")
And now your index action can look like this
def index
boats = Boat.includes(:year)
render json: boats
end
And output:
{
"boats": [
{
"name": "Boaty McBoatFace",
"year": {
"name": "2018"
}
},
{
"name": "Titanic",
"year": {
"name": "1911"
}
}
],
"meta": {
"total_count": 2
}
}
I think it's a little easier to parse this count for different endpoints and you will get it automatically while responding with a collection, so your controllers will be a little simpler.

How to change default behavior of empty Jbuilder partials?

Model relation: Article belongs_to Author
Sample jbuilder view:
json.extract! article,
:id,
:created_at,
:updated_at
json.author article.author, partial: 'author', as: :author
What happens when Article has no Author:
{
"id": 1,
"created_at": "01-01-1970",
"updated_at": "01-01-1970",
"author": []
}
Question:
Is there a clean way to force jbuilder to display null or {} when variable passed to associated template is empty? This problem is prevalent across quite big application and adding code like that article.author.empty? ? json.author(nil) : json.author(article.author, partial: 'author', as: :author) everywhere is not something I'd want to do. Perhaps some form of helper that wouldn't require too much refactoring?
I don't want to override core jbuilder functionality as I don't want to break it (partials accepting multiple variables for example).
Related jbuilder issue: https://github.com/rails/jbuilder/issues/350
This will accomplish what you want
json.author do
if article.author.blank?
json.null!
else
json.partial! 'authors/author', author: article.author
end
end
I would suggest a helper though to avoid all the duplication:
module ApplicationHelper
def json_partial_or_null(json, name:, local:, object:, partial:)
json.set! name do
object.blank? ? json.null! : json.partial!(partial, local => object)
end
end
end
Then you would call it like this:
json_partial_or_null(json, name: 'author', local: :author, object: article.author, partial: 'authors/author')

Rails 5 - include nested associations not working

I have two simple models:
note.rb
with
:title -> string, :content -> string
has_and_belongs_to_many :tags, join_table: :tags_notes
accepts_nested_attributes_for :tags
tag.rb
with
:name -> string
has_and_belongs_to_many :notes, join_table: :tags_notes
Both models are connected through has_and_belongs_to_many relationship.
The association table is called tags_notes as indicated above.
Well, the problem I have here is, in my RESTful controller, to get notes, I have this:
GET /api/notes
This only returns Note objects:
[
{
"id": 1,
"title": "12231",
"content": "121213"
},
{
"id": 2,
"title": "test",
"content": "testtest"
}
]
However, each note has tags, and I would like to dump those in the response as well, like this:
[
{
"id": 1,
"title": "12231",
"content": "121213",
tags: [
{
"name": "test",
"id": 1
},
{
...
}
]
},
...
]
In my controller, I've tried
Note.includes(:tags).
Current controller code:
def index
notes = Note.includes(:tags)
render json: notes, status: :ok
end
They only seem to return notes, without tags. Same is the case with Note.eager_load(:tags) What am I doing wrong? Cannot find enough documentation that will help me fix this issue.
If someone can help me with this I will be grateful.
Thanks a bunch.
Shortly after posting my question I found the answer myself. The include has to go in render.
So the controller code
def index
notes = Note.all
render json: notes, :include => :tags, status: :ok
end
Seems to do the trick!

Rails Active model serializer returns array instead of json

I am using Rails to create APIs containing basic todo information: name, list, and items. I want it to return json format, to look something like this:
{
"data": [
{
"type": "posts",
"id": "1",
"attributes": {
"title": "JSON API is awesome!",
"body": "You should be using JSON API",
"created": "2015-05-22T14:56:29.000Z",
"updated": "2015-05-22T14:56:28.000Z"
}
}
],
"links": {
"href": "http://example.com/api/posts",
"meta": {
"count": 10
}
}
}
^code from Active Serializer's Github.
When I look on my localhost http://localhost:3000/api/users/, it shows
[{"id":1,"username":"Iggy1","items":[{"id":1,"list_id":1,"name":"Wash dishes","completed":true},{"id":7,"list_id":1,"name":"Finish this assignment","completed":false}],"lists":[{"id":1,"name":"Important!","user_id":1,"permission":"private"},...
It is returning an array of hashes. I am sure I missed an important step when I was setting up my serializer. How can I reformat my array of hashes into JSON API format?
I've read getting started guide, rendering, and JSON API section but I still couldn't figure it out. I might have overlooked it.
Some of my codes:
app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :username#, :email
has_many :items, through: :lists
has_many :lists
end
app/controllers/api/users_controller.rb
def index
#users = User.all
render json: #users, each_serializer: UserSerializer
end
routes
Rails.application.routes.draw do
namespace :api, defaults: { format: :json } do
resources :users do
resources :lists
end
end
end
Let me know if I can clarify it better. Thanks!!
(Answer from comments)
To use the JSON API adapter, you need to declare you want to use it.
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi
As per the AMS readme.
ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`
from 0-10-stable documentation
However, this did not solve my problem. It was due to the Hash being nested as discussed in this SO post

How to get nested JSON / hash elements using postgres_ext-serializers?

I'm trying to get the postgres_ext-serializers gem working, and I built a test project very similar to https://github.com/dockyard/postgres_ext-serializers/blob/master/test/test_helper.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :mobile
embed :ids, include: true
has_one :address, serializer: AddressSerializer
def include_mobile?
false
end
alias_method :include_address?, :include_mobile?
end
class AddressSerializer < ActiveModel::Serializer
attributes :id, :district_name
embed :ids, include: true
end
When I try to run the serializers the output doesn't seem to have nested elements. For example my serializer to_json output is:
"{\"users\":[{\"id\":1,\"name\":\"Aaron\",\"mobile\":null}, \n {\"id\":2,\"name\":\"Bob\",\"mobile\":null}],\"addresses\":[{\"id\":1,\"district_name\":\"Rob's Address\"}]}"
Notice how users and address are two separate elements of a hash, intead of being nested. If I remove the postgres_ext-serializers gem, then the output is as expected:
"[{\"id\":1,\"name\":\"Rob\",\"mobile\":null,\"address\":{\"id\":1,\"district_name\":\"Rob's Address\"}},{\"id\":2,\"name\":\"Bob\",\"mobile\":null,\"address\":null}]"
The address is embedded in the user hash exactly how I'm expecting it.
What am I missing, do I need to change anything to make the elements nested when using postgres_ext-serializers?
Thanks!
It seems, that the JSON you receive after serialization is what postgres_ext-serializers expects. Take a look into this test. expected_json in the first test case case is:
{
"users": [
{
"id": <UserID>,
"name": "John",
"mobile": "51111111",
"offer_ids": [],
"reviewed_offer_ids": []
}
],
"offers": [],
"addresses": [
{
"id": <AddressID>,
"district_name": "mumbai"
}
]
}
It looks very similar to the JSON you have received. But, to be honest, as include_address? method in your example returns false, I expect you must have not "addresses" field included into resulting JSON at all.

Resources