UNIONを使いたいときはVIEWを作った方が手っ取り早い

ActiveRecordを使ってUNIONを実装しようとしていたのですが、どんどん複雑になっていきます。
それなら、VIEWを作った方が手っ取り早いです。

migrationファイルなど、SQLを直接記述するようなこともありますが、環境を意識しながら実装していくのであれば問題ないかと。

なお、Data Baseが
* MySQL
* PostgreSQL (native and pure ruby)
* SQL Server
であれば、[rails_sql_views](http://activewarehouse.rubyforge.org/rails_sql_views/)というgemもあります。
ですが、ここでは他のData Baseでも対応できるように、ベタなサンプルで実装しています。

# 実装環境
<dl>
  <dt>OS</dt>
  <dd>CentOS 6.5</dd>
  <dt>Ruby</dt>
  <dd>2.1.5</dd>
  <dt>Rails</dt>
  <dd>4.2.0</dd>
  <dt>DB</dt>
  <dd>MySQL</dd>
</dl>

# サンプル用のテーブル作成
今回、サンプル用に作成するテーブルです。
わざわざ二つのテーブルに分ける必要は無いのですが、サンプル用ということで勘弁してください。

**ユーザテーブルの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`の部分は適宜、年月日時分秒を指定してください。）

```rb:xxxxxxxxxxxxxx_create_view_people.rb
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は普通のテーブルとなんら変わりません。

```rb:app/models/view_person.rb
class ViewPerson < ActiveRecord::Base
end
```

これだけです。
（必要に応じて、メソッド等は追加してください。）

ここで注意が必要です。

こんなことをやってみました。

```rb
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***

```rb
ViewPerson.from("#{User.select("'user' as tbl_type, name, email").union_all(Guest.select("'guest' as tbl_type, name, email")).to_sql} view_people")
```
もしくは

```rb
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***

```rb
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")
```

となります。