rails: JSON does not render type attribute for single-table inherited models

I have the following classes participating in an STI hierarchy:

class Server < ActiveRecord::Base
  # has database column named "type"
end

class WindowsServer < Server
end

class LinuxServer < Server
end

In my ServersController class, I have code snippet that says:

def show
  @server = Server.find(params[:id])
  debugger
  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @server }
  end
end

When I examine the @server object in the debugger, I get the following:

(rdb:43) @server.class.name
"WindowsServer"
(rdb:43) @server.type
"WindowsServer"
(rdb:43) @server
#<WindowsServer id: 5, hostname: "server1", ip_address: "10.1.2.3", description: "", environment_id: 1, type: "WindowsServer", created_at: "2011-11-04 03:38:13", updated_at: "2011-11-04 03:38:13">
(rdb:43) @server.to_json
"{\"created_at\":\"2011-11-04T03:38:13Z\",\"description\":\"\",\"environment_id\":1,\"hostname\":\"server1\",\"id\":5,\"ip_address\":\"10.1.2.3\",\"updated_at\":\"2011-11-04T03:38:13Z\"}"

It’s curious that the type attribute isn’t included in the JSON string. Sure enough, the type attribute is also not rendered when I fetch this resource in JSON format:

$ curl http://localhost:3000/servers/5.json
{"created_at":"2011-11-04T03:38:13Z","description":"","environment_id":1,"hostname":"server1","id":5,"ip_address":"10.1.2.3","updated_at":"2011-11-04T03:38:13Z"}

Seems that the type attribute ought to be included.

About this issue

  • Original URL
  • State: closed
  • Created 13 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

I ran into this issue today and found that in my case it was easier to change the structure of the delivered JSON than to change the way the client consumes the response. Inside the model class definition:

def serializable_hash options=nil
  super.merge "type" => type
end

The :methods option works as well

Server.first.to_json(only: :id, methods: :type)
# => {"id": "1", "type": "WindowsServer"}

The project I work on ran into this issue recently. While @HandyAndyShortStack’s workaround does the trick, I decided to dig into the history a bit to find out what was going on with this one line of code and why this issue is still causing trouble after having been known for years.

  • It all starts with a 2006 commit when Base#to_xml was first written by @dhh. Judging from the tests it appears that :type (later refactored to self.class.inheritance_column) is explicitly excluded from the serialized attributes because it was assumed to be always present in the root XML element, and thus redundant.
  • The 2007 commit referenced above copied the XML serialization logic to the new Base#to_json method. However, the JSON serialization did NOT include any root element, and so any STI type information was lost.
  • This issue was addressed in a 2008 commit through the addition of the include_root_in_json property. It was initially false by default, then changed to default to true for Rails 3 in a 2010 commit.
  • However, this didn’t last long. include_root_in_json was set to false by default in Rails 3.1 scaffolding initializer files in a 2011 commit.
  • Finally, Rails 4 now fully defaults include_root_in_json to false in a 2012 commit, removing the need for the initializer scaffolding.

Long story short, with include_root_in_json now defaulting to false, this issue has become more annoying and unexpected, since there is no documentation of this specific behavior other than the source code itself. The proper fix would filter out the inheritance_column attribute only when the include_root_in_json attribute is explicitly set to true (which has been deprecated since 2011 and disabled by default since 2012).

Just want to chime in from 2022 and this “feature”, in rails 7, still causes unexpected breakage

It seems it has been like this since the beginning, and I’d argue this is because it’s a server concern related to how the data is stored, and not a client concern - meaning that you don’t need to expose that you use STI for some reason through the serialized object. And I think it’s ok to work this way.

In any case, you should be able to force adding the attribute if you really want it, by giving in the :only option. I’m closing here, please let us know if you still find this an issue. Thanks!

def index
  @users = Users.active
  respond_to do |format|
    format.json { render :json => @users, methods: :type }
  end
end

This works like a charm 🚀

Not sure I’m following the reasoning here. Take the index action for example:

def index
  @servers = Server.all
  render :json => @servers
end

The output will just be a generic list of servers with no way to differentiate between the windows and linux servers, yeah?