Edited at

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

More than 3 years have 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にチャレンジするのが良さそう!