remi / her

Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects. It is designed to build applications that are powered by a RESTful API instead of a database.
MIT License
2.05k stars 322 forks source link

Using has_many with differing JSON root object names #481

Closed samstarling closed 6 years ago

samstarling commented 6 years ago

Hey @edtjones @remiprev – I'm having some issues with has_many and parse_root_in_json. The API I'm working with looks a bit like this:

GET /users/1       { user: { name: peter } }
GET /users/1/items { items: [ { name: apple }, { name: orange } ] }
GET /items/1       { item: { name: apple } }

My classes are like this:

class User
  include Her::Model
  parse_root_in_json :client
  has_many :items
end

class Item
  include Her::Model
  parse_root_in_json :item
end

However, when I call User.find(1).items, I get this error:

undefined method `each' for #<Item:0x007faf89dc3cb0>

Is there something I'm doing wrong here? Can I make this one class work properly? Thanks!

zacharywelch commented 6 years ago

Hi @samstarling, parse_root_in_json expects arrays to be returned as

GET /users/1/items  #=> [{ item: { id: 1, name: "apple" } }]

If there's a root element on the array you should be able to use the active_model_serializers format

GET /users/1/items  #=> { items: [{ item: { id: 1, name: "apple" } }] }
GET /users/1/items  #=> { items: [{ id: 1, name: "apple" }] }

class Item
  include Her::Model
  parse_root_in_json true, format: :active_model_serializers
end

Hope this helps 😺

samstarling commented 6 years ago

@zacharywelch Aha, that makes perfect sense. I'll give that a go! Thanks for the speedy reply. I'm also having problems parsing responses like this:

{
  client: {
    name: Sam,
    surname: Starling
  },
  addresses: [
    foo: bar
  ]
}

I'm trying to parse this with a standard Her::Model class called Client and using format: :active_model_serializers, but I think I lose access to addresses.

zacharywelch commented 6 years ago

client and addresses are at the same level and considered two distinct resources. Do you have control over the api where you could return something along the lines of?

{
  client: {
    name: "Sam",
    surname: "Sterling",
    addresses: [{ line1: "123 Sesame St" }]
  }
}
samstarling commented 6 years ago

@zacharywelch Sadly I don't have any control over this API, so if there was any other way around this, that would be really useful to know about. I had a dig through the source code, but couldn't see much of a way forward. Thanks in advance for any advice!

edtjones commented 6 years ago

@samstarling I've taken to massaging the incoming json for APIs like this in a middleware. Here, for example, is a middleware to take Zoho CRM's json and turn it into something Her will understand: https://github.com/errorstudio/zoho-ruby/blob/master/lib/zoho/crm/middleware/response_parser_middleware.rb

You wouldn't have to do much rework on the incoming json in your case. My example is a bit bananas.

edtjones commented 6 years ago

(and here is where you invoke the middleware in the Her setup - https://github.com/errorstudio/zoho-ruby/blob/master/lib/zoho/crm/crm.rb#L30 )

samstarling commented 6 years ago

@edtjones Oh nice – that looks like a good solution. The API I'm working with has a slightly odd format for errors as well, so I think this provides a nice solution for that too. I'll give it a go, thanks!

zacharywelch commented 6 years ago

Going to close the issue now that we have a solution. Thanks again 😺