13

I'm building a JS app with a Rails backend and in order not to confuse snake and camel cases, I want to normalize it all by returning camelcase key names from the server. So user.last_name would return user.lastName when returned from the API.

How do I achieve this? Thanks!

Edit: Added Controller Code

class Api::V1::UsersController < API::V1::BaseController
  # authorize_resource
  respond_to :json, only: [:index]
  def sky
      @user = User.find_by_id(params[:user_id])

      if @user
          obj =  {
              sky: {
                  sectors: @user.sectors,
                  slots: @user.slots
              }
          }

          render json: obj
      else
          raise "Unable to get Sky"
      end
  end
end
Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
Zack Shapiro
  • 6,648
  • 17
  • 83
  • 151

4 Answers4

12

The way I do it is using ActiveModelSerializer and the json_api adapter:

In your Gemfile, add:

gem 'active_model_serializers'

Create a new file /config/initializers/ams.rb containing:

ActiveModelSerializers.config.adapter = :json_api
ActiveModelSerializers.config.key_transform = :camel_lower

Your controller action should look like this:

class ApiController < ApplicationController
  def sky
    @user = User.find_by_id(params[:user_id])

    if @user
      render json: @user, serializer: UserSkySerializer
    else
      raise "Unable to get Sky"
    end
  end
end

Now you need to create a serializer. First, create a new directory app/serializers/.

Next, create a new serializer app/serializers/user_sky_serializer.rb, containing:

class UserSkySerializer < ActiveModel::Serializer
  attributes :sectors, :slots
end

The result will be similar to what you describe in your obj hash, but all attribute keys will be rendered in camelCase using the jsonapi standard.

moveson
  • 5,103
  • 1
  • 15
  • 32
  • Thanks moveson, I did that and I'm getting an unserialized version of the json with `[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash` in my console. Any ideas here? – Zack Shapiro Jun 16 '17 at 20:48
  • Could it be because if my inheritance? `class Api::V1::UsersController < API::V1::BaseController < ActionController::Base` (I combined the inheritance for brevity for this post) – Zack Shapiro Jun 16 '17 at 20:50
  • FWIW, this is a Rails app, not a rails-api app – Zack Shapiro Jun 16 '17 at 21:41
  • How are you setting `@resource` in your controller? Please edit your question to show the controller code. – moveson Jun 20 '17 at 14:06
  • I just wanted to follow up on this since ActiveModelSerializers seems like the best, least hacky way to achieve the lowercased camelcase that I'm going for in my API response. Thanks! – Zack Shapiro Jun 20 '17 at 19:39
  • @ZackShapiro If you feel this answer was the most helpful, please choose it as the correct answer when you have a moment. – moveson Jun 07 '18 at 14:52
  • 1
    Can this be changed on-the-fly? All of our JSON is snake_case but a new vendor seems to prefer ClassCase. I only want to use ClassCase in serializers in their namespace. Is that possible? – Dan Mar 20 '19 at 18:23
  • 1
    @Dan I don't believe this can be changed without a server restart. – moveson Mar 20 '19 at 19:10
  • @moveson well that's a bummer. I'm not terribly surprised to hear that is the case. My plan now is to push back on the vendor asking for this. It's not often I am the customer and in a position to be bossy. – Dan Mar 21 '19 at 20:03
6

Another option is the use the olive_branch gem. As described in this post, all you need to do is:

  1. Add this gem
  2. add config.middleware.use OliveBranch::Middleware in application.rb.

Then, add this header to requests from your client-side app:

'X-Key-Inflection': 'camel'

If you are using libs like axios, you can add this header to a constant along with other headers:

const HEADERS = {
  ...
  'X-Key-Inflection': 'camel'
}

const request = axios.post(url, param, HEADERS)

This way you don't need to deep_transform keys manually on the server side. Even deeply-nested json keys will be camelized. Example response from such request:

[
  {
    "id": 1,
    "firstName": "Foo",
    "lastName": "Bar",
    "createdAt": ...,
    "updatedAt": ...,
    ...
    "cabinAssignments": [
      {
        "id": 1,
        "cabinKeeperId": 1,
        "userId": 1,
        "houseId": 1,
        ...
      }
    ]
  }
]
dimitry_n
  • 2,939
  • 1
  • 30
  • 53
2

You don't mention your Rails version, so for others who look for this, I'll mention that in Rails 5 the answer is to use #camelize from ActiveSupport::Inflector. In my case, I had to call it in a chain on an ActiveRecord model (t below):

  def index
    trucks = AvailableTruck.all.map do |t|
      t.as_json(only: AvailableTruck.exposed)
       .deep_transform_keys(&:camelize).deep_transform_values(&:upcase)
    end
    render json: trucks
  end

#deep_transform_keys may or may not be necessary depending on your application.

Derrell Durrett
  • 544
  • 10
  • 26
0

You can achieve that with ruby's method_missing method.

Create a file something like concerns/method_missing_extension.rb and put following code in it.

module MethodMissingExtension
  def method_missing(method_name, args = {})
    if self.class.column_names.include? method_name.to_s.underscore
      send method_name.to_s.underscore.to_sym
    else
      super
    end
  end
end

Include this module in each model like include MethodMissingExtension.

Now whenever you do user.firstName then user.first_name will returned.

Radix
  • 2,527
  • 1
  • 19
  • 43
  • I hate downvote's without reason. Downvoters any reason to downvote? – Radix Jun 20 '17 at 01:32
  • Sure, I down voted because a similar answer already exists and I wrote a comment with the reason as to why. I only down voted as a signal for future people that come across this post as to where the correct answer may lay. Hope that clears it up – Zack Shapiro Jun 20 '17 at 15:48
  • That answer is very different. Their implementation of `method_missing` is a bit different and need to create that for every attribute for each model. But, here it's only one time written module which can be included in all required `models` and you get the desired result. Hope it clarify your confusion. – Radix Jun 20 '17 at 16:19
  • And yes, if you can achieve something using your own little code then why to use some library or gem. – Radix Jun 20 '17 at 16:21
  • What I was going for, respectfully, is that I'm not interested in `method_missing`, because I'd have to implement it for every field on every model which is onerous and inefficient. My goal is to use `active_model_serializers` – Zack Shapiro Jun 20 '17 at 16:22
  • Okay, it's your choice to use any option you want, our aim is try to provide an answer. And yes, you don't need to explicit implement `method_missing` for each attribute for each model. BTW, I'm not sure it won't help future readers. :) – Radix Jun 20 '17 at 16:29