RubyOnBasis[11] 新人Railsエンジニア向け講座

このシリーズについて

RubyOnBasisはRuby on Railsを業務に利用する新人Webエンジニアのための、Railsを使わないテキストだ。この講座ではWEBrickを使ってWebアプリを書きながらHTTPを始めとする基礎知識をキャッチアップしてRailsがどうやって作られ動くのかを理解していく。

このシリーズを始めから読む

Chapter.11 使おう、リソース!

前回はHTTPのURLとメソッドという概念を説明したね。今日はリソースを作ってコードを書いてCRUDしてみよう。

さて、どんなリソースを作ってみようか。このドキュメントではちょっとしたサンプルコードなら大抵は猫クラスが登場する。ただ、オブジェクト指向プログラミングやリソースのサンプルとしては不適切だ。可愛いけれど。

このドキュメントの想定読者は既にrailsで業務コーディングをしていると思うので、ビジネスっぽい例えの方が身近で良いと思う。だから今日は商品(product)クラスを考えてみよう。ちょっとした商品管理ソフトを作るんだ。

Productクラス

Productクラスは属性としてname(名前)、price(価格)、stock(在庫数)を持っている。

class Product
  attr_accessor :name, :price, :stock

  def initialize(attrs)
    attrs.each {|attr, value| instance_variable_set("@#{attr}", value) }
  end
end

new_product = Product.new(name: "ぺんぎんのぬいぐるみ", price: 2900, stock: 1)

puts new_product.name
puts new_product.inspect

# => ぺんぎんのぬいぐるみ
# <Product:0x007fac3111f5a0 @name="ぺんぎんのぬいぐるみ", @price=2900, @stock=1>

attr_accessorについては知ってる? シンボルを引数として使うことで以下のようなメソッドを定義するのと同様の働きをする。いわゆるgetter/setterメソッドを提供するようになるってワケ。

# => attr_reader :name
def name
 @name
end

def name=(arg)
 @name = arg
end

initializeメソッドはRailsActiveRecordモデルと似せて書いてみたよ。initializeメソッドが何かっていうと、何らかのクラス.newメソッドを使った時に呼び出される初期処理メソッドだ。他の言語で言えばコンストラクタってヤツだね。あとわからないのがinitializeメソッドで何をやっているかかな? 少し丁寧に書いてみよう。

  def initialize(attrs)
    # attrs => {name: 'ぺんぎん', price: 2900 ... }(Hashオブジェクト)
    attrs.each do |attr, value|
      # Hash#eachはハッシュの要素(キーと値)の数だけブロックを繰り返し実行する
      # 仮引数を2つ設定した場合はキーと値がそれぞれの変数に格納される
      # attr => :name、 value => 'ぺんぎん'
      
      instance_variable_set("@#{attr}", value)
      # instance_variable_setメソッドはインスタンス変数への代入と同義
      # => @name = 'ぺんぎん'
    end
  end

ちょっとした準備、Indexアクション

さて、それじゃあ早速CRUDを実装して行きたいんだけど、その前に一つだけ追加のリソースとメソッドを作っておこう。どんなリソースかといえば、Products リソースだ。つまりProductオブジェクトを要素とする配列ってワケ。

これがないと作成した商品データをどこに保存しておけばわからないだろう? このリソースにCRUDのうち、Readつまり内容を返す処理だけ作っとこう。単なる配列でもいいんだけど、あとでちょっとメソッドを追加したりするからArrayクラスを継承した独自のクラスにしておくよ。

require 'webrick'

# リソースのクラス定義
class Product
  attr_accessor :name, :price, :stock

  def initialize(attrs)
    attrs.each {|attr, value| instance_variable_set("@#{attr}", value) }
  end
end

class ProductList < Array
end

# 初期データの設定
products = []
products << Product.new(name: "ぺんぎんのぬいぐるみ", price: 2900, stock: 1)
products << Product.new(name: "食ぱんクッション", price: 5600, stock: 21)

# Webickのサーバ設定と起動
server = WEBrick::HTTPServer.new({:Port => 3000})
server.mount_proc('/products') do |req, res|
  res.body = product.map(&:inspect).join("\n")
end

trap(:INT){ server.shutdown }
server.start

オーケー! /productsというurlを作って商品の一覧を返すようにしたぞ。ちょっと難しい書き方をしてしまったので少し補足しておこう。

server.mount_proc('/products') do |req, res|
  res.body = product.products.map(&:inspect).join("\n")
end
['neko world', 'inu land', 'alpaca farm'].map { |word| word.capitalize }
=> ["Neko world", "Inu land", "Alpaca farm"]

['neko world', 'inu land', 'alpaca farm'].map(&:capitalize)
=> ["Neko world", "Inu land", "Alpaca farm"]

理屈の説明をここではしないが、ブロックの仮引数のメソッドを使って返すだけの場合には二つ目のコードのようにも書ける。 参考リンク: ブロックの変わりにProcオブジェクトを引数に渡す

それはさておき、コードを実行して http://localhost:3000/products にアクセスしてみよう。

<Product:0x007f973291f098 @name="ぺんぎんのぬいぐるみ", @price=2900, @stock=1>
<Product:0x007f973291edf0 @name="食ぱんクッション", @price=5600, @stock=21>

無事に商品情報のリストが取り出せた。すっごーい! ところで、このproductsを作っていて何かを思い出さないかな? そう、Railsのscaffoldが作ってくれるIndexアクションだ。Railsやscaffoldが何をしているのか、少しずつわかってきたね。

<< 前のチャプターへ戻る | 次のチャプターへ進む >>