Ruby on Railsで複合キーを扱う(4)
2012/03/28
前回から、文字列と「期間」を複合キーとして用いているデータベーステーブルをRailsで扱う話を書いています。
ある程度使いやすそうなDepartment.findメソッドを作ったところで終わりましたので、今回はProduct#departmentメソッドやDepartment#productsメソッドを作ります。モデル間の関連づけです。
RSpecによる試験コード(1)
まずは、Product#departmentメソッドから。
新規ファイルspec/model/product_spec.rb を次のように作成します。
# coding: utf-8
require 'spec_helper'
describe Product do
let(:department0) { FactoryGirl.create(:department,
code: "robot", name: "Department0",
started_on: Date.new(2000, 1, 1), ended_on: Date.new(2002, 1, 1)) }
let(:department1) { FactoryGirl.create(:department,
code: "robot", name: "Department1",
started_on: Date.new(2002, 1, 1), ended_on: nil) }
before do
department0
department1
end
it "2001年1月1日当時の部門(department)と関連づけできる" do
product = FactoryGirl.build(:product)
product.code = "alpha"
product.started_on = Date.new(2000, 1, 1)
product.ended_on = Date.new(2004, 1, 1)
product.department_code = "robot"
product.save!
DurationLimited.current_date = Date.new(2001, 1, 1)
p = Product.find("alpha")
p.department.name.should == department0.name
end
it "2002年1月1日当時の部門(department)と関連づけできる" do
product = FactoryGirl.build(:product)
product.code = "alpha"
product.started_on = Date.new(2000, 1, 1)
product.ended_on = Date.new(2004, 1, 1)
product.department_code = "robot"
product.save!
DurationLimited.current_date = Date.new(2002, 1, 1)
p = Product.find("alpha")
p.department.name.should == department1.name
end
end
また、新規ファイルspec/factories/products.rbを次のように作成します。
FactoryGirl.define do
factory :product do
sequence(:code) { |n| "product_%02d" % n }
name { code.camelize }
started_on { 2.years.ago }
ended_on { nil }
department_code do |p|
FactoryGirl.create(:department,
started_on: p.started_on, ended_on: p.ended_on).code
end
end
end
実装(1)
今回も途中経過は省いて、ソースコードの最終形のみを示します。
app/models/product.rbを次のように修正します。
class Product < ActiveRecord::Base
include DurationLimited
def department
Department.where(code: department_code).first
end
end
これでテストは通ります。
単一の主キーidを用いているテーブルの場合は、
belongs_to :department
と書くだけでしたが、ActiveRecordには頼れないのでこのようにメソッドを作っていくことになります。
「これではRailsの良さが生かせない」と思われた方がいるかもしれません。しかし、「単一の主キー」というRailsの本流から外れるわけなので、多少面倒になるのは仕方がありません。私に言わせれば、「たった3行で書けたよ」と威張りたいところです。
RSpecによる試験コード(2)
次に、Department#productsメソッドを実装します。
spec/model/department_spec.rb を次のように修正します。
# coding: utf-8
require 'spec_helper'
describe Department do
let(:department0) { FactoryGirl.create(:department,
code: "robot", name: "Department0",
started_on: Date.new(2000, 1, 1), ended_on: Date.new(2002, 1, 1)) }
let(:department1) { FactoryGirl.create(:department,
code: "robot", name: "Department1",
started_on: Date.new(2002, 1, 1), ended_on: nil) }
let(:department2) { FactoryGirl.create(:department,
code: "ship", name: "Department1",
started_on: Date.new(2002, 1, 1), ended_on: nil) }
before do
department0
department1
department2
end
it "部門(department)を複合キーで検索できる" do
DurationLimited.current_date = Date.new(2001, 1, 1)
dep0 = Department.find("robot")
dep0.name.should == department0.name
DurationLimited.current_date = Date.new(2003, 1, 1)
dep1 = Department.find("robot")
dep1.name.should == department1.name
end
it "2001年1月1日当時の製品(product)リストを取得できる" do
FactoryGirl.create(:product, name: "p0", department_code: "robot",
started_on: Date.new(1999, 1, 1), ended_on: Date.new(2000, 8, 1))
FactoryGirl.create(:product, name: "p1", department_code: "robot",
started_on: Date.new(2000, 1, 1), ended_on: Date.new(2002, 12, 1))
FactoryGirl.create(:product, name: "p2", department_code: "robot",
started_on: Date.new(2001, 1, 1), ended_on: nil)
FactoryGirl.create(:product, name: "p3", department_code: "robot",
started_on: Date.new(2002, 1, 1), ended_on: nil)
FactoryGirl.create(:product, name: "p4", department_code: "ship",
started_on: Date.new(2000, 1, 1), ended_on: nil)
DurationLimited.current_date = Date.new(2001, 1, 1)
dep = Department.find("robot")
dep.should have(2).products
products = dep.products.order("products.started_on")
products[0].name.should == "p1"
products[1].name.should == "p2"
end
end
dep.products が返す配列の要素が2であること、dep.products.order(...)という書き方もできること、などを確かめています。
実装(2)
app/models/department.rbを次のように修正します。
class Department < ActiveRecord::Base
include DurationLimited
def products
Product.where(department_code: code)
end
end
試験コードを書くのは大変でしたが、実装はあっけなく終わりました。
次回は、Category(カテゴリー)モデルを追加して、Productモデルとの間に多対多の関係を構築してみます。
