こんにちは、やぎにいです!
やぎすけ Advent Calendar 2016の23日目です。
残すところ今日含めあと3日……!年の瀬ですね……。
昨日は僕がRuby on RailsでつくってみるAPI(2日目)を書きました。
今日は昨日一昨日とやって出てきた問題点を解決したりRailsの気づきをまとめます
昨日一昨日の記事を前提に、アイマス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 |
実装としては
といった内容です。
まずいケースとしては天海春香と天海はるかというアイドルが別に同時に存在した場合です。
両方のふりがなはあまみはるかだとして、もし/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 |
ついでに@付きの変数の意味を調べ、【まとめ】インスタンス変数、クラス変数、クラスインスタンス変数を参考に、このケースでは必要ないな、と思い消しています。
これで無事理想の絞り込み検索メソッドが出来上がりました。よかったよかった。
これも昨日の記事で返したいが、できなかったのでhead 404としてとりあえず404エラーを返して居たところです。
これに関してはホントにコードをぼーっと眺めていて「もしや?!」という気付きからだったのですが、現在CharactersControllerはApplicationControllerを継承しています
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はちょこちょこ作っていくつもりなので、適宜何かアウトプットしていきたいと思います。
今考えている今後やりたいのは
の2つは脳内高層で存在します。
前者はこのアプリにテストコードを追加してくれたうなすけが居なかったらそもそもテストという発想にはいたらなかったと思います。
それでは 以上、やぎにいでした。
<< やぎすけアドベントカレンダー25日目 Ruby on RailsでつくってみるAPI(2日目) >>
2018やぎ小屋