オープンではないサービスのAPIを使うとき、APIクライアントが無いってことが多いです。なのでアプリケーションから使いたいAPIだけを叩くような簡易なAPIクライアントを自分で書きます。そのときはrest-clientあたりを使ってベタっと書いてしまうことがほとんどです。
rest-client
まずはサクッと
例をいきなり書いてしまうと↓な感じでやります。
1class ApiClient
2 ENDPOINT = 'https://api.example.com/items.json'.freeze
3
4 class << self
5 def items(search)
6 url = ENDPOINT
7 params = {s: search}
8 get(url, params)
9 end
10
11 def item
12 # ...
13 end
14
15 private
16
17 def get(url, params)
18 res = RestClient.get(url, params: params)
19 return nil if res.code != 200
20
21 JSON.parse(res.body)
22 end
23 end
24end
25
26# こんな感じで使う
27ApiClient.items('けんさく') => [...]
はい、これだけ。
レスポンスを工夫する
なんですが、レスポンスをJSON.parseしたHashそのままというのはなんだかイケてないので、rest-clietを使っているAPIクライアントライブラリはどうしているのか、みたいなことを調べました。いい感じの実装を発見したので、それをインスパイアしてみました。
まず、Hashの扱いが少し変わります。
1def items(search)
2 url = ENDPOINT
3 params = {s: search}
4 get(url, params)
5 return [] if data.nil?
6
7 data['items'].map do |item|
8 ItemResponse.new(item)
9 end
10end
こうするとApiClient.items('xxx')の結果はItemResponseの配列となります。
で、このItemResponseとは何者かといいますと、レスポンスのHashをJSのオブジェクトっぽいものに変換する(ドットでアクセスできるようにする)ためのクラスです。
1hash = {hoge: 'hogehoge', fuga: 'fugafuga', hash: { key1: 'value1', key2: 'value2'}}
2obj = ApiClient::Response.new(hash)
3
4obj.hoge => 'hogehoge'
5obj.fuga => 'fugafuga'
6obj.hash => {:key1=>"value1", :key2=>"value2"}
実装が半端なので入れ子のHashはメソッドでアクセスできるようになっていませんが、僕のケースはこれで事足りたのでそれ以上はがんばりませんでした。
ちなみに、このResponseクラスの実装はpayjp-rubyのこの辺のコードを参考にしています(実際には参考にしたのはしばらく前なので、そのときから更に実装が変わっているみたい。Hashのネストも対応してそう)。
Responseとそれを継承したItemResponseというのをApiClient内に用意する形にしました。
1class ApiClient
2 # (略)
3
4 class Response
5 def initialize(hash)
6 @values = {}
7 instance_eval do
8 add_accessors(hash.keys)
9 end
10
11 hash.each do |k, v|
12 @values[k] = v
13 end
14 end
15
16 private
17
18 def add_accessors(keys)
19 metaclass.instance_eval do
20 keys.each do |k|
21 k_eq = :"#{k}="
22 define_method(k) { @values[k] }
23 define_method(k_eq) do |v|
24 if v == ""
25 raise ArgumentError.new(
26 "You cannot set #{k} to an empty string." \
27 "We interpret empty strings as nil in requests." \
28 "You may set #{self}.#{k} = nil to delete the property.")
29 end
30 @values[k] = v
31 end
32 end
33 end
34 end
35
36 def metaclass
37 class << self; self; end
38 end
39 end
40
41 class ItemResponse < Response
42 # 必要であればメソッド生やす
43 def my_method
44 end
45 end
46end
プロキシサーバーを経由する
冒頭に「オープンではないサービスのAPI」と書きました。そういう場合って、特定のIPアドレスからしかアクセスできませんみたい制約があったりするので、開発時には工夫が必要です。
このクライアントを実装したときには、プロキシサーバーのIPを許可してもらい、クライアントはプロキシサーバー経由でAPIリクエストを投げるようにしました。
rest-clientの場合のプロキシ設定は非常に簡単で、
1RestClient.proxy = "https://accountname:password@54.xxx.yyy.zzz"
としておくだけでOK。Railsならinitializerで書いておくのが良いです。