11

I am building a Rest API using rails-api and active-model-serializer to easily filter the required fields in the JSON. I am also using the has_one association in these serializers. All I wanted to know is how do I specify a different key name for the has_one attribute.

That is, I have two models say: Employee and Address, and there is a has_one :address in say EmployeeSerializer. The response that I get is:

{
  id: 1,
  address: {
   street: "some_street",
   city: "some_city"
  }
}

But I would like to get the following response:

{
  id: 1,
  stays: {
   street: "some_street",
   city: "some_city"
  }
}

I tried using has_one :address, :key => :stays, but that doesn't seem to work.

swaroopsm
  • 1,389
  • 4
  • 18
  • 34
  • Which version of AMS do you use? I'm not intimately familiar with the code base, but from a quick look they `key` argument seems not to have been included in all recent ones. As a workaround, try defining `has_one :stays` together with `def stays; object.address; end`. – janfoeh Dec 30 '14 at 15:24
  • @janfoeh I'm using the 0.9.0 version. Also I am using a different serializer for the association. i.e., `has_one :address, key: 'status', serializer: MyAddressSerializer`. It's taking the attributes from `MyAddressSerializer` but the attribute key still is `address` – swaroopsm Dec 30 '14 at 16:21
  • Ah. If I read the [changes between 0.9.0 and 0.9.1](https://github.com/rails-api/active_model_serializers/compare/v0.9.0...v0.9.1) correctly, the argument was called `embed_key` in 0.9.0, not `key`. – janfoeh Dec 30 '14 at 16:37
  • It's still the same. No luck! :( – swaroopsm Dec 30 '14 at 16:47
  • Sorry, then I'm clueless as well. I use 0.8.1, and I just successfully tested `has_one :uploader, key: 'user', serializer: 'SpecialSerializer'` - both arguments work as expected. – janfoeh Dec 30 '14 at 16:56
  • Alright let me check with 0.8.1 version. – swaroopsm Dec 30 '14 at 17:04

3 Answers3

10

You can define stays as one of the attributes you want to have in your json.

Naturally, the serializer will go to the model instance and not find an attribute in the model with that name. So, it will be sitting there scratching its head as to what in the world the value of :stays should be.

That's OK, because you can define an instance method in your serializer telling it exactly what that value should be. In that instance method, you are given object variable which is the object the serializer is currently processing. object.address therefore will be your Address instance.

Once you have your address instance, you can instantiate a serializer which will use that instance to display the fields outlined inside of it. I believe, root: false is necessary as otherwise, :stays attribute (or whatever the serializer gives you back in this case) will be displayed inside another :stays attribute.

Your final serializer for Employee should look as follows:

class EmployeeSerializer < ActiveModel::Serializer
  attributes :id, :name, :stays

  def stays
    MyAddressSerializer.new(object.address, root: false)
  end

end
Kalman
  • 8,001
  • 1
  • 27
  • 45
  • Add some description to why it will do the job. Right now as the answer stands on its own. It is of very low quality and does not suit the SO Q&A style very well and has been marked as a Low Quality Post. – Cheesebaron Dec 30 '14 at 18:03
  • 1
    @Cheesebaron how is this? :) – Kalman Dec 30 '14 at 18:36
  • 2
    Would this work for a `has_many` relationship that needs a `each_serializer` as well? – swaroopsm Dec 31 '14 at 05:50
  • 1
    For a `has_many` this is what I do: `ActiveModel::ArraySerializer.new(object.my_has_many_assoc, each_serializer: MySerializer). Thank you @KalmanHazins! ` – swaroopsm Dec 31 '14 at 06:42
  • 1
    For anyone trying to implement this strategy with the additional details by @swaroopsm in the latest AMS master (>=0.10.0 as of April 2016), please keep in mind that the answer posted will not work Use this instead: ActiveModel::Serializer::CollectionSerializer.new(object.my_has_many_assoc, each_serializer: MySerializer) – Alfredo Gallegos Apr 11 '16 at 03:06
7

@janfoeh is right on. I just tested this in version 0.8 and it works just fine

  1. gem "active_model_serializers", "~> 0.8.0" in your Gemfile
  2. Kill the server
  3. $bundle install
  4. has_one :address, key: "stays"
  5. Start the server back up

Works great!

As the documentation for the gem says (here), "you probably want to use version 0.8"

Kalman
  • 8,001
  • 1
  • 27
  • 45
  • If I use the `0.8.0` version, the `MyAddressSerializer` doesn't work in: `has_one :address, key: 'status', serializer: MyAddressSerializer` – swaroopsm Dec 30 '14 at 16:22
  • @swaroopsm is `Address` always serialized with `MyAddressSerializer`? If yes, you can define that on `Address` itself with `def active_model_serializer; MyAddressSerializer; end`. – janfoeh Dec 30 '14 at 16:33
  • This worked for me using `active_model_serializers (0.10.7)` – João Ferreira Jul 20 '18 at 09:29
0

From 0.10.0 as of April 2016 (thanks to @AlfredoGallegos in the comments)

The answer posted will not work for 0.10.0 +

Use this instead:

ActiveModel::Serializer::CollectionSerializer.new
  (object.my_has_many_assoc, each_serializer: MySerializer)

Also it seems most people are moving to the Netflix JSONAPI gem, it has more support and is 25 times faster (however please note it does not support the JSON default setting of AMS).

rmcsharry
  • 5,363
  • 6
  • 65
  • 108