Rails5へのアップグレードや変更点まとめ

Rails4系で動いているサービスをRails5にバージョンアップする機会があったので、その時に実施した手順や調べた内容をまとめてみた。
  
_注意：
Rspecなどのテスト関連についてはこの記事では扱ってないです。
勉強会で使用した内容を組み合わせて書いているので、かなり箇条書きになってます。_


#Rails 5の概要

- 2016年7月ごろに正式リリース
- RailsGuide 日本語版
    - http://railsguides.jp/5_0_release_notes.html
    - http://railsguides.jp/upgrading_ruby_on_rails.html
- Action Cable等の新機能
- Ruby 2.2.2以上で動作
- 内部的に大きな変更が色々

#Rails 5の主な変更点

- フレームワークのAPIはほとんどRails4と同じ
    - Rails 4の知識の大半はRails5で活かせそう

- Ruby 2.2.2以上が必須（Rails 4は1.9.3以上）
  - 基本的には最新版を使用
  
- railsコマンド
Rails4.x系で使用していたrakeコマンドはrailsコマンドで代用できるようになった。

```bash:例）
$ bin/rails db:migrate   # bin/rake db:migrate
$ bin/rails test   # bin/rake test
$ bin/rails restart   # touch tmp/restart.txt
$ bin/update   # bundle install, db:migrate等を実行
```

- belongs_toの参照先がnilの場合はバリデーションエラーになる

```rb:model
validates :model, presence: true # デフォルト
```

この挙動は以下の`config/initializers/new_framework_defaults.rb`を変更することでRails4.x系に戻すことができる。

```rb:config/initializers/new_framework_defaults.rb
Rails.application.config.active_record.belongs_to_required_by_default = true
```

新規作成時には注意する。
アップグレードの場合はデフォルトでfalseになっている。

- `ActiveRecord`のモデルが`ApplicationRecord`から継承されるようになった

```rb:app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end
```

アプリケーションのモデル全体に機能を追加したい場合は`Application Record`にメソッドを追加する。

- `ActiveJob`のジョブが`ApplicationJob`から継承されるようになった

```rb:app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end
```


- `ActionMailer`が`ApplicationMailer`から継承されるようになった

```rb:app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
```

- `ActiveRecord`コールバックの止め方

```rb:model
before_save :ensure_admin

def ensure_admin
  throw(:abort) unless self.admin
end
```

`throw(:abort)`ではなく`false`だと処理が続行される。


- orメソッド

```ruby
User.where(name: 'Tom').or(User.where('height > ?', 179))
#=> SELECT * FROM users WHERE name = 'Tom' OR height > 179;
```

Rails 4では生のSQLを使うかArelを使って実現していた。

- `ActiveRecord`の#saveにtouchオプションが追加

```rb
updated_at = article
artile.save!(touch: false)
article.update_at == updated_at
=> true
```

この例ではarticleデータを更新しても`update_at`は更新されない。

- migration versioning  
マイグレーションファイルに、Railsのどのバージョンで生成されたマイグレーションファイルなのかという情報を付与し、そのバージョン情報によりAPIの挙動を変えるというもの。

```rb
class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.string :name

      t.timestamps
    end
  end
end
```

[5.0]というマイグレーションファイル作成時のRailsのバージョンが追加される。


- セキュアなトークンを扱うAPIの拡張
    - has_secure_token
        - ワンタイムトークンの実装
        - MySQLの設定を上手くしないと衝突の問題あり

```rb:model
class User < ApplicationRecord
  has_secure_token :auth_token   # カラムを指定
end
```

```rb
>> user = User.new(name: 'Tom')
>> user.save
=> true

>> user.auth_token
=> "fppFh6xAsGfNTiDxzreW283f"
```

- `rails new`した時のフォルダ構成

```
.
|-- .gitignore
|-- Gemfile
|-- Gemfile.lock
|-- README.md
|-- Rakefile
|-- app
|   |-- assets
|   |   |-- config
|   |   |   `-- manifest.js
|   |   |-- images
|   |   |   `-- .keep
|   |   |-- javascripts
|   |   |   |-- application.js
|   |   |   |-- cable.js
|   |   |   `-- channels
|   |   |       `-- .keep
|   |   `-- stylesheets
|   |       `-- application.css
|   |-- channels
|   |   `-- application_cable
|   |       |-- channel.rb
|   |       `-- connection.rb
|   |-- controllers
|   |   |-- application_controller.rb
|   |   `-- concerns
|   |       `-- .keep
|   |-- helpers
|   |   `-- application_helper.rb
|   |-- jobs
|   |   `-- application_job.rb
|   |-- mailers
|   |   `-- application_mailer.rb
|   |-- models
|   |   |-- application_record.rb
|   |   `-- concerns
|   |       `-- .keep
|   `-- views
|       `-- layouts
|           |-- application.html.erb
|           |-- mailer.html.erb
|           `-- mailer.text.erb
|-- bin
|   |-- bundle
|   |-- rails
|   |-- rake
|   |-- setup
|   |-- spring
|   `-- update
|-- config
|   |-- application.rb
|   |-- boot.rb
|   |-- cable.yml
|   |-- database.yml
|   |-- environment.rb
|   |-- environments
|   |   |-- development.rb
|   |   |-- production.rb
|   |   `-- test.rb
|   |-- initializers
|   |   |-- application_controller_renderer.rb
|   |   |-- assets.rb
|   |   |-- backtrace_silencers.rb
|   |   |-- cookies_serializer.rb
|   |   |-- filter_parameter_logging.rb
|   |   |-- inflections.rb
|   |   |-- mime_types.rb
|   |   |-- new_framework_defaults.rb
|   |   |-- session_store.rb
|   |   `-- wrap_parameters.rb
|   |-- locales
|   |   `-- en.yml
|   |-- puma.rb
|   |-- routes.rb
|   |-- secrets.yml
|   `-- spring.rb
|-- config.ru
|-- db
|   `-- seeds.rb
|-- lib
|   |-- assets
|   |   `-- .keep
|   `-- tasks
|       `-- .keep
|-- log
|   `-- .keep
|-- public
|   |-- 404.html
|   |-- 422.html
|   |-- 500.html
|   |-- apple-touch-icon-precomposed.png
|   |-- apple-touch-icon.png
|   |-- favicon.ico
|   `-- robots.txt
|-- test
|   |-- controllers
|   |   `-- .keep
|   |-- fixtures
|   |   |-- .keep
|   |   `-- files
|   |       `-- .keep
|   |-- helpers
|   |   `-- .keep
|   |-- integration
|   |   `-- .keep
|   |-- mailers
|   |   `-- .keep
|   |-- models
|   |   `-- .keep
|   `-- test_helper.rb
|-- tmp
|   |-- .keep
|   `-- cache
|       `-- assets
`-- vendor
    `-- assets
        |-- javascripts
        |   `-- .keep
        `-- stylesheets
            `-- .keep

44 directories, 75 files
```

- `rails new`した時にできるGemfile

```rb
source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console'
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
```

#Rails 5の主な新機能

##Action Cable

>Action Cable はRails 5 に新しく導入されたフレームワークであり、Rails アプリケーションで WebSockets とその他の部分をシームレスに統合します。
> 通常のRailsアプリケーションと同じスタイル・方法でリアルタイム機能をRubyで書くことができる

- リアルタイムに双方向通信を行う機能
    - 内部的にはWebSocketプロトコルを使っている
    - サーバー側はRuby、クライアント側はCoffeeScriptで実装
- 用途：チャット、プッシュ通知、オンラインゲーム？

```bash
$ bin/rails generate channel chat   # コードを生成
```

##Rails API

>APIのみを提供するシンプルなアプリケーションをRailsで簡単に作成できるようになりました。 Twitter APIや GitHub APIのような一般公開APIサーバーはもちろん、カスタムアプリケーション用APIサーバーの作成・公開にも便利です。

```bash
$ bin/rails new my_api --api
```

- Railsを使って、画面がない、Web APIのみのアプリケーションを簡単に作るための仕組み
- formatがjsonになる
- APIのための機能がつくわけではない
    - RailsからAPIにとって不要な要素を除くだけ

#Rails5へのアップグレード

- 今回の対象サービス
    - Rails 4.2.5
    - Ruby 2.0.0
    - rvm 1.27
    - Passenger

- アップグレード作業
    - 公式ドキュメント見る（ネットで調べる）
    - アップグレード用の作業ブランチを作る
    - 現状のテストが全部パスすることを確認する
    - Rubyのバージョンを最新版にアップグレードする
    - テスト関連のgemを最新版にアップグレードする
    - Rails 5.0.0にアップグレードする
    - Rails 5の書き方に変更する

今回のアップグレードでは3、5はやってない。本当はやるべき。

## 公式ドキュメント見る（ネットで調べる）

参考にしたもの

- http://tech.grooves.com/entry/2016/07/01/184458
- http://qiita.com/jnchito/items/fa680e104d4bf49ae06f
- http://qiita.com/ryo511/items/2a387df126268fec8c78

## アップグレード用の作業ブランチを作る

```bash
$ git checkout -b rails-5 origin/rails-5
```

- いきなりmasterで作業するのは危ないので作業用ブランチを作って最後にマージするようにした
- いつでもROLLBACKできるようにしておく

##Rubyのバージョンを最新版にアップグレードする

今回は2.3.0にした。

- rbenv使っている場合

```bash
$ rbenv install 2.3.0
$ rbenv global 2.3.0
$ rbenv rehash
$ ruby -v   # 2.3.0
```

- rvm使っている場合

```bash
$ rvm install 2.3.0
$ rvm use 2.3.0
$ ruby -v   # 2.3.0
```

## Rails 5.0.0にアップグレードする

- Gemfileを変更

```rb:Gemfile
gem 'rails', '5.0.0'
```

- bundle updateする

```bash
$ bundle update rails
```

```bash
$ bin/rails -v   # Rails 5.0.0
```

- 依存関係でエラーが出た場合は関連するgemも一緒に最新版にアップグレードする
- Rails5に対応していないgemがあった場合はwarningが出る

ここで試しに`bin/rails server`でサーバーが動くか試したところ動かない・・・

```
vendor/bundle/ruby/2.3.0/gems/sinatra-1.0/lib/sinatra/showexceptions.rb:1:in `require': cannot load such file -- rack/showexceptions (LoadError)
```

調査した結果、原因は`sinatra`と`Rails 5`でサポートしているrackのバージョンが違うため？

参考：https://github.com/splitrb/split/issues/354

gem fileを該当箇所を以下に変更

```rb:Gemfile
gem 'sinatra', github: 'sinatra/sinatra'
```

そして`bundle install`すると

```bash
$ bin/rails server
=> Booting Puma
=> Rails 5.0.0 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.5.2 (ruby 2.3.0-p0), codename: Amateur Raccoon Rocketry
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
```

無事サーバが動いた

- 設定ファイルをアップグレードする

```bash
$ bin/rails app:update
```

```bash
    conflict  config/boot.rb
Overwrite /Users/username/Project/project_name/config/routes.rb? (enter "h" for help) [Ynaqdh] y
       force  config/routes.rb
```

既存ファイルを上書きしていいか聞かれるのでここでは全てYesと答える

- 上書きされた差分を`git diff`を確認しながら必要に応じて元に戻す
    - 地道な作業
    - `routes.rb`の設定とか全て消えてるので注意
    - `Action Mailer`の設定も上書きされるので注意

ここまででアップグレードは大体終わる。

##Rails 5の書き方に変更する

- `schema.rb`の更新
   - Rails 5では形式が変わっている

下記コマンドでOK

```bash
$ bin/rails db:migrate
```

- `ApplicationRecord`へ変更
   - `app/models/application_record.rb`を作成
   - 既存モデルの継承元を`ActiveRecord::Base`から`ApplicationRecord `に変更

```rb:app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end
```

```rb:〇〇model
class Product < ApplicationRecord
```

- `ApplicationJob`へ変更
    - `app/jobs/application_job.rb`を作成
    - 既存ジョブの継承元を`ActiveJob::Base`から`ApplicationJob`に変更
    - Modelと同じ感じ

- `ApplicationMailer`へ変更
    - `app/mailers/application_mailer.rb`を作成
    - 既存ジョブの継承元を`ActionMailer::Base`から`ApplicationMailer`に変更
    - これもModelと同じ感じ

- ログの`DEPRECATION WARNING`をひたすら修正する
    - 古いバージョンの書き方をしている箇所で発生
    - gemが対応してなくて発生
    - Rails 5ではAPIが色々変わっているために発生したりもする
    - ログに内容が出るので一つずつ修正していく

以降は動作確認しながら適宜修正


- 動作確認すると特定のページでエラー発生

`ActiveRecord::StatementInvalid - Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column`

```rb:原因
Hoge.all.group("fuga")
```

テーブルの構成の問題？よくわかってない。。。
やりたいこととしては特定の２カラムに対して、重複を除いたレコードを取得したかったので以下のように変更した。

```rb:対応
Hoge.select(:fuga, :piyo).distinct
```

参考：http://qiita.com/bboobbaa/items/9fdca834076cb4c3389e


##番外編

- 本番デプロイ時にハマったこと

```bash:本番サーバ
$ rvm use 2.3.0   # ruby 2.3.0を使う
$ git pull
$ bin/update
$ bin/rails restart    # これで動くはず！
```

結果、動かない・・・
Railsのログにも情報がない・・・

Apacheのログを確認すると・・・

`Could not find rake-10.4.2 in any of the sources (Bundler::GemNotFound)`

？？？

ruby 2.3.0に切り替えて`bundle install`しているのに使われてないような気がする・・・

原因は`Passenger`だった

`Passenger`はRailsアプリのデプロイツールで、アプリが使用するrubyのデフォルトバージョンを設定でき、これが`ruby 2.0.0`のままだった。。。

設定ファイルを変更し、Apache再起動で無事デプロイ完了！


## 所感

- 細かい設定の変更はあるが、一人でもできそう
- エラーが出たらよく内容を読んで頑張る（笑）
- 正式リリース直後は英語のドキュメントが多くて苦労したけど、今は日本語のドキュメントもまとまってる
- Rails 4の知識があればそんなに困らない
- これから勉強するならまず、Rails 4から始めてその後にRails 5にチャレンジするのが良さそう！