Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2007年09月27日]

railsでCREATE VIEWは使えるか?

2007年09月28日 11:24更新 筆者:K.Ktouth

7月からrailsの練習をガシガシ進めてる……つもりですが、全然形になりません(ぉ
railsは強力だけど、やはり個人で(SQLとセットで)勉強しながらだと結構時間がかかる……むぅ、悩ましいげー

で、いくつかの関連が指定されたモデルAに対して絞り込みをできるようなクエリを作成。
サブクエリを6段ほど組み合わせる上に、モデルAの読み込み自体 :include オプションで3つの関連モデルを同時読み。
結果として1回のクエリの長さが相当なものに。読みづらい(笑)

とりあえず検索で使うサブクエリをビューとして登録したいのだけど……何かスマートな方法はないかと試行錯誤。
一応rails上で管理できる形でCREATE/DROP VIEWできるようになりました。かなり力業だけど(ぉ

まずはMigration

通常のモデル用のテーブル同様、Migration用のファイルを作成してそこで作成・削除のコードを登録する事に。
とはいえテーブルと違ってMigrateでの定義周りにビュー定義機能はないので、executeで直に記述。

def self.up
 execute <<-ENDE
  CREATE VIEW example_view AS
   SELECT * FROM examples, taggings, tags WHERE (中略);
 ENDE
end

def self.down
 execute <<-ENDE
  DROP VIEW example_view;
 ENDE
end

これだけで、rake db:migrate するときちんとデータベースの更新がうまくいく事を確認。

だけど、このテーブルをテストで使用するとうまくいかない。
処理を見てると、まずテスト用のデータベースを最初に作成してる際に上記コードが実行されていない模様。
どーも、db:schema:dumpタスクで生成されるスキーマファイルを使用し、migrateファイルは使ってないという事らしい……

ActiveRecord::SchemaDumper クラスをいじれ!

スキーマファイル(schema.rb)を生成しているのはAR::SchemaDumperというクラスらしい。
そのクラスの定義されたコードを見てびっくり。スキーマファイルの内容は、データベースの内容から逆算して算出してましたびっくり
ち、力業だ……

重要なのは、その処理ではあくまでもテーブル定義だけを取得しているため、それ以外のSQLはスキーマファイルに書き込まれないと言う事。
このあたりの処理をAR::SchemaDumperで実装しているので、その一部を自前の処理で上書きしてやる事に。

require 'active_record/schema_dumper'

class ActiveRecord::SchemaDumper
# 元のコード
# def trailer(stream)
#  stream.puts 'end'
# end
#
# ↓
#
 def trailer(stream)
  if @info['version'].to_i >= (マイグレーション番号)
   stream.puts ' execute <<-ENDE rescue nil'
   stream.puts (ビュー作成SQL)
   stream.puts ' ENDE'
  end
  stream.puts 'end'
 end
end

マイグレーション番号は、先ほど作ったMigrationと同じ番号を指定。コレがないとバージョン指定で古いスキーマ生成した際にも作ってしまう。
ビュー作成SQLはMigrationでexecuteしたものと同じ。定義を複数の場所で書くのは望ましくないので、実際のSQLはMigration側から抽出して別ファイルにしています。

コレもかなり力業。
テーブル同様スキーマから抽出可能だと思うので、暇があったらこの辺作り替えてみます。
で、このファイルを読ませてやればいいんですが、まだ作業は残ってます。

db:schema:dump タスクを改造

実際のスキーマの生成は db:schema:dump タスクで行われています。
このタスクの中で 'active_record/schema_dumper' ライブラリを読み込み、インスタンスを生成して出力……という処理を行っています。

ところが。
ライブラリ読み込みからインスタンス生成までに割り込む余地がない。さて困ったげー

まず、自前のタスクの追加は rails でサポートしています。
この場合、"#{RAILS_ROOT}/lib/tasks" 以下に拡張子 .rake のファイルを作成しておけば rake が自動で読み込んでくれます。rails 標準のものの後に
さらに rake の同名タスクの追加処理は常に後付の模様。

そこでこんなタスクを書いてみました。

namespace :db do
 namespace :schema do
  # 追加処理はライブラリを読み込むだけ
  task :dump => :environment do
   require "#{RAILS_ROOT}/lib/(SchemaDumper改造ライブラリ)"
  end
  # 無理矢理タスクの順番を入れ替える
  task(:dump).instance_eval do
   @actions[-2], @actions[-1] = @actions[-1], @actions[-2]
  end
 end
end

SchemaDumper改造ライブラリは上記のやつを記述したライブラリです。
require は同名のライブラリのロードはスキップするので、標準タスクの前に外部ライブラリを先に読んでおくだけでOK。
とはいえ、追加しただけでは順番が後付なので、タスクインスタンスの内部をいじって、処理の順番を入れ替えています。

コレでようやく出力されるスキーマファイル(schema.rb)にビューの定義が含まれるようになりました。
テストでもビューが使用できるようになったので、とりあえず完成です。

……識者ならもっとスマートにやるんだろうな……あせ

本日のリンク元
アンテナ
その他のリンク元
検索