コピーアプリ 1 : DropBox データモデリングに苦戦・・
今回のお題はDropBoxのコピーアプリ!
Webアプリのコピーなので、ローカルとのファイル連携は無視して、 単純に階層的なフォルダ情報を管理して、その中にファイルをアップロード出来るところを目指す。
(・・と、この一文を書いて既に昨日までせっせとやってたモデリングの最初の段階でつまづきがあったことに気づいた;;)
オリジナルの観察
URL構造
ホーム
- APP_ROOT/home : ユーザーのホーム
ホームではルートディレクトリに当たるファイルorフォルダが一覧表示される
サブディレクトリ
- APP_ROOT/home/myFolder/mySubFolder : ホームから2階層進んだ例
フォルダの名前でURLがどんどん深くなる。
Railsのroutesでこういう表現方法・・出来るの・・・??
結局まだこの問題の解決策模索中。。。(クリティカルな機能じゃないので、とりあえず無視で進むことにする)
悩ましいポイント
- 階層構造である
- ファイルシステムの制限に従う必要がある
階層構造
自己参照みたいな感じで、モデルが自分と同じモデルへの参照を再帰的に持っている状態の構造が、うまく表現出来る自信がない;;
とりあえず2個のアプローチをやってみた
- parent_folder : カラムに親フォルダへの参照を追加してみた
- hierarchy モデル : フォルダ同士の関連を保持するテーブルを作ってみた
カラムに親フォルダへの参照を追加
parent_folderは同一モデルへの参照。 (モデル名とカラム名が一致しない状態になったので、class_nameを指定)
モデルのname属性が、以下の条件でユニークにしなければならない
- ownerのユーザー
- フォルダ階層
app/models/file_info.rb
class FileInfo < ActiveRecord::Base ... belongs_to :user belongs_to :parent_folder, class_name: "FileInfo" validates_presence_of :name, :user_id, :file_info_type validates_uniqueness_of :name, scope: [ :parent_folder_id, :user_id ] ... def children FileInfo.where(parent_folder_id: self.id, user: self.user) end end
自分の階層にいる子ファイル達の取得が面倒なので、 children
メソッドを定義してみた。
フォルダ同士の関連を保持するテーブル
RailsTutorialでuser同士のrelationshipsを表現している箇所と似ている気がする・・と思い、フォルダ同士の階層関係を切り出してみた。
app/models/hierarchy.rb
class Hierarchy < ActiveRecord::Base belongs_to :parent, class_name: FileInfo belongs_to :child, class_name: FileInfo validates_presence_of :parent_id, :child_id end
app/models/file_info.rb
class FileInfo < ActiveRecord::Base ... has_many :hierarchies_parent, class_name: "Hierarchy", foreign_key: "parent_id", dependent: :destroy has_many :children, through: :hierarchies_parent, source: :child has_one :hierarchies_child, class_name: "Hierarchy", foreign_key: "child_id", dependent: :destroy has_one :parent, through: :hierarchies_child, source: :parent ... end
今度は children
をあえて定義しなくても、関連でちゃんと取得できて、こっちのほうがRailsっぽい感じ!
・・で一瞬気に入ったんだけど、name属性を同一階層ユニークにするvalidation scopeが書けなくなった;;
nameに親フォルダの名前をスラッシュ区切りで入れちゃう??とか思ったけど、そうすると親のフォルダの名前がリネームされた時に、 階層を辿っていって一緒にリネームしなきゃいけないフォルダが増えてきそうで、絶対やりたくない処理な気がする・・・ (ので却下)
制約がうまく付与出来そうもないので、外部テーブル案を(泣く泣く)却下・・
まとめ書いてて気づいたこと・・!
やっぱブログにまとめるのって自分のためだよな・・
改めてまとめてたら、いくつか気づいた点が!
フォルダを表すモデルについて
失敗1(最初のアプローチ)
最初、DropBoxのオリジナルをじ〜っと眺めてて、あるフォルダを選択したら、その階層にあるフォルダ名とファイル名が一覧表示されるので、ファイルを表すモデルとフォルダを表すモデルを作ってみた。
(ちなみにFileというモデル名はRailsでは使えないので、考えた末にPureFile, PureFolderという命名に。。 なんかイマイチ感が;;)
結局一覧表示時に扱いにくい&ほとんど同じ構造だったので、1つもモデルに統合すればよいのでは・・?と気づいてこのモデリング案は却下。
失敗2(ファイルとフォルダをまとめてモデル化してみた)
ファイルシステム上ではフォルダはそもそもファイルの特殊形態として表現されていたような・・とうろ覚え知識から、FileInfoというモデルを作って、ファイルとフォルダを表現してみることに。
file_info_typeに :file, :folderを値として入れてみたものの、結局その後フォルダに関する処理ばっかりしか書いてない。
そもそもファイル新規作成はファイルアップロードで実現されるから、フォルダ情報のCRUDと全然違う気がしてきた。。 (ファイルのアップロードまでまだ到達していないので、「やってみたらなんか違ってた・・」が発生する可能性も捨てきれないけど)
結論:ファイルとフォルダを同一モデルで表現してたけど、そもそもフォルダだけを表現すればよかった・・
フォルダとファイルとの関連は別テーブルで表現すれば良いだけな気がする。
・・ただそれだと、一覧表示時に異なるモデルを一緒に表示する状態になるのかな。。
ROOT階層をの表現方法
親無し(nil)状態をROOTにすればいいじゃん!と思ってやってみたんだけど、 あっちこっちでnil?でチェックするという悲しい事態が発生・・
悲しい事態が発生している箇所:例
フォルダの作成箇所で、作成成功した後に、作成したフォルダの親フォルダのパスで画面を再描画したい箇所。
app/controllers/folder_infos_controller.rb
def create @file_info = FileInfo.new(file_info_params) @file_info.user = current_user @file_info.file_info_type = :folder if @file_info.save msg = '新しいフォルダを作成しました。' if @file_info.parent_folder.nil? redirect_to root_path, notice: msg else redirect_to file_info_path(@file_info.parent_folder), notice: msg end else redirect_to root_path, alert: '新しいフォルダを作成出来ませんでした。' end end
親フォルダが無し(nil)の場合はroot_pathにリダイレクトさせたい・・
(file_infos#showのパスで、引数がnilだった時にエラーが発生するから)
今ブログに書くのでオリジナルDropBoxのスクリーンショットを撮っててハタと気づいたんだけど、homeってパス、そもそもコレを「homeという名前を持つフォルダ」で良いんじゃ・・・?と。。
そうすればROOT階層だけ特別扱いする必要ないし・・
すべてのユーザーに最初の時点でhomeフォルダを作っちゃえば良いような気がする。。 (削除されないように、ROOT階層である、というフラグを用意する必要あるかも)
反省
ここまで行きつ戻りつして、モデル作る・・なんかうまく行かずに別のアプローチ・・ であちこち試行錯誤しているだけで、目標にしてた12時間が終わってしまった;;
データモデリングがRailsに合う形でパパっと出来るかどうかが、開発スピードの差だな〜・・ (こんなんじゃマダマダだぁ;;)