Skip to content

Instantly share code, notes, and snippets.

@SnoopSqueak
Created March 19, 2018 23:25
Show Gist options
  • Save SnoopSqueak/9fccf64c8e0af7be70f343c8949f79ae to your computer and use it in GitHub Desktop.
Save SnoopSqueak/9fccf64c8e0af7be70f343c8949f79ae to your computer and use it in GitHub Desktop.

Serializers

Why Serialize?

API's either receive data-modifying requests (post, put, delete) or data-accessing requests (get), and in either case, their response can involve retreiving data, serializing that data into JSON, and returning it.

In Rails, this serialization could be done manually, by, say, creating a serializer method:

# User.rb
def serialize
  hash = {}
  attributes.each{ |key, val| hash[key] = val }
  hash
end

But already, we run into problems of access. What if we don't want to send certain attributes (say, timestamps, or ids, or protected password information)? We could specifically create and continually update hashes of only the desired data. But then what if we want different information to be serialized in different contexts, or for different users? And what if we want non-attribute information (formatted timestamp, or full name, for example)?

Things can quickly grow out of hand.

Active Model Serializers

Rails being Rails, they've got a nice solution that's only a gem away: Active Model Serializers.

So let's add the gem to our Gemfile:

gem 'active_model_serializers', '~> 0.10.0'

Bundle install, and then, to save some time and thought, let's use the baked-in generators to build our first serializer:

rails g serializer user

That should create a new serializers folder in app, and a user_serializer.rb file within it. It'll look something like this:

class UserSerializer < ActiveModel::Serializer
  attributes :id
end

So how does this work? We add attributes that we want serialized as arguments to the attributes method. If we want to add faux-attributes or overwrite attributes with different serialized versions, we add them to the arguments list and then define them in the same file.

The use of object in the below code refers to the object being serialized. In this case, it's a single user.

class UserSerializer < ActiveModel::Serializer
  attributes :id, :created_at, :full_name, :email, :bio

  # Delegate the practical definition of `full_name` to
  # the User model, where it belongs, rather than
  # (re)defining it here.
  def full_name
    object.full_name
  end

  def created_at
    object.created_at.strftime('%B %d, %Y')
  end
end

You can test this out in your Ruby console by initializing a new UserSerializer for a user object:

UserSerializer.new(User.first).as_json
# => "{\"user\":{\"id\":1,\"full_name\":\"Fake Name\",\"email\":\"fake@email.com\",\"created_at\":\"December 31, 2000\"}}"

That was pretty easy, but what if we want varied serializers for varied situations? Let's create a new InsecureUserSerializer which serializes exactly the information that someone would need to sign in as a user:

class InsecureUserSerializer < ActiveModel::Serializer
  attributes :id, :email, :password, :full_name

  def full_name
    object.full_name
  end
end

Just throw this class into the same serializers directory.

We can use this one identically in the console.

Actually Using Serializers

Well, cool, but what's the point, right?

Serializers' uses become much clearer when creating API controllers. Take a look at this example API users_controller:

class Api::UsersController < ApiController
  def index
    return permission_denied_error unless conditions_met

    users = User.all

    render json: users, each_serializer: InsecureUserSerializer
  end

  private

  def conditions_met
    true # We're not calling this an InsecureUserSerializer for nothing
  end
end

For more information on this ApiController class (and where that permission_denied_error comes from), take a look at our resource on Cross-Site Request Forgery.

And that's a wrap. All you need to do is make a GET request of the route for the above index action, and deal with the JSON response:

{
  "users": [
    {
      "id": 1,
      "email": "email@fake.com",
      "password": "password",
      "full_name": "John Smith"
    },
    {
      "id": 2,
      "email": "fake@email.com",
      "password": "password2",
      "full_name": "Sally Smith"
    }
  ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment