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
モデルとの間に多対多の関係を構築してみます。