30歳パパエンジニアのアウトプット帳

30歳に突入した1児のパパエンジニアのブログ

Rails4.2から5.1へアップグレードした

お仕事で関わっているRailsアプリを4.2から5.1へアップグレードした。

もうRails4系から5系へのバージョンアップをやったブログとかかなりあるのでアレだが、

初めて自分もRailsのメジャーバージョンアップの対応をやったのでメモとして残そうと思う。

所感

あとはつらつらやった作業のメモを残すだけになってしまったので、最初にまとめというか所感を。

  • まさに辛かったの一言
  • 世の中で言われている当たり前のことをちゃんとやってないと、こういうときに牙をむくことを改めて痛感
    • ライブラリはこまめにアップデートとか、コードの書き方統一しましょうとか、テストちゃんと書きましょうとかとか
  • 最後にまとめてbundle updateやったけど影響あるときにどれが関連しているかを調べるのに苦労したので、1つ1つやったほうがよかった

辛かった話は毎月参加しているkawasaki.rbでも話して鬱憤を晴らさせてもらった。

speakerdeck.com

現状

アップグレード前のRubyRailsのバージョンは下記。

Ruby:2.3.0

Rails:4.2.6

アップグレードしてこの記事を書くまでちょっと空いてしまったが、

アップグレード検討を始めたときの最新版などを調査してRubyRailsを下記にすることとした。

Ruby:2.4.2

Rails:5.1.4(ver4は4.2.9, 4.2.10.rc1がでてる)

事前調査

まずはRails5で何が変わるかなどを公式のリリースノートを見た。

https://railsguides.jp/5_0_release_notes.html

https://railsguides.jp/5_1_release_notes.html

アップグレードの方針

初めてのメジャーバージョンアップなのでどうやるのが良いのかを色々調べた。

まずは、公式のアップグレードガイド。

https://railsguides.jp/upgrading_ruby_on_rails.html


そして、先人の知見を見て回る。

https://qiita.com/yamamuteki/items/cfc7bb9999518bc5b19c

https://qiita.com/jnchito/items/fa680e104d4bf49ae06f

http://blog.rista.jp/entry/2017/06/11/190616

https://dev.classmethod.jp/server-side/ruby-on-rails-5-0-upgrade/

http://r7kamura.hatenablog.com/entry/2016/12/10/045755

http://blog.spacemarket.com/code/how-to-upgrade-rails5-1-2/

http://tech.grooves.com/entry/2016/07/01/184458

https://blog.mwed.info/posts/rails50.html

https://qiita.com/zenpou/items/950432bae246063786e8

http://akasata.com/articles/319

https://doruby.jp/users/bibio/entries/Ruby-on-Rail-4-2-%E3%81%8B%E3%82%89-5-1-%E3%81%B8%E7%A7%BB%E8%A1%8C%E3%81%97%E3%81%9F%E9%9A%9B%E3%81%AE%E3%83%A1%E3%83%A2

https://qiita.com/kenchan0130/items/f5ae55a6e23268238a44

色々と調べた結果、いきなり最新の5.1にするのではなく少しずつ刻みながらアップグレードしたほうが安全そうなので下記の流れでやる方針とした。

  • Rails4.2系の最新(4.2.6→4.2.9)へ
  • Rails5.0系(5.0.6)へ
  • Rails5.1系(5.1.4)へ
  • Rubyを2.3.0から2.4.2へ

gemも全てをいきなりバージョンアップするのではなく、まずはRailsのアップグレードに必要なものだけを対応して、その他はRails5.1へアップグレード対応後にやるようにした。

アップグレード作業

4.2系の最新版へ

specの実行

まず現段階でテストがオールグリーンなのを確認。

Gemfileの更新

1.Gemfileのrailsのバージョンを変更

gem 'rails', '4.2.6'
↓
gem 'rails', '4.2.10'

2.bundle update railsを実行

railsのアップデートは問題なく完了

3.specの実行

ここでエラーやWarningがないかを確認する。

Warningが出た。

Sprocketsが3.6から3.7にアップデートされたことによってsass-railsにSprocketsで非推奨の記述がされているらしい。

DEPRECATION WARNING: Sprockets method `register_engine` is deprecated.
Please register a mime type using `register_mime_type` then
use `register_compressor` or `register_transformer`.
https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors
 (called from block (2 levels) in <class:Railtie> at /app/tmp/bundler/ruby/2.3.0/gems/sass-rails-5.0.3/lib/sass/rails/railtie.rb:57)
DEPRECATION WARNING: Sprockets method `register_engine` is deprecated.
Please register a mime type using `register_mime_type` then
use `register_compressor` or `register_transformer`.
https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors
 (called from block (2 levels) in <class:Railtie> at /app/tmp/bundler/ruby/2.3.0/gems/sass-rails-5.0.3/lib/sass/rails/railtie.rb:58)

なので、sass-railsもアップデートする必要がある。

bundle update sass-rails アップデート後、再度specを実行するとWarningが出なくなった。

rake rails:update

マイナーバージョンアップなので特にconfigとかに変更はないと思うが一応やった。

案の定特に変更はなかった。

5.0系の最新版へ

5.0系の最新版5.0.6へバージョンアップする

Gemfileの更新

1.Gemfileのrailsのバージョンを変更

gem 'rails', '4.2.10'
↓
gem 'rails', '5.0.6'

2.bundle update railsを実行

Bundler could not find compatible versions for gem 'xxx'との長い戦い。。。

結局色々bundle updateに含めて下記のようになった

bundle update rails simple_form coffee-rails devise rspec-rails slim-rails guard-rspec jquery-rails

jquery-railsはbootstrapの関連ライブラリやテーマの依存関係もあるのでここでは最小限のアップデートしかしなかった。

3.specの実行

rspecをバージョンアップしたので、deprecateエラーが出る。。。

DEPRECATION WARNING対応

  • rspecのgetやpostなどでparamsの指定が変わる
DEPRECATION WARNING: Using positional arguments in functional tests has been deprecated,
in favor of keyword arguments, and will be removed in Rails 5.1.

下記のような書き方をしないといけないらしい。

get :index, {name: 'tom'}
↓
get :index, params: {name: 'tom'}

適当に正規表現で置換してみたけど、記法がばらばらで結局都度spec実行しながら1つずつ対応していかなければならず辛かった。。

  • deviseのアップデートによる変更対応
DEPRECATION WARNING: [Devise] including `Devise::TestHelpers` is deprecated and will be removed from Devise.
For controller tests, please include `Devise::Test::ControllerHelpers` instead.
 (called from <top (required)> at /app/spec/xxx/xxxx_spec.rb

このDEPRECATION WARNINGが大量に出ている。

なので、rspecでDevise::TestHelpersを使っている箇所をDevise::Test::ControllerHelpersに変更してやる必要がある。

grepしてみるとrails_helper.rbにしかなかったのでその1箇所を修正した。

  • xxx_filter対応
DEPRECATION WARNING: before_filter is deprecated and will be removed in Rails 5.1. Use before_action instead. 

これはcancancanとtwo_factor_authenticationのバージョンが古くて内部で使っていたみたいなので最新にバージョンアップした。

※このtwo_factor_authenticationのアップデートが後に牙を向くことになる。。。

  • xhr :getなどの記法変更 xhrの書き方が変わった
xhr :get, :index
↓
get :index, xhr: true
DEPRECATION WARNING: Accessing mime types via constants is deprecated. Please change `Mime::JSON` to `Mime[:json]`. (called from require at /usr/local/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib/bundler/runtime.rb:77)

jbuilderが利用しているようなのでアップデートした

  • raise_in_transactional_callbacksがなくなる
DEPRECATION WARNING: ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement. (called from <top (required)> at /app/config/environment.rb:5)

application.rbにあるraise_in_transactional_callbacksはもう不要になる

  • alias_method_chainが使えなくなる
DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.

prepend使うように修正した

DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead)
  • render :nothingが使えなくなる
DEPRECATION WARNING: `:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.
  • paramsがHash継承ではなくActionController::Parametersオブジェクトになる
DEPRECATION WARNING: #to_hash unexpectedly ignores parameter filtering, and will change to enforce it in Rails 5.1. Enable `raise_on_unfiltered_parameters` to respect parameter filtering, which is the default in new applications. For the existing deprecated behaviour, call #to_unsafe_h instead.

DEPRECATION WARNING: Method map is deprecated and will be removed in Rails 5.1, as `ActionController::Parameters` no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.6/classes/ActionController/Parameters.html

内部のAPI連携でstrong parameter対応サボっているところやHashのつもりでその後の処理をしようとしている箇所で発生していた。

結局ここでDEPRECATION WARNINGが出るものは幸せで、外部連携とかでモック化していてテストではエラーやWARNINGにならないところが多く後々の打鍵テストで色々見つかった。。。

  • redirect_to :backが使えなくなる
DEPRECATION WARNING: `redirect_to :back` is deprecated and will be removed from Rails 5.1. Please use `redirect_back(fallback_location: fallback_location)` where `fallback_location` represents the location to use if the request has no HTTP referer information.

redirect_back使うように修正

rails app:update

diffを見ながらアップデートした。

rspecのエラー対応

  • redirect_to request.refererのエラー

http://haito.hatenablog.com/entry/2017/07/13/150454

https://stackoverflow.com/questions/12591763/how-do-i-set-a-request-referrer-inside-my-rspec

なにが正解かいまいちわからないが、とりあえずrequest.env['HTTP_REFERER'] = '/test'のようにしてテストが通るようにした。

allow(request).to receive(:referer).and_return('/test')でrefererのスタブ化ができなくなっていた。

そもそもredirect_to request.referer自体がまずくてredirect_back(fallback_location: root_path)とかにする必要あると思っている(NOTE

  • rspecで空のパラメータがnilになる
put :update, params: {id: "12345678", book: {}}

とやっている箇所でparams[:members]がnilになってしまう。

put :update, params: {id: "12345678", book: {tags: []}}としてもidしかコントローラにこない。。。

4.2ではログのparametersに{id: "12345678", book: {}}と出ていたが、5.0では{id: "12345678"}になっていた。

深くは追ってないがputなりでからのパラメータを送らなくなったか受け取るrails側で削除するようにしたのだろう。

actionpack/lib/action_controller/test_case.rbがrailsのバージョンアップでかなり変わったので、その変更で空のパラメータは送らなくなった?

  • sessionがgetなど毎にリセットされるようになった?
get :index, params: {code: "000001"}

get :index

みたいに1つ目のgetでsessionにcodeを覚えさせて2つ目のgetでもsessionが保持されていたみたいだけど、されなくなったみたい。

rails db:migrateの実行

マイグレーションのファイル追加は無いけどrails db:migrateを実行するとdb/schema.rbが変更される。

add_indexがなくなって各テーブル内のt.indexに変わる。

developmentで動かしてみてのエラー

  • Attempting to generate a URL from non-sanitized request parameters! An attacker can inject malicious data into the generated URL, such as changing the host. Whitelist and sanitize passed parameters to be secure.

エラー箇所を見るとkaminariのpaginate @booksみたいにページネートを表示するところでエラーになっている。

kaminariをアップデートしたら解決した。

その他

  • ApplicationRecordクラスやApplicationJobクラスを作ってRails5のお作法に合わせた

5.1系の最新版へ

Gemfileの更新

  1. Gemfileのrailsのバージョンを変更
gem 'rails', '5.0.6'
↓
gem 'rails', '5.1.4'


  1. bundle update railsを実行

activerecord-session_storeの依存関係で怒られたので

bundle update rails activerecord-session_store

rails app:update

diffを見ながらアップデートした。

new_framework_defaultsについては下記を見ておいたほうがいい。

http://y-yagi.tumblr.com/post/144067860535/rails-5%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%81%9Finitializer%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81 http://y-yagi.tumblr.com/post/160114728555/rails-51%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%95%E3%82%8C%E3%81%9Fconfig%E3%81%BE%E3%81%A8%E3%82%81


3. specの実行

またdeprecation warningが。。。

DEPRECATION WARNING対応

  • halt_callback_chains_on_return_falseは使えなくなる
DEPRECATION WARNING: ActiveSupport.halt_callback_chains_on_return_false= is deprecated and will be removed in Rails 5.2. (called from <top (required)> at /app/config/initializers/new_framework_defaults.rb:25)

halt_callback_chains_on_return_falseは使えなくなるので削除した

  • belongs_toのclass_nameオプションにクラス名の文字列を指定しなくてはいけなくなる
DEPRECATION WARNING: Passing a class to the `class_name` is deprecated and will raise an ArgumentError in Rails 5.2. It eagerloads more classes than necessary and potentially creates circular dependencies.

クラス名していからクラス名の文字列に変更

  • ifやunlessオプションに文字列の指定ができなくなる
DEPRECATION WARNING: Passing string to be evaluated in :if and :unless conditional options is deprecated and will be removed in Rails 5.2 without replacement. Pass a symbol for an instance method, or a lambda, proc or block, instead.

シンボルかlambdaなどを使う形式に変える

  • schema_migrations_table_nameが使えなくなる
DEPRECATION WARNING: schema_migrations_table_name is deprecated and will be removed from Rails 5.2 (called from block (2 levels) in <top (required)> at /app/spec/spec_helper.rb:87)

DatabaseRewinder.clean_allとしているところでエラーになっているので、database_rewinderをアップデートした。

rails db:migrateの実行

マイグレーションのファイル追加は無いけどrails db:migrateを実行するとdb/schema.rbが変更される。

careate_tableにid: :serialが付いた。

あとは見栄えをそろえるためにカラム名やnullオプションなどなどにスペースをいくつも入れて整形していた箇所が基本スペース1つになったみたい(なんでや・・・ )

  create_table "books", force: :cascade do |t|
    t.string  "title",    null: false
    t.integer "quantity", null: false, default: 0
  end
↓
  create_table "books", id: :serial, force: :cascade do |t|
    t.string "title", null: false
    t.integer "quantity", null: false, default: 0
  end

ActiveRecord::Migrationのバージョン指定対応

とりあえず全て[4.2]をつけた

今後[5.1]を使いたいなと思ったがidやxxx_idをbigintに対応するのが必要なのでここでは見送った。

bundle update

そのほかのgemも最新版にした。

しかし、bootstrap-datepicker-railsとbootstrap-sassは最新にすると(というか現行バージョン以外だと)表示が崩れてしまう。。

バージョンアップすることによってかなり変わってしまい、どうしたら回避というか正常にできるか目処がたたなかった(自身の力不足)のでこの2つだけは、現行のままで行くことにした。

その他、capistranoもバージョンアップして少し書き方変わるところがあったのでその対応などした。

【訂正】Rails5.1のconfig.load_defaultsを利用しているとconfig/initializers/new_framework_defaults.rbの値は上書きされない(activerecord-session_store利用時)

訂正しました (2017/10/18)

どうやら、上書きされないのではなくて、下記の現象が起きていたようです。

[Rails]... | 日々雑記


まだどれが影響を与えているのかわかってないですが、他のRailsアプリだとちゃんと上書きされていました。。。

このアプリだけなにやってもActiveRecord::Base.belongs_to_required_by_defaultがnilになっていました。

> ActiveRecord::Base.belongs_to_required_by_default
=> nil
> Rails.application.config.active_record.belongs_to_required_by_default
=> false

さらに追記

どうやらactiverecord-session-storeが原因みたい。

activerecord-session-storeを利用していると、config.load_defaults 5.1ありでconfig/initializers/new_framework_defaults.rbにRails.application.config.active_record.belongs_to_required_by_default=falseを指定した場合は上書きされない。

> ActiveRecord::Base.belongs_to_required_by_default
=> true
> Rails.application.config.active_record.belongs_to_required_by_default
=> false

config/application.rbにconfig.active_record.belongs_to_required_by_default=falseを書いた場合はちゃんと両方falseになる。 (initializersはgemの読み込み後に実行されるらしい

[config/application.rb]

module アプリ名
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.1
    config.active_record.belongs_to_required_by_default = false # ここに直接書くとちゃんと上書きされる

rails c
> ActiveRecord::Base.belongs_to_required_by_default
=> false
> Rails.application.config.active_record.belongs_to_required_by_default
=> false

activerecord-session_storeのバージョン

GIT
  remote: https://github.com/rails/activerecord-session_store.git
  revision: b5e7da210c937c0aa580b8094b64019cef20e95a



ちょっとはまったのでメモ。


[config/application.rb]

module アプリ名
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.1

のように、config.load_defaults 5.1を記述すると

config/initializers/new_framework_defaults.rbにbelongs_to_required_by_defaultなどの設定を変更する記載があっても上書きはされない。

[config/initializers/new_framework_defaults.rb]

# Require `belongs_to` associations by default. Previous versions had false.
Rails.application.config.active_record.belongs_to_required_by_default = false

config/application.rbの後にconfig/initializers/new_framework_defaults.rbも読まれているので上書きされるのかと思っていたけど違うみたい。


下記にある通り、Railsが推奨する設定を使う場合にconfig.load_defaultsを利用するので上書きできなくてもいいんだと思うけど。

Rails 5.1で追加されたconfigまとめ | 日々雑記

Railsのinculudesについて

以前は普通にincludesを使っていたけど、includes(:books).references(:books)みたいに書くようになってからちょっと疑念を抱いていた。

そして、確かr7kamuraさんのamakanの実装についての記事でpreloadとかを使っていて、やっぱりpreloadやeager_loadを明示的に使ったほうがぱっと見分かりやすいのでは?と思って、includesを使わないように意識していた。


そんな中、下記の記事を最近読んだ。

techracho.bpsinc.jp

あぁ、やっぱりincludes(:books).references(:books)みたいに書くの冗長だと思っている人いるよね。 (includes支持者ももちろんいるみたいだけど


これからも、やはりincludesは極力使わずにpreloadやeager_loadを使うようにしていこうと思った。



なお、未だにpreloadやeager_loadなどが自信無くなるので下記によくお世話になってる。

qiita.com

cancancaでAbilityクラスにcurrent_user以外の引数を渡す方法

Cancan(Cancancan)使ってて、今までは下記みたいにuserさえあればよかったけど、ある時sessionのある値をAbilityクラスに渡したくなった。

class Ability
  include CanCan::Ability

  def initialize(user) ←(user, other_param)みたいにしたい!
    ・・・
  end
end

ぐぐっていると下記を見つけた。

github.com

なので、そこにあるようにapplication_controller.rbでcurrent_abilityをオーバーライドすればOK。

react-railsとreact_on_railsについてのメモ

個人用の調べたことや思ったことメモ。

RailsでReactを利用するのに使われる2大Gem。 どちらもSSRに対応している。

webpackerでreactやるんではダメなん?

webpacker:install:reactのこと。 やれなくはないと思う。

しかし、今回はSPAではなく一部分に導入(ボタン等)したかったので、react_componentのようなヘルパーメソッドがほしかった(部分適用が楽)

下記のようにやる必要があるので、部分適用で箇所が多くなるとツライと思う。。

document.addEventListener("DOMContentLoaded", e => {
  ReactDOM.render(<Hello name="React" />, document.getElementById("ターゲットのid"))
})

react-rails

https://github.com/reactjs/react-rails

  • react登場の初期の頃からある認識
  • execjs依存
  • 単体利用だとコンポーネントをグローバルに紐付ける
    • webpackerをサポートしたので、webpackerを利用していればグローバルではなくなる
  • CSRFトークンをpropで渡す必要がある
  • デフォルトはsprockets利用
    • レールに乗ったままいける
    • 確か内部でreact.jsを持っている(バージョンがちょっと古い)
      • reactのaddonsの利用をサポートしているから?
    • すぐにnpm使いたくなる
      • 今はwebpackerと組み合わせる方法がある
      • 他はbrowserify-rails使う方法かRailsAssets.orgかな?

react_on_rails

https://github.com/shakacode/react_on_rails

  • execjs依存
  • Rails疎結合でReactを利用できる
    • Rails側で使いやすいようにヘルパーやnpmのライブラリを提供している
  • CSRFはnpmでライブラリが用意されている
    • ReactOnRailsで取得可能
  • 非SPAだと重厚すぎないか?
csrfToken = ReactOnRails.authenticityToken();

// compose Rails specific request header as following { X-CSRF-Token: csrfToken, X-Requested-With: XMLHttpRequest }
header = ReactOnRails.authenticityHeaders(otherHeader);
  • 利用例はr7kamuraさんのamakanが秀逸

http://r7kamura.hatenablog.com/entry/2016/10/10/173610

その他

ExecJSとは

Rubyからjavascriptを実行できるようにするためのもの。

javascriptランタイムを抽象化して同じインターフェースで利用できるようにする。

ランタイム

  • therubyracer
    • Gemfileでデフォルトで入っていたやつ
    • メモリ使用量が多いのが難点
    • インストールも苦労するのだが
    • 最近はmini_racer推奨
  • Node.js
    • いわずとしれたサーバサイドでも使えるjavascript
  • mini_racer
    • therubyracerよりも高速、安定、省メモリ
    • therubyracerが抱えていた古いV8エンジンの問題も解決

Railsのwebpackerでbin/webpack-dev-serverをした時にinvalid host headerになってしまう

Railsのwebpackerでbin/webpack-dev-serverをしてブラウザからアクセスするとinvalid host headerとJSのエラーになってしまった。

(Virtualboxに開発環境を作ってホストOSのブラウザからアクセスしている)


とりあえず、下記の設定を入れてエラーを回避しているが、どうやるのが正しいのだろう。

[config/webpack/development.js]

module.exports = merge(sharedConfig, {
    ・・・

  devServer: {
    disableHostCheck: true,
    ・・・
  }

HerokuのRailsでnpm installをできるようにする

1. 下記の手順に従ってrubyとnodejsのビルドパックを追加する

devcenter.heroku.com

$ heroku buildpacks:add --index 1 heroku/nodejs
Buildpack added. Next release on アプリ名 will use:
  1. heroku/nodejs
  2. heroku/ruby
Run git push heroku master to create a new release using these buildpacks.

2. assets:precompileの前にnpm installを行うRakeタスクを作成する

[lib/tasks/before_precompile.rake]

task :npm_install do
  sh "npm install"
end

Rake::Task["assets:precompile"].enhance(%i(npm_install))

手順2. は要らなさそう。Herokuがyarnをサポートしているようなのでyarn.lockファイルがあるとyarn installが自動で走るみたい。

devcenter.heroku.com