Nwht0xn1

WikiHub APIのControllerの実装Created on 2016-05-09 by r7kamura

WikiHub APIを公開しました - WikiHub Help で紹介したREST APIの内部実装の解説。

コード

2016年5月9日時点のソースコードそのまま。これは記事データを操作するエンドポイント用のController。

# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < BaseController
      before_action :require_oauth_access_token_value
      before_action :require_oauth_access_token
      before_action :require_valid_scope
      before_action :require_has_blogs_flag
      before_action :require_valid_community
      before_action :require_record_destroyability, only: :destroy
      before_action :require_record_updatability, only: :update
      before_action :validate_pagaination_parameters, only: :index

      private

      # @note Override
      def find_record_from_request!
        find_article_from_request!
      end

      # @note Override
      def parameters_for_create_action
        parameters_for_update_action.merge(user: current_user)
      end

      # @note Override
      def parameters_for_update_action
        params.permit(:body, :tag_names_string, :title)
      end

      # @note Override
      def relation_for_index_action
        current_community.articles.order(created_at: :desc).preload(:user)
      end

      # @note Override
      def representation_class
        ::Wikihub::Representations::Api::Article
      end
    end
  end
end

概要

  • #create, #destroy, #index, #show, #update は親クラスで定義する
  • 代わりに子クラスでは必要なメソッドを実装する
  • before_action は末端の子クラスにしか書かないことにする (親クラスには書かない)

ActiveRecord

ActiveRecordのインターフェースを重度に使うことを前提として、全てのactionを親クラスで実装する形で設計してみた。この選択には、ActiveRecordべったりになってしまう (ActiveRecordから脱却できない) という問題があるが、REST APIを提供するWebアプリの開発にRubyを選択する理由の大半がActiveRecordの恩恵を享受できることではないかという気はしていて、だったらいいかということでこの選択は妥当と考えることにした。

一応ActiveRecord::RelationとActiveRecord::Baseとほぼ同じインターフェースさえ持っているオブジェクトであれば差し替えは可能なので、何らかの事情で局所的に独自の実装に差し替えることはできる。例えば検索APIなどで単純な配列を返したいときは、Kaminari::PaginatableArray あたりでwrapすれば対応できる。

Representation

これはJSONを返すControllerにおけるViewのようなもので、#initialize(record)#to_json を実装したクラスを返してもらえれば適切に動作するということを期待している。実際には json_world というGemの実装を利用したクラスを用意している。簡単に言うと、以下のように使える。

article = ::Article.find(params[:article_id])
render json: ::Wikihub::Representations::Api::Article.new(article)

親クラスのコード

ちなみに親クラスのpublicなインスタンスメソッドの部分はこういう感じになっている。

module Api
  module V1
    class BaseController < ApplicationController
      def create
        record = relation_for_create_action.create!(parameters_for_create_action)
        render json: representation_class.new(record), status: 201
      end

      def destroy
        find_record_from_request!.destroy!
        head 204
      end

      def index
        render json: relation_for_index_action.page(page).per(per_page).map { |record| representation_class.new(record) }
      end

      def show
        render json: representation_class.new(find_record_from_request!)
      end

      def update
        record = find_record_from_request!
        record.update!(parameters_for_update_action)
        render json: representation_class.new(record)
      end
    end
  end
end