Ruby
Rails
Rails5

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

More than 1 year has passed since last update.

Rails4系で動いているサービスをRails5にバージョンアップする機会があったので、その時に実施した手順や調べた内容をまとめてみた。

注意:
Rspecなどのテスト関連についてはこの記事では扱ってないです。
勉強会で使用した内容を組み合わせて書いているので、かなり箇条書きになってます。

Rails 5の概要

Rails 5の主な変更点

  • フレームワークのAPIはほとんどRails4と同じ

    • Rails 4の知識の大半はRails5で活かせそう
  • Ruby 2.2.2以上が必須(Rails 4は1.9.3以上)

    • 基本的には最新版を使用
  • railsコマンド
    Rails4.x系で使用していたrakeコマンドはrailsコマンドで代用できるようになった。

例)
$ 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の場合はバリデーションエラーになる
model
validates :model, presence: true # デフォルト

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

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

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

  • ActiveRecordのモデルがApplicationRecordから継承されるようになった
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

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

  • ActiveJobのジョブがApplicationJobから継承されるようになった
app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end
  • ActionMailerApplicationMailerから継承されるようになった
app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
  • ActiveRecordコールバックの止め方
model
before_save :ensure_admin

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

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

  • orメソッド
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オプションが追加
updated_at = article
artile.save!(touch: false)
article.update_at == updated_at
=> true

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

  • migration versioning
    マイグレーションファイルに、Railsのどのバージョンで生成されたマイグレーションファイルなのかという情報を付与し、そのバージョン情報によりAPIの挙動を変えるというもの。
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の設定を上手くしないと衝突の問題あり
model
class User < ApplicationRecord
  has_secure_token :auth_token   # カラムを指定
end
>> 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
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で実装
  • 用途:チャット、プッシュ通知、オンラインゲーム?
$ bin/rails generate channel chat   # コードを生成

Rails API

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

$ 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はやってない。本当はやるべき。

公式ドキュメント見る(ネットで調べる)

参考にしたもの

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

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

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

今回は2.3.0にした。

  • rbenv使っている場合
$ rbenv install 2.3.0
$ rbenv global 2.3.0
$ rbenv rehash
$ ruby -v   # 2.3.0
  • rvm使っている場合
$ rvm install 2.3.0
$ rvm use 2.3.0
$ ruby -v   # 2.3.0

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

  • Gemfileを変更
Gemfile
gem 'rails', '5.0.0'
  • bundle updateする
$ bundle update rails
$ 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)

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

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

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

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

そしてbundle installすると

$ 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

無事サーバが動いた

  • 設定ファイルをアップグレードする
$ bin/rails app:update
    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

$ bin/rails db:migrate
  • ApplicationRecordへ変更
    • app/models/application_record.rbを作成
    • 既存モデルの継承元をActiveRecord::BaseからApplicationRecordに変更
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end
〇〇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

原因
Hoge.all.group("fuga")

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

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

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

番外編

  • 本番デプロイ時にハマったこと
本番サーバ
$ 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にチャレンジするのが良さそう!