Ruby on Rails 5.0 に アップデートした時に発生した問題と対応方法

みんなのウェディング、松久(@kamonegi1977) です。

利用している Ruby on Rails のバージョンを 4.2 から 5.0 にアップデートしました。
5.0 へのアップデートで行った事をまとめます。

アップデートまでの流れ

  1. Rails 5 へのアップデートプルリクエストができる
  2. 作られたプルリクエストを開発環境にデプロイし、手動でブラウザ確認する
    • 日々の変更を毎日取り込む
  3. 手動でのブラウザ確認、リリース後見つかったエラーは、再現テストを作って対応する
  4. プルリクエストをマージしてリリースする

最初、アップデートに必要なことをプルリクエストを用意しました。
その後、ブラウザ確認を行いエラーになったところを見つけたら再現テストを用意していき、リリースをしました。

gem の変更と削除

gem のアップデートは、日々追従していますが、Rails 5.0 に合わせて変更や追加が必要になった gem があります。

quiet_assets

Rails 5.0 で同等の機能が入ったので削除しました。

activerecord5-redshift-adapter

ActiveRecord 経由で Redshift に接続するために利用します。
Rails 4.2 の時は、activerecord4-redshift-adapter を利用していました。

rails-controller-testing

コントローラーのテストで assigns を使う場合にエラーになるので必要になりました。
Rspec を利用してテストをしているのですが、Rspec は 3.5 から Rails 5 に対応しています。

sprockets-rails

sprockets-rails について、バージョン指定(2.3.3)を外しました。

ActiveRecord の変更に対応する

CircleCI の環境を MySQL 5.6 にする

本番環境とテスト( CircleCI 1.0 )環境で MySQL のバージョンが違いました。

CircleCI だけで落ちるテストがあり、ActiveRecord 5 + MySQL 5.7 の組み合わせで発生するエラーに気づきました。そこで、CircleCI でも MySQL 5.6 を利用するようにしました。

一部ですが、MySQL 5.6 へのダウングレードを行なっていた時の設定です(現在は、 CircelCI 2.0 に移行しました)。

circle.yml

machine:
  pre:
    - sudo sh -c 'echo 'Asia/Tokyo' > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata'
  ruby:
    version: 2.3.3
  environment:
    TEST_CLUSTER_COMMAND: /home/ubuntu/wedding/elasticsearch-2.3.4/bin/elasticsearch
    TEST_DATABASE_URL: mysql2://root:@127.0.0.1:3306/circle_test?encoding=cp932
    DEBIAN_FRONTEND: noninteractive
    MYSQL_SERVER_VERSION: mysql-5.6
  services:
    - docker

checkout:
  post:
    - cp dot.env .env && cp config/database.yml.ci config/database.yml
    - ./bin/ci_restore_cache.sh
dependencies:
  cache_directories:
    - .dependency-cache
  pre:
    - bash ./bin/ci_downgrade_mysql.sh
    - sudo service mysql restart
    - rm -rf /home/ubuntu/.bundle/cache
    - gem up bundler
database:
  override:
    - bundle exec gem uninstall -a mysql2
    - (bundle check || bundle install -j 4 --path /home/ubuntu/.bundle) && bundle exec rake db:create db:test:load RAILS_ENV=test

bin/ci_downgrade_mysql.sh

#!/bin/bash

sudo apt-get remove --purge "^mysql.*"
sudo apt-get autoremove
sudo apt-get clean
sudo rm -rf /etc/mysql /var/lib/mysql
sudo mkdir -p /var/lib/mysql

env | grep DEBIAN_FRONTEND

sudo apt-get install debconf-utils
sudo debconf-get-selections | grep mysql

wget https://repo.mysql.com/mysql-apt-config_0.8.0-1_all.deb
echo mysql-apt-config mysql-apt-config/select-server select mysql-5.6 | sudo debconf-set-selections
echo mysql-apt-config mysql-apt-config/enable-repo select mysql-5.6 | sudo debconf-set-selections
sudo -E dpkg -i mysql-apt-config_0.8.0-1_all.deb

sudo sh -c "echo 'Package: *
Pin: origin \"repo.mysql.com\"
Pin-Priority: 999' >> /etc/apt/preferences.d/mysql"
sudo apt-key adv --keyserver pgp.mit.edu --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5

sudo apt update
sudo -E apt -y install mysql-client mysql-server libmysqlclient-dev
mysql --version

where 句を空にするとエラーになる

where 句を空にするとエラーが発生するようになりました。

元のコード

Place.where(['', {}])

変更後のコード

Place.where(*['', {}])

alias_attribute を Enum では使用できなくなった

Enum についていくつか変更がありました。
リリース前に気づいたのは alias_attribute を Enum では使用できなくなったことです。

元のコード

class Place < ApplicationRecord
  alias_attribute :category_id, :category1
  enum category_id: { foo: 0, bar: 1 }
end

変更後のコード

class Place < ApplicationRecord
  alias_attribute :category_id, :category1
  enum category1: { foo: 0, bar: 1 }
end

https://github.com/rails/rails/pull/25998 で修正され、 5.1 では対応済みのようです

ActiveRecord::Relation で * などが使えない

*(アスタリスク) や slice などが使えなくなりました。Array のように扱うことができないようになっているようです。

対応方法

to_a を使って対応

StrongParameters の厳格化

StrongParameters が厳しく適用されるようになりました。フォームオブジェクト を使っているところで指摘されることが多かったです。

元のコード

class ReviewsController < ApplicationController
  def index
    # 処理
  end

  private
  def set_review_search_form(review_search_form = {})
    @review_search_form = ReviewSearchForm.new(review_search_form)
  end
end

変更後のコード

class ReviewsController < ApplicationController
  def index
    # 処理
  end

  private
  def set_review_search_form
    @review_search_form = if params.key? :review_search_form
      ReviewSearchForm.new(params.require(:review_search_form).permit(:place_id))
    else
      ReviewSearchForm.new
    end
  end
end

params.merges

params.merge を利用している箇所で指摘されます。

元のコード

params.merge(foo: 0)

変更後のコード

params.permit(:foo).merge(foo: 0)

Rspec で、メールのテストがエラーになる

ActionMailer#deliver_later が使われているところのテストは、 perform_enqueued_jobs を使います。ActionMailer#deliver_later は、非同期でのメール送信になっているので、ActiveJob を利用した非同期処理を同期実行することでテストできます。

RSpec.configure do |config|
  config.include ActiveJob::TestHelper
end

around を利用して対応しました

around {|e| perform_enqueued_jobs { e.run } }

Rspec で HTTP request methods の引数の変更

ActionController::TestCase HTTP request methods という WARNING が発生します。
対応方法をサンプルと一緒に教えてくれるので、言われる通りに変更します。

元のコード

get :index, prefecture_name: 'tokyo'

変更後のコード

get :index, params: { prefecture_name: 'tokyo' }

リリースまでにしたこと

  1. URL(画面)の一覧を作る
  2. ブラウザーでの動作チェック
  3. Rails 4.2 でも対応できるなら変更のプルリクエストを出す
  4. 動作しなかったところはテストを書く

メジャーアップデートなので、ブラウザーでの動作チェックを行うことにしました。
チェック時に見つかったエラーは、テストを書いて対応をするようにしました。

本番環境へのリリースは、3回行いました。発生した代表的なエラーと対応を紹介します。

cache_if でキーがない

cache_if で、本番環境だけでキャッシュを利用する箇所でエラーが発生しました。

cache_key を指定しないと、モデルの updated_at もしくは Rails でキーを作っていたようです。
Rails 5.0 では、updated_at がないとキーを作らないようになったので、エラーが発生していました。ランキング情報でしたので、updated_at は不要と判断してテーブルを設計していました。

元のコード

cache_if Rails.env.production?, rankings do
  # 処理
end

変更後のコード

  1. ステージング環境でも気づけるようにした
  2. ランキング情報は created_at をキャッシュのキーに指定
cache_if %w(production staging).include?(Rails.env), rankings.cache_key(:created_at) do
  # 処理
end

クエリストリングが UTF-8 のみに

サイト内検索で 400 エラーが発生していることに、リリースして約1時間後に気づきました。
エラー検知で BadRequest を除外していたので発生時、すぐには気づけませんでした。

原因は、旧システム( MobaSiF )からサイト内検索へのリクエストが Shift-JIS の URLエンコードだったためでした。
Rails 5 からは UTF8 以外の文字コードでの URL エンコードは、BadRequest が発生します(該当箇所:actionpack/lib/action_dispatch/request/utils.rb)。また、Rack::QueryParser::InvalidParameterError も発生します。

対応策

旧システムからのリクエストについては UTF8 に書き換える Rakc MiddleWare を用意して対応しました。

エラー検知については、config.action_dispatch.rescue_responses に Rack::QueryParser::InvalidParameterError を internal_server_error に設定し、Zabbix で検知するようにしました。

今後とまとめ

今回のバージョンアップでは、ブラウザーテストで不具合を見つけてテスト(spec)を書きました。そのため、アップデートの作業にかかる時間が見積もりにくく、サービス開発の時間が減っていきました。
「テストを書く時間がないのではなく、テストを書かないから時間がなくなるのです」( James Grenning )を実感しました。

今後は、すでにリリースされている Ruby on Rails 5.1 へのバージョンアップ対応を考えています。


2019年卒のインターンシップを募集しています。
開発経験だけではなく、ユーザーファーストなサービス開発が体験できる内容になっています!


みんなのウェディングでは、一緒に働く仲間を募集しています。
興味のある方は、Wantedlyからご連絡ください。または @kamonegi1977 に直接連絡いただいても大丈夫です。