Saturday 27 December 2014

API Development in Ruby On Rails, Entities

In my last blog post I presented in general the features I like the most about the grape gem. Now I will present the grape entities which are related to the presentation of the returned data. I like these entities a lot because they are an OO way to present data and they help a lot to keep my presentation layer DRY.

The last statement of every grape call is returned to the caller as JSON.
Despite this simple efficiency I like to have more control over the returned data and I use the grape entities to format the returned data wherever I can. I can also rspec them and I am doing it extensively because I am a Test Driven Guy ;)

Let me present some examples of entities their usage and how do I spec them:

Here is the spec for the entity:

describe GameEntity do
  describe 'fields' do
    subject(:subject) { GameEntity }
    it { is_expected.to represent(:id) }
    it { is_expected.to represent(:user_id).as(:owner_id) }
...


Here is the entity itself:

class GameEntity < Grape::Entity
  expose :id
  expose :user_id, as: :owner_id
...

The part of the grape API:

  namespace :games do
    desc "Retrieve all the games"
    params do
      optional :include_reviews, type: Boolean, default: true, desc: 'Accepts: true/false.'
    end
    get do
      games = Game.actual.limit(500)
      present games, with: GameEntity

This was simple so far. But I can use a more sophisticated GameEntity in case I want to present the included reviews. In all other cases I will use my old simple GameEntity to present the Game.

Let's see that case too:

I modify the grape API to this:

    ...
    if params[:include_reviews]
      games = games.includes(:reviews)
      present games, with: DetailedGameEntity
    else
      present games, with: GameEntity
    end

I also spec the new entity:

describe DetailedGameEntity do
  describe 'special fields' do
      subject(:detailed_game) { DetailedGameEntity(game).as_json }
      specify { expect(detailed_game[:reviews]).to be_an(Array) }
  end

Of course the above was a very simple example.

In real world scenarios we can find ourselves that we are reusing the entities like this:

class DetailedGameEntity < Grape::Entity
  expose :game, using: GameEntity do |game, options|
    game
  end

  expose :venue, using: VenueEntity do |game, options|
    game.the_venue
  end

  expose :visitor_team, using: TeamEntity do |game, options|
    game.visitor_team
  end

  expose :host_team, using: TeamEntity do |game, options|
    game.host_team
  end
end

As you can see we are keeping our entity codebase DRY by calling the TeamEntity twice in the DetailedGameEntity. This is a typical use of delegation of the Entities.

Let me show a more complex example of our entities:

The usage of the entities:

  desc "Retrieve all the reviews belonging to a team"
    get do
      ...
      present reviews, :with => ReviewPresenter, user: current_user
    end

The entity itself:
  class ReviewPresenter < Grape::Entity
    expose :current_user_likes do |review, options|
      review.likes.pluck(:user_id).include? options[:user].id if options[:user].present?
    end
  end

And finally the spec for the entity:
describe ReviewPresenter do
  describe 'special fields' do
    context 'without passing a user' do
      subject(:presented_review) { ReviewPresenter.new(image.review).as_json }
      specify { expect(presented_review[:current_user_likes]).to be_nil }
    end

    subject(:presented_review) { ReviewPresenter.new(image.review, user: current_user).as_json }
    ...
      context 'user is the current_user' do
        specify { expect(presented_review[:current_user_likes]).to eq(true) }
      end
    end
    ...
  end
end

What I was doing here is to populate the field 'current_user_likes'
 to true or false depending if the current_user likes or not the presented review.

As you can see the presentation layer of any grape API could became more DRY and versatile by using the grape entities.

Enjoy using them ;)