Ruby on RailsをOracle Autonomous Databaseに接続する

# はじめに

いまじわじわと話題になってる？Autonomous DatabaseをRailsから使うための設定方法です。

こちらのスクリプトは、Docker環境でOracle Autonomous Database(Autonomous Transaction Processing)を利用したRailsの設定となります。

「Oracle Code TokyoでRailsのハンズオンやろうぜ」と誘われ、「いいよ」とノリで答えてしまってからGKBRしておりました。

実は一年ほど前に、Oracleとの接続をためしたのですがそのときはあえなく撃沈。先にお断りしておきますが、MacOSだと事情があってこの手順そのままでは動きません。今回はOracle LinuxのDockerを利用することで比較的すんなりと接続することができました。

5/17にOracle Code Tokyoのハンズオンセッションで実施する内容とほぼ同じです。どこかのRubyの勉強会で呼んでもらえたら実演しにいきます。

## Docker作成と実行

ざくっとDockerfileを書きました。これをコピペしてDockerfileという名前にしてください。

```dockerfile
# LICENSE UPL 1.0
#
# Copyright (c) 2014-2018 Oracle and/or its affiliates. All rights reserved.
#
# ORACLE DOCKERFILES PROJECT
# --------------------------
#
# Dockerfile template for Oracle Instant Client
#
# HOW TO BUILD THIS IMAGE
# -----------------------
#
# Run:
#      $ docker build -t oracle/instantclient:18.3.0 .
#
FROM oraclelinux:7-slim

# # docker login container-registry.oracle.com
# FROM oracle/serverjre:8


RUN  curl -o /etc/yum.repos.d/public-yum-ol7.repo https://yum.oracle.com/public-yum-ol7.repo && \
     yum-config-manager --enable ol7_oracle_instantclient && \
     yum -y install oracle-instantclient18.3-basic oracle-instantclient18.3-devel oracle-instantclient18.3-sqlplus && \
     rm -rf /var/cache/yum && \
     echo /usr/lib/oracle/18.3/client64/lib > /etc/ld.so.conf.d/oracle-instantclient18.3.conf && \
     ldconfig

RUN echo 'export LD_LIBRARY_PATH="/usr/lib/oracle/18.3/client64/lib"' >> ~/.bash_profile
RUN echo 'export TNS_ADMIN="/usr/local/etc"' >> ~/.bash_profile
RUN echo 'export NLS_LANG="Japanese_Japan.AL32UTF8"' >> ~/.bash_profile
RUN yum install -y git unzip vim gcc openssl-devel readline-devel zlib-devel sqlite-devel nodejs bzip2 make
RUN git clone https://github.com/sstephenson/rbenv.git /usr/local/rbenv
RUN mkdir /usr/local/rbenv/shims /usr/local/rbenv/versions /usr/local/rbenv/plugins
RUN groupadd rbenv
RUN chgrp -R rbenv /usr/local/rbenv
RUN chmod -R g+rwxXs /usr/local/rbenv
RUN git clone https://github.com/sstephenson/ruby-build.git /usr/local/rbenv/plugins/ruby-build
RUN chgrp -R rbenv /usr/local/rbenv/plugins/ruby-build
RUN chmod -R g+rwxs /usr/local/rbenv/plugins/ruby-build
RUN /usr/local/rbenv/plugins/ruby-build/install.sh
RUN git clone https://github.com/sstephenson/rbenv-default-gems.git /usr/local/rbenv/plugins/rbenv-default-gems
RUN chgrp -R rbenv /usr/local/rbenv/plugins/rbenv-default-gems
RUN chmod -R g+rwxs /usr/local/rbenv/plugins/rbenv-default-gems
RUN echo 'export RBENV_ROOT="/usr/local/rbenv"' >> /etc/profile.d/rbenv.sh
RUN echo 'export PATH="/usr/local/rbenv/bin:$PATH"' >> /etc/profile.d/rbenv.sh
RUN echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh
RUN echo '%rbenv ALL=(ALL) ALL' >> /etc/sudoers
RUN su -l root -c '/usr/local/rbenv/bin/rbenv install 2.6.3 -v'
RUN su -l root -c '/usr/local/rbenv/bin/rbenv rehash'
RUN su -l root -c '/usr/local/rbenv/bin/rbenv global 2.6.3'
RUN su -l root -c 'gem install -N rails ruby-oci8'
RUN su -l root -c 'mkdir /usr/local/app && cd /usr/local/app'
RUN su -l root -c 'rails new dummy && cd dummy && bundle package --all'

ENV PATH=$PATH:/usr/lib/oracle/18.3/client64/bin
ENV LD_LIBRARY_PATH="/usr/lib/oracle/18.3/client64/lib"
ENV TNS_ADMIN="/usr/local/etc"


CMD ["/bin/bash", "--login"]

```

DockerfileができたらDocker buildします。

```sh
docker build -t oracle-code-tokyo2019/rails-atp:1.0 .

docker run -it -p 3000:3000 --name oracle-ruby-atp oracle-code-tokyo/rails-atp:1.0 /bin/bash --login

```


OracleLinuxインスタンスで試す場合の手動構成スクリプト　ここから下の構成はDockerfileでは自動化済み。実験したインスタンスに入ってたinstantclientが18.5だったのでDocker版とは微妙に異なります。

```sh

echo 'export ORACLE_HOME="/usr/lib/oracle/18.5/client64"' >> ~/.bash_profile
echo 'export LD_LIBRARY_PATH="/usr/lib/oracle/18.5/client64/lib"' >> ~/.bash_profile
echo 'export TNS_ADMIN="/usr/local/etc"' >> ~/.bash_profile
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'export NLS_LANG="Japanese_Japan.AL32UTF8"' >> ~/.bash_profile

cd; yum -y install git
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
exec $SHELL
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

一度抜ける

localpc$ docker start oracle-ruby-atp
localpc $ docker exec -it oracle-ruby-atp /bin/bash —login


sudo yum install -y gcc openssl-devel readline-devel zlib-devel sqlite-devel nodejs bzip2 make
rbenv install 2.6.3
rbenv global 2.6.3
mkdir /usr/local/app
cd /usr/local/app
gem install -N rails
```






# Oracle Autonomous DB(ATP)接続設定

### インスタンスの場合

ローカルPCからWalletのコピー

```sh
scp -i ~/.ssh/id_rsa Wallet_atp.zip opc@<innstance_ipaddr>:
```

ターゲットインスタンスで以下実行。sqlnnet.oraで環境変数TNS_ADMINと一致させる部分を書き換えます。

```sh
localpc#  ssh opc@<innstance_ipaddr> -i ~/.ssh/id_rsa
sudo cp Wallet_atp.zip /usr/local/etc/
cd /usr/local/etc/
sudo unzip Wallet_atp.zip
sudo cp sqlnet.ora sqlnet.ora.org && cat sqlnet.ora.org | sudo sh -c "sed -e 'N;s/\?\/network\/admin/\/usr\/local\/etc/g' > sqlnet.ora"

```


### Dockerの場合

別ターミナルから、docker container idを確認

例　00407e328a55

```sh
docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'
docker cp Wallet_atp.zip 00407e328a55:/usr/local/etc
```
docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'
docker cp Wallet_atp.zip 00407e328a55:/usr/local/etc

複合スクリプト

```sh
docker cp Wallet_atp.zip `docker ps |grep oracle-code-tokyo/rails-atp | awk '{ print $1 }'`:/usr/local/etc

```


dockerにもどりWalletの解凍　sqlnet.oraの修正


```

cd /usr/local/etc/
unzip Wallet_atp.zip

cp sqlnet.ora sqlnet.ora.org && cat sqlnet.ora.org | sed -e 'N;s/\?\/network\/admin/\/usr\/local\/etc/g' > sqlnet.ora
```

### Oracleへのコマンドからの接続確認

```sh
sqlplus admin/Oracle123456@atp_tp

Password: HogeHoge123456
```
つながるはずです。



### Rails動作確認

まず、この状態でsqlite3を使って動作確認します。

```sh
rails new toy_app && cd toy_app/ && rails s -b 0.0.0.0
curl localhost:3000
```


# Rails をOracle Autonomous DBで動かす

### Oracle Driver のインストール

Oracleで使う場合は、activerecord-oracle_enhanced-adapter　と　ruby-oci8のgemをGemfileに追加してください。


```ruby
# Use oracle as the database for Active Record
gem 'activerecord-oracle_enhanced-adapter', '~> 5.2.0'
gem 'ruby-oci8' # only for CRuby users
```

Oracle Driver追記スクリプト。めんどくさいのでコピペ一発のスクリプト化しました。

```sh
cd /usr/local/app/toy_app/
# sed -i -e "s/gem 'sqlite3'/\# gem 'sqlite3'/g" Gemfile

cat << 'EOS' | sed -i '9r /dev/stdin' Gemfile
# Oracle drivers
gem 'activerecord-oracle_enhanced-adapter', '~> 5.2.0'
gem 'ruby-oci8' # only for CRuby users
EOS


# Use oracle as the database for Active Record
```


#### Oracle Tips 10,000から始まるidを1から始まるように変更

ここで一旦うごいたのですが、idが10000から始まるという現象にノックアウト気味でした。どうやら設定が必要です。

https://doruby.jp/users/tips4tips/entries/RailsでOracle～-導入と文字列の扱いについて



Oracleのデフォルトではidが10000から始まるので、1から始まるように設定変更

```sh

cat <<EOS > config/initializers/oracle.rb
# It is recommended to set time zone in TZ environment variable so that the same timezone will be used by Ruby and by Oracle session
ENV['TZ'] = 'UTC'

ActiveSupport.on_load(:active_record) do
  ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
    # true and false will be stored as 'Y' and 'N'
    self.emulate_booleans_from_strings = true

    # start primary key sequences from 1 (and not 10000) and take just one next value in each session
    self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1"

    # Use old visitor for Oracle 12c database
    # self.use_old_oracle_visitor = true

    # other settings ...
  end
end
EOS
```

gemを有効化

```sh
bundle install
```

database.ymlを修正 tnsname.oraのtp設定を記入します。改行がはいってしまっているのは、ブロック単位で切り出して一行にｎつなげてあげなければいけません。

DBユーザー名は、ここではadminにしています。実運用時にはちゃんとappユーザーを作るべきですね。
enncodingはutf-8を指定しないとひどい目に遭った気がします。

```yaml
mv config/database.yml config/database.yml.org

cat <<EOS > config/database.yml
default: &default
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  adapter: oracle_enhanced
  username: admin
  password: HogeHoge123456
  timeout: 5000
  encoding: utf8

development:
  <<: *default
  database: (description=(address=(protocol=tcps)(port=1522)(host=adb.ap-tokyo-1.oraclecloud.com))(connect_data=(service_name=hogehogehogehoge_atp_tp.atp.oraclecloud.com))(security=(ssl_server_cert_dn="CN=adb.ap-tokyo-1.oraclecloud.com,OU=Oracle ADB TOKYO,O=Oracle Corporation,L=Redwood City,ST=California,C=US")))

test:
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  database: db/test.sqlite3

production:
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  database: db/production.sqlite3
EOS
```

## Rails + ATPで実際にアプリを動かす

ここまで来たら、あとは普通に使うことができます。

```sh
rails generate scaffold User name:string email:string
rails db:migrate

rails s -b 0.0.0.0
```

お疲れ様です！

### Tips

- 順序が自動生成されているので、DB作り直しのときにSQL Developerから削除が必要でした
- rails db:migrate:resetがエラーを吐く気がします。