Alfredo Gallegos
Alfredo Gallegos

Reputation: 643

Active Model Serializers -- Customizing JSON response

Quick question here. Tried searching to no avail.

I'm working to get a RESTful API off the ground for my current project. I've found Rails to be an absolute darling for this purpose, so check another mark into rails' awesomeness!

However I've hit a snag while designing my current API response for /users which is a method that's supposed to return a JSON array of User objects:

My User model:

class User < ActiveRecord::Base

  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable

  has_many :favorites
  has_many :reservations

  has_many :venues, through: :favorites
  has_many :venues, through: :reservations

  belongs_to  :gender
  belongs_to  :city
  belongs_to  :role
  has_one  :avatar

  has_many :payments
  has_one  :payment_history

  has_many :promotion_claims
  has_many :social_identifiers

end

My routes:

Rails.application.routes.draw do

  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      devise_for :users

      ..........................

      resources :roles
      resources :genders
      resources :cities

      #Users method
      get 'users', to: "users#index"

    end
  end

My controller:

class API::V1::UsersController < API::V1::ApplicationController
  before_action :set_address, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
    render json: @users
  end
end

My serializers:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :cellphone, :email, :birthday, :username
  has_one :gender
  has_one :city
  has_one :role
end

class GenderSerializer < ActiveModel::Serializer
  attributes :name
end

class CitySerializer < ActiveModel::Serializer
  attributes :name
end

class RoleSerializer < ActiveModel::Serializer
  attributes :name
end

This achieves a pretty satisfying result as far as what I get as a json response:

// GET http://localhost:3000/api/v1/users

[
  {
    "id": 1,
    "first_name": "Richmond",
    "last_name": "Huels",
    "cellphone": "29 8244 9100",
    "email": "[email protected]",
    "birthday": "2011-02-23T19:24:00.151Z",
    "username": "alba.ortiz",
    "gender": {
      "name": "Male"
    },
    "city": {
      "name": "San Pedro Garza García"
    },
    "role": {
      "name": "admin"
    }
  },

However, what I want my JSON response to be is something more like:

// 20160225162402
// GET http://localhost:3000/api/v1/users

[
  {
    "id": 1,
    "first_name": "Richmond",
    "last_name": "Huels",
    "cellphone": "29 8244 9100",
    "email": "[email protected]",
    "birthday": "2011-02-23T19:24:00.151Z",
    "username": "alba.ortiz",
    "gender": "Male",
    "city": "San Pedro Garza García",
    "role": "rp"
  },

I tried overriding the attributes method in UserSerializer and make it so I can do whatever I want with the json hash before returning it but it didn't work:

  #attributes override to get the right format for the response
  def attributes
    data = super
    data[:city] = data[:city][:name]
    data[:gender] = data[:gender][:name]
    data[:role] = data[:city][:name]
    data
  end

Is there any way to achieve what I need for my API?

Thank you rubyists, you rule and keep being amazing!

Upvotes: 3

Views: 1368

Answers (2)

Alfredo Gallegos
Alfredo Gallegos

Reputation: 643

I figured it out:

I feel like it's a hacky approach since basically, I'm not using the serializers for the models that are related to my User model and I'm overriding the attributes method, but what I did is that I removed all relationships from my User serializer then I overrode attributes exactly as I wrote it before:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :gender, :cellphone, :city, :email, :birthday, :username, :role

  def attributes
    data = super
    data[:gender] = data[:gender][:name]
    data[:city] = data[:city][:name]
    data[:role] = data[:role][:name]
    data
  end
end

This gets me the JSON response that I need perfectly:

enter image description here

EDIT: I found an even better way:

My serializer:

class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :gender, :cellphone, :city, :email, :birthday, :username, :role

  def gender
    return object.gender.name
  end

  def city
    return object.city.name
  end

  def role
    return object.role.name
  end

end

A lot cleaner and more object-oriented in my opinion.

Make sure that all the other serializers are in place and that the models have the proper validations for their fields.

The results are exactly the same:

enter image description here

Upvotes: 2

aldrien.h
aldrien.h

Reputation: 3635

If you want to merge the result into single hash.

You can use flatten method, example.

s = [ 1, 2, 3 ]           #=> [1, 2, 3]
t = [ 4, 5, 6, [7, 8] ]   #=> [4, 5, 6, [7, 8]]
a = [ s, t, 9, 10 ]       #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
a.flatten                 #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In your case, after getting all the result and pass it to variable "data"

data.flatten

Upvotes: 0

Related Questions