Ruby on Railsのマイグレーションのまとめ

Ruby on Railsにはマイグレーションという、データベースの変更管理を簡単に行うための便利な枠組みが用意されています。

この投稿では、このマイグレーション機能の使い方についてまとめたいと思います。(徐々に更新していきます。)

マイグレーションファイルの作成

マイグレーションファイルとは、データベースの変更内容を記述したruby形式のファイルで、app/db/migrate以下に保存します。このファイルをコミットに含めることで、ソースコードの変更に同期する形で、データベースの変更履歴をGit等のソースコードリポジトリ上で管理することができます。

マイグレーションファイルは、手動で作成しても良いですが、作成するためのrailsコマンドが用意されています。

$ rails g migration <変更内容>

変更内容には、どのような変更を行うのかを英語で記述します。例えば、新しくusersテーブルを作成する場合は

$ rails g migration create_users

とします。上記はスネークケースで書きましたが、パスカルケースで

$ rails g migration CreateUsers

としても同じ結果が得られます。このコマンドを実行すると、app/db/migrateディレクトリ以下に次のような名前のファイルが作成されます。

20201207233110_create_users.rb

最初の数字の列はマイグレーションファイルの作成日時を「yyyyMMddhhmmss」形式で入力されます。

ファイルの中身は次のようになります。

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
    end
  end
end

Jenkinsで古いビルドを削除する

Jenkinsを利用していると、実行済みのビルドがどんどん溜まっていってストレージを圧迫するため、定期的に古いビルドを削除する必要があります。

個別のビルドについては、ビルドの詳細ページから「Delete build ‘#xxx’」をクリックして削除できますが、一つ一つ実行するのは手間です。

そういう時はスクリプトコンソールを利用すると便利です。スクリプトコンソールは文字通りJenkins上でスクリプト(Groovy)を実行することができる機能で、「Jenkinsの管理」画面からアクセスすることができます。

特定のジョブのビルドをすべて削除する

jobName変数に削除したいジョブ名を設定し、次のスクリプトを実行します。

def jobName = "<ジョブ名>"  
def job = Jenkins.instance.getItem(jobName)  
job.getBuilds().each {
  it.delete()
}  

すべてのジョブについて古いビルドを削除する

MAX_BUILDS定数に、各ジョブにおいて残したい最新のビルドの数を設定し、次のスクリプトを実行します。

// 残すビルド数を設定
MAX_BUILDS = 20

for (job in Jenkins.instance.items) {
  def recent = job.builds.limit(MAX_BUILDS)
  for (build in job.builds) {
    if (!recent.contains(build)) {
      build.delete()
    }
  }
}

Bootstrap 4でモバイル端末ではタブの代わりにセレクトボックスを表示させる

CSSフレームワークのBootstrap 4を使うと、タブを使って表示するコンテンツを切り替える機能を簡単に実現できます。(Bootstrap 4のJavascriptを有効にしておく必要があります。)

<!-- タブ -->
<ul class="nav nav-tabs" id="myTab" role="tablist">
  <li class="nav-item" role="presentation">
    <a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
  </li>
  <li class="nav-item" role="presentation">
    <a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
  </li>
  <li class="nav-item" role="presentation">
    <a class="nav-link" id="contact-tab" data-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Contact</a>
  </li>
</ul>

<!-- コンテンツ -->
<div class="tab-content" id="myTabContent">
  <div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">...</div>
  <div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">...</div>
  <div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">...</div>
</div>

ただ、レスポンシブ対応のサイトにおいて、スマートフォン等の画面の小さいデバイスでアクセスした場合、タブの数が多いとタブの行が複数行になったりして、見栄えが良くありません。

そのような場合に、タブの代わりにセレクトボックスで表示するコンテンツを選べるようにするのが、一つの対処法だと思います。

まず、既存のタブを画面が小さいデバイスでは表示させないようにするため、上のコードの<ul>タグに対して、d-noneとd-md-flexクラスを追加します。

<!-- タブ -->
<ul class="nav nav-tabs d-none d-md-flex" id="myTab" role="tablist">
...

これによって、デバイスの横幅が768px未満の場合はタブを表示させないようにすることができます。768pxという閾値については、「md」を「sp」や「lg」に変更することで変えることができます。詳しくは、Bootstrap 4の公式ページをご参照ください。

https://getbootstrap.com/docs/4.5/layout/overview/

次に、セレクトボックスのコードをタブのコードの前か後ろに追加します。

<!-- セレクトボックス -->
<select class="form-control nav-select d-md-none">
    <option value="#home">Home</option>
    <option value="#profile">Profile</option>
    <option value="#contact">Contact</option>
</select>

d-md-noneによって、768px以上のデバイスでは表示されなくなります。

最後にセレクトボックスの選択に連動して表示するコンテンツを切り替えるためのJavascript(JQueryを利用)のコードを追加します。

<script>
  $('.nav-select').on('change', function(e) {
    var id = $(this).val();
    $('a[href="' + id + '"]').tab('show');
  });
</script>

以上です。

RailsでエラーをSlackに通知する

Rails標準のLoggerを活用して、エラーログをSlackのチャンネル上に表示する方法について説明いたします。

Slack Notifierのインストール

まず、RailsからSlackへのメッセージの送信に関しては、slack-notifierという便利なgemが公開されておりますので、こちらを利用させていただきます。

https://github.com/stevenosloan/slack-notifier

Slack Notifierをインストールするには

gem "slack-notifier"

をGemfileに追加し、

$ bundle install

を実行します。

Slack通知用のLoggerの作成

次に、Slack Notifierを利用してSlackにメッセージを通知するためのLoggerを、Rails標準のLoggerを継承して作成します。

class SlackLogger < ActiveSupport::Logger
  def initialize
    super(SlackDevice.new)
  end

  class SlackDevice
    def initialize
      webhook_url = '<Webhook用のURL>'
      channel = '<チャンネル名(#含む)>'
      username = '<投稿ユーザー名>'
      @notifier = Slack::Notifier.new webhook_url, channel: channel, username: username
    end

    def write(message)
      @notifier.ping message
    end

    def close
      # Do nothing
    end
  end
end

SlackLoggerにはオーバーライドしたコンストラクタ(initialize)とSlackDeviceというサブクラスを持たせています。

コンストラクタでは、SlackDeviceを初期化し、そのインスタンスを継承元のコンストラクタに渡しています。

通常、継承元のActiveSupport::Loggerの初期化時(内部ではRuby標準のLoggerを呼んでいます。)には、その引数にログを書き込むファイルパスかIOオブジェクトを指定します。ただ、ソースコードを追ってみると、writeメソッドとcloseメソッドを持つオブジェクトであれば、とりあえず動作することが分かります。(将来的にこの挙動が続く保証はありませんので、Rubyのバージョンアップにはご注意ください。)

https://github.com/ruby/logger/blob/master/lib/logger/log_device.rb

今回はその特性を利用し、ファイルパスやIOオブジェクトの代わりに、writeメソッドとcloseメソッドを持つSlackDeviceというクラスを定義し、そのオブジェクトを渡しています。

SlackDeviceはコンストラクタにおいて、先ほどインストールしたSlack Notifierを初期化しています。その際、引数としてSlackへの接続情報を渡します。(実際には、接続情報はハードコードせず、設定ファイルから渡すと良いと思います。)

Webhook用のURLの取得の仕方はSlackの公式ドキュメントをご参照ください。

https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8

また、SlackDeviceのwriteメソッドではSlack Notifierオブジェクトのpingメソッドを実行しています。closeメソッドは特になにもしません。

Loggerの設定

Railsでは標準でプロジェクトルートのlogフォルダ以下に<環境名>.logという名前のログファイルを作成します。

その挙動を維持したまま、Slackへの通知を追加するには、次のコードをapp/config/environments/<環境名>.rbに追加します。(環境別に分ける必要がない場合は、app/config/application.rbでも大丈夫です。)

logger = ActiveSupport::Logger.new("#{Rails.root}/log/#{Rails.env}.log")

# Slack連携用Logger
slack_logger = SlackLogger.new
extended_logger = logger.extend(ActiveSupport::Logger.broadcast(slack_logger))

config.logger = extended_logger

非同期処理への対応

Slackへの通知は同期的に行われるため、ログレベルをdebug(config.log_level = :debug)やinfoに設定していると、画面表示に非常に時間が掛かるようになり、現実的ではありません。ログレベルをerror以上に設定するか、または、Sidekiq等を利用して、Slackへの通知を非同期的に処理する仕組みを入れるのが良いかと思います。

WordPressの独自テーマで固定ページにも抜粋を設定

WordPressの投稿記事における「抜粋」設定機能は、投稿の一覧画面において記事のサマリとして表示させたり、またdescriptionのmetaタグに設定することで検索エンジン等の検索結果に表示させたりするのに便利です。

ただ、投稿では標準で抜粋は有効ですが、固定ページにおいては無効になっています。

これを有効にするためには、利用しているテーマのfunctions.phpに次の行を追加します。

add_post_type_support('page', 'excerpt');

Dockerでイメージを最新に更新

Docker Hubから取得したイメージは自動的には更新されません。特定のイメージを利用してコンテナを作成した場合、初回は最新のイメージをDocker Hubから取得しますが、二回目以降はタグがlatestであってもローカルにあるイメージが利用されます。

ローカルのイメージを更新するには、pullコマンドを利用します。

$ docker pull <リポジトリ名>[:<タグ名>]

Rails(Turbolinks)でGoogle Tag Managerを利用

TurbolinksはPjax(pushState + Ajax)を実現するためのライブラリで、Rails 5では標準で有効になっています。Turbolinksを使うと、ページ遷移時に画面全体の読み込みが行われないため、SPAにしなくても、非常にスムーズな操作性を実現することができます。

ただ、Turbolinksを利用している場合、ページ遷移時にGoogle Tag Manager(以下GTM)のページビューイベントが発火しません。

発火させるためには、次のJavascriptのコードを利用します。

document.addEventListener('turbolinks:render', function() {
    dataLayer.push({
      'event': 'pageview',
      'virtualUrl': window.location.pathname
    });
  });

ページ遷移時に無名関数が実行され、GTMのスクリプトによって定義されたdataLayer配列にpageviewイベントを挿入することで、GTMにイベントが送られます。

GTM上でこのイベントをキャッチするには、新たなトリガーを作成する必要があります。トリガーのタイプを「カスタム イベント」とし、イベント名には先ほど指定した「pageview」というイベント名を指定します。(コードと同じであれば別のイベント名でもOK)

作成したトリガーを「配信トリガー」として、必要なタグに割り当てれば完了です。

DockerでOCI runtime create failedエラー

CentOS環境において、誤ってDockerのコンテナを起動したまま、yumでdocker-ceパッケージをアップデートしてしまいました。

その後、再度コンテナを起動しようとしたのですが、次のエラーにより起動ができない状態になりました。

$ docker start postfix
Error response from daemon: OCI runtime create failed: container with id exists: d21dd44a4cd471de93a5869922288143d7a1e2395f90b9bee9dc4ca5476524cc: unknown
Error: failed to start containers: postfix

次のフォルダにある、コンテナ名を持つフォルダを削除することで、起動が可能になりました。

/run/docker/runtime-runc/moby

Top