Search code examples
ruby-on-railsactive-model-serializersrails-api

Custom attributes not showing in Active Model Serializer


I am getting incomplete fields for my serializers custom records when they include custom fields:

Category Serializer:

class CategorySerializer < ApplicationSerializer
  attributes :id, :name, :localized_name, :picture, :status, :visible, :country

def localized_name
  I18n.t((object.name + '_name').to_sym)
end

end

Match Serializer:

class MatchSerializer < ApplicationSerializer
  attributes :id, :player1, :player2, :match_type, :status, :winner, :score, :match_categories

  def match_categories
    #adds international category array to the intersection of player1 and player2 category arrays
    #this is done so that players playing against other countries player will allways
    #play at least international categories and any other categories common to both

    Category.where(country_id: 1) +
(Category.where(country_id: object.player1.country) & Category.where(country_id: object.player2.country))
  end

end

When rendering the category, I get the complete fields as a JSON response:

{
  "data": [
    {
      "id": "1",
      "type": "categories",
      "attributes": {
        "name": "Sports",
        "localized_name": "Deportes",
        "picture": "default_url",
        "status": "active",
        "visible": false,
        "country": {
          "id": 1,
          "name": "International",
          "country_code": "NA",
          "picture": "default_url",
          "language_code": "en",
          "status": "active",
          "visible": false,
          "created_at": "2016-06-06T16:12:29.701Z",
          "updated_at": "2016-06-06T16:12:29.701Z"
        }
      }
    },

When rendering a match, the Match serializer will calculate an array of categories involved in the match and render Category information for each. However, as you can see, the category custom field: localized_name is not included.

>{
  "data": [
    {
      "id": "1",
      "type": "matches",
      "attributes": {
        "player1": {
          "id": 1,
          "provider": "email",
          "uid": "[email protected]",
          "country_id": 2,
          "name": null,
          "first_name": null,
          "last_name": null,
          "gender": null,
          "fb_auth_token": null,
          "nickname": null,
          "image": null,
          "email": "[email protected]",
          "friend_count": null,
          "friends": null,
          "status": "active",
          "premium": null,
          "created_at": "2016-06-06T16:13:58.711Z",
          "updated_at": "2016-06-06T16:14:15.722Z"
        },
        "player2": {
          "id": 2,
          "provider": "email",
          "uid": "[email protected]",
          "country_id": 2,
          "name": null,
          "first_name": null,
          "last_name": null,
          "gender": null,
          "fb_auth_token": null,
          "nickname": null,
          "image": null,
          "email": "[email protected]",
          "friend_count": null,
          "friends": null,
          "status": "active",
          "premium": null,
          "created_at": "2016-06-07T08:03:38.472Z",
          "updated_at": "2016-06-07T08:03:38.611Z"
        },
        "match_type": "Duel",
        "status": "active",
        "winner": null,
        "score": null,
        **"match_categories": [
          {
            "id": 1,
            "country_id": 1,
            "name": "Sports",
            "picture": "default_url",
            "status": "active",
            "visible": false,
            "created_at": "2016-06-06T16:12:29.893Z",
            "updated_at": "2016-06-06T16:12:29.893Z"
          },
          {
            "id": 2,
            "country_id": 2,
            "name": "Futbolistas Peruanos",
            "picture": "default_url",
            "status": "active",
            "visible": false,
            "created_at": "2016-06-06T16:12:29.961Z",
            "updated_at": "2016-06-06T16:12:29.961Z"
          },

How can I get the localized_name field to display in this scenario?

EDIT: Requested controllers. Pretty standard

class MatchesController < ApplicationController
  before_action :set_match, only: [:show, :update, :destroy]

 # GET /matches
  def index
    @matches = Match.all

    render json: @matches
  end

  # GET /matches/1
  def show
    render json: @match
  end

  # POST /matches
  def create
    @match = Match.new(match_params)

    #if player1 not explicitly included will set player1 as uid header of API call
    @match.player1_id ||= current_user.id
    #if player2 parame set as random will generate random user from all curently active users
    if params[:match][:player2_id] == 'random'
      #call model method that returns a random array of userids to play as player 2 but
      #passing player1_id so that method will remove it from possible responses
      #since player1 cannot play itself
      @match.player2_id = @match.active_random_player2(@match.player1_id)
    end

    if @match.save
      render json: @match, status: :created, location: @match
    else
      render json: @match.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /matches/1
  def update
    if @match.update(match_params)

      render json: @match
    else
      render json: @match.errors, status: :unprocessable_entity
    end
  end

  # DELETE /matches/1
  def destroy
    @match.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_match
      @match = Match.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def match_params
  params.require(:match).permit(:player1_id, :player2_id, :match_type, :bet, :status, :winner, :score)
    end
end


class CategoriesController < ApplicationController
  before_action :set_category, only: [:show, :update, :destroy]

  # GET /categories/newmatch
  def newmatch
    @categories = Category.where(country: current_user.country)

    render json: @categories
  end

  # GET /categories
  def index
    if params[:all] && params[:all] != 'false'
      @categories = Category.all
    else
      @categories = Category.where(country: current_user.country)
    end

    render json: @categories

  end

  # GET /categories/1
  def show
    render json: @category
  end

  # POST /categories
  def create

    @category = Category.new(category_params)
    if @category.save!
      render json: @category, status: :created, location: @category
    else
      render json: @category.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /categories/1
  def update
    if @category.update(category_params)
      render json: @category
    else
      render json: @category.errors, status: :unprocessable_entity
    end
  end

  # DELETE /categories/1
  def destroy
    @category.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_category
      @category = Category.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def category_params
      #add permitted translation locale parameters based on required translations
      #resource agnostic method in application controller
      params.require(:category).permit(:name, :picture, :status, :visible, :country_id, :all)
    end
end

Edit:

when trying Francesco Lupo's suggestion I get this information in the match_categories query field, but I'm still not getting the custom localized_name field.

"match_categories": [
          {
            "object": {
              "id": 1,
              "country_id": 1,
              "name": "Sports",
              "picture": "default_url",
              "status": "active",
              "visible": false,
              "created_at": "2016-07-08T23:11:43.304Z",
              "updated_at": "2016-07-08T23:11:43.304Z"
            },
            "instance_options": {
              "root": false
            },
            "root": false,
            "scope": null
          },

Solution

  • You need to serialize each of your categories, querying is not enough, maybe try this:

    def match_categories
      categories = Category.where(country_id: 1) + (Category.where(country_id: object.player1.country) & Category.where(country_id: object.player2.country))
      categories.map { |category| CategorySerializer.new(category, root: false) }
    end
    

    This way all of your categories will be serialized properly.

    According to @Augusto comment, maybe an update of active_model_serializer is required.