概要
Google App Engine(GAE)とJRuby+Datamapperを使用して多対多のデータの保持を行います。GAEのDatastoreにはList Propertyという多対多のデータを保持するための機能があるためRDBMSとは一味違った考え方が必要になります。課題
下の図の様に社員とプロジェクトというオブジェクトがあり1人の社員は複数のプロジェクトにアサインされ、1つのプロジェクトには複数の社員がアサインされているという状況を表したいと思います。多対多の保持方法
RDBMSの場合は多対多の関係を表すためにはその関係を保持するためのテーブルを互いの間に入れる必要があります。GAEのDatastoreの場合はList Propertyを使うことによって図1のデータモデルをそのまま表現することが可能になります。(図3の○○IDsとしているのがList Propertyです)
サンプルソース
サンプルソースのダウンロードproject.rb
class Projectサンプルソースを見て頂ければわかりますが、employee.rbも同様です。
include DataMapper::Resource
storage_names[:default] = "Project"
property :id, Serial
property :name, String, :required => true, :length => 500
timestamps :at
def employees
if self.employee_ids == nil
[]
else
Employee.all(:id=>self.employee_ids)
end
end
def add_employee(emp)
self.employee_ids = self.employee_ids.to_a << emp.id
end
def remove_employee(emp)
self.employee_ids = self.employee_ids.to_a
self.employee_ids.delete(emp.id)
end
private
property :employee_ids, List
end
projects_controller.rb(アサイン部分抜粋)
def joinemployees_controller.rb
emp = Employee.find(params[:emp_id])
prj = Project.find(params[:prj_id])
emp.add_project(prj)
prj.add_employee(emp)
emp.save!
prj.save!
redirect_to :controller=>:employees, :action=>"show", :id=>emp
end
def show
@employee = Employee.find(params[:id])
@belongs = @employee.projects
@projects = Project.all() - @belongs
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @employee }
end
end
おまけ1
Datastore ViewerでList Propertyのフィールドを見るとこうなります。おまけ2
DatamapperでListPropertyにしたフィールド(上では:employee_ids)のクラスはJavaのArrayListとなっています。ですのでself.employee_ids.remove(emp.id)としたかったのですが、boolean remove(Object o)ではなくObject remove(int index)が使われてしまいました。idフィールドがLong型(Viewerで末尾にLが付いているためそう思われる)のためオートボクシング&型変換が働いてしまったのではないかと考えています。その為、JavaのArrayListをto_aを使うことでRubyのArrayへ変換しています。(正直ArrayListのまま扱ったサンプルも作りたかったのです上記の解決方法が分からず断念です。)
add_○○とremove_○○を交互に繰り返すと何度もto_aが行われてしまいますが、Arrayのto_aはselfを返すため問題ありません。
なお、ArrayListのaddは普通に使えます。