Ruby on RailsでつくってみるAPI(まとめと考察)

やぎすけAdventCalendar2016 Rails

Posted on Dec 23


こんにちは、やぎにいです! やぎすけ Advent Calendar 2016の23日目です。
残すところ今日含めあと3日……!年の瀬ですね……。

昨日は僕がRuby on RailsでつくってみるAPI(2日目)を書きました。

今日は昨日一昨日とやって出てきた問題点を解決したりRailsの気づきをまとめます

searchメソッドでの処理が甘い

昨日一昨日の記事を前提に、アイマスAPIでの話をします。

Charactersコントローラーでのアイドルの絞り込み検索であるsearchメソッドはこういう実装をしていました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GET /characters/search
# 必須パラメータ name:string
def search
  if params[:name].blank?
    render json: [{"error": "100", "msg": "必須パラメーターがありません", "required": {"key": "name"}}]
  else 
    @result = Character.where("name like ?", "%" + params[:name] + "%")

    if @result.empty?
      @result = Character.where("phonetic like ?", "%" + params[:name] + "%")
    end

    render json: @result
  end
end

実装としては

  • 必須パラメータがない場合はエラーのjsonを返却する
  • 受け取ったパラメータをまず名前で検索する
  • もし名前で一致しないならふりがなで検索する

といった内容です。
まずいケースとしては天海春香天海はるかというアイドルが別に同時に存在した場合です。
両方のふりがなはあまみはるかだとして、もし/characters/search=name?はるかを叩いたらどうなるでしょうか。

まず名前で検索され、天海はるかがヒットします。
この時点で一致した物があるため、ふりがなでの検索は行いません。
結果天海はるか1件だけ返ってきます。
欲しい理想の結果は天海春香がふりがな検索で一致して、天海はるかが名前検索で一致する2つが返ってくることです。

以下のように変更しました

1
2
3
4
5
6
7
8
9
10
11
12
13
# GET /characters/search
# 必須パラメータ name:string
def search
  if params[:name].blank?
    render json: [{"error": "100", "msg": "必須パラメーターがありません", "required": {"key": "name"}}]
  else 
    @result = Character.where("name like ?", "%" + params[:name] + "%")
    @result += Character.where("phonetic like ?", "%" + params[:name] + "%")


    render json: @result
  end
end

単に名前検索の結果にふりがな検索の結果を足しています。
これで両方ヒットして返ってくるように成りました。

が、次の問題が発生しました。
天海はるか?name=はるかを渡すと名前でもふりがなでもヒットしてしまい、これでは天海はるか天海春香天海はるかの3件がjsonで返ってきました。
重複は要らないのでrenderの行をrender json: @result.uniq!に変更しました。
これで?name=はるかで叩いたときは天海春香天海はるかが返ってきました。よさそうです。

が、また次の問題が発生しました。
今度は?name=天海春香で検索したときにnullが返ってくるのです。
これには悩まされましたが、うなすけの力を借り、class Array (Ruby 2.3.0) #uniqにたどり着きました。

uniq! は削除を破壊的に行い、削除が行われた場合は self を、 そうでなければnil を返します。

つまり、もし重複していない場合render json: result.uniq!をするとnilが返ってそれがnullとして表示されていました。
なるほど……。

そして最終的に以下のようになりました

1
2
3
4
5
6
7
8
9
10
11
12
13
# GET /characters/search
# 必須パラメータ name:string
def search
  if params[:name].blank?
    render json: [{"error": "100", "msg": "必須パラメーターがありません", "required": {"key": "name"}}]
  else 
    tmp = Character.where("name like ?", "%" + params[:name] + "%")
    tmp += Character.where("phonetic like ?", "%" + params[:name] + "%")

    result = tmp.uniq
    render json: @result
  end
end

ついでに@付きの変数の意味を調べ、【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数を参考に、このケースでは必要ないな、と思い消しています。
これで無事理想の絞り込み検索メソッドが出来上がりました。よかったよかった。


/characters で仕様をHTMLで表示したい

これも昨日の記事で返したいが、できなかったのでhead 404としてとりあえず404エラーを返して居たところです。

これに関してはホントにコードをぼーっと眺めていて「もしや?!」という気付きからだったのですが、現在CharactersControllerApplicationControllerを継承しています
application_controller.rbを覗いてみると、これはActionController::APIを継承していました。
このApplicationController < ActionController::APIを見たときに「もしかして」と思って、GitHubにある適当なRailsアプリのソースを読みました。
すると大体のアプリがApplicationControllerはActionController::Baseを継承していたことがわかりました。

「もしかして」が当たっているかもという感じになり、このアプリでもActionController::Baseを継承すると、無事に/charactersでもcharacters/index.html.erbが表示されるように成りました。

ActionController::APIはrailsアプリを作成する際に--apiを指定してRails5からのAPIモードで作成するとそうなるようですが、極力viewに関する処理を取り除いたAPI特化なモードがAPIモードになっているので、こういうような形になっているようです。

今回はAPIモードでつくるという形でやってきたので、なるべくそのままでやりたかったのですが今のところ「とりあえず」という形でActionController::Baseを継承しています。

ついでにwelcomeページも自分で表示させるようにしました。

コードについては掲載すると長くなるのでこのコミットを参照してください。

なんとか::APIのままでもhtmlを返せるように模索していくつもりです。

おわりに

これで3日間のRailsでやってみる記事は一旦終了となります。
が、これからもこのAPIはちょこちょこ作っていくつもりなので、適宜何かアウトプットしていきたいと思います。
今考えている今後やりたいのは

  • 今回のsearchメソッドのような事が起きないためにもテストをちゃんと書いてCIで回す
  • VPSでこのアプリを動かす

の2つは脳内高層で存在します。
前者はこのアプリにテストコードを追加してくれたうなすけが居なかったらそもそもテストという発想にはいたらなかったと思います。

それでは 以上、やぎにいでした。



comments powered by Disqus

<< やぎすけアドベントカレンダー25日目     Ruby on RailsでつくってみるAPI(2日目) >>



2016 やぎ小屋