Chapter.12 責務の分離
2017-10-03
責務の分離
このチャプターのコード:
https://github.com/owlworks/ruby_on_basis/tree/13_change_routing_process_and_add_id_column_to_product
さてProductのGET(Read)、PUT(Update)、Deleteを実装する予定だが今日はその下準備だ。まずコードが少しややこしくなってきた。具体的にはmount_procメソッドのブロック内で処理を書きすぎている。そこでリクエストからレスポンスを作るまでの実処理をControllerオブジェクトへ任せよう。
class Controller def initialize @products = [] @products << Product.new(name: 'ぺんぎんのぬいぐるみ', price: 2900, stock: 1) @products << Product.new(name: '食ぱんクッション', price: 5600, stock: 21) end def get_products(request) @products.map(&:inspect).join("\n") end def post_product(request) q = request.query new_product = Product.new( name: q['name'], price: q['price'].to_i, stock: q['stock'].to_i ) @products << new_product new_product.inspect end end # リクエストのデータを処理してレスポンスを作成するControllerオブジェクト controller = Controller.new # Webickのサーバ設定と起動 server = WEBrick::HTTPServer.new(Port: 3000) server.mount_proc('/products') do |req, res| case req.request_method when "GET" res.body = controller.get_products(req) end end server.mount_proc('/product') do |req, res| req.query.each { |key, value| req.query[key] = value.force_encoding('utf-8') } case req.request_method when "POST" res.body = controller.post_product(req) end end trap(:INT) { server.shutdown } server.start
レールの上に乗る(on rails)
うーん。しかし、どこか冗長なコードに思えるね。やり過ぎかも知れないがもう少しだけ手を入れてみよう。
# Webickのサーバ設定と起動 server = WEBrick::HTTPServer.new(Port: 3000) RESOURCES = ['products', 'product'] RESOURCES.each do |resource| server.mount_proc("/#{resource}") do |req, res| req.query.each { |key, value| req.query[key] = value.force_encoding('utf-8') } method_name = "#{req.request_method.downcase}_#{resource}" # sendメソッドを使うとメソッド名を動的に生成して呼び出せる # 'abc:efg'.send(:split, ':')と'abc:efg'.split(':')は同じ挙動になる res.body = controller.send(method_name.to_sym, req) end end trap(:INT) { server.shutdown } server.start
わかるかな? Controllerで定義するメソッド名に規則性を持たせて自由に命名できなくなった代わりに使いたいリソースをRESOURCESで定義すればリクエストに対応したメソッドを呼んでくれるというワケ。例えば/productsにGETメソッドでリクエストを送ればControllerのget_productsメソッドが呼び出されるといった具合。
このように命名規則などの規約を決めることで処理を省略したり複雑で細かい関連処理を意識しなくても良くなる。たとえばreq.queryのエンコード問題とかね。Railsはまさにこういった規約(レール)の上に乗ることで手軽さを手に入れる。モデルとコントローラーの命名、viewファイルの名前まで規約によって決められているだろう?
また、このmount_procを自動生成するやり方はRailsのルーティングでresources :products
とするのに似ているのも気が付いたかな。実際、この部分の処理はRailsで言うところのルーティングだ。URLとメソッドの組合せでControllerのどのactionを実行すべきかを定義しているんだからね。
識別子くんのこと
それで、POST以外のメソッドを実装するんだけどその前にまだ少しだけ寄り道をする。今までの話で更新するだの削除するだのと言って来たが待ってほしい。どの商品を削除するかどうやって指定するんだ?そう。指定する方法がない。そこでどの商品か一意に特定するための属性を設定しよう。identifier(識別子)だから@idだ。そう、これもRailsのActiveRecordでプラマリーキーとして使ったことがあるよね。これはDBじゃないけど理屈と必要性は同じだ。
# リソースのクラス定義 class Product @@id_num = 0 attr_accessor :name, :price, :stock def initialize(attrs) @@id_num += 1 @id = @@id_num attrs.each { |attr, value| instance_variable_set("@#{attr}", value) } end end
Productクラスにクラス変数@@id_numを作り、新規にインスタンスが作られる度にインクリメントしてその値をインスタンスの@idとして持たせる。クラス変数はその名の通りクラス自体が保持出来る変数だ。話がややこしくなるからクラスとインスタンスについて説明したチャプターでは説明しなかったけどね。
これで一度http://localhost:3000/productsへアクセスしてみよう。
#<Product:0x007fc3650bc068 @id=1, @name="ぺんぎんのぬいぐるみ", @price=2900, @stock=1> #<Product:0x007fc3650be638 @id=2, @name="食ぱんクッション", @price=5600, @stock=21>
Tallyho! 無事にインクリメントされた値が@idへ格納されているようだ。これで次からは@idを指定すれば更新や削除が出来るというワケ。実際のシステムでは、こうしたidよりも「絶対に被らないユニークな文字列」をcodeといった名前の属性として定義して、API上のプライマリーキーとして使うことも多いけれどね。
何故かって? 1つはその方が人間に優しいからだ。id: 1998931よりもcode: honsha-charactor-sanrio-kitty-021のほうが間違いや内容がわかりやすいだろう? もう1つはこの@idというのはあくまでもこのアプリケーションだけでの識別子だからだ。他のシステムでも商品データを扱っている場合は、両方で共通する値で指定できないと困る。それぞれが勝手なidを作ってしまうからね。これはURLの指定でIPアドレスではなくドメイン名が使われるのと同じだ。

[22]