4
votes

I'm seeing a strange behaviour regarding rails 5, active model serializer and the json-api adapter.

Given the following User model with the Rolify gem:

class User < ActiveRecord::Base
  #
  # Gem Includes
  #
  rolify


  # Include devise modules.
  devise :database_authenticatable,
      :recoverable, :rememberable, :trackable, :validatable
  include DeviseTokenAuth::Concerns::User

  #
  # Callbacks
  #
  after_create :assign_default_role

  #
  # Attributes
  #
  attr_accessor :remote_image

  #
  # Validations
  #
  validates :name, presence: true, length: {in: 1..100}
  validates :last_name, presence: true, length: {in: 1..100}
  validates :role_ids, presence: true, on: :update

  #
  # Relations
  #
  belongs_to :current_scenario, class_name: "Scenario"


  #
  # Private Instance Methods
  #
  def assign_default_role
    self.add_role(:user) if self.roles.blank?
  end

end

and the following controller code:

def show
  @user = User.find(params[:id])
  authorize @user
  render json: @user, include: ['roles'], status: :ok
end

As you can see, I'm including the roles relationship to be rendered as part of the json api response, with json-api adapter format.

FYI, the UserSerializer:

class UserSerializer < ActiveModel::Serializer
  #
  # Attributes
  #
  attributes :id, :email, :name, :last_name, :image_url, :image_thumb_url, :created_at, :updated_at, :current_scenario_id, :last_sign_in_at

  #
  # Relations
  #
  has_one :current_scenario
  has_many :roles

  #
  # Methods
  #
  def image_url
    object.image_url
  end

  def image_thumb_url
    object.image_url(:thumb)
  end
end

When retrieving the json response, I get the following:

{
  "data": {
    "id":"2",
    "type":"users",
    "attributes": {
      "email":"[email protected]", ...
    },
    "relationships": {
      "current-scenario": {
        "data": {
          "id":"204",
          "type":"scenarios"
        }
      },
      "roles": {
        "data": [
          {
            "id":1,
            "name":"user",
            "resource-type":null,
            "resource-id":null,
            "created-at":"2017-01-23T10:27:08.707-03:00",
            "updated-at":"2017-01-23T10:27:08.707-03:00"
          },
          {
            "id":2,
            "name":"admin",
            "resource-type":null,
            "resource-id":null,
            "created-at":"2017-01-24T09:40:53.020-03:00",
            "updated-at":"2017-01-24T09:40:53.020-03:00"
          }
        ]
      }
    }
  }
}

As you can see, the included relationship roles with all its attributes is inside the relationships fragment of the json-api response. Shouldn't the roles data be inside the included fragment, which by the way is missing? Moreover inside the relationship fragment roles should appear only as a reference like: {relationships: {roles: [{id: "1", type: "role"}, {id: "2", type: "role"}]} am I wrong?

To contrast this, look what happens when also including the current_scenario relationship:

{
  "data": {
    "id":"2",
    "type":"users",
    "attributes": {
      "email":"[email protected]",
      "name":"Tomás",
      "last-name":"Alvarez", 
      ...
    },
    "relationships": {
      "current-scenario": {
        "data": {
          "id":"204",
          "type":"scenarios"
        }
      },
      "roles": {
        "data": [
          {
            "id":1,
            "name":"user",
            "resource-type":null,
            ...
          }
        ]
      }
    },
    "included": [
      {
        "id":"204",
        "type":"scenarios",
        "attributes": {
          "name":"Scenario reload II",
          "description":null,
          "created-at":"2017-04-18T11:55:35.242-03:00",
          "updated-at":"2017-04-18T11:55:35.242-03:00"
        },
        "relationships": {
          "scenario-stocks": {
            "data":[]
          }
        }
      }
    ]
  }
}

See how now the included fragment appears with all the information about current_scenario and only the reference to current_scenario is added to the relationships fragment. Is this because roles is a has_many relationship in the active model serializer while current_scenario is a belongs_to ? Am I understanding wrong the json-api adapter specification?

Many thanks!

3

3 Answers

1
votes

Ouch. The inconsistency in the JSON-API response was because i forgot to add a Role model serializer in the backend side (Rails 5). This is the json response now, which is what i was looking for:

{
"data": {
    "id": "2",
    "type": "users",
    "attributes": {
        "email": "[email protected]",
        "name": "Tomás",
        "last-name": "Alvarez",
        "image-url": "http://localhost:3001/uploads/user/image/2/05a4dc7.jpg",
        "image-thumb-url": "http://localhost:3001/uploads/user/image/2/thumb_05a4dc7.jpg",
        "created-at": "2017-01-23T10:39:12.485-03:00",
        "updated-at": "2017-04-25T16:32:14.610-03:00",
        "current-scenario-id": 204,
        "last-sign-in-at": "2017-04-25T16:29:03.559-03:00"
    },
    "relationships": {
        "current-scenario": {
            "data": {
                "id": "204",
                "type": "scenarios"
            }
        },
        "roles": {
            "data": [{
                "id": "1",
                "type": "roles"
            }, {
                "id": "2",
                "type": "roles"
            }]
        }
    }
},
"included": [{
    "id": "204",
    "type": "scenarios",
    "attributes": {
        "name": "Scenario reload II",
        "description": null,
        "created-at": "2017-04-18T11:55:35.242-03:00",
        "updated-at": "2017-04-18T11:55:35.242-03:00"
    },
    "relationships": {
        "scenario-stocks": {
            "data": []
        }
    }
}, {
    "id": "1",
    "type": "roles",
    "attributes": {
        "name": "user"
    }
}, {
    "id": "2",
    "type": "roles",
    "attributes": {
        "name": "admin"
    }
}]

}

0
votes

Sorry for this bug. We haven't figured out how to determine the type for a relationship when no serializer is found. Would it have been more helpful if you got an exception?

-1
votes

This is how JSON API works you can not retrieve desired model + relationship in 'one object'. Relationships always are separated. So you need to 'glue' it somehow. You can use gem that can help you with it, or you can do it on front end side (all big frameworks support it). In general this approach with 'relationships' looks weird, but when you have complex object with tons of dependencies, this is the only one way that works.