外部APIに接続するようなクラスの設計
Railsで外部のAPIサービスにつなぐクラスなどを書く際に、将来的にGemにすることを見越してできる限りそのプロジェクト自体に依存しないように設計すると思います。(ここでいう、「依存しない」とは、たとえばそのプロジェクトのSettingsLogicをこのクラスから見に行かないなど。)
ここで、外部のAPIが接続時にAPIキーと、トランザクション毎に変わる情報を渡さないといけないとすると、APIキーは一度Railsを立ち上げると変わらないのでクラスに持たせて、トランザクションは引数で渡す、ということをやると思います。
class ExternalServiceAdapter
include ActiveSupport::Configurable
config_accessor :api_key
def create_transaction(transaction_info)
do_something(self.api_key, transaction_info)
end
private
def do_something(api_key, transaction_info)
puts "api_key: #{api_key}"
puts "transaction: #{transaction_info}"
end
end
では ExternalServiceAdapter.api_key
はどこで埋めるかというと、Railsには config/initializers という起動時に読み込まれるものがあるので、そこで以下のように書きます。
ExternalServiceAdapter.configure do |config|
config.api_key = 'my_api_key'
end
こうすることで、起動時にAPIキーを渡すことができて問題なく動くし、Gemも自プロジェクトに依存するようなロジックが入らないのでGem公開に近づいて夢が広がります。
だがうまく動かない。現実は厳しい。
ところが、development環境でこの外部接続クラスを編集しながら開発しているとExternalServiceAdapter.api_keyが消えるという事象に遭遇します。
初回
こういうcontrollerを追加
class NanikaController < ApplicationController
def index
ex = ExternalServiceAdapter.new
ex.create_transaction("a")
end
end
※ あくまでサンプルで、controllerから外部接続API直接呼ぶのはおおむね良くないケースが多いので注意
実行 =>
api_key: my_api_key
transaction: a
ExternalServiceAdapterに #do_another というメソッドを追加する
def create_transaction(transaction_info)
do_something(self.api_key, transaction_info)
+ do_another
end
---
+ def do_another
+ puts 1
+ end
この後に再び先ほどのcontrollerを実行
=>
api_key:
transaction: a
1
なぜ消えるのか
理由は明快で、developmentモードでソースを編集すると、Railsがリロードしてくれます。便利。
ただしinitializersの中は走らない。
どうすればいいんだ
というわけでRailsを再起動しましょう。編集するたびに再起動しましょう。
とかやってると6回ぐらい再起動したあたりで精神が崩壊して最悪の場合死に至るのでおすすめできない。
本当の対処
精神を崩壊させたくなかったので調べたところ、
Rails.application.config.to_prepare
を使えばproduction環境なら最初の一回だけ読んでくれて、development環境ならリクエストの度に読み込む、というのを実現してくれます。
今回のソースで言うと、以下のようになります。
Rails.application.config.to_prepare do
ExternalServiceAdapter.configure do |config|
config.api_key = 'my_api_key'
end
end
これで ExternalServiceAdapterを編集してもapi_keyは消えません。
依存の少ないコードを書きつつ、開発も快適に進められます。
参考リンク
動作確認バージョン
- Ruby 2.1.3
- Rails 4.1.6