Rails with snakecase/camelcase conversion / frontend adapter pattern

Rails API is growing in popularity, but from what I’ve seen there’s no easy way to auto-convert the snake_case of rails to the preferred camelCase of typical frontends (i.e. JS).

There’s ways to do it, but they all feel hacky. Serializers was almost the thing, but that struggled as well in baffling ways, also due to the snake_case conversion. Expressing relationships was painful. Heavier frameworks like JSON:API seemed to heavy-handed and required completely restructuring our frontend adapters as well.

This conversion seems like a super common use-case, and I’m so confused how it’s not something immediately configurable.

We’ve been left with rolling our own frontend “adapter” system to convert data the way we want for frontend to be able to read it.

Then we had other baffling side effects, like when we’re trying to POST to an endpoint that has more than one word.

      params.require(:read_receipt).deep_transform_keys!(&:underscore)
        .permit(:last_read_at)

If you make a POST request from the API, e.g.

axios.post('/frontend/read_receipts', { readReceipt: { lastReadAt: ... } })

It will shockingly create multiple nestings in the parameters, basically converting the params into something like { read_receipt: { readReceipt: { ... } } }

It took us a ton of time to figure out you could drop the explicit object naming on the frontend request, and then somehow weirdly it works out:

axios.post('/frontend/read_receipts', { lastReadAt: ... })

That was… a solid hour or two of WTF.

@Zee could also chime in with his own horror stories of this experience. :wink: We’ve easily spent 20 hrs riffing on our adapter solution and it’s only just starting to come together, but it’s still frustrating and rough. Making something that expresses well with ActiveRecord but provides an encapsulation for frontend representation has been a breathtaking amount of work.

One could argue that there shouldn’t be an abstraction between Rails and the frontend, that if you just structure the data the way that’s expected by Rails we’re all hunky dory. That’s fair, and also I don’t think is realistic given the evolution of the frontend world.

… and we could at least start by making it easy to convert snake_case to camelCase and back.

I think the “Rails Way” here is officially to use jbuilder, although I will admit that my team does something else.

I’m curious about why people either don’t notice that jbuilder is the official Rails solution here, or do notice but choose to go a different direction.

while jbuilder is neat, it’s also pretty hidden. it isn’t mentioned in the API part of the guide, and its only proper mention in the guide (that i could find) is in the ActionView guide, and in my experience when making API-only services people usually eschew the view part altogether and they don’t care to look at this specific part of the guide :stuck_out_tongue:

in fact, that may be the reason. the view part is eschewed, and jbuilder is kind of a view thing, so people don’t use it…

i’m curious, what does your team use? when doing APIs with Rails my experience has been just handling hashes manually which isn’t particulary great haha

1 Like

THAT makes total sense. Probably something to think about given the smaller stack that --api targets.

My team uses a microframework I handrolled, tbh. API looks something like:

class UserSerializer < TinySerializer
  serialize :name, :email, :post_count
  serialize :posts, using: PostSerializer::List

  def post_count
    object.posts.count
  end
end

Handles camelCasing automatically – post_count would be transformed to postCount in the returned JSON.

Works pretty okay, maybe I’ll open source it at some point; but also, that would probably mean needing to make it ~~configurable~~ and at that point you start losing the simplicity that makes it actually good.

1 Like

my past team used GitHub - Netflix/fast_jsonapi: A lightning fast JSON:API serializer for Ruby Objects., which works fine, but it feels a little clunky?? in a way, and i feel like everyone used it a little bit in a bespoke or “wrong” way, by just kind of using the basic options like your handcrafted microframework instead of the Full Power of Netflix™ Fast® JSON API© but eh, it worked fine. i would have loved to have used/made a fork of your library instead because it feels like it would’ve fit our purposes better, but that’s now in the past…

i mean, you can just open it up without making it ~~configurable~~, if you want to change something make your own fork, etc. i particularly don’t see anything wrong with that but i suppose that’s a matter of taste lol.