MySQL上でVIEWを生成し、RailsからはModelとして認識させて振る舞いたい等のケースを想定しています。
PostgreSQL等では rails_sql_views という必殺gemがあるので大丈夫なんですが、
MySQLでは残念ながらそういうのが無いのです。
migration時にSQLを実行してVIEWを作る
migrationでSQLを直接吐いてVIEWを作成します。
基本的にはこれだけで大丈夫です。
class CreateMyView < ActiveRecord::Migration
def self.up
execute <<-SQL
CREATE VIEW my_view AS SELECT ...
SQL
end
def self.down
execute <<-SQL
DROP VIEW my_view
SQL
end
end
テストDB/CI対策
通常通りmigrationが進めば問題ないのですが、rake test/spec
等で利用するテスト用のDBに対しては、
通常 rake db:test:prepare
によってDBスキーマが準備されます。
A Guide to Testing Rails Applicationsによると
rake db:test:prepare
Check for pending migrations and load the test schema
となっています。どういうことかというと、
The rake db:migrate above runs any pending migrations on the development environment and updates db/schema.rb.
The rake db:test:load recreates the test database from the current db/schema.rb.
- 開発環境のmigrationによって db/schema.rb が生成される
- テストデータの準備などにはmigrationを行わず、db/schema.rbを参考にして一気にテーブル構造を準備する
といった動作がなされるということです。通常のケースであれば問題ないのですが、
今回のようなSQLを直接流しているような migration が含まれていた場合、
db/schema.rb からは正常にテーブルを再現できないケースがあります。
例えば、今回のケースだと my_view
は物理テーブルとして作成されてしまう等です。
Travis等でテストをしている場合も同じ問題にあたると思います。
丁度、同じ問題にあたっている方がいらっしゃったので引用。
というわけでこの場合は、rake db:test:prepare を改造します。
# Rakefile
Rake::TaskManager.class_eval do
def remove_task(task_name)
@tasks.delete(task_name.to_s)
end
end
# lib/tasks/db/test.rake
Rake.application.remove_task "db:test:prepare"
namespace :db do
namespace :test do
task :prepare do
system("rake db:drop RAILS_ENV=test")
system("rake db:create RAILS_ENV=test")
system("rake db:migrate RAILS_ENV=test")
end
end
end
これでテスト用DBもmigrationで作成するようになるので、ひとまず動作できるはず。
Travis CIを利用している場合は上記対策をしておけばこれだけで問題ないです。
# .travis.yml
script: "bundle exec rake test/spec"
rake test/spec
でテストが走る前に改造された db:test:prepare
がタスクとして動作するってことですね。