20190819のRailsに関する記事は14件です。

Railsチュートリアル(第9章)

はじめに

Railsチュートリアルの第9章が終わりました。
ポイントだけメモしておきます。

トークンの考え方

永続的なログインを実装するために、cookieというものに値を保持します。
しかし、そのままログイン情報を保持してしまうと、情報を盗み見られたりするとサイバー攻撃に繋がってしまうため、複雑にして保持する必要があります。
流れを簡単にまとめると以下のようなイメージです。

・ログイン時、ユーザーIDを暗号化したものと、ランダムな文字列トークンをcookieに保持
・ランダムな文字列トークンをハッシュ化して、ユーザーテーブルに保持
・ログインチェック時、cookieからユーザーIDを取り出してテーブル検索
・取得したデータのトークンとcookieのトークンのハッシュ値を比較

トークンの生成

以下の記述で、64種類の文字(A–Z、a–z、0–9、"-"、"_")からランダムに22文字生成してくれます。

SecureRandom.urlsafe_base64

モデルの仮想の属性

モデルにトークンを保持する際、データベースに保存せずに値を取得したいです。
その際に、以下のようにattr_accessorでゲッターとセッターを定義して仮想の属性を定義できます。
rememberメソッドの2行目は、トークンをハッシュ化してデータベースに保存しています。

class User < ApplicationRecord
  attr_accessor :remember_token

 # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end
end

cookieの保持

実際にcookieに値を保持する際は、sessionと同じようにハッシュの形式で値を設定します。
以下のように、expiresによって有効期限を設定できます。

cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc }

また、以下のようにpermanentメソッドを使用することで、自動的に20年を設定してくれます。

cookies.permanent[:remember_token] = remember_token

ユーザーIDを保持する際など、暗号化して格納する場合は、signedメソッドを使用します。

cookies.signed[:user_id] = user.id

2つを合わせて記述すると以下のようになります。

cookies.permanent.signed[:user_id] = user.id

取り出す際は同じようにcookies.signed[:user_id]で複合化して取得できます。

cookieの削除

cookieを削除する際は、deleteを使用します。

cookies.delete(:user_id)
cookies.delete(:remember_token)

チェックボックス

form_forの中にチェックボックスを記述する際は、f.check_boxで記述します。
以下のように、ラベルの内側に書くことで、チェックボックスと文字列をひとまとまりにしています。

    <%= form_for(:session, url: login_path) do |f| %>
      ...
     <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>
      ...
    <% end %>

値を取得する際は、チェックボックスがオンの時1、オフの時0になります。

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

初学者がRuby on Railsでポートフォリオを作成しました。

はじめに

今回の内容は就職活動中で作成しているポートフォリオの説明です。
アドバイスなどありましたらよろしくお願いいたします。

開発環境

  • Ruby 2.5.1
  • Rails 5.2.3
  • DB:Mysql5.6
  • AWS/S3
  • 少しだけVue.js

アプリ概要

・業者などに頼むほどではない作業や困りごとを得意な人に助けてもらう。
・使う機会がないスキルや自分の得意なことを活かして、困っている人を助ける。
そのような人と人が助け合うアプリがあればいいなという思いから作成しました。

接続先情報

⚠️URLはデプロイなどで接続できないタイミングがございます。その際は少し時間をおいてから接続してください。
⚠︎簡単ログインでログインした方は終わる時にログアウトもお願いいたします。

何ができるか?(機能)

Koto_Kotoは、以下のことができます。

・困りごと,スキルの作成/編集/削除

ezgif.com-video-to-gif.gif

困りごとを作成したいときは、demandボタン、スキルを提案したいときはsupplyボタンから作成することができ、目的別に分けて投稿ができます。
編集や削除は下の画像の赤丸のところからできます。
スクリーンショット 2019-08-19 21.52.53.png

・クレジット登録機能

クレジット登録機能

ezgif.com-video-to-gif (1).gif

Test用としてこちらを入力して頂ければ使用できます。

  • カード番号 4242424242424242
  • 有効期限 12/22
  • CVC番号 123
  • 名前   適当に入力してください

カードの削除も可能です。

・スキル,困りごとの助け合い機能(購入機能)

ezgif.com-video-to-gif (2).gif

カードを登録した場合のみ購入,助けることが可能です。(ポートフォリオなので、万が一購入しても、仮想的に作っているので、お支払いは発生しません。)

あと、助けるボタンを押した場合は、購入ではないので、クレジットからお支払いは発生しないような作りになっています。

・購入後のチャットのやりとり

ezgif.com-video-to-gif (3).gif

スキル購入、困りごとへの助けるボタンを押したら、チャットルームが作成される仕様になっており、お互いでやりとりが可能です。

・契約完了後の評価

ezgif.com-video-to-gif (4).gif
契約完了したら、スキル購入の場合は購入者,困りごと解決の場合は困りごと出品者がその契約内容に関して、評価する仕様になっています。その評価に応じて対象者の信頼スコア(各々が持っている評価指標。マイページの右側の円に囲まれたもの)が加算されます。信頼スコアが高ければ高いほど、その人の評価が高いことになります。

⚠︎評価画面の見た目部分は後々、モーダルで作成したいため、いじっておりません。

・ポイント機能

スクリーンショット 2019-08-18 15.13.28.png

評価されて信頼スコアが上がるだけだとモチベーションupには繋がらないと思ったので、ポイント機能を作成しました。
信頼スコアをあげればあげるほど、ポイント倍率が高くなって、ポイントをためやすくなる仕様にしています。
今後、このポイントを使用して、スキル購入の時に割引として使えるなどの機能を実装したいと考えています。

・ユーザー登録/編集/退会機能

deviseは使用せずに作成しました。(Session&Cookieの概要を学ぶため)
ユーザー登録をすると、確認メールが届くようにしており、そのメールに書かれているリンクを押して初めて、ユーザーが登録される仕様になっています。

・簡単ログイン機能

スクリーンショット 2019-08-19 22.41.08.png

就活用に簡単ログイン機能を実装しました。
ボタン一つでログインできれば、就活の際に見てもらいやすくなるのではないかと考え実装に至りました。

・スキルや困りごとのコメント機能

スキルや困りごとに対してコメントができます。
ezgif.com-video-to-gif (5).gif

・レスポンシブ対応

ezgif.com-video-to-gif (6).gif

スマートフォンが主流なので、そちらでも使いやすくするためにレスポンシブ対応にしました。

課題&追加したい機能

  • vueでのモーダル実装(評価する部分)
  • ランキング機能(頑張った人ほど目に止まる率が高くなる機能を作りたいため)
  • ポイント機能の使用(スキル購入の時に割引として使えるなど)
  • カテゴリー別一覧表示
  • Rspecテスト

最後に

記事を読んでいただきありがとうございました。
また機能を追加次第、更新していきたいと思います。

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

大学2年生のオリジナルWebアプリケーション開発4日目

今日の流れ

  1. Yarnのアップデート
  2. 基本的なログイン機構の作成

今日は時間がなかったためあまり進められませんでした。

1. Yarnのアップデート

開発中にGitHubから次のような Security Alertsが来ました。
security_alerts.png
どうやら「Yarnのバージョンが古いからセキュリティー上危険な状態だぞ」ということらしいです。ということで、Yarnのアップデート方法について調べて、解決しました。具体的な方法が気になる方は、次の記事Cloud9上でのYarnのアップデート(Windows)をご覧ください。

2. 基本的なログイン機構の作成

Railsチュートリアルの第8章にあたる部分です。個人的にRailsチュートリアルを勉強していた時に、一番頭が追い付かなかったのがこのログイン機構の作成です(特に第9章)。たださすがに3週目なのか第8章はすらすら理解できました。
ログイン機構とは別に、Bootstrapのドロップダウン機能について公式サイトやRailsチュートリアルで呼んでいると、JavaScriptの機能なのだと認識でき、Ruby、Railsしか勉強していない自分からすると、少しJavaScriptを使ったことに感動しました笑。まぁ、最終的にこのアプリケーションではどんどん使っていくつもりではありますが。

終わりに

今日は復習メインとなってしまいましたが、Yarnなど初めてきくものもあり、まだまだ知らないことだらけだと実感しました。明日は発展的なログイン機構の作成ですので、気を引き締めていきたいです。
昨日のサマソニはすごかった。

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

Railsのvalidatesとは?

validatesとは?

フォームに値や文字を入力するときに、必須の項目ってありますよね。

スクリーンショット 2019-08-19 21.16.33.png

こんな感じの。会員登録の時、「必ず入力してください」っていう項目、誰でも一度は目にしたことがあると思います。

このように入力欄に制限をかける機能のことをvalidatesといいます。

例:空欄を拒否するvalidation

では、実際にコードを記述してvalidationを設定します。

validationはmodelファイルで設定します。

product.rb
class Product < ApplicationRecord
  validates :name, presence: true
end

validates カラム名, バリデーションの条件の記述でバリデーションを設定しました。

今回はproductモデルのnameカラムにpresence: trueというバリデーションをかけました。

presence: true空ではないという条件のバリデーションです。これによりnameカラムに何らかの文字列が入ってないといけない状態になりました。

例:値を被らないようにするvalidation

次に同じ名前が被らないようnameカラムにバリデーションをかけます。

product.rb
class Product < ApplicationRecord
  validates :user_name, uniqueness: true
end

uniqueness: trueでnameカラムの値がかぶることを防ぐことができます。

様々なバリデーション

バリデーションにはたくさんの種類があるので、以下の記事を参考にしてみてください

Railsバリデーションまとめ

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

Cloud9上でのYarnのアップデート(Windows)

はじめに

Railsでアプリケーション開発中にGitHubから次のような Security Alertsが来ました。
security_alerts.png
どうやら開発アプリケーションのherokuフォルダ内のyarn.lockというファイルに問題があるそうです。
Yarnのバージョンが古いらしくセキュリティー上に欠陥がありそうです。この問題を解決するためにYarnのバージョンアップデートの方法について調べ、まとめました。

この記事では、Cloud9上でのYarnのアップデート(Windows)の方法をご紹介します。

環境

windows(64bit)
ruby 2.6.3p62
Rails 5.2.2
Cloud9上で開発

Yarnとは

YarnとはFacebookなどによって開発された新しいJavaScriptパッケージマネージャーのことです。

アップデート方法

Yarnのアップデートを行うには、yarn.lockというファイルに変更を加えなければなりません。
yarn.lockとは、インストールされたYarnの各依存関係のバージョンを記録しておくファイルです。
ここで注意しなければならないことがあります。Yarnの公式サイトによると

yarn.lock ファイルは自動生成されるため、Yarn を使わずに手動で編集するべきではありません。 Yarn CLI を使用して依存パッケージの add/upgrade/remoe を実行すると、yarn.lockファイルも自動的に更新されます。 簡単に壊れてしまうため、このファイルを直接編集しないでください。

つまり、「yarn.lockに勝手に触れるな、yarnコマンドを使え」ということです。なのでそれに従ってアップデートを行おうと思います。

1. Node.jsのバージョンアップ

まず初めにNodeのstableなバージョンをインストールします。

$ nvm install stable

ちなみに僕のバージョンはv12.8.1でした。(2019年8月19日現在)

2. Yarnのインストール

次にYarnをインストールします。

$ npm install -g yarn

ちなみに僕のバージョンは1.17.3でした。(2019年8月19日現在)

3. Yarnのアップデート

実際に僕がアップデートした lodash.merge というYarnを例にとってみます。バージョンを4.6.2にするように求められたので、次のようにしてアップデートしました。

$ yarn upgrade lodash.merge@^4.6.2

これでアップデート完了です!
残りのものも同様にアップデートしたところGitHubからの Security Alerts は来なくなりました。

参考文献

(1) Yarn公式サイト. https://yarnpkg.com/ja/, (参照 2019-8-19)
(2) "cloud9でvue-cli 3 環境構築". Qiita. https://qiita.com/MssKnd/items/381483cad854b257586d, (参照 2019-8-19)

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

Railsのイベントで初心者ながらすごいなと思ったまとめ

先日Ruby/Railsのイベントに行ったので印象的だったことを簡単にはなりますがまとめました。
Railsは初めて触ったので認識が誤っている点があればご指摘ください...!

scaffoldコマンド

MVCモデルに沿ったファイルの雛形を作ってくれる。
CRUD機能(Create, Read, Update, Delete)を簡単かつ一気に実装できるコマンド

$ rails generate scaffold モデル名 カラム名: 

一気にいろいろできるので処理が止まるまで少々待ちましょう。
基本的な機能は揃っていますがカスタマイズしたい場合などはscaffoldではなく、ひとつひとつ作るのがいいですとのことでした。
scaffoldを失敗してしまったと気づいたときはこちら

※scaffoldしたあとはマイグレーションファイルが作成されるので、テーブルを作成したことをDBに認識させるために rails db:migrate とサーバーを再起動しないとうまく読み込めなくなるので注意が必要
scaffold コマンドで設計図(migrationファイル)ができる
rails db:migrate で設計図(migrationファイル)の通りにDBが作成される、ということでした。
こちらを参考にさせていただきました。
@scivola さんにご指摘いただいたので確認しまして修正しました。ありがとうございました!

現在定義されているルートの確認

①ターミナルに表示する方法

rails routes

 →laravelの php artisan route:list と似た感じで、ターミナルに表示されます

②サーバーに表示する方法

localhost:3000/rails/info/routes

 →個人的にはこちらのほうが見やすかったです

gem

ライブラリやアプリケーションのパッケージで自分で実装すると時間がかかる機能も簡単にできるようになります。
ページネーション、画像を指定のサイズで作成など‥
gemの一種のBundlerをいれることで、Gemfileというファイルにパッケージ名、バージョンを記述してgem同士の互換性を保ちながらパッケージの種類やバージョンを管理してくれます。
Ruby gems

ちょっとしたコードのテスト

直感的で使いやすく、ちょっとしたコードの確認ができます。見やすいし、初心者にはありがたい
TryRuby

番外編:ターミナルからVScodeを起動する方法

 1. command + shift + p
 2. shell を入力
 3. shell command:install 'code' command in PATH を選択
 4. ターミナルで code .
 参考にさせていただきました
 macにもともとあるターミナルからVScodeが立ち上がるのですが、その発想なかったな〜と何度か試してしまいました。おもしろい!

まとめ

laravelとルーティングの書き方が似ていたので想像しやすい部分もあったり、初めてでも便利なコマンドがたくさんあり面白かったです。
たくさんの地域コミュニティがあったり、なんとメンターさんが一人ひとりについてくれたり(!)、アットホームでわからないことを聞きやすい雰囲気が印象的で参加してよかったなぁと思いました。

Rails Girls ガイド
初心者のひとは環境設定に躓くような気がしているのですが、全てわかりやすくのっているので簡単にできました:slight_smile:

 

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

capybaraでファイルダウンロードをテストする

Capybaraでファイルをダウンロードするテストを書いたので備忘録

Capybaraでファイルをダウンロードすると、デフォルトのダウンロードディレクトリ(~/Downloads)にファイルがダウンロードされるため、テストがしづらい。
設定を追加して、システムテストを実行しやすくする。

まずは、Capybaraで利用するWebDriverにダウンロードディレクトリを設定する

# spec/support/capybara.rb
# frozen_string_literal: true

require 'capybara/rspec'

# 読みこんで、Capybara.current_driverの初期化を済ませる
require 'action_dispatch/system_test_case'

Capybara.register_driver(:pc_headless) do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.headless!

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

RSpec.configure do |config|
  config.before(type: :system) do |example|
    driven_by(:pc_headless)

    # ここ。ダウンロード先を指定する
    page.driver.browser.download_path = CapybaraDownloadsHelper.path
  end
end

続いて、ダウンロードファイルをテストで利用するためのヘルパーを用意する

# spec/support/xxx_helper.rb
# frozen_string_literal: true

# Capybaraでダウンロードしたファイルを管理する
module CapybaraDownloadsHelper
  PATH = Rails.root.join('tmp', 'data', 'downloads').to_s.freeze

  class << self
    # ダウンロード先のディレクトリ
    #
    # @return [String]
    def path
      File.join(PATH, Thread.current.object_id.to_s)
    end

    # ダウンロードしたファイル一覧を返す
    #
    # @return [Array<String>]
    def files
      Dir[File.join(path, '*')]
    end

    # ダウンロードが完了したか
    #
    # @return [boolean] ダウンロードが完了していればtrue
    def downloaded?
      files.grep(/\.crdownload$/).none? && files.any?
    end

    # ダウンロードしたファイルを削除する
    #
    # @return [void]
    def delete_all
      FileUtils.rm_f(files)
    end
  end
end

RSpec.configure do |config|
  config.before(:all) do
    FileUtils.mkdir_p(CapybaraDownloadsHelper.path)
  end

  config.after(:all) do
    FileUtils.rm_rf(CapybaraDownloadsHelper.path)
  end

  config.before do
    CapybaraDownloadsHelper.delete_all
  end
end

これで、簡単にテストを行うことができるようになる。

RSpec.describe 'download', type: :system do
  it 'downloads file' do
    find('#download_link').click

    Timeout.timeout(10.second) do
      loop do
        break if CapybaraDownloadsHelper.downloaded?
      end
    end

    file = CapybaraDownloadsHelper.files[0]
    expect(file).to test_something
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsに触れてみる

はじめに

普段はPHP&JSで開発を行っているのですが、夏休みに入り時間的余裕ができたので、以前から気になっていたRuby on railsに触れたいと思います。
本記事ではUbuntu19.04上で開発環境を整えることをやっていきます。

インストール

今回インストールするものは以下です!

  • rbenv
  • ruby-build

rbenv

Rubyのバージョン管理をしてくれるやつです。pythonで言うpyenvですね。
次のコマンドでインストール

ターミナル
# apt install rbenv
# rbenv -v ←入ったかどうかバージョンを見て確認!

Ruby-build

rbenvのinstallコマンドを提供しているプラグインらしいです。
これが古いと新し目のバージョンがinstall listにないので気をつけてください。
さっきのコマンド実行時にインストールを一緒にしてくれているのですが、2.4.*のバージョンまでしかlistに出てこなかったので、以下のコマンドで新しくしてください。

ターミナル
# git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-buil

早速Rubyをインストール

ターミナル
# rbenv install -l  ←インストールできるバージョンを表示
----(略)----
  2.6.0-rc2
  2.6.0
  2.6.1
  2.6.2
  2.6.3
  2.7.0-dev
  2.7.0-preview1
----(略)-----
# rbenv install 2.6.0  ←2.6.0をインストール
# rbenv versions ←インストールしたバージョンを確認
* system (set by /root/.rbenv/version)
  2.6.0
# rbenv global 2.6.0 ←システムで使用するRubyを2.6.0に設定
# ruby -v ←バージョンの確認
ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-linux-gnu]

・・・変わってないだと!?

Rubyのバージョンがしっかり変わらない問題

調べたらPATHがしっかり通っていませんでした。
ubuntuなら以下のコマンドを実行してbash_profileを編集すれば直ります!

ターミナル
# vi ~/.bash_profile
--以下のものを追記------
export PATH="~/.rbenv/shims:/usr/local/bin:$PATH"
eval "$(rbenv init -)"
----------------------
# source ~/.bash_profile ←変更を適用
# rbenv global 2.6.0
# ruby -v
ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-linux]

これで、しっかり変わりました!

railsをインストール

ターミナル
# apt-get install build-essential liblzma-dev patch ruby-dev zlib1g-dev
# gem install rails
# rails --version
Rails 5.2.2

これでRailsのインストールが終了しました。

とりあえずプロジェクトを作成する

インストールできたので適当なサンプルプロジェクトを作ってみます。
とりあえずデスクトップに作っていきます。

ターミナル
# rails new foo
# cd foo
# rails s
(略)
ker/configuration.rb:91:in `rescue in load': Webpacker configuration file not found /home/hara/デスクトップ/foo/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /home/hara/デスクトップ/foo/config/webpacker.yml (RuntimeError)

なんかエラーコード吐かれた・・・

webpackerの設定が見つからないらしいので、そこらへんの情報を検索し以下のコードを実行したら直りました。

ターミナル
yarnをインストール
# curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
# echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
# sudo apt-get update && sudo apt-get install yarn

作ったプロジェクトフォルダー内で
# rails webpacker:install

終わったあと
# rails s
=> Booting Puma
=> Rails 6.0.0 application starting in development
=> Run 'rails server --help' for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.0-p0), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

これでサーバが起動したのでlocalhost:3000 にアクセス
下のような画面が出れば無事Ruby on Railsを使う準備ができました。
FireShot Capture 001 - Ruby on Rails - localhost.png

とりあえずこれからなにかWebサービスを作ってみていろいろ試します〜〜

参考にさせていただいた記事・サイト

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

rails6アップデート時のrails app:update(devise)エラー,ActionDispatch::Routing::RouteSet:Class

rails6にあげるときこんなエラーが出た

> % rails app:update
rails aborted!
NoMethodError: undefined method `alias_method_chain' for ActionDispatch::Routing::RouteSet:Class
Did you mean?  alias_method
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/devise-1.5.4/lib/devise/rails/routes.rb:14:in `<class:RouteSet>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/devise-1.5.4/lib/devise/rails/routes.rb:2:in `<module:Routing>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/devise-1.5.4/lib/devise/rails/routes.rb:1:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/devise-1.5.4/lib/devise/rails.rb:1:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/devise-1.5.4/lib/devise.rb:445:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/Users/jin/workspace/hoge/fuga/piyo/config/application.rb:20:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:48:in `require_relative'
/Users/jin/workspace/hoge/fuga/piyo/Rakefile:6:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:319:in `block in load'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:319:in `load'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/rake/rake_command.rb:22:in `block in perform'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/command.rb:48:in `invoke'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands.rb:18:in `<main>'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/Users/jin/workspace/hoge/fuga/piyo/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
bin/rails:6:in `<main>'
(See full trace by running task with --trace)

Gemfileを以下に設定すると成功する

gem 'devise', git: 'https://github.com/plataformatec/devise'

参考

https://github.com/plataformatec/devise/issues/5119

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

Rails / Bootstrap 4

概要

Rails で Bootstrap 4 を利用するための初期設定

動作環境

  • Rails 5.2.3
  • ruby 2.5.5

表記の注意事項

「$」 記号は ターミナルにおける /bin/bash のコマンドプロンプトを表します

「+ 」 (プラスとスペース1字)は、ファイル編集において、追記した行を表します
「- 」 (マイナスとスペース1字)は、ファイル編集において、削除した行を表します

導入手順

Gem

Gemfile
+ gem 'bootstrap', '~> 4.3.1'
+ # Bootstrap JavaScript depends on jQuery. If you're using Rails 5.1+, add the jquery-rails gem to your Gemfile
+ gem 'jquery-rails'
$ bundle install

SCSSマニフェストファイル

CSSマニフェストファイルを SCSSマニフェストファイルに変更

$ git mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss

SCSSマニフェストファイル設定

app/assets/stylesheets/application.scss
-  *= require_tree .
-  *= require_self

   */
+ // Custom bootstrap variables must be set or imported *before* bootstrap.
+ @import "bootstrap";

JSマニフェストファイル設定

app/assets/javascripts/application.js
  //= require turbolinks
+ //= require jquery3
+ //= require popper
+ //= require bootstrap-sprockets
  //= require_tree .

参考

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

Rails6 のちょい足しな新機能を試す66(query with large number編)

はじめに

Rails 6 に追加されそうな新機能を試す第66段。 今回は、 query with large number 編です。
Rails 6 では、 検索条件の値が Integer などの型の範囲外の値であっても検索できるようになりました。

Ruby 2.6.3, Rails 6.0.0.rc1, Rails 5.2.3 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

(Rails 6.0.0 がリリースされましたが、確認当時は Rails 6.0.0.rc1 がリリースされた時でした。悪しからず。)

$ rails --version
Rails 6.0.0.rc1

今回は、 User モデルに :bigint を指定した age を追加して、 age の範囲外の値を検索条件にして検索してみます。

プロジェクトを作る

rails new rails6_0_0rc1
cd rails6_0_0rc1

model を作る

User モデルを作ります。
name の他に :bigint を指定した age を追加します。

bin/rails g model User name age:bigint

seed データを作る

1件だけですが、 seed データを作成します。

db/seeds.rb
User.create(name: 'Taro', age: 1)

User モデルを修正する

User モデルに scope を5つ作ります。

ここで条件に age の値の範囲に含まれない大きな値と小さな値を指定します。

app/models/user.rb
class User < ApplicationRecord
  LARGE = 9223372036854775808
  SMALL = -9223372036854775809
  scope :age_in_large, -> { where(age: [1..LARGE]) }
  scope :age_eq_large, -> { where(age: LARGE) }
  scope :age_not_eq_large, -> { where.not(age: LARGE) }
  scope :age_in_small, -> { where(age: [SMALL..1]) }
  scope :age_in_small_large, -> { where(age: [SMALL..LARGE]) }
end

seed データを登録します。

$ bin/rails db:create db:migrate db:seed

rails console で確認する

rails console で確認してみます。

irb(main):001:0> User.age_in_large
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."age" >= $1 LIMIT $2  [["age", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):002:0> User.age_eq_large
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE 1=0 LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
irb(main):003:0> User.age_not_eq_large
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE 1=1 LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):004:0> User.age_in_small
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."age" <= $1 LIMIT $2  [["age", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):005:0> User.age_in_small_large
  User Load (0.8ms)  SELECT "users".* FROM "users" WHERE 1=1 LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>

ここで、実際に発行されているSQLに注目してください。SQL自体には、 -9223372036854775809 や 9223372036854775808 は登場しません。
これは、 age の値として、 -9223372036854775809 や 9223372036854775808 は、あり得ない範囲外の値なので、検索条件に含まなくても良いためです。
工夫されてますね。 User.age_not_eq_large の条件の where 1=1 など、なるほどと思ってしまいます。

Rails 5では

検索条件の値が、 age の範囲に収まらないため、 RangeError になってしまいます。

irb(main):001:0> User.age_eq_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):002:0> User.age_not_eq_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):003:0> User.age_in_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):004:0> User.age_in_small
Traceback (most recent call last):
ActiveModel::RangeError (-9223372036854775809 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):005:0> User.age_in_small_large
Traceback (most recent call last):
ActiveModel::RangeError (-9223372036854775809 is out of range for ActiveModel::Type::Integer with limit 8 bytes)

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try066_query_with_large_number

参考情報

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

#Rails - GrapeLogging でパスワードのパラメータをフィルタする設定例

Rails-Configuration example for filtering password parameters with GrapeLogging

logger.formatter = GrapeLogging::Formatters::Default.new
use GrapeLogging::Middleware::RequestLogger,
  logger: logger,
  include: [
    GrapeLogging::Loggers::FilterParameters.new
  ]

# ログの例 (一部)

"password":"[FILTERED]","password_confirmation":"[FILTERED]"

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2313

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

Rails deviseの画面をカスタマイズ

はじめに

deviseの準備はできましたが、カスタマイズもしてみたいと思いました。
今回はdeviseのログイン画面をそれっぽくカスタマイズします。

ビューをカスタマイズする

ビューをカスタマイズするには

Rails deviseの準備でも記述したように、deviseのカスタマイズ用のビューを生成する必要があります。

今回はログイン画面をカスタマイズしますので、生成された「app/views/devise/sessions/new.html.erb」をカスタマイズします。

修正前の画面

修正前の画面は次のようになります。
deviseのデフォルトのログイン画面です。

sign_in_000.png

ブラウザはChromeとなります。

修正後のコード

「app/views/devise/sessions/new.html.erb」を次のように修正します。

app/views/devise/sessions/new.html.erb
<div class="box">
  <div class="box-inner">
    <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
      <h1>ログイン</h1>
      <div class="box-email">
        <%= f.label :email, "Eメール" %><br />
        <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
      </div>
      <div class="box-password">
        <div class="a-row">
          <div class="column-left">
            <%= f.label :password, "パスワード" %>
          </div>
          <div class="column-right">
            <%= link_to "パスワードを忘れた場合", new_user_password_path %>
          </div>
        </div>
        <%= f.password_field :password, autocomplete: "current-password" %>
      </div>
      <div class="box-login">
        <%= f.submit "ログイン" %>
        <div class="legal-text">
          続行することで、Hogezonの
          <%= link_to "利用規約", "#" %>
          および
          <%= link_to "プライバシー規約", "#" %>
          に同意するものとみなされます。
        </div>
        <% if devise_mapping.rememberable? %>
          <div class="remember">
            <%= f.check_box :remember_me %>
            <%= f.label :remember, "ログインしたままにする" %>
          </div>
        <% end %>
      </div>
    <% end %>

    <div class="box-newusr">
      <p><span>------------</span> Hogezonの新しいお客様ですか? <span>------------</span></p>
      <%= link_to "Hogezonアカウントを作成", new_user_registration_path %>
    </div>
  </div>
</div>

利用規約とプライバシー規約は未実装です。
雰囲気だけ出してます。

CSSファイルを追加します。

app/assets/stylesheets/signup.css
* {
  margin: 0px;
  padding: 0px;
  text-decoration: none;
}
.box {
  width: 350px;
  height: 450px;
  margin: 60px auto 0;
  border-radius: 4px;
  border: 1px #ddd solid;
}

.box-inner {
  padding: 20px 26px;
}

h1 {
  font-weight: 400;
  font-size: 28px;
  line-height: 1.2;
  margin-bottom: 10px;
  padding-bottom: 4px;
  color: #111111;
}

.box-email {
  margin-bottom: 13px;
}

.box-password {
  margin-bottom: 22px;
}

.box-email label, .box-password label {
  font-weight: 700;
  font-size: 13px;
  padding-left: 2px;
  padding-bottom: 2px;
}

.box-email input[type="email"], .box-password input[type="password"] {
  border-style: none;
  box-sizing: border-box;
  width: 100%;
  height: 31px;
  border: 1px solid #a6a6a6;
  box-shadow: 0 1px 0 rgba(0,0,0,.07) inset;
  border-radius: 3px;
  padding: 0 7px;
}

.column-left {
  float: left;
}

.column-right {
  float: right;
}

.box-password a {
  font-size: 13px;
}

.box-login {
  margin-bottom: 26px;
}

.box-login input[type="submit"] {
  border-style: none;
  width: 100%;
  height: 31px;
  background: linear-gradient(to bottom,#f7dfa5,#f0c14b);
  border-radius: 3px;
  border: 1px #a88734 solid;
}

.box-login input[type="submit"]:hover {
  cursor: pointer;
  background: #f0c14b;
}

.legal-text {
  margin-top: 18px;
  font-size: 12px;
}

.remember {
  font-size: 13px;
  margin-top: 18px;
  padding-left: 3px;
}

.box-newusr p {
  text-align: center;
  font-size: 12px;
  color: #767676;
  margin-bottom: 14px;
}

.box-newusr p span {
  text-decoration: line-through;
  text-decoration-color: #e7e7e7;
}

.box-newusr a {
  display: block;
  width: 100%;
  height: 31px;
  font-size: 13px;
  color: #111;
  line-height: 31px;
  text-align: center;
  background: linear-gradient(to bottom,#f7f8fa,#e7e9ec);
  border-radius: 3px;
  border: 1px #a2a6ac solid;
}

.box-newusr a:hover {
  background: #e7e9ec;
}

修正後の画面

ログイン画面がカスタマイズされました。

sign_in_001.png

おわりに

「Hogezonの新しいお客様ですか?」の部分の横線がうまく表現できず、手を抜いて実装してしまいました。
deviseのビューをカスタマイズできたということで、よしとします。

間違っている箇所とかがありましたら、教えていただけると助かります。

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

永久保存版!?伊藤さん式・Railsアプリのアップグレード手順

はじめに

Railsアプリケーションを長く運用していると避けて通れないのがRailsのバージョンアップです。

古いバージョンのRailsは順次サポートの対象から外れていく(=不具合修正やセキュリティ対応がされなくなる)ため、バージョンアップをせずに運用するわけにはいきません。

そこでこの記事では僕・伊藤淳一がRailsアプリのバージョンをアップグレード(アップデート)する手順を紹介します。

この手順はこれまで何度もRailsアプリケーションをアップグレードしてきた僕の知見が詰まった、いわば「秘伝のタレ」的なアップグレード手順です。

想定するRailsアプリケーション

この記事で想定しているのは以下のようなRailsアプリケーションです。

  • 開発者1人でもなんとか面倒が見れるレベルの規模(=アップグレードは1人で作業する想定)
  • 趣味で作っているのではなく、外部のユーザーがいるRailsアプリ(=不具合が発生すると困る人が出てくるサービス)

また、この記事でいうアップグレードとは、マイナーアップグレード(例:5.1系から5.2系)とメジャーアップグレード(例:5.2系から6.0系)のことです。

手順の概要

この記事は長いので、先に手順の概要を示しておきます。

  • Rails以外のgemを最新にする
  • Rubyのバージョンを最新にする
  • Railsをバージョンアップする
  • rails app:updateタスクを実行する
  • rails crails sでRailsが正常に起動することを確認する
  • 自動テストと手動テストで動作確認する
  • 問題が無さそうならサーバーにデプロイする

ざっくり言うとこんな感じになります。
それでは以下が本文です。

1. 公式のアップグレードガイドに目を通す

Railsガイドにはアップグレードの方法を説明している項があります。
アップグレードする際の主な注意点もバージョンごとに載っているので、まずはこのページに目を通します。

Rails アップグレードガイド - Rails ガイド

日本語訳が追いついていないケースもあるので、英語版にも目を通しておく方がよいでしょう。

Upgrading Ruby on Rails — Ruby on Rails Guides

2. テストが全部パスすることを確認する

実際のアップグレード作業を開始する前に、RSpecやMinitestで書いたテストコードを実行し、全部テストがパスすることを確認します。

bundle exec rspec

カバレッジにも注目し、必要に応じてテストを書き足す

テストが全部パスしていても、カバレッジが低いとテスト不足のため、アップグレード版をリリースした後にシステムエラーが発生、ということが起きかねません。

Simplecovのようなgemを使って、どのファイルがどれくらいテストされているか確認しておきましょう。

そして、「このロジックをテストコードでテストしていないのは怖い」という箇所が見つかったら、アップグレードする前にテストを書いておきましょう。

テストコードを書くのは面倒かもしれませんが、このあとのステップで何度もテストを実行します。
急がば回れの精神でテストを書きましょう。
トータルで見ればその工数は必ずペイできるはずです。

3. 開発用ブランチを作成する

いつでもバージョンアップ前のコードベースに戻せるよう、開発用ブランチを作成しておきます。

# 例:Rails 6に移行するための開発用ブランチを作成する
git checkout -b rails-6-0-migration

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

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

そもそも、古いgemが大量に混じっていると、bundle update railsを実行したときにバージョン番号の解決がうまくいかず、bundle updateが正常に完了できない、ということもよく起きます。

なので、先にRails以外のgemを可能な限り最新版にバージョンアップしていきます。
具体的な手順は以下のとおりです。

4-a. 最新ではないgemを探す

bundle outdatedというコマンドを使うと、Railsアプリ内で最新バージョンを使っていないgemの一覧が得られます。
さらに、bundle_outdated_formatterというgemを使うと、bundle outdatedの結果を見やすく整形することができます。

参考:bundle outdatedコマンドの出力を汎用的な形式に変換するgemを作りました - Qiita

# bundle_outdated_formatterの出力例
bundle outdated | bof --format markdown

| gem | newest | installed | requested | groups |
| --- | --- | --- | --- | --- |
| bootstrap-sass | 3.4.1 | 3.4.0 | | default |
| capybara | 3.28.0 | 3.12.0 | | test |
| childprocess | 2.0.0 | 0.9.0 | | |
| chromedriver-helper | 2.1.1 | 2.1.0 | | test |
| coffee-rails | 5.0.0 | 4.2.2 | | default |
| dotenv | 2.7.5 | 2.5.0 | | |
| dotenv-rails | 2.7.5 | 2.5.0 | | development, test |

上の出力例をマークダウンのテーブルとして表示した場合↓

gem newest installed requested groups
bootstrap-sass 3.4.1 3.4.0 default
capybara 3.28.0 3.12.0 test
childprocess 2.0.0 0.9.0
chromedriver-helper 2.1.1 2.1.0 test
coffee-rails 5.0.0 4.2.2 default
dotenv 2.7.5 2.5.0
dotenv-rails 2.7.5 2.5.0 development, test

この結果を見ながら、どのgemがどれくらい最新バージョンから古くなっているのかを分析します。

コラム:原則としてGemfileにバージョンは指定しない

特別な理由がない限り、Gemfileにはgemのバージョンを指定しないようにします。

Gemfile
# NG: バージョンを指定する
gem 'devise', '~> 4.5.0'

# OK: バージョンを指定しない
gem 'devise'

バージョンを指定しない方が、このあとで全部のgemを一気に最新版にアップデートしやすくなるためです。

バージョンを指定する明確な理由がある場合はその理由をコメントとして書いておきます。
以下はその記述例です。

Gemfile
# 4.6以上にするとxxxでエラーが発生する。以下のissueが解決したら最新版を使う
# https://github.com/plataformatec/devise/issues/9999999
gem 'devise', '~> 4.5.0'

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

アプリケーションの動きに影響を与える可能性が低い、developmentグループとtestグループのgemから先にアップデートします。
以下のコマンドを使うとグループ単位で一気にgemをアップデートできます。

bundle update -g development -g test

アップデートしたらテストが全部パスすることを確認します。

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

bundle outdatedの結果を見て、メジャーバージョン番号が変わっているgemや、自分の経験上アップデート時にトラブルが発生しやすいgemを1つずつ慎重にアップデートします。

アップデートを実行する前にリポジトリのCHANGELOGを確認して、トラブルを引き起こしそうな(=後方互換性が大きく失われるような)変更履歴がないかどうか、チェックすることも重要です。

例:DeviseのCHANGELOG
https://github.com/plataformatec/devise/blob/master/CHANGELOG.md

CHANGELOGを確認したら、gemを1つずつアップデートしていきます。

# 例:deviseだけをアップデートする
bundle update devise

gemをアップデートしたら、テストを実行したり、実際に画面を操作したりして、今まで通り使えているかどうかを確認します。

このようにして、トラブルが起きやすそうなgemを1つずつアップデートしていきます。

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

トラブルが起きやすそうなgemのアップデートが一通り終わったら、その他のgemをまとめてアップデートします。

# その他のgemをまとめてアップデートする
bundle update

なお、この作業を行う前に、Railsのバージョンが固定されていることを確認しておいてください。
(Railsだけは、まだバージョンを変えたくないため)

Gemfile
# Railsは特定のバージョンに固定されていること
gem 'rails', '5.2.1'

その他のgemをアップデートしたら、テストを実行したり、実際に画面を操作したりして、今まで通り使えているかどうかを確認します。

特に、何らかの理由でテストを自動化できていないロジック(たとえば、テストを書きづらい複雑なドラッグアンドドロップ機能など)は、手動テストを忘れずに行ってください。

最後に、もう一度bundle updatedを実行して、ほぼすべてのgemが最新バージョンにアップデートされたことを確認します。
(gemをひとつ残らず最新にするのは無理だと思うので、そこにはこだわらないようにしましょう)

コラム:gemは普段からこまめにバージョンアップしよう

gemのバージョンアップ作業は実際にやってみると、かなり骨が折れると思います。
古いバージョンのgemが多ければ多いほど、この作業のしんどさが増えるので、日頃からこまめにgemを最新版にアップデートしていくことをお勧めします。

また、そもそもの話ですが、Gemfileに記述するgemを増やしすぎない(=gemを追加するときは、その必要性を慎重に吟味する)ということも大事だと思います。

5. Rubyのバージョンを最新にする

Railsアプリで使用しているRubyのバージョンが古い場合は、このタイミングで最新にしておくと良いかもしれません。
Rubyは後方互換性をかなり重視しているので、バージョンを上げてもトラブルが起きる可能性は低いはずです。
ですが、それでもゼロとは言えないので、Rubyのバージョンを上げたらテストがすべてパスすることを確認してください。

# rbenvで最新のRubyを使うようにする
# (rbenvとRuby 2.6.3のインストールはすでに終わっている前提)
rbenv local 2.6.3

# gemを再インストール
bundle install

# テストがパスすることを確認
bundle exec rspec

なお、新しいRailsは古いバージョンのRubyでは実行できません。
たとえばRails 6はRuby 2.5.0以上が必須です。

バージョンごとのRailsとRubyの対応関係はRailsガイドを参照してください。

Rails アップグレードガイド - Rails ガイド

6. Railsを最新のパッチバージョンに上げる

Railsのマイナーバージョン、またはメジャーバージョンを上げる前に、パッチバージョンを最新に上げます。
たとえば、Rails 5.2.1をRails 6.0に上げる前に、Rails 5.2.3に上げる、という感じです(Rails 5.2.3は本記事執筆時点でのRails 5.2系の最新バージョン)。

Gemfile
 # Railsのパッチバージョンを最新まで上げる
-gem 'rails', '5.2.1'
+gem 'rails', '5.2.3'
bundle update rails

パッチバージョンを上げたら、テストがすべてパスすることを確認します。
テストの実行中に警告メッセージが出ていたら、この時点で警告をなくす修正を行います。

なお、Railsのバージョン履歴は、rubygems.orgで確認できます。

https://rubygems.org/gems/rails/versions

7. Railsのメジャーバージョン、またはマイナーバージョンを上げる

さて、いよいよRailsのバージョンを上げるときがやってきました。
周辺のgemが最新版になっていれば、比較的すんなりbundle updateが完了するはずです。

Gemfile
 # Railsのメジャーバージョンを上げる
-gem 'rails', '5.2.3'
+gem 'rails', '6.0.0'
# 新しいバージョンのRailsをインストール
bundle update

ただし、バージョンを上げる際は間のバージョンをスキップせずに、1つずつアップグレードしてください。(例:5.0から6.0ではなく、5.1、5.2、6.0と順にアップグレードする)

また、上の例では"6.0.0"になっていますが(本記事執筆時点では6.0.0が最新であるため)、0より新しいパッチバージョンが出ていれば最新のパッチバージョンを指定します。

Gemfile
# 0より新しいパッチバージョンが出ていれば最新のパッチバージョンを指定する
gem 'rails', '6.0.2'

bundle updateが終わっても今回はまだテストを実行せず、先に以下の作業を行います。

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

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

# 実行例は https://railsguides.jp/upgrading_ruby_on_rails.html より引用
$ rails app:update
   identical  config/boot.rb
       exist  config
    conflict  config/routes.rb
Overwrite /myapp/config/routes.rb? (enter "h" for help) [Ynaqdh]
       force  config/routes.rb
    conflict  config/application.rb
Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
       force  config/application.rb
    conflict  config/environment.rb
...

上書き更新されるファイルは次のように対応方法を質問されるので、 Y/n/a/q/d/hのいずれかのキーを入力します。

Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]

各キーの意味は次の通りです。

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

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

必要に応じて上書きされた設定を元に戻す

Yで上書き実行すると、それまで使っていた重要な設定が失わることがあります。
その場合はgitのdiffをチェックしながら、上書きされて消えてしまった設定を自力で戻していきます。

config/environments/production.rb
 # 例:上書き更新でコメントアウトされたforce_sslの設定を元に戻す
-# config.force_ssl = true
+config.force_ssl = true

ちなみにRubyMineを使うと、GUI上で視覚的にdiffの確認とコードの修正(ロールバック)ができるのでとても便利です。

9. railsdiff.orgを参考にして、新しく追加されたgem等を確認する

app:updateコマンドを実行しても、Gemfileのようにまったく更新されないファイルもあります。
ですが、rails newした直後のGemfileを比較すると、デフォルトでインストールされるgemの種類やバージョンには違いがあります。
Railsのバージョンを上げたのであれば、こういった部分も新しいRailsに合わせておく方が安心です。

そういうときに便利なのが http://railsdiff.org です。
このサイトではRailsの各バージョンごとに、rails newした直後のファイルのdiffを確認することができます。

たとえば以下のURLを開くとRails5.1.7と5.2.3のdiffを確認できます。

http://railsdiff.org/5.1.7/5.2.3

Gemfileを見るとbootsnapのような新しいgemがいくつか追加されているので、この情報を見ながら自力で差分を埋めていきます。

Gemfile
 # 例:Rails 5.2からはGemfileにbootsnap gemが追加されているので、自分のアプリにもbootsnapを追加しておく
+# Reduces boot times through caching; required in config/boot.rb
+gem 'bootsnap', '>= 1.1.0', require: false

他のファイルに対しても、必要に応じて自力で差分を埋めていきましょう。

10. 動作確認を行う

そろそろ新しいバージョンのRailsを起動する準備が整いました。
ですが、いきなりテストを動かしても意味不明なエラーが出て面食らうこともよくあります。
なので、僕は次のような順番で動作確認を行います。

10-a. rails cが起動するか?

最初はrails crails console)が起動することを確認します。
この時点でエラーが出て起動に失敗する場合は、エラーメッセージをよく読んでエラーを修正します。

rails consoleが起動し、User.countのような簡単なコードが実行できればOKです。

10-b. rails sが起動するか?

次はrails srails server)が起動することを確認します。
サーバーの立ち上げ時や、サイトアクセス時にエラーが発生する場合は、エラーメッセージをよく読んでエラーを修正します。

画面のデザイン崩れがなく、いくつかの画面が正常に開くようになればOKです。

10-c. 全部テストがパスするか?警告も出ないか?

ここまでくればテストコードも問題なく実行できるはずなので、テストを実行してみましょう。

パスしなかったテストがあれば、原因を調べて修正してください。

また、テストはパスしていても、ターミナルに警告文が表示されていることもよくあります。
"DEPRECATION WARNING"のような文字が出力されていないか、テスト実行後のターミナルを確認してください。

警告文には警告の原因やコードの修正方法、詳細な説明ページのURL、警告が発生したコード行等が載っていることも多いので、英語が苦手な人もまずは英文をじっくり読むことが解決への近道になります。

10-d. load_defaultsやnew_framework_defaults_x_x.rbを設定する

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

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

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

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

10-e. 自分の手と目でテストする

自動テストが全部パスしていても100%安心はできません。
テストはパスしていても微妙に画面の表示が崩れているケースもあるので、自分の手と目でも動作確認しておきましょう。

また、画面のデザイン崩れだけでなく、自動テストで担保できていない複雑な機能も手動でテストする必要があります。

この作業が完了すれば、ローカル環境での動作確認はおしまいです。

11. ステージング環境にデプロイして動作確認する

次に、ステージング環境(本番環境と同じ構成のテスト環境)にRailsアプリをデプロイします。

ローカルではちゃんと動いていても、デプロイしたらサーバーの起動に失敗した、なんていうこともたまにあります。
その場合はエラーログを見ながら、問題を修正してください。

ステージング環境でアプリケーションが動き始めたら、手動テストを行います。
Amazon S3へのファイル保存や外部APIとの連携機能など、ローカル環境では動作確認しづらい機能があれば、必ずここで動作確認しておきます。

動作確認は1日で終わらせるのではなく、できれば数日間動かしてみて、何か問題が起きないか確認する方が望ましいです。

12. プルリクエストを作成し、コードレビューしてもらう

ステージング環境でも動作確認ができたら、本番リリースに向けてプルリクエストを作成し、他のメンバーにコードレビューしてもらいましょう。

もしかすると、コードの修正漏れを指摘されたり、経験者ならではのアドバイスをもらえたりするかもしれません。

13. 本番環境にデプロイして動作確認する

いよいよ最後のステップです。
プルリクエストをマージし、バージョンアップしたRailsアプリを本番環境にデプロイします。

ステージング環境と本番環境がまったく同じ構成であればトラブルが発生する可能性は低いですが、念のため、可能な範囲で手動テストしておきましょう。

バージョンを上げたことに起因するトラブルが突然起きる可能性もあるので、リリース後、数日間はログやサーバーのリソース状況等を注意して監視してください。

まとめ

お疲れ様でした。Railsアプリケーションをアップグレードする手順は以上になります。

アップグレード作業の大変さ(作業開始から完了までにかかる工数)は、アプリケーションの規模やテストのカバレッジ率、gemのアップデートをサボっていた期間の長さ等によって大きく変わってきます。
ですが、運用を続ける限りは「大変そうだから、アップグレードを見送る」という選択肢はまずありません。
技術的負債が雪だるま式に膨らむ前に、早め早めにバージョンを上げていきましょう!

宣伝:テストが苦手な方は「Everyday Rails - RSpecによるRailsテスト入門」をどうぞ!

この手順を最後まで読まれた方はよくわかると思いますが、Railsのアップグレード作業には自動テストが必要不可欠です。
「テストを書くのが苦手」「どうやってテストを書けばいいのかわからない」という方はぜひ、僕が翻訳した電子書籍「Everyday Rails - RSpecによるRailsテスト入門」を読んでみてください。
RSpecや自動テストは初めて、という方でもわかるよう、Railsアプリケーションのテストの書き方を詳しく、丁寧に説明します。

「Everyday Rails - RSpecによるRailsテスト入門」は、以下のLeanpubのサイトから購入可能です。

https://leanpub.com/everydayrailsrspec-jp
hero.png

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