データベース内のテーブルにアクセスするためのActiveRecordベースのRuby REST API

Rubyや他の多くの言語には、DBMSにプログラムでアクセスするための便利なORMソリューションがあります。 データベースで動作するWebベースのアプリケーションを簡単かつ便利に作成するためのRubyOnRailsなどのフレームワークもあります。 簡単な規則により、小さなコードを記述し、同時に強力なインターフェイスを作成できます。



ただし、次のような状況を想像してください。多くのエンティティが存在する大規模で開発中の企業システムがあります。 非常に多くの場合、複雑なビジネスロジックを含む多数のエンティティには、多くの小さな依存関係があります。 たとえば、辞書。



Railsのエンティティを操作する機能を取得するための標準アクションは、モデルの作成、コントローラーの作成、ビューの作成です(RESTアクションの完全なセットがある場合は、2つ-リストを取得し、APIを使用する場合に1つのレコードを取得します)。 アクションはシンプルで、ファイルは非常にシンプルです。 また、辞書がたくさんある場合は、大量のファイルも取得します。 多数の単純で類似したファイル。 そして、それらのどこかで、大きくて非定型のファイルは失われます。 多数のファイルに加えて、ルートを登録する必要があるという問題が発生します。 そのようなシステムを1年以上サポートしている人でさえ、新しい開発者のようではなく、プロジェクトをナビゲートするのは難しいと感じるでしょう。



ここでRailsの魔法が助けになります。



簡単にするために、JSON APIを構築しているとします。 XMLサポートの追加を妨げるものは何もありませんが。



したがって、1つのファイルと1つのクラスは、同じタイプとクラスの非常に多数の小さなファイルを置き換えることができます。



小さくてシンプルな辞書またはシンプルなテーブルは、標準のRESTアクションを少し超えて成長する可能性がありますが、これらのアクションを使用する能力を失いたくないでしょう。 もちろん、私たちはソースコードを二度と書きたくありません。 したがって、基本クラスUniversalApiControllerを作成します。 必要に応じて、使用可能なすべての機能を受け取って、それから継承できます。



class UniversalApiController < ApplicationController before_action :prepare_model, only: [:index, :show, :create, :update, :destroy] before_action :find_record, only: [:show, :update, :destroy] def index @res = @model_class @res = @res.limit(params[:limit].to_i) if params[:limit] select_list = permitted_select_values @res = @res.select(select_list) if select_list @res = @res.ransack(params[:q]).result render json: @res end def show render json: @res end def create if @res = @model_class.create(permitted_params) render json: @res else invalid_resource!(@res) end end def update if @res.update_attributes(permitted_params) render json: @res else invalid_resource!(@res) end end def destroy @res.destroy raise @res.errors[:base].to_s unless @res.errors[:base].empty? render json: { success: true }, status: 204 end protected def permitted_select_values if params[:select] case params[:select] when String permitted_select_value params[:select] when Array params[:select].map { |field| permitted_select_value field }.compact end end end def permitted_select_value field @select_fields ||= @model_class.column_names + extra_select_values (@select_fields.include? field) ? field : nil end def extra_select_values [] end def permitted_params params.permit![_wrapper_options.name] params[_wrapper_options.name].extract! @model_class.primary_key params[_wrapper_options.name] end def get_model_name params[:model_name] || controller_name.classify end def prepare_model model_name = get_model_name raise "Model class not present" if model_name.nil? || model_name.strip == "" @model_class = model_name.constantize raise "Model class is not ActiveRecord" unless @model_class < ActiveRecord::Base end def find_record @res = @model_class.find(params[@model_class.primary_key.to_sym]) end end
      
      





config / routes.rbのいくつかのエントリ:



 Rails.application.routes.draw do ... scope 'universal_api/:model_name', controller: 'universal_api' do get '/', action: 'index' get '/:id', action: 'show' post '/', action: 'create' put '/:id', action: 'update' delete '/:id', action: 'destroy' end ... end
      
      





順番に行きましょう:



 scope 'universal_api/:model_name', controller: 'universal_api'
      
      





この行を使用して、ユニバーサルAPIの個別のスコープを作成し、コントローラーでモデル名を使用できるようにします。 これはモデルクラス名であることになっています。 主なアクションのルーティングを以下に説明します。



コントローラー:



  before_action :prepare_model, only: [:index, :show, :create, :update, :destroy] before_action :find_record, only: [:show, :update, :destroy]
      
      





モデルを準備するために必要なすべてのアクションの前、および表示、更新、削除の前に、レコードを見つける必要があります。



最も単純な場合のモデルの名前は、params [:model_name]で利用できます。 ただし、子孫クラスでは、通常、モデル名はコントローラー名から取得できます。 場合によっては、Railsがコントローラー名を必要なモデル名に適切に変換できない場合、明示的に設定できる必要があります。 したがって、モデルの名前を返す別のメソッドと、モデルを実際のクラスに変換する別のメソッドを作成します。



  def get_model_name params[:model_name] || controller_name.classify end def prepare_model model_name = get_model_name raise "Model class not present" if model_name.nil? || model_name.strip == "" @model_class = model_name.constantize raise "Model class is not ActiveRecord" unless @model_class < ActiveRecord::Base end
      
      





それでも、Rubyは素晴らしい言語です。 また、Railsは優れたフレームワークです。



通常、表示、変更、または削除のレコードは、主キーによって取得できます。 ただし、場合によっては(大規模で企業システムの開発の長い歴史がある場合は何でも起こります)、別の方法でレコードを取得する必要がある場合があります。 たとえば、いくつかのパラメーターの組み合わせ。 この場合、相続人では、単にこのメソッドをオーバーライドします。



  def find_record @res = @model_class.find(params[@model_class.primary_key.to_sym]) end
      
      





準備が完了したら、特定のアクションに移りましょう。



1つのレコードを削除して表示する



最も単純なアクション、追加するものは何もありません。



  def show render json: @res end ... def destroy @res.destroy raise @res.errors[:base].to_s unless @res.errors[:base].empty? render json: { success: true }, status: 204 end
      
      





すべてのレコードを取得する



  def index @res = @model_class @res = @res.limit(params[:limit].to_i) if params[:limit] select_list = permitted_select_values @res = @res.select(select_list) if select_list @res = @res.ransack(params[:q]).result render json: @res end
      
      





多くの場合、クライアントに発行されるレコードの数を制限するために、params [:limit]を使用してこれを行います。 また、クライアントは常にモデルのすべてのフィールドを必要とするわけではありません。 使用可能な値を表で使用可能な列に制限し、必要に応じて子孫が必要な値を追加する機会を提供します。 selectメソッドを使用するため、制限が必要です。これにより、任意の行を使用できます。 つまり、ネストされたクエリ、および一般的にはすべてのクエリです。



  def permitted_select_values if params[:select] case params[:select] when String permitted_select_value params[:select] when Array params[:select].map { |field| permitted_select_value field }.compact end end end def permitted_select_value field @select_fields ||= @model_class.column_names + extra_select_values (@select_fields.include? field) ? field : nil end def extra_select_values [] end
      
      





レコード数の単純な制限に加えて、非常に多くの場合、よりインテリジェントな制限を使用できるようにしたいと考えています。 並べ替え、グループ化など これはRansackを使用して簡単に実装できます。



 @res = @res.ransack(params[:q]).result
      
      



-この行は、モデルパラメーター、関連付けによって非常に複雑な検索を実行する機会を提供し、モデルクラスのスコープとメソッドをソートおよび使用する機能も提供します。



レコードの作成と編集:



  def create if @res = @model_class.create(permitted_params) render json: @res else invalid_resource!(@res) end end def update if @res.update_attributes(permitted_params) render json: @res else invalid_resource!(@res) end end
      
      





Railsでは、デフォルトで、パラメータからのモデル属性の大量入力は禁止されています。 単純な辞書の場合、これはほとんど無関係です。 さらに複雑なケースでは、一括入力に使用できる属性のリストを明示的に定義できます。



  def permitted_params params.permit![_wrapper_options.name] params[_wrapper_options.name].extract! @model_class.primary_key params[_wrapper_options.name] end
      
      





作成と編集はそれぞれPOSTメソッドとPUTメソッドを使用して実行され、データはリクエスト本文で送信されます。 Railsは、このデータを別のパラメーターで親切に解析およびラップします。



まとめ



上記の方法を使用すると、かなり狭い範囲のタスクを解決できます。プロジェクトではそれほど頻繁にデータにアクセスする必要はありません。 ただし、開発者の特定のサークルにとっては非常に近く有用です。同じタイプのダミーファイルの数は4(!!!)回に削減され、ソースコードは標準の方法から解放されます。 また、一般的に、単純なモデルを使用した単純な作業の組織化のために、車輪をもう一度考えて再発明する必要はありません。



必要に応じて、少し考えを続けて、個別のビューを使用してレンダリングする可能性を認識できます。



この記事では、アクセス制御の問題については取り上げませんでした。 通常、アクセス制御は別のサブシステムが担当します。 また、ActiveRecord、ActiveSupport、スタンドアロンのRansackなど、Railsスタック全体のすべてのテクノロジーがここで使用されているわけではないことも別に注意したいと思います。 少しの努力で、これをSinatraまたは他のRailsフレームワークに実装できます。



All Articles