20200727のRailsに関する記事は20件です。

マイグレーションファイルやテーブルの削除手順書

Railsを使い始めた頃に、マイグレーションファイルの扱いに苦戦していた時がありました。
手順や状態の確認もしないまま「いらないマイグレーションだから消してしまえ!」とか...

部分的なコマンドだけではなく、「実際の流れ」や「意図」を交えて書いていきます。
スクショも使ってあるので、見難かったらすみません...汗
$ は入力しなくても大丈夫です!

はじめにマイグレーションファイルの状態を確認します

プロジェクト内のターミナルで下記を実行

$ rails db:migrate:status

すると、現在実行されているマイグレーションファイルの状態が分かります。

※ 行いたい事:
一番下にあるCreate text matelialsのマイグレーションファイルで作成したテーブルを削除してからマイグレーションファイルも削除したい時の説明です。

スクリーンショット 2020-07-27 20.49.05.png

1: Statusの状態を確認する

左側のStatusは全てupになっています。

upになっているということはデータベースは、このマイグレーションファイルの構造を取り入れている(マイグレーションファイルはすでに実行済みのファイル)ということです。
up状態のマイグレーションファイルは削除したり編集したりしてはいけません。

状態がわかったので次は、

2: upをdownを切り替える方法

ここで必要な情報は Migration ID の数字!!
今回で言うと20200608181608です。

プロジェクト内のターミナルで下記を実行

$ rails db:migrate:down VERSION= "Migration ID"

今回だと下記になります。

$ rails db:migrate:down VERSION=20200608181608

すると、こんな画面になる。
エラーが出なければOKです!
スクリーンショット 2020-07-27 21.10.22.png

もう一度Statusを確認します
rails db:migrate:statusを実行

スクリーンショット 2020-07-27 21.27.11.png
ちゃんとdownになっています。
down状態なので編集と削除ができます。

3: テーブルを編集、削除する手順

"db/maigrate"内にあるマイグレーションファイルを開いて追記します。

(↑ は後に重要なことなので、太めに書いておきました)

今回は、"db/maigrate"内にある
20200608181608_create_text_matelials.rbファイルを開きます。

中のコードが下記の時

class CreateTextMatelials < ActiveRecord::Migration[6.0]
  def change
  end
end

この中へ drop_table :削除したいテーブル名 を追記します。
※自身のテーブル名を確認したい場合はdb/schema.rbの中を確認してください。
※注意:ActiveRecord::Migration [6.0] の部分は自身のRailsのバージョンに指定してください。(例) [5.1]

「Railsのバージョンがわからない...」場合は
ターミナルでrails -vを実行するとわかりますよ。

追記後は下記です。

class CreateTextMatelials < ActiveRecord::Migration[6.0]
  def change
    drop_table :text_matelials
  end
end

削除する追記が完了したらrails db:migrateを実行します。
これでテーブルの削除ができました。

db/schema.rbの中を確認すると指定したテーブルは削除されているはずです。

【何が起きていたのか文章で言うと..】
drop_table :text_matelialsとしてtext_matelialsテーブルを削除する内容を記述したファイルを実行したのでテーブルを削除できたというわけです。
rails db:migrate:statusでステータスを確認してみると
rails db:migrateを実行したのでStatusの状態はupになっています。
こんな感じでしょうか..

引き続き"マイグレーションファイルの削除"を説明します。

4: マイグレーションファイルを削除する手順

今は、
"db/maigrate"内にある
20200608181608_create_text_matelials.rbファイルをこのまま削除する方法です。

プロジェクト内のターミナルで下記を実行

$ rm -rf db/migrate/20200608181608_create_text_matelials.rb

rails db:migrate:statusを実行して、ステータスを確認してください。
削除されていることがわかると思います。

5: マイグレーションを間違って削除してしまった場合

データベースに反映したマイグレーションファイルを不要だと思い、誤って削除した時にrails db:migrate:statusを実行してステータスを確認すると下のように"NO FILE"と出てきてしまった時の対処です。

スクリーンショット 2020-07-27 22.26.45.png

手順に入る前になぜNO FILE になっているのか状況を説明します。

【 NO FILE になっている状況の説明 】

上の画面から分かることはStatusが NO FILE となっているマイグレーションがupになっている!
ということはデータベースはこのマイグレーションファイルの構造を取り入れているということですね。

up状態のマイグレーションファイルを手動でカラムの削除したり、カラムを使っていないということでマイグレーションファイルを削除したのかもしれません。

NO FILE になるのは、
マイグレーションファイルを削除してもdb/migration内のschema_migrationsテーブルにバージョンが保存されているためです。

schema_migrationsテーブルから削除してしまったマイグレーションファイルのバージョンが保存されているレコードを削除すればこの表示は消えます。

※ 単にdb/migration/schema.rbの内部を削除しただけでは
rails db:migrateするとまた復活してしまいます。

6: NO FILE状態のschema.rb内のテーブルとNO FILEを削除する手順

[3: テーブルを編集、削除する手順]で説明したように

"db/maigrate"内にあるマイグレーションファイルを開いて追記が必要

ですが、今回の場合はマイグレーションファイルが無いので、マイグレーションファイルを作成するところからはじめます。

はじめにrails db:migrate:statusを実行してMigration ID を確認しましょう。

スクリーンショット 2020-07-27 22.26.45.png

今回だと20200601140204になります。

マイグレーションファイルを作成する

rails プロジェクトの db/migrateに移動
プロジェクト内のターミナルで下記を実行

$ cd db/migrate

マイグレーションファイルを作成します。

db/schema.rbの中でテーブルを確認した時に、
今回はconversationsテーブルを削除したい。とすると
touch マイグレーションID_ファイル名.rb
(※最終的に削除します。一時的な使用なのでのでファイル名はなんでも構いません)
(※もしテーブルを使いたい場合は、適した名前にしてください)

今回は下記のようにしました
プロジェクト内のターミナルで実行

$ touch 20200601140204_tmp.rb

これでtmp.rbというマイグレーションファイルができました。
次に
[3: テーブルを編集、削除する手順]
[4: マイグレーションファイルを削除する手順]
を行います。

...と省略されると不安に思う人(自分..)もいると思うので、ダブりますが続きも書いておきますね。

テーブルを編集、削除する

エディタからdb/migrate内の
20200601140204_tmp.rbファイルを開いて下記を記述
※注意:ActiveRecord::Migration [6.0] の部分は自身のRailsのバージョンに指定します。(例) [5.1]
「Railsのバージョンがわからない...」場合は
ターミナルでrails -vを実行するとわかります。

class Tmp < ActiveRecord::Migration[6.0]
  def change
  end
end

schema.rb内にテーブルを作成していないファイルならこのまま削除しますが今回は先ほど作成したマイグレーションファイルを使ってschema.rb内のテーブルを削除したいので更に drop_table :削除したいテーブル名 を追記します。

※テーブル名を確認したい場合はdb/schema.rbの中を確認。

今回はconversationsテーブルを削除したいとします。

class CreateConversations < ActiveRecord::Migration[6.0]
  def change
    drop_table :conversations
  end
end

編集が終了したので
rails db:migrateをしてテーブルを削除します。

db/schema.rbの中を確認すると指定したテーブルが削除されていると思います。

マイグレーションファイルを削除する

残るは先ほどNOFILE状態だったファイル(現在の 20200601140204_tmp.rb)ですね。

まずはup状態をdown状態にします!!...と
その前にdrop_table :conversationsがあるとdownできないので削除しておきましょう。

down状態にするのに必要な情報は Migration ID の数字です。
今回で言うと20200601140204ですね。

プロジェクト内のターミナルで下記を実行

$ rails db:migrate:down VERSION=20200601140204

rails db:migrate:statusを実行してステータスを確認。
downになっていればOKです!

"db/maigrate"内にある
20200601140204_tmp.rbファイルをこのまま削除します。

プロジェクト内のターミナルで下記を実行

$ rm -rf db/migrate/20200601140204_tmp.rb

ステータスを確認
rails db:migrate:statusを実行
するとはじめにNOFILEだったものも削除されていることがわかるります!

スクリーンショット 2020-07-27 23.33.03.png

自分も経験した勘違い

サーバーにアップされた時に、マイグレーションファイルは全てdown状態なのでrails db:migrateコマンドを実行します。

例えばusersテーブルを作成するマイグレーションファイルを削除してしまって、その後にusersテーブルにカラムを追加するマイグレーションファイルがあった時、存在しないusersテーブルにカラムを追加することはできないのでエラーが出てしまいます。

ローカルではエラーは出ないのに、サーバーにアップしたらなぜかエラーになる...
こんな状況になるときがありました。

ローカル環境ではusersテーブルが削除される前にrails db:migrateで実行してありマイグレーションファイルによって既に作成されているので、マイグレーションファイルを誤って削除してしまった場合でもエラーが出ない。
ただ、
サーバーにアップされた時は、マイグレーションファイルは全てdown状態になっているのでrails db:migrateコマンドを実行することになります。

でも、usersテーブルを作成するマイグレーションファイルを削除してしまっている状態では存在しないusersテーブルにカラムを追加することはできないぞ!とエラーが出てしまう。

「ローカルとサーバーにアップした時で上記のような齟齬がないようにするため」と、
「チーム開発している場合に、他の開発者を混乱させる要員になる」ので注意して行う。
マイグレーションファイルを削除する際は必ずdown状態になっているのを確認してから編集や削除する!ということを忘れないようにしたいですね。

手動で削除してしまえばいいだろ!とはいかないのです。

最後に

意味がわからない..と焦りますよね。
私もそうです。(現在進行形)笑
今回は長くなってしまいましたが、「コマンドだけ」とか「ここを参考」とかをあえて使わず、内容ダブってもいいから書こう!その方が流れが分かりやすいと思い書きました。

初学者目線だと「流れ」が分かれば、理解は早いと感じてます。
少しでも参考になれば嬉しいです!

間違い等ございましたら、勉強して修正させて頂きますのでご指摘ください!
以上です。おつかれさまでした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyとRailsの勉強録0

Railでいろんなアプリ作りたいんや!

っと意気込んで、プログラミングを始めてみました。

これまで、プログラミングはMQLという超マイナーな言語を軽くしか触ってないので、本格的に勉強するのは始めてです。

会社では、プログラミングとは無関係な仕事をしています。
そのため、モチベーションが下がらいないようにするため、また、分からないことやこれまで何を考えて勉強してきたかを記録する意味でもブログを開始しようと思います。

ちなみ、ブログを書くのも初心者ですし、Qiitaの使い方もよく理解していないので、ブログを書きながら勉強していこうと思います。

なぜRubyを勉強しようと思ったか

クラウドソーシングのサイトを作ってみたかったからです。

他にも、ツイッターとかインスタグラムもどきを簡単に作れるってすごくない!?という安直な理由からです。
他の言語でも作れるんでしょうけど、なんとなく自分の作りたいものを一番表現しやすい言語なのかなと思いました。

今までやってきたこと

  • 本(なお買っただけの模様)
    • プロを目指すためのRuby入門
    • 現場で使えるRuby on Rails5速習実践ガイド
  • ドットインストール(やった順)
    • Ruby入門 (全26回)
    • Ruby on Rails 5入門 (全28回)
    • はじめてのRuby (全9回)
    • UNIXコマンド入門 (全24回)
    • Active Record入門 (全19回)

以上のことをやってきたのですが、、、

全くわからん!!

なんとなくブログっぽいものができたけど、なぜできたのか一切理解できていない!!

と、危機感を覚えたのもブログを開始した理由の一つです。

これからやること

HTML、CSS、JavaScriptは必須!と色々書かれていますが、モチベが上がらないので、必要になったらやろうと思います。

だって早くアプリ作りたいじゃん?

ということで、とりあえずrailsを中心にやりながら、必要になったものを必要になった時だけ勉強しようと思います。

これで良いのか分かりませんが、これが一番モチベ上がります!w

ということで、次回から早速railsの勉強を記録していこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アウトプット

railsのアウトプット

コントローラーのアクション

  • index :一覧ページを表示をする
  • new :新規投稿ページを表示する
  • create :データ投稿を行う
  • show :個別詳細ページを表示する
  • edit : 編集ページを表示する
  • update :データ編集を行う
  • destroy :データを削除する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby Error[Your Ruby version is 2.6.3, but your Gemfile specified 2.6.5] とでた時

ユーザー認証機能を実装中サーバーを再起動した時にそれは起きました。

Ruby Error[Your Ruby version is 2.6.3, but your Gemfile specified 2.6.5]

本来であればruby 2.6.5のはずなのに。。なにもいじっていないのに。。環境設定の画面を確認します。

%less ~/.zshrc

Linuxコマンドlessでファイルの中身を確認することができます。

export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"
export BASIC_AUTH_USER='admin'
export BASIC_AUTH_PASSWORD='2222'

どうやら環境変数を定義した際にrvenvの設定を変えてたみたいです。

eval "$(rbenv init -)"
export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"
export BASIC_AUTH_USER='****'
export BASIC_AUTH_PASSWORD='****'

設定を変えて更新したいと思います。

% source ~/.zshrc
 % ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]

無事元に戻りました。Macでは元々rubyの環境が元々インストールされているみたいで環境を変えたことによりそっちの方が適応されてたみたいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで認証APIを作る

作るもの

ターミナル
$ curl -X POST -H 'Content-Type:application/json' -d '{ "mail": "test", "password": "test" }' http://localhost:3000/api/v1/auth
# => unauthorized  or  auth_token

モデルの作成

ターミナル
$ rails g model user mail:string password:string auth_token:token

でモデルを作成し、

db/migrate/YYYYMMDDHHMMSS_create_users.rb
- t.string :auth_token
+ t.string :auth_token, charset: 'utf8', collation: 'utf8_bin'

と編集してtokenの衝突を防いだ後、(参考

ターミナル
$ rails db:migrate

テストデータの登録

db/seeds.rb
User.create([{ mail: "test", password: "test" }])

と編集して、

ターミナル
$ rails db:seed

rails db:seedコマンドはdb/seeds.rbを実行しているだけなので、実行した分だけデータが追加される。(データの重複が起きる場合がある。)

コントローラーとルーティング

app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class UsersController < ApplicationController
      def auth
        user = User.find_by(mail: params[:mail], password: params[:password])
        if user == nil
          render plain: "unauthorized", status: :unauthorized
        else
          user.regenerate_auth_token
          render plain: user.auth_token
        end
      end
    end
  end
end

auth_tokentoken属性で作っているのでuserモデルにregenerate_auth_tokenというトークン再生成メソッドが追加されている。

config/routes.rb
Rails.application.routes.draw do
  namespace "api" do
    namespace "v1" do
      post "auth" => "users#auth"
    end
  end
end

確認

ターミナル
# 認証失敗
$ curl -X POST -H 'Content-Type:application/json' -d '{ "mail": "foo", "password": "bar" }' http://localhost:3000/api/v1/auth
# => unauthorized

# 認証成功
$ curl -X POST -H 'Content-Type:application/json' -d '{ "mail": "test", "password": "test" }' http://localhost:3000/api/v1/auth
# => wcHUobhucFFA4zDejr2C3btY

使い方

app/controllers/api/v1/foos_controller.rb
module Api
  module V1
    class FoosController < ApplicationController
      include ActionController::HttpAuthentication::Token::ControllerMethods

      before_action :authorized?, only: [:bar, :baz]

      def bar
        # 認証が必要な処理
      end

      def baz
        # 認証が必要な処理
      end

      def qux
        # 認証が不要な処理
      end

      private

      def authorized?
        authenticate_or_request_with_http_token do |token, options|
          user = User.find_by(auth_token: token)
          user.present?
        end
      end
    end
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mysql2::Error::ConnectionErrorが出た場合

localhost:3000で出たエラーを解決させました

自作アプリの開発中に起きたエラーを紹介します。

環境

・macOS
・Rails 5.2.4.3
・Ruby 2.5.1

今日(投稿日)はエンゼルスの大谷投手が693日ぶりに登板と言う事で、めちゃくちゃテレビを見たい気持ちを抑えて、いつも通りの朝活を行う事に。。。

そして、自作アプリを開くとこんな画面に遭遇しました。
スクリーンショット 2020-07-27 7.32.29.png

へ?

何だこれは??

一瞬頭が真っ白になってしまいました。

なかなか自作アプリ開発が思う様に進まない中で、もはや自分でアプリを見ることすら出来ないのかと愕然。

5分くらい絶望した後に、取り敢えずエラーの解析から始めました。

Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)って何??

エラーの内容を調べていると結構同じ様なエラーに遭遇されている方が多かったので、その記事を見ていくと、

クライアント(自分のPC) → /var/lib/mysql/mysql.sock にデータを見に行こうとしたら、そんな場所ないじゃん。って感じのエラーなんだと理解。

socketとは

プログラムとネットワークを結ぶ接続口。仲介役。仲介業者。仲人。合コンで言う幹事。
14102808098_o.jpg

って事で正しい出入り口(ソケット)を探す旅に

socketってどこにあんの??

他の記事を読み漁り、socketの存在を調べました

特に参考にさせてもらった記事
Can't connect to local MySQL server through socket '/tmp/mysql.sock'

記事を読み比べる中で

'/tmp/mysql.sock'
'/var/lib/mysql/mysql.sock' 

と言う2つのソケットを見比べて、

「自分のエラーの場合は'/var/lib/mysql/mysql.sock'に行こうとしたが、そんな場所はないと言われているので、そこを見つけて修正すれば良いのか!!」と推測。

で、どこに書いてあんの?

解決法

この記事を読んで私も解決しました
【Rails】ローカル環境におけるmysql接続エラー対処法【Can't connect to local MySQL server through socket】

database.yml
#修正前
development:
  <<: *default
  database: データベース名_development
  username: root
  password: <%= ENV['DATABASE_PASSWORD'] %>
  socket: /var/lib/mysql/mysql.sock

#修正後
development:
  <<: *default
  database: データベース名_development
  username: root
  password: <%= ENV['DATABASE_PASSWORD'] %>
  socket: /tmp/mysql.sock

/var/lib/mysql/mysql.sock → /tmp/mysql.sock に変更してます

コードの修正を行ったので、再起動させます。

rails s

これでソケット名が正しくなったので、無事に閲覧が出来ました!!

原因

database.ymlですが、特に自分で何かを記述した訳ではなくて、何かしらの教材や記事を
コピペしてしまう事で、起きやすい事象なのかと思いました。
その分、最初はすごく焦りましたが、今まで気にしなかった所を考えたり、エラー文を調べていく中で、同じ様なエラーに遭遇されている方が多かった事も、自分の中では収穫でした。ymlを一度見てもらうのも良いかも知れません。

最後にsocketの

'/tmp/mysql.sock'
'/var/lib/mysql/mysql.sock' 

の箇所ですが、使われている方によっては、

/tmp/mysql.sock → /var/lib/mysql/mysql.sock

が正解のパターンも多くあると思いますので、まずは自身のsocketの所在を確認してみてください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails マイグレーション

マイグレーション

マイグレーションファイルを作成しただけではデータベースは変更されず、マイグレーションを適用して初めてデータベースが変更される。どのマイグレーションファイルが適用されているかは、schema_migrationsというテーブルで管理されている。

また、マイグレーションはデータベースごとに適用する必要があるため、「開発用」「本番用」「テスト」ごとにマイグレーションする必要がある。(※デフォルトでは開発用データベースに対して適用される)

マイグレシーション適用でエラーが出ても、エラーのおきたマイグレーションファイルを適用する前の状態のまま。そのため、そのままコードを修正してマイグレーションを行えば良い

マイグレーション時に使うコマンド集

#「開発用」最新までマイグレーションを適用
rails db:migrate

#「本番用」最新までマイグレーションを適用
rails db:migrate RAILS_ENV=production

#「テスト」最新までマイグレーションを適用
rails db:migrate RAILS_ENV=test

#特定のバージョンまでマイグレーションを適用 ※数字の部分はマイグレーションファイルの数字部分
rails db:migrate VERSION=20200701051010

#バージョンを一つ戻す
rails db:rollback

#バージョンを指定したステップ数だけ戻す
rails db:rollback STEP=3

#バージョンを一つ戻し、1つあげる ※バージョンは最終的には変化しないがバージョンを戻す処理が適切にできているか確認的る
rails db:migrate:redo

Screen Shot 2020-07-27 at 19.21.37.png

参考|現場で使える Ruby on Rails 5速習実践ガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

No route matches [GET] になる原因がフロント要素のjsにある謎

備忘録メモ。都度更新

POSTメソッドをとって欲しいのに、No route matches [GET] になる原因がフロント要素のjsにあるってのが謎だったのでまとめる。

バックエンドからフロントまで結合しているから

railsはバックエンドからフロントエンドまで(一部インフラも)すべてが設計的に結合しているというのが特徴があるみたい。こういうフレームワークをフルスタックフレームワークと呼ばれている。

で、フルスタックフレームワークであるrails全部一個のフレームワークで担っているから、railsの一部であるjs(rails-ujs)は同じくrailsのサーバサイドのコードが吐くHTMLと設計的に結合しています(具体的にはHTMLのdata-method属性をrails-ujsが読み込んでいる)。

簡単に言うと???

railsの内部でフロントエンドとバックエンドが勝手に結合してしまっているので何も知らなくてもいろいろなことをよしなにやってくれる一方で、仕組みを知らないとトラブルシューティングが難しく、railsの推奨する方法の外に行くと大幅に困難になるという問題がある。

う〜ん。とりあえず記録として残して内容を更新していくようにする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Github初心者]ターミナルで新しいブランチを作成する方法

掲示板を作成中、掲示板の基本機能のみ作ってherokuでデプロイした。
ユーザーログインという新規機能安全にデプロイする為に、新機能開発の前にあらかじめgitでmasterではないブランチを作って、テストを行ってからmasterブランチにmergeを行いたい。

ブランチ作成&そのブランチに切り替え

$ git checkout -b create-users-table-model

Switched to a new branch 'create-users-table-model'

存在するブランチの確認コマンド

$ git branch

* create-users-table-model
  master

ブランチを切り替える

$ git checkout master

Switched to branch 'master'

git checkout create-users-table-model

Switched to branch 'create-users-table-model'

新しいDBテーブルの作成

$ rails g migration create_users

〜省略〜
warning: The called method `initialize' is defined here
      invoke  active_record
      create    db/migrate/20200727081835_create_users.rb

完成。

ここから開発を行っていく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Github初心者]ターミナルで新しいブランチを作成してマスターブランチとマージする方法

掲示板を作成中、掲示板の基本機能のみ作ってherokuでデプロイした。
ユーザーログインという新規機能安全にデプロイする為に、新機能開発の前にあらかじめgitでmasterではないブランチを作って、テストを行ってからmasterブランチにmergeを行いたい。

ブランチ作成&そのブランチに切り替え

$ git checkout -b create-users-table-model

Switched to a new branch 'create-users-table-model'

存在するブランチの確認コマンド

$ git branch

* create-users-table-model
  master

ブランチを切り替える

$ git checkout master

Switched to branch 'master'

git checkout create-users-table-model

Switched to branch 'create-users-table-model'

新しいDBテーブルの作成

$ rails g migration create_users

〜省略〜
warning: The called method `initialize' is defined here
      invoke  active_record
      create    db/migrate/20200727081835_create_users.rb

完成。

ここから開発を行っていく。

DBに必要カラムを追記して作成

db/migrate/'date'_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :username
      t.string :email
      t.timestamps #created_atなどが自動で追加される
      #passwordカラムはあとから追加する
    end
  end
end

userモデル作成

modelsフォルダの下にuser.rbを追加

models/user.rb
class User < ApplicationRecord

end

保存。$rails cでuserが作成できるか確認。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミドルウェア(unicorn + nginx)周辺のメモ

記事を書く目的

Railsでつくったアプリをデプロイしたときの構成として、ミドルウェアにNginxとUnicornを使用しました。
なぜこれらを本番環境で使用するのか調べていて、まとめたいなと思ったのでメモ。

ミドルウェア

osとアプリケーションの間に入って働くプログラムのこと。

  • Webサーバー(Nginx)
  • アプリケーションサーバー(Unicorn)

のこと。

こちらの記事のとおりに構成を実施。
(デプロイ編②)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで - Qiita

Webサーバー, アプリケーションサーバーの役割

  • Webサーバー(Nginx)
    静的コンテンツを返す役割を持つもの。
    誰がみても同じ内容のもの。
    誰かの記事を見るなど、誰がみてもその人の記事の内容は同じなのでそれを表示させるのには、Webサーバーだけで十分。

  • アプリケーションサーバー(Unicorn)
    動的コンテンツを返す役割を持つもの。
    各ユーザーにより、処理が異なるもの。
    ログインだったり、記事を投稿する場合だったりデータベースとのやり取りが必要なもの。

RailsにはPumaがある

Rails5からはWebサーバーの役割をもつPumaが標準装備されているとのこと。
ということはPuma + Nginx でもいいのではないか?と思っていたらPumaとUnicornを比較した記事がありました。
↓↓
Pumaの本当の力を引き出す - Qiita

結果的にデフォルトのPumaは貧弱なため、Unicornを使った方が処理がスムーズなるけど、ポートフォリオくらいならPumaでも問題はないのかなと。

つまり

デフォルトのPumaは貧弱なので、Unicornを使う。
Unicornを使う場合、ドキュメントではNginxと組み合わせることを推奨しているため、Unicornを選んだ場合、webサーバも自然とNginxになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】【CircleCI】【Terraform】【Rails】CircleCIでECSに自動デプロイをする時にハマったとこ

状況

config.yml
version: 2.1 
orbs:
  aws-ecr: circleci/aws-ecr@6.11.0
  aws-ecs: circleci/aws-ecs@1.2.0

Orbsを使用してデプロイを実行しようとすると以下のエラーが発生

An error occurred (ClientException) when calling the DescribeTaskDefinition operation: Unable to describe task definition.
スクリーンショット 2020-07-27 15.36.22.png

エラー内容と対処

調べると、aws ecs describe-task-definitionコマンドを叩いて、うまくいかなかった時のエラーであるとわかる

circleciのlogから、awsコマンドを叩いてる部分を探すと以下の記述に出会う

circleci
PREVIOUS_TASK_DEFINITION=$(aws ecs describe-task-definition --task-definition ${MY_APP_PREFIX}-service --include TAGS)

注意するのは、引っ張りたいタスク定義のfamilyの末尾は-serviceである必要があるということ

なので、Terraformでは

task_definition.tf
resource "aws_ecs_task_definition" "manabi-app" {
  family = "app名" #これをapp名-serviceに変更
  container_definitions = file("./container_definitions/service.json")
  task_role_arn = data.terraform_remote_state.iam.outputs.ecs_task_role_arn
  execution_role_arn = data.terraform_remote_state.iam.outputs.ecs_task_role_arn
  network_mode = "bridge"
}

うまくデプロイできた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのmysql接続でユーザー権限がなくconnection errorになるときの対処法

ユーザー作成

CREATE USER 'test-user'@'localhost';

ユーザー確認

SELECT user, host FROM mysql.user;

権限確認

SHOW GRANTS;

権限付与

grant all on *.* to 'test-user'@'localhost';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails環境下でjqueryにて画面のバリデーションを設定する

背景

現在、自分が作成しているポートフォリオについて、データの更新、新規登録時にバリデーションをかけたい項目がある。個人的には、Vue.jsを学習中のため、こちらで実施したかったが、いろいろ試した結果について次のとおりにまとめる。

環境

項目 内容
OS.Catalina v10.15.4
Ruby v2.5.1
Ruby On Rails v5.2.4.3
MySQL v5.6

対応手順

【手順1】
1)公式サイトからバリデーションを実行してくれるJSファイルをダウンロードする
jquery-validation-1.19.1/dist/jquery.validate.js
jquery-validation-1.19.1/src/localization/messages_ja.js

2)以下に格納する
app/assets/javascripts/jquery.validate.js
app/assets/javascripts/messages_ja.js

3)JSファイルの設定
①対象となるフォームオブジェクトのIDを設定する(2行目)
②rulesオブジェクトに対象となるテキストボックスのオブジェクトIDを設定する(4行目)
→ここで、正確なオブジェクトidはデベロッパーツールを使用すること
③対象オブジェクトにバリデーションに引っかかった場合の出力メッセージを設定する(9行目)

app/assets/javascripts/genre_edit_vaidate.js
1 $(function(){
2     $("#new_genre").validate({
3        rules : {
4             "genre[in_genre]": {
5                 required: true
6             }
7         },
8         messages: {
9             "genre[in_genre]": {
10                 required: "必須入力です",
11             }
12         }
13     });
14 });

4)htmlファイルの設定
今回例では、ジャンル名の登録テキストボックスに必須バリデーションを設定した。

new.html.erb
<title>メモのカテゴリを登録</title>
<p>ジャンルの追加<br></p>
<%= form_for (@genre), url:  genres_path(:id), method: :post do |f| %>
      <%= f.label :id,"ユーザ名" ,class: "com-box" %>
      <%= f.text_field :user_id ,class: "com-box" ,value: current_user.name, readonly:true %>
      <%= f.hidden_field :user_id  ,value: current_user.id%>
      <%= f.label :id,"ジャンル名を登録してください" ,class: "com-box" %>
      <%= f.text_field :in_genre ,class: "com-box" %>
      <%= f.submit "登録する" %>
<% end %>

test.gif
→「必須入力です」と出力されています。あとはスタイルシートで加工すればOKです。簡単にできました!

以上です。

参考文献

【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装 〜導入編〜

【Rails】簡単にバリデーションチェックできるjQueryValidationプラグインの実装手順を解説

jQuery Validation 簡単にフォームをチェック バリデーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

datetimeカラムのデフォルト値について

datetimeカラムのデフォルトを現在時間にしたい

この前提のもと、migrationに

t.datetime :created_at, default: DateTime.now

と安直に書いてmigrationすると、migrationされた日がデフォルト値になってしまう。
datetimeみたいに動的な値は、migrationされた時にしか計算されないためだ、と。
Railsガイド>Active Record マイグレーション>3.5 カラム修飾子

設定方法

こちらのPRで上がってました

t.datetime :created_at, default: -> { 'NOW()' }

これでdatetimeのデフォルト値を設定できました!

参考

他の記述方法もありました!参考にさせていただきました。
Ruby on Railsのmigrationでdatetimeのデフォルト値をCURRENT_TIMESTAMPにする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

devise_token_auth ログアウトのテストコード実装時に苦戦したことをまとめる

devise_token_auth でログアウトのテストコード作成時、エラー解決の情報が少なかったのでまとめます。
※ user の新規登録、ログイン、ログアウト機能を実装しているものとします。
※ FactoryBot を導入してます。

headers にログインに必要な情報が入っていないため、エラー発生

エラー発生時のコード

describe "DELETE /v1/auth/user/sign_out" do
  subject { delete(destroy_v1_user_session_path) }
  context "トレーナーログアウト時" do
    let(:user) { create(:user) }
    it "トークンを無くし、ログアウトできる" do
      subject
      expect(response).to have_http_status(200)
    end
  end
end

エラー内容

Failure/Error: expect(response).to have_http_status(200)
expected the response to have status code 200 but it was 404

正常なレスポンス(200)ではなく、リクエストが存在しない(404)エラーが出てます。との内容。
subject の下に binding.pry を入れ、デバック画面にしuser を実行

#<Trainer id: 532, provider: "email", uid: "6_hannah@emmerich.name", allow_password_change: false, name: "松本 七海", nickname: nil, email: "6_hannah@emmerich.name", address: nil, birthday: nil, created_at: "2020-07-26 06:10:35", updated_at: "2020-07-26 06:10:35">

user の中にデータがあることを確認できました。
次に headers 情報を確認するため、response.headers を実行。

{"X-Frame-Options"=>"SAMEORIGIN",
 "X-XSS-Protection"=>"1; mode=block",
 "X-Content-Type-Options"=>"nosniff",
 "X-Download-Options"=>"noopen",
 "X-Permitted-Cross-Domain-Policies"=>"none",
 "Referrer-Policy"=>"strict-origin-when-cross-origin",
 "Content-Type"=>"application/json; charset=utf-8",
 "Cache-Control"=>"no-cache",
 "X-Request-Id"=>"a2ad39e6-d2b2-4ae8-93c1-5ddcd2ea22d1",
 "X-Runtime"=>"0.010570",
 "Content-Length"=>"71"}

headers の情報は返ってきましたが、ログアウトに必要な情報
access-tokenclientuid が入ってないことが分かります。
headers にログアウトに必要な情報が入ってないから、404の異常系のエラーが発生している。
新規作成時、ログイン時にトークン情報が発行され、その情報をもとにログアウトの処理を行うのですが、現状のコードでは user の情報は作成されても、トークン情報は作成されていない状況です。
なので、トークン情報を入れてあげましょう。

describe "DELETE /v1/auth/user/sign_out" do
  subject { delete(destroy_v1_user_session_path, headers: headers) }
  context "トレーナーログアウト時" do
    let(:user) { create(:user) }
    let(:headers) { user.create_new_auth_token }
    it "トークンを無くし、ログアウトできる" do
      subject
      expect(response).to have_http_status(200)
    end
  end
end

create_new_auth_tokenメソッドで、必要なメタデータ全てを含む新しい認証トークンを作成します。
subject の下に binding.pry を入れheadersを実行し、データが入っていればオッケーです。

ログアウト時にトークン情報が残ったままのため、エラー発生

ログアウトは、「有効なトークン情報がなくなる」なので、先ほどのコードにトークン情報の有無を確認するテストコードを追記しました。

describe "DELETE /v1/auth/user/sign_out" do
    subject { delete(destroy_v1_user_session_path, headers: headers) }
    context "トレーナーログアウト時" do
      let(:user) { create(:user) }
      let(:headers) { user.create_new_auth_token }
      it "トークンを無くし、ログアウトできる" do
        subject
+       expect(user.tokens).to be_blank
        expect(response).to have_http_status(200)
      end
    end
  end

エラー内容

Failure/Error: expect(trainer.tokens).to be_blank
expected `{"LT4SdlN2czyzNnwTfLGjTw"=>{"expiry"=>1596957557, "token"=>"$2a$10$BgZ4YItos1hQRTp4y1JvZ.mIsreaIxD1NC3ckRk0euxsk8IBkoM7K", "updated_at"=>"2020-07-26 07:19:17 UTC"}}.blank?` to return true, got false

トークン情報が残ったままとのエラーです。
ログアウトに必要な情報を user に入れ、ログアウトの処理はできても、user の値は更新されないので残ったままになっています。
user の値を更新するためには、user.reload.tokenと記述してあげる必要があります。

describe "DELETE /v1/auth/user/sign_out" do
    subject { delete(destroy_v1_user_session_path, headers: headers) }
    context "トレーナーログアウト時" do
      let(:user) { create(:user) }
      let(:headers) { user.create_new_auth_token }
      it "トークンを無くし、ログアウトできる" do
        subject
        expect(user.reload.tokens).to be_blank
        expect(response).to have_http_status(200)
      end
    end
  end

以上の内容にすれば、ログアウト後の user の値が更新されるため、user にトークン情報がなくなります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]find_eachが無限ループして本番環境のメモリを食いつぶした話

ActiveRecordにある便利メソッドfind_each
実装の仕方が良くなくて無限ループしてしまい、本番環境でOOM killerによって強制停止させられるという事象が発生したのでその話について書きます。
※ この記事に登場するテーブルや実装などはすべて記事用にカスタマイズしています :bow:

find_eachとは

find_eachは何をしてくれるかというと、大量のデータを一度に取得してループするのではなく決まった単位(デフォルト1,000件)ごとに取得してループしてくれます。
大量データを扱うときに一括で取得してしまうと大量のメモリを使っていしまうのですが、find_eachを使って分割して取得することで少ないメモリで処理することができます。

言葉で書いてもわかりづらいですね。下記が実行例です。

# userが1万人いる場合
pry(main)> User.all.count
   (1.1ms)  SELECT COUNT(*) FROM `users`
=> 10000

# eachを使うと一括で1万件取得される
pry(main)> User.all.each {|user| p user.id}
  User Load (4.5ms)  SELECT `users`.* FROM `users`
1
2
3
...
10000

# find_eachを使うと1,000件ずつ取得される
[8] pry(main)> User.all.find_each {|user| p user.id}
  User Load (3.9ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1000
1
2
3
...
1000
  User Load (0.8ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` > 1000 ORDER BY `users`.`id` ASC LIMIT 1000
1001
1002
1003
...
2000
  User Load (0.8ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` > 2000 ORDER BY `users`.`id` ASC LIMIT 1000
2001
...
10000

詳細はRailsガイドをご覧ください。
https://railsguides.jp/active_record_querying.html#find-each

find_eachが無限ループ!!

便利メソッドfind_eachですが、最初に書いたとおり実装をミスって無限ループさせてしまいました。
無限ループになった実装を説明する前に、そもそもfind_eachはどのような仕組みで動いているのか確認します。

find_eachの仕組み

最初に載せた実行例を使ってどのように動いているのか確認します。

一番最初に発行されるSQLを見てみましょう。

SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1000

limit 1000を指定して1000件取得しています。
ここで注目すべきはPRIMARY KEY(id)の昇順に並べているところです。

それでは次の1000件はどのように取得しているのでしょうか?

SELECT `users`.* FROM `users` WHERE `users`.`id` > 1000 ORDER BY `users`.`id` ASC LIMIT 1000

SQLを使って次の1000件を取得する場合はLIMITとOFFSETを使う事がよくありますが、今回のSQLにはOFFSETは使われていません。
代わりにwhere句にusers.id > 1000という条件が増えていることがわかります。

users.id > 1000の1000は最初に取得した1000件の最後のidが指定されています。
今回のデータはidの昇順に並んでいるため、最後のidより大きいデータを取得するという意味のusers.id > 1000を条件に指定することでOFFSETを使わずに次の1000件を取得しています。

無限ループになる実装

無限ループが発生したfind_eachは下記のように実装されていました。
何が起きるのでしょうか?

# users.idとbooks.titleしか使わないのでselectで必要なデータのみ取得する
Book.joins(:user).select('users.id, books.title').find_each do |book|
  p "user_id: #{book.id}, title: #{book.title}"
end

最初は下記のSQLが発行されます。

SELECT users.id, books.title FROM `books` INNER JOIN `users` ON `users`.`id` = `books`.`user_id` ORDER BY `books`.`id` ASC LIMIT 1000

1件目のSQLは特に問題ありません。
それでは次の1000件を取得するSQLはどうなるのでしょうか?

SELECT users.id, books.title FROM `books` INNER JOIN `users` ON `users`.`id` = `books`.`user_id` WHERE `books`.`id` > 1000 ORDER BY `books`.`id` ASC LIMIT 1000

books.id > 1000とい条件が追加されています。条件の1000は最初に取得した1000件の最後のデータのidです。
SQLだけを見ていると気づきづらいですが、今回のSQLで取得しているidはbooks.idではなくusers.idです。
そのため、books.id > 1000に設定されている1000は最後のデータのusers.idが指定されています。

このSQLではbooks.idの昇順になっており、users.idの順序は特に制御されていません。
そのため、次の1000件目の最後のデータがbooks.id: 2000, users.id: 1ということもありえます。
この場合、次に発行されるSQLは下記のようになります。

SELECT users.id, books.title FROM `books` INNER JOIN `users` ON `users`.`id` = `books`.`user_id` WHERE `books`.`id` > 1 ORDER BY `books`.`id` ASC LIMIT 1000

条件がbooks.id > 1となってしまい、1つ前のSQL(books.id > 1000)より前のデータを取得してしまいます。
このようにbooks.idの条件に順序が制御されていないusers.idが入ることで、取得するデータが前後してしまい、最悪の場合何度も同じデータを取得してしまい無限ループしてしまいます。

この問題の厄介なところは常に無限ループになるわけではなく、データによってはbooks.id > #{最後のusers.id}がたまたまいい感じに指定されてそれっぽく完了してしまうこともあることです。
その場合、エラーにはならないけどデータが微妙におかしいという気づきづらいバグになってしまうので、無限ループになってくれたほうがマシかもしれません。

修正方法

上記の例の場合であれば、selectで取得カラムを絞らなくするとbooks.idも取得するようになるので正しく動作するようになります。
selectで取得カラムを絞る場合でも下記のようにbooks.idもきちんと取得するようにすれば正しく動作します。

Book.joins(:user).select('books.id AS id, users.id AS user_id, books.title').find_each do |book|
  p "user_id: #{book.user_id}, title: #{book.title}"
end

上記の通り修正すれば修正完了ですが、今回の問題は自動テストがなかったことだと思っています。
該当の処理を通るテストはあったのですが、find_eachを2ループ以上するテストは書いていませんでした。
テストがあれば、無限ループするか結果がおかしくなるのでバグに気づけていた可能性が高いです。
これをきっかけにfind_eachが2ループ以上するテストも追加しました。

まとめ

今回のバグはfind_eachの仕組みを正しく理解していたとしても、コードレビューなど机上確認だけで気づくのは難しいと思います。
また、1000件以上になることが稀な処理であり、1000件という単位もただのプログラム上の都合なので、ブラックボックスな動作テストでも気づかれずにしばらく潜在バグとして潜んでいました。

これに事前に気づくにはどうすればよかったのかと考えると、ホワイトボックステストでfind_eachが2ループするテストをするしかないと思いました。
ホワイトボックステストを手動でワンタイム実行してももったいないので、きちんと自動テストを書いて継続的に検証できるようにしておくとよいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails】activehashについて

Active Hashとは

ハッシュ形式の値をActive Recordと同じように操作できるようにするgem
Relationも貼れるので便利

基本的な使い方

以下のような書き方ができる

app/models/plan.rb
class Plan < ActiveHash::Base
  self.data = [
    {id: 1, name: "free"},
    {id: 2, name: "silver"},
    {id: 3, name: "gold"},
  ]
end

class Plan < ActiveHash::Base
  self.data = [
    {id => 1, name => "free"},
    {id => 2, name => "silver"},
    {id => 3, name => "gold"},
  ]
end

class Plan < ActiveHash::Base
  fields :name
  add id: 1, name: "free"
  add id: 2, name: "silver"
  add id: 3, name: "gold"
end
ターミナル
pry(main)>Plan.first
=> #<Plan:0x00007ffd3d2c43f8 @attributes={:id=>1, :name=>"free"}>



relationを貼りたいモデルに以下を記載すればアソシエーションを利用してデータを操作できる。
ActiveHashのモデル側には特に追記することはない

extend ActiveHash::Associations::ActiveRecordExtensions
belongs_to_active_hash :ActiveHashのモデル名

参考
https://qiita.com/DON4024/items/78edb7a309ee96766952
https://qiita.com/Toman1223/items/8633142312bfa886d50b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsアプリで画像の保存先をS3に変更する。その2

はじめに

前回の記事からの続きです。

安全にAWSのキーを扱えるようにする

AWSキーの漏洩を防ぐため、キーの内容は環境変数に設定します。
環境変数が分からない人はググって調べてみましょう!

AWSのキーの設定

carrierwave.rbのなかに[:access_key_id]や[:secret_access_key]といった記載がありました。
ここには予め設定したキーが入り、Rails5.2では「credentials.yml.enc」というファイルで管理されます。

それではcredentials.yml.encをエディタで開いてみましょう。
以下のように暗号化された文字列が表示されると思います。
スクリーンショット 2020-07-26 23.23.21.png

これをターミナルからVSCodeを起動できるよう設定を行います。
VSCodeで、「Command + Shift + P」を同時に押してコマンドパレットを開きます。
続いて、「shell」と入力しましょう。
メニューに「PATH内に'code'コマンドをインストールします」という項目が表示されるので、それをクリックします。
この操作を行うことで、ターミナルから「code」と打つことでVSCodeを起動できるようになります。

それではターミナルから以下のコマンドを実行しましょう。
復号化されたcredentials.yml.encがVSCodeで表示され、編集可能となるはずです。

% EDITOR='code --wait' rails credentials:edit

AWSのaccess_key_idとsecret_access_keyを以下のように編集しましょう。
dcEU4PX7s6ICcTD1595774207_1595774265.png

master.keyの設定

credentials.yml.encはmaster.keyというファイルで復号化を行います。
しかし、本番環境にmaster.keyを配置することはセキュリティ上問題があります。
そのため、本番環境の環境変数にmaster.keyの中身を設定しましょう。

EC2インスタンスにログインし、環境変数の設定を行うファイルを開きます。

sudo vim /etc/environment

ローカル開発環境で「config/master.key」の値をコピーして、本番環境のRAILS_MASTER_KEYに設定します。

RAILS_MASTER_KEY='master.keyの値'

これで環境変数が設定できたはずなので、EC2インスタンスにログインし直して以下のコマンドで環境変数を確認しましょう。

env | grep RAILS_MASTER_KEY

まとめ

環境変数を参照する流れをまとめますと以下のようになります。
1.credentials.yml.encをローカル環境のmaster.keyで復号する
2.credentials.yml.encを編集し、access_key_idとsecret_access_keyを設定する。
3.本番環境にデプロイする
4.master.keyの内容を本番環境の環境変数に設定する。
5.本番環境のcredentials.yml.encが環境変数を用いて復号可能となる。

前回の記事と上記の設定を行えば、S3に画像をアップロード出来るようになるはずです!多分!以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Railsのバージョンを5.0→5.2に上げてみた

やりたいこと

Rails5.0系からRails5.2系にバージョンを上げたい。(Rails5.2から使えるようになったActive Storageを使いたかった。)

この記事の趣旨は、Rails5.0系からRails5.2系にバージョンを上げるために必要な手順その際に発生したエラー対応をご紹介したい(そしてActive Storage使いたい)というものです。

修正(2020/07/27)

伊藤さんご本人からコメントいただき、bootsnap gemを追加する方法に変更いたしました。
詳しくはこの記事のコメントをご確認ください?‍♂️

環境

Ruby 2.5.1
Rails 5.0.7.2

手順

伊藤さんのQiita記事を大いに参考にさせていただきました。
永久保存版!?伊藤さん式・Railsアプリのアップグレード手順

基本的には伊藤さんの記事を見ていただくのがわかりやすいです(この記事の意味)。
ただ、「とりあえず早いとこバージョン上げたい!!」という方も少なからずいらっしゃるかと思うので、邪道的な形で見ていただけると幸いです。

また、本来はテストまでカバーすべきですが、今回は目的がActive Storageを使うことなので、そちらに関しては割愛します。

1. Rails以外のgemをバージョンアップする

Rails本体のバージョンを先に上げてしまうと、DeviseやCarrierwaveのような周辺のgemが最新のRailsに対応しておらず、思いがけないエラーが起きるかもしれません。

はい。まさに起きました。笑

1-1. developmentとtestグループのgemを先にアップデートする

terminal
# 該当のアプリのディレクトリで実行
$ bundle update -g development -g test

1-2. トラブルが起きやすそうなgemを1つずつアップデートする

bundle outdatedコマンドを実行すると、最新でないgemが一覧表示されます。
ただ、そこまでgemの数が多くない場合は、一気にアップデートしてしまってもいいかもしれません。

1つずつアップデートする場合は、bundle updateの後ろにgem名を指定します。

terminal
# deviseだけをアップデートしたい時
$ bundle update devise

1-3. その他のgemをまとめてアップデートする

gem名を指定せず、bundle updateです。
※なお、このタイミングではRailsのバージョンは変えないでください(元のバージョン指定のままにしておく)。Railsのバージョンアップは別途行います。

Gemfile
# Railsのバージョン指定は元のままにしておく
gem 'rails', '~> 5.0.7', '>= 5.0.7.2'

Railsのバージョン指定がされていることを確認したら、bundle update

terminal
# gemをまとめてアップデートする
$ bundle update

2. Railsのバージョンを上げる

GemfileでRailsのバージョン指定を変更して、bundle update railsです。

Gemfile
# gem 'rails', '~> 5.0.7', '>= 5.0.7.2'
gem 'rails', '~> 5.2.4', '>= 5.2.4.2'
terminal
$ bundle update rails

3. rails app:updateタスクを実行する

次にrails app:updateタスクを実行します。
このタスクを実行すると、新しいバージョンで必要になる新しいファイル作成や、既存ファイルの変更を対話形式で行うことができます。

terminal
$ rails app:update
    conflict  config/boot.rb
Overwrite config/boot.rb? (enter "h" for help) [Ynaqdhm]

上記のように、コンフリクトが起きるファイルの扱いをどうするかを一つずつ確認していく作業です。
Y/n/a/q/d/hのいずれかのキーで回答します。

Y - Yes。上書き実行
n - No。上書きしない
a - All。このファイル以降の全ファイルを上書き
q - Quit。処理中断
d - Diff。新旧ファイルのdiffを表示
h - Help。入力する各キーの意味を表示

「これ一個ずつ確認するの面倒…まとめてやっていいんじゃ?」

はい、僕もそう思って、a = allを実行しました。
するとroutes.rbがまっさらに生まれ変わりました(白日か)。

routes.rbだけはしっかり確認しましょう!というよりnにしましょう!!

伊藤さんもこうおっしゃっています。

僕はd = diffを確認した上で、Yかnを入力することが多いです。
(でも、routes.rb以外はほとんどYを入力しているかもしれない)

4. 動作確認(rails sが起動するか)

他にもテストコードや自分の目で見て確認も必要ですが、今回はrails s(とりあえず開発環境でサーバー起動させる)を目指します。

4-1. エラー①

意気揚々とrails sしたところまずはこちらのエラーから。

require': cannot load such file -- bootsnap/setup (LoadError)とのエラーが。

terminal
$ rails s
Traceback (most recent call last):
    3: from bin/rails:3:in `<main>'
    2: from bin/rails:3:in `require_relative'
    1: from /myapp/config/boot.rb:4:in `<top (required)>'
~~~config/boot.rb:4:in `require': cannot load such file -- bootsnap/setup (LoadError)

こちらの方法で対応できました。変更箇所は2箇所(ファイル)。

Gemfile
# 全部の環境に適用させたいので、グループの中には入れない
gem 'bootsnap', require: false
config/initializers/new_framework_defaults.rb
~
# この行↓をコメントアウト
# ActiveSupport.halt_callback_chains_on_return_false = false
~

4-2. エラー②

「さあ、今度こそサーバー起動だ!」と思ってrails sしたらまたもエラーが。

<top (required)>': undefined method `halt_callback_chains_on_return_false=' for ActiveSupport:Module (NoMethodError)

terminal
=> Booting Puma
=> Rails 5.2.4.3 application starting in development 
=> Run `rails server -h` for more startup options
Exiting
Traceback (most recent call last):
~~~/config/initializers/new_framework_defaults.rb:23:in `<top (required)>': undefined method `halt_callback_chains_on_return_false=' for ActiveSupport:Module (NoMethodError)

こちらで対応できました。

config/initializers/new_framework_defaults.rb
~
# この行↓コメントアウト
# Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
~

必読の記事

今回僕が遭遇したエラーは、伊藤さんがまさにおっしゃっている部分なので、この記事は読んで理解しておくと今後に役立つと思います。

Railsのバージョンが上がると、従来の挙動とは異なる、新しい挙動が導入される場合があります。
バージョンアップ後はすべて新しい挙動に合わせられるのが理想的ですが、場合によっては一部の挙動を古いRailsに合わせないといけないかもしれません。

こうした挙動の変更はload_defaultsやnew_framework_defaults_x_x.rbで行います。

load_defaultsとnew_framework_defaults_x_x.rbの関係については以下の記事で詳しく説明しているので、こちらを読んで適切に設定を変更してください。

config.load_defaultsとnew_framework_defaults_x_x.rbの関係を詳しく調べてみた - Qiita

Railsのバージョンアップは完了

これでRailsのバージョンアップは完了です。
上記手順で実行してもうまくいかない時は、サーバーの再起動(ctrl + cで終了、rails sで起動)を試してみてください。

5. 環境変数周りのファイルってどうなるの?

環境変数を定義するファイルは、Rails5.0系ではsecrets.ymlでしたが、Rails5.2系以降はcredentials.yml.encmaster.keyを使用するのが一般的です。

5-0. 「あれ?credentialsもmasterキーもなくない??」

Railsのバージョンアップをすれば勝手に作られるものだと思っていましたが、そんな美味しい話はありませんでした。
そもそもmaster.keyrails new時に作成されるもののはずなので、当たり前といえば当たり前ですが…

5-1. 結論:secrets.ymlのままで良い

特に環境変数ファイルはいじることなく動作しました。笑

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む