背景
現在開発中のシステムのバックエンドを担当しており、一部のAPIレスポンスを 「form形式で返す」というような実装をしました。この時に用いた設計が汎用的だなと思ったので記事にしようと思いました。
本質的な意味合いは異なりますが、見方によってはデザインパターンでいうアダプタっぽいかなと思っています。正しいかどうかは定かではないです。。
言語はruby、フレームワークはrailsを想定しています。
要件
Testsテーブル
カラム | 型 |
---|---|
id | int8 |
first | varchar(255) |
second | varchar(255) |
上記のようなテーブルがあり、その1レコードを以下のようなJSONレスポンス形式で返したいとします。
{
"form_items" : [
{
"item_name" : "first",
"value" : "hoge" # firstカラムの値
},
{
"item_name" : "second",
"value" : "fuga" # secondカラムの値
}
]
}
実際のシステムではitem_name, valueだけでなくメタデータなどが含まれて複雑な構造になっているのですが、今回は便宜上シンプルな設計にしています。今後カラム数が増えて1つ1つのカラムに対してform_itemsの配列の要素が比例すると想定してください。
設計と実装
5つのクラスを用意しました。
3つはFieldを定義するクラスです。抽象クラスとしてFieldBaseクラスを作成しています。
一見なぜこのような設計したか疑問に思うかもしれませんが、カラムとカラムに付随するメタデータが増えることを考えるとこのような設計が無難かなと思いました。
class FirstField < FieldBase
COLUMN_NAME = 'first'
end
class SecondField < FieldBase
COLUMN_NAME = 'second'
end
class Base
def initialize(record)
@record = record
end
def value
@record.send(self.class::COLUMN_NAME)
end
end
4つ目と5つ目はセットで説明します
まず、FormItemクラスではitem_nameとvalueのセットをインスタンスとして保持するために作成しています。具体的には5つ目のクラスを見ればわかると思います。
class FormItem
attr_reader :item_name, :value
def initialize(field_instance)
@item_name = field_instance.class::COLUMN_NAME
@value = field_instance.value
end
end
5つ目はform_itemの配列を作成するクラスです
class FormItemList
attr_reader :form_items
def initialize(record)
@form_items = []
# 以下form_itemの順番がレスポンスとして送るformの順番に直結する
form_item(FirstField.new(record))
form_item(SecondField.new(record))
end
private
def form_item(field_instance)
@form_items += [FormItem.new(field_instance)]
end
end
あとは以下のようにcontrollerで呼び出してあげると、form形式にオブジェクト化されたものが要素となる配列が得られます
record = Test.first
form_items = FormItemList.new(record).form_items
pp form_items
# => [FirstFieldインスタンスから作成したFormItemインスタンス, FirstFieldインスタンスから作成したFormItemインスタンス]
ここまできたら配列内のオブジェクトをjsonへserializeするだけです。
serializeの方法は色々あるので説明は省きます。
まとめ
カラム数やメタデータが少ないとあまり恩恵を受けられていないように感じますが、増えた際にカスタマイズしやすいようになっているのがよかったかなと思います!
最近Twitterを始めたので是非フォローお願いいたします!