#chiroito ’s blog

Java を中心とした趣味の技術について

GAE+JRuby+Datamapperで多対多

概要

 Google App Engine(GAE)とJRuby+Datamapperを使用して多対多のデータの保持を行います。GAEのDatastoreにはList Propertyという多対多のデータを保持するための機能があるためRDBMSとは一味違った考え方が必要になります。

課題

 下の図の様に社員とプロジェクトというオブジェクトがあり1人の社員は複数のプロジェクトにアサインされ、1つのプロジェクトには複数の社員がアサインされているという状況を表したいと思います。

図1:課題のデータモデル

多対多の保持方法

 RDBMSの場合は多対多の関係を表すためにはその関係を保持するためのテーブルを互いの間に入れる必要があります。

図2:RDBで表したデータモデル

 GAEのDatastoreの場合はList Propertyを使うことによって図1のデータモデルをそのまま表現することが可能になります。(図3の○○IDsとしているのがList Propertyです)

図3:GAEのDatastoreでのデータモデル

サンプルソース

サンプルソースのダウンロード
project.rb
class Project
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
 サンプルソースを見て頂ければわかりますが、employee.rbも同様です。

projects_controller.rb(アサイン部分抜粋)
def join
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
employees_controller.rb
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は普通に使えます。