コントローラーで API のアクセス・エラーハンドリングなどやっていると、どうしてもコントローラが厚くなります。 今回は、API へのアクセスをモデルに包んで、なるべくコントローラを薄くするように努めてみます。
目次
作るもの 郵便番号から、住所の検索を行う画面を作ってみます。zipcloud - 郵便番号検索 API を使用します。
実装 コントローラにベタ書きしてみる app/controllers/testa_controller.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class TestaController < ApplicationController def search @zipcode = params[:zipcode ] valid = true unless @zipcode .match( /^[0-9]+$/ ) flash[:alert ] = "入力に数字以外が使用されています。" valid = false end unless @zipcode .length == 7 flash[:alert ] = "入力桁数が7桁ではありません。" valid = false end if valid begin res = RestClient .get("https://zipcloud.ibsnet.co.jp/api/search" , {params: {zipcode: @zipcode }}) @addresses = JSON .parse(res) rescue => e logger.error "ERROR:#{e.inspect} " end end render "index.html" end end
コントローラにべた書きしているので、バリデーションもだらだら書き下す様子になりました。
この時の view の実装は次の通りです。
app/views/testa/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <div > <p class ="notice" > <%= notice %></p > <p class ="alert" > <%= alert %></p > </div > <%= form_with(url: "/testa/search" , local: true ) do |form | %> <div class ="field" > <%= form.label :zipcode %> <%= form.text_field :zipcode ,value: @zipcode %> </div > <div class ="actions" > <%= form.submit "Serche" %> </div > <% end %> <% if @addresses .present? %> <% if @addresses ["status" ] == 200 && @addresses ["results" ].present? %> <table > <tbody > <tr > <th > 住所1</th > <th > 住所2</th > <th > 住所3</th > </tr > <% @addresses ["results" ].each do | address | %> <tr > <td > <%= address["address1" ] %> </td > <td > <%= address["address2" ] %> </td > <td > <%= address["address3" ] %> </td > </tr > <% end %> </tbody > </table > <% elsif @addresses ["status" ] == 200 && @addresses ["message" ].present? %> <%= @addresses ["message" ] %> <% else %> 該当の住所はありません <% end %> <% end %>
エラーメッセージは、Flash メッセージを使用して表示するようにしています。
モデルに包んでみる 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class TestbController < ApplicationController def index @zipcode = Zipcode .new() end def search @zipcode = Zipcode .new(zipcode_params) @addresses = @zipcode .call render "index.html" end private def zipcode_params params.require (:zipcode ).permit(:code ) end end
モデルに包んだ結果、Form オブジェクトのように使用できたので、ストロングパラメータも使用しています。 というかやってみたら、Form オブジェクトの実装とほとんど同じだったわけですが。
search メソッドの実装がかなりシンプルになりました。
この時の Zipcode クラスの実装は以下の通りです。
app/models/zipcode.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Zipcode include ActiveModel::Model attr_accessor :code validates :code , presence: true validates :code , length: { is: 7 } validates :code , numericality: { only_integer: true } def call return nil if invalid? begin res = RestClient .get("https://zipcloud.ibsnet.co.jp/api/search" ,{params: {zipcode: code}}) addresses = JSON .parse(res) return addresses rescue => e logger.error "ERROR:#{e.inspect} " return nil end end end
バリデーションは、Zipcode クラスに実装があります。validates
を使用してinvalid?
が使えるのは便利です。call
メソッドの中でinvalid?
を使用し問題なければ、API にアクセスします。
この時の、view の実装は以下の通りです。
app/views/testb/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <ul > <% @zipcode .errors.full_messages.each do |msg | %> <li > <%= msg %> </li > <% end %> </ul > <%= form_with model: @zipcode , url: "/testb/search" , local: true do |form | %> <div class ="field" > <%= form.label :code %> <%= form.text_field :code %> </div > <div class ="actions" > <%= form.submit "Serche" %> </div > <% end %> <% if @addresses .present? %> <% if @addresses ["status" ] == 200 && @addresses ["results" ].present? %> <table > <tbody > <tr > <th > 住所1</th > <th > 住所2</th > <th > 住所3</th > </tr > <% @addresses ["results" ].each do | address | %> <tr > <td > <%= address["address1" ] %> </td > <td > <%= address["address2" ] %> </td > <td > <%= address["address3" ] %> </td > </tr > <% end %> </tbody > </table > <% elsif @addresses ["status" ] == 200 && @addresses ["message" ].present? %> <%= @addresses ["message" ] %> <% else %> 該当の住所はありません <% end %> <% end %>
エラーメッセージを Flash メッセージではなく、@zipcode.errors.full_messages
を使って実装できるのが便利です。
今回は、WebAPI へのアクセスをモデルに包んで実装してみました。
コントローラーをシンプルに実装できる
バリデーションをきれいに実装できる
この 2 つがとてもよかったです。 積極的にこのパターンを使ってゆきたいと感じました。
ではでは。