ActiveRecordを使ってUNIONを実装しようとしていたのですが、どんどん複雑になっていきます。
それなら、VIEWを作った方が手っ取り早いです。
migrationファイルなど、SQLを直接記述するようなこともありますが、環境を意識しながら実装していくのであれば問題ないかと。
なお、Data Baseが
* MySQL
* PostgreSQL (native and pure ruby)
* SQL Server
であれば、rails_sql_viewsというgemもあります。
ですが、ここでは他のData Baseでも対応できるように、ベタなサンプルで実装しています。
実装環境
- OS
- CentOS 6.5
- Ruby
- 2.1.5
- Rails
- 4.2.0
- DB
- MySQL
サンプル用のテーブル作成
今回、サンプル用に作成するテーブルです。
わざわざ二つのテーブルに分ける必要は無いのですが、サンプル用ということで勘弁してください。
ユーザテーブルのmigrationファイルの作成
bundle exec rails g model user name:string email:string
ゲストテーブルのmigrationファイルの作成
bundle exec rails g model guest name:string email:string
VIEWの作成
サンプルのテーブルをUNION ALL
して、一つのVIEWとします。
generate
では作成できないので、手動で実装します。
今回は、users
とguests
を統合して、view_people
というVIEWを作成します。
(ファイル名のxxxxxxxxxxxxxx
の部分は適宜、年月日時分秒を指定してください。)
class CreateViewPeople < ActiveRecord::Migration
DB_NAME = "view_people" # VIEWの名称
def up
execute create_view_sql
end
def down
execute drop_view_sql
end
def create_view_sql
db_adapter = ActiveRecord::Base.connection_config[:adapter]
case db_adapter
when 'mysql2' then
create_mysql_view_sql
else
raise Exception, "Not Support Data Base [#{db_adapter}]"
end
end
def drop_view_sql
"DROP VIEW #{DB_NAME}"
end
def create_mysql_view_sql
"
create or replace view #{DB_NAME}
as
select
'user' as tbl_type,
name ,
email ,
created_at ,
updated_at
from
users
union all
select
'guest' as tbl_type,
name ,
email ,
created_at ,
updated_at
from
guests
;
"
end
end
さて、単純にdef up
にCREATE VIEW
を書いても良いのですが、直接SQLを書くことを考慮して、Data Base別に実装できるようにしています。
ActiveRecord::Base.connection_config[:adapter]
は、config/database.yml
のadapter
を取得します。
これで実装相手のData Baseを判断して、それに合ったCREATE VIEW
を生成します。
def down
は、Data Base毎に書き方が変わることはないかと思い、そのまま書いています。
もし、違う場合があれば、def up
同様、切り分けてください。
最後に、rake db:migration
をして、エラーが出なければOKです。
MODELの作成
上記でVIEWはできましたが、VIEWを参照するために、モデルを作成します。
このMODELは普通のテーブルとなんら変わりません。
class ViewPerson < ActiveRecord::Base
end
これだけです。
(必要に応じて、メソッド等は追加してください。)
ここで注意が必要です。
こんなことをやってみました。
obj = ViewPerson.new
obj.tbl_type='guest'
obj.name='hoge'
obj.email='hoge@hoge.com'
obj.save
インスタンスから値の代入までは問題なく実行できるのですが、save
は失敗しました。
MySQLからERRORが戻ってきます。
つまり、UNIONを使用したVIEWでは保存処理はできません。
ですが、MySQLからERRORが戻ってきたということは、保存できるようなVIEWであれば、save
もできそうです。(未検証)
VIEWを使わなかったら
ActiveRecordでUNIONは以下のように書きます。
UNION
ViewPerson.from("#{User.select("'user' as tbl_type, name, email").union_all(Guest.select("'guest' as tbl_type, name, email")).to_sql} view_people")
もしくは
ViewPerson.from("#{Arel::Nodes::Union.new(User.select("'user' as tbl_type, name, email").ast, Guest.select("'guest' as tbl_type, name, email").ast).to_sql} view_people")
UNION ALL
ViewPerson.from("#{Arel::Nodes::UnionAll.new(User.select("'user' as tbl_type, name, email").ast, Guest.select("'guest' as tbl_type, name, email").ast).to_sql} view_people")
となります。