20200205のRailsに関する記事は19件です。

【Rails6】DEPRECATION WARNING: update_attributes is deprecated and will be removed from Rails 6.1 (please, use update instead)

TL;DR

update_attributes => update に変更しましょう。
※安心してください。動作は変わりません。

環境

  • Ruby: 2.6.3
  • Rails: 5.2.4.1 => 6.0.2.1 へアップデート

なんかWarning出たな? :thinking:

DEPRECATION WARNING: update_attributes is deprecated and will be removed from Rails 6.1 (please, use update instead)

update_attributes, update_attributes! を使っているところでこんなWarningが出ました。

update_attributes は Rails 6.1 で削除するから、代わりに update を使ってねって言われています。

変更しちゃって大丈夫? :thinking:

Railsのソースを見てみましょう。
https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/persistence.rb#L625

alias update_attributes update
deprecate update_attributes: "please, use update instead"

update_attributesupdate のaliasとして定義されています。
なので、どちらを使っても動作は変わらないです。

update_attributes! も同様ですね。
https://github.com/rails/rails/blob/v6.0.2.1/activerecord/lib/active_record/persistence.rb#L639-L640

ガッと update, update! へ変更してWarning解消出来ました。

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

【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント4つ

はじめに

Docker環境下でこんなシーンに遭遇したことはありませんか?

「Railsでバグ発生!デバッグしなきゃ!よーし、binding.pryしよう!」

「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」

こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1
Docker: 19.03.5
docker-compose: 1.24.1

1.binding.pryのコンソールが出ない

docker-compose upなどでrails serverを立ち上げていて、binding.pryを入力した箇所で動作が止まっているのに、

「コンソールが出ない!どうしよう!」

という状態を想定しています。

解決法

$docker container ls

でRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1とします。

$ docker attach rails_app_web_1

docker attachで該当コンテナにattachします。
これで、binding.pryしたときにコンソールが表示されます。

※もし反応がなければ、Enter押下するとコンソールが出るかもです。

2.せっかくコンソールが出たのに終了の仕方がわからない

多くの場合はbinding.pryを一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。

下手にCtrl + Cで終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。

解決法

pryの画面から終了するならcontinueと入力してrails serverを通常動作に戻し、docker attachを維持するのが便利です。

この状態であれば、次にbinding.pryしたときもスムーズにデバッグ作業が可能です。

※デバッグが終了してコンテナから抜けたい場合、Ctrl + P + Q(Macの場合)でコンテナを停止せずに抜けられます。

3.pryで日本語入力出来ない

少し外れますが、そもそもrails consolepryを使っていて、日本語が入力できないパターンもあるかもしれません。

解決法

Dockerfile
ENV LANG C.UTF-8

Dockerfileに上記のように追記すれば解決できます。

これを忘れるとpry日本語入力が効きません。

4.コンテナがすぐ落ちる、コンソールに文字が入力できない

2020/2/6追記

解決法

docker-compose.yml
web:
  tty: true
  stdin_open: true

上記がdocker-compose.ymlのRailsに関係する箇所に書かれているかどうか確認します。(今回はwebとしています。)

  • tty: true ポート待受などをしていないコンテナを起動させ続けるオプション
  • stdin_open: true 標準入力出来るようになるオプション

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

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

【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント3つ

はじめに

Docker環境下でこんなシーンに遭遇したことはありませんか?

「Railsでバグ発生!デバッグしなきゃ!よーし、binding.pryしよう!」

「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」

こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1
Docker: 19.03.5
docker-compose: 1.24.1

1.binding.pryのコンソールが出ない

docker-compose upなどでrails serverを立ち上げていて、binding.pryを入力した箇所で動作が止まっているのに、

「コンソールが出ない!どうしよう!」

という状態を想定しています。

解決法

$docker container ls

でRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1とします。

$ docker attach rails_app_web_1

docker attachで該当コンテナにattachします。
これで、binding.pryしたときにコンソールが表示されます。

※もし反応がなければ、Enter押下するとコンソールが出るかもです。

2.せっかくコンソールが出たのに終了の仕方がわからない

多くの場合はbinding.pryを一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。

下手にCtrl + Cで終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。

解決法

pryの画面から終了するならcontinueと入力してrails serverを通常動作に戻し、docker attachを維持するのが便利です。

この状態であれば、次にbinding.pryしたときもスムーズにデバッグ作業が可能です。

※デバッグが終了してコンテナから抜けたい場合、Ctrl + P + Q(Macの場合)でコンテナを停止せずに抜けられます。

3.pryで日本語入力出来ない

少し外れますが、そもそもrails consolepryを使っていて、日本語が入力できないパターンもあるかもしれません。

解決法

Dockerfile
ENV LANG C.UTF-8

Dockerfileに上記のように追記すれば解決できます。

これを忘れるとpry日本語入力が効きません。

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

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

RSpecの導入【備忘録】

準備

Gemfileを編集

RSpecを使用するアプリのGemfileを以下の通りに編集する
・「group :development, :test do 〜 end」に「gem 'rspec-rails'」を追加
・「group :development do」に「gem 'web-console'」を追加

group :development, :test do
  gem 'rspec-rails'
end

group :development do
  gem 'web-console'
end

インストール

・ターミナルで「bundle install」を実行

# RSpecを使用するアプリのディレクトリへ移動
cd ~/projects/sample-app

# 「bundle install」を実行
$ bundle install

RSpecの設定

RSpec用の設定ファイルを作成

・下記のコマンドを実行

$ rails g rspec:install

.rspecを編集

・下記を追加

--format documentation

RSpecのディレクトリ

・specフォルダ以下にモデルやコントローラーごとにフォルダを作成し、specファイルを管理する。

sample-app
└ spec
 └ models
  └ specファイル
 └ controllers
  └ specファイル  

specファイルの命名規則

対応するクラス名_spec.rb
(例)post_spec.rb

テスト実行

・ターミナルで以下のコメントを実行

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

RailsでHamlを書く

概要

初心者向けにhamlの基礎的な書き方とhamlにおけるrailsヘルパーメソッドのルールを書きました。
特にヘルパーメソッドの書き方は初心者がよくsyntaxエラーを起こしやすい箇所なので参考になればと思います。

HTMLタグ

基礎

HTMLタグは基本的に「%」をつければOKです。

haml
%div
%a
%input
%header
結果(HTML)
<div></div>
<a></a>
<input>
<header></header>

クラス名、ID名

クラス名は「.(ドット)」、ID名は「#(シャープ)」をそれぞれHTMLタグの後ろにつけることで作成できます。
「.」や「#」を連続することで複数のクラス名やID名をつけることができます。

また、HTMLタグ無しでクラス名やID名を記述するとdivタグになります。(divタグの省略記法)

haml
%div.class-name
%div#id-name
%div.class1.class2.class3
.no-html-tag
#no-html-tag
結果(HTML)
<div class="class-name"></div>
<div id="id-name"></div>
<div class="class1 class2 class3"></div>
<div class="no-html-tag"></div>
<div id="no-html-tag"></div>

入れ子(ネスト)

ネストは半角2スペースで表現します。
(厳密に言うと2スペース以外もできますが、通例では2スペースです。)

haml
.parent
  .child
結果(HTML)
<div class="parent">
  <div class="child"></div>
</div>
haml
.parent
  .child
    子供だよ
結果(HTML)
<div class="parent">
  <div class="child">子供だよ</div>
</div>

ネストの深さでどのHTMLタグがどのHTMLタグの親かが決定します。
最初は見慣れないですが、慣れると可読性が高く、記述もHTMLより素早くできます。

読むコツとしてはそのHTMLタグから視線をまっすぐ下に降ろすことです。

<下記hamlコードを例に>
「.parent」の子供はどこまでかを把握したい場合
「.parent」と同じネストの深さ(0 space)で書かれているのは「.parent-brother」なので、それまでの「.child」「.grand.child」「.brother」が子供です。

「.child」の子供はどこまでかを把握したい場合
「.child」と同じネストの深さ(2 space)で書かれているのは「.brother」なので、それまでの「.grand.child」が子供です。

「.brother」の子供はどこまでかを把握したい場合
次の行が自分より浅いネスト(0 space)の「.parent-brother」なので「.brother」は子供がありません。

haml
.parent
  .child
    .grandchild
  .brother
.parent-brother
  .child
結果(HTML)
<div class="parent">

  <div class="child">
    <div class="grandchild"></div>
  </div>

  <div class="brother"></div>
</div>

<div class="parent-brother">
  <div class="child"></div>
</div>

railsヘルパーメソッド

基礎

erbでは変数やヘルパーメソッドなどの表示したいものは「<%= 記述 %>」
ifやeachなどの表示したくない処理等は「<% 記述 %>」で記述していました。

hamlでは以下になります。

  • 表示したいものは「= 記述」
  • 表示したくないものは「- 記述」
erb
<%= link_to root_path %>
<% hello = "こんにちわ" %>
<%= hello %>
haml
= link_to root_path
- hello = "こんにちわ"
= hello
結果(HTML)
<a href="/">/</a>
こんにちわ

<!-- 「- hello = "こんにちわ"の部分は表示されない」 -->

クラス名・ID名

ヘルパーメソッドにクラス名・ID名を付与する場合はHTMLタグとは違い、それぞれのヘルパーメソッドの構文に従います。
例えばLink_toは以下のような書き方が可能なのでそれに従って書きます。

構文
link_to(body, url = {}, html_options = {})
  # url is a String; you can use URL helpers like
  # posts_path

link_to(url, html_options = {}) do
  # name
end
haml
= link_to "リンクだよ",root_path,{class:"class-name",id:"id-name"}
= link_to root_path,{class:"class-name",id:"id-name"} do
  リンクだよ
HTML(結果、どちらも同じ)
<a class="class-name" id="id-name" href="/">リンクだよ</a>

ネスト

ネストのルールはHTMLの項目で述べたものと同じく半角2スペースが通例です。
ここで意識すべきは記述の始まりは「=」や「-」であることです。
以下は「link_to」の始まりが揃っていますが、「=」の位置が違うのでネストの深さが違います。
初心者はよくここでエラーを起こしやすいです。

haml
%div
  = link_to root_path -#=>正 2space nest
 =  link_to root_path -#=>誤 1space nest
結果(HTML)
<div>
  <a href="/">/</a>
</div>

また、逆にrailsヘルパーメソッドへ他の要素をネストさせる場合はヘルパーメソッドの構文に従います。

ここで意識すべきは「do end」で閉じる系のメソッドはendの閉じタグが不要になることです。
例えば「do end」で閉じて他の要素をネストさせることができるlink_toは以下のようになります。

erb
<%= link_to root_path do %>
  リンクだよ
<% end %>
haml
= link_to root_path do
  リンクだよ
結果(HTML)
<a href="/">リンクだよ</a>

おわりに

初心者に「公式ドキュメントを読め!」は流石に鬼畜な気もするので近日中に頻出ヘルパーメソッドについてはまとめようかと思います。
また、当記事を読んで疑問・修正等があればコメントいただけると助かります。
いいねをしていただけるとモチベーションになります!!

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

【heroku】ActiveRecord::IrreversibleMigration が出たときの対処法

nemlog検索サイト https://searchnemlog.herokuapp.com/
作成の時herokuデプロイで詰まってしまったので解決方法をメモしときます。

ActiveRecord::IrreversibleMigration

おそらくスクレイピングを通してデータベースに保存していたので起きてしまったエラーと思われます。

以下からはこのエラーの解決手順を記したいと思います。

ロールバック

heroku run rake db:migrate:reset

恐らくエラー文が表示されると思います。

そこに

DISABLE_DATABASE_ENVIRONMENT_CHECK=1

と書かれていたらそれを利用してDB生成が可能になります。

ドロップ

heroku run RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop

先ほどのエラー文を利用してドロップするとなんとかドロップしてくれます。

DB生成

あとはdb:createを実行。

heroku run rails db:create

マイグレーション

最後はいつも通り heroku run をしましょう。

heroku run rake db:migrate

この手順でいけばなんとかアプリが立ち上がると思います。

まとめ

データベース系のエラーにハマるとデリケートなイメージなのでめちゃくちゃビビり倒してしまいます。

herokuエラーは前回大概ハマったと思っていましたがまだまだ見知らぬエラーがたくさんである意味奥が深いですね。

とりあえず誰かの参考になればと思います。

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

bin とは

binファイルとは?
binとはbinaryの略で、binファイルとは
テキスト形式ではなくバイナリ形式で書かれているデータを扱うファイル。

バイナリデータとは、コンピュータが理解するためのプログラムが書かれたデータのことである。

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

SSH認証するための鍵をBitbucketに設定する。

そもそも鍵とは...

そもそも鍵はなぜ必要なのか。
リポジトリにアクセスするために、SSH認証キーを使う。
SSH認証することで、
・セキュリティ向上(言わずもがな)
・アクセス時に毎回パスワードを聞かれなくてストレスフリー
私が今回この記事を書こうと決めたのは、パスワードを毎回聞かれ、
ストレスがやばかったから。
では、そのSSH認証するための鍵の生成について見ていきましょう。


SSH認証するための鍵をBitbucketに設定する

1.SSH認証の公開鍵/秘密鍵を作成
2.公開鍵をクリップボードにコピー
3.Bitbucketに公開鍵を登録


SSH認証の公開鍵/秘密鍵を作成

ターミナル上で以下コマンドを叩いて、公開鍵と秘密鍵を生成。

bash
$ cd ~/.ssh
$ ssh-keygen -t rsa -C hoge@example.com  # 自分のメールアドレス

以下コマンドを叩いて、生成されたファイルを確認。

bash
$ ls -l 
合計 12
-rw------- 1 hoge 1831  2月 5 18:00 id_rsa
-rw-r--r-- 1 hoge  405  2月 5 18:00 id_rsa.pub

各ファイル名は下記です。
秘密鍵:id_rsa
公開鍵:id_rsa.pub

公開鍵をクリップボードにコピー

cat(またはless)コマンドで公開鍵の内容をクリップボードにコピー。

bash
$ cat id_rsa.pub

Bitbucketに公開鍵を登録

1.Bitbucketにログイン後、左下のユーザアイコンをクリックし、view profileをクリック。

2.「設定」をクリックし、「セキュリティ」の中の「SSH 鍵」をクリック。

3.「鍵を追加」をクリックし、「Label」に適当な名前を入力し、「Key」に先ほどクリップボードにコピーした公開鍵の中身をペースト。

4.「鍵を追加」をクリックして設定完了。

これで、リポジトリにプッシュ!!!

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

link_to の復習

link_toの基本形

<%= link_to "テキスト", "リンク先のパス" %>
<!-- 例 -->
<%= link_to "Yahoo!", "https://www.yahoo.co.jp/" %>

link_toのmethodオプション

<!-- 例 -->
<%= link_to "削除", "/users/1", method: :delete %>

何も指定しないとGETが指定される。

link_toのtargetオプション

_blankで新規のウィンドウを開いて表示させる。
_selfで現在のウィンドウで表示させる

<%= link_to 'link_toリファレンス', yokota_path , { :class => "outer", :target => "_blank"} %>

link_toのclassオプション

<%= link_to "トップページ", "/", class: "hoge" %>

link_toとhtmlの合わせ技

<%= link_to "/" do %>
  <i class="fas fa-home"></i> トップページ
<% end %>

画像のリンクを作成しよう

<%= link_to image_tag('test.jpg', class: "クラス名"), 'パス' %>

dataオプションを使う

<%= link_to "削除", user_path(user), method: :delete, data: { confirm: "本当に削除しますか?" } %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails_same_site_cookie gemで、RailsアプリにChrome 80向けのSameSite属性を指定する

はじめに

以下の記事にあるとおり、Chrome 80では2020年2月17日の週以降にデフォルトのSameSite属性が変更されます。

Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita

この変更が入ると、次のように「決済や認証などで外部サービスを利用し、外部サイトからPOSTで戻ってくるサイト」でユーザーが識別できないエラーが発生します。

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_117852_84f60298-2a3f-65b0-5ff6-daacf12a1c4b.png
(画像の引用元:Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita

本記事ではRailsアプリケーションでこの問題に対応する方法を紹介します。

免責事項 (disclaimer)

この記事のとおりにあなたのRailsアプリケーションを変更して、何らかの不具合やセキュリティ上の問題が発生しても筆者は一切の責任を負いません。

技術的な背景と最新の技術情報を十分理解した上で、本記事の内容を適用してください。
(適用する必要がない場合は何もしないでください)

対応方法

rackのバージョンを2.1.0以上に上げます。
(2.1.0以上でないと、SameSite=None属性に対応していないため - 参考

(訂正:rails_same_site_cookieはrackの機能ではなく、独自にCookieを設定しているため、必ずしもrack 2.1.0以上に上げる必要はないようです)

rails_same_site_cookie gemをインストールします。

Gemfile
gem 'rails_same_site_cookie'
$ bundle install

対応は以上です。

rails_same_site_cookie gemがやってくれること

rails_same_site_cookie gemをインストールすると、自動的に全cookieにSameSite=None; Secure属性が追加されます。

ただし、iOS 12とmacOS 10.14のSafariなど、SameSite=None; Secure属性を付けると不具合が発生するブラウザ(参考)に対してはこの属性を付与しません。

rails_same_site_cookie gemがやってくれないこと

rails_same_site_cookie gemはRails側のCookieを変更するだけで、JavaScript側で設定するCookieには何も変更を加えません。
もしJS内でCookieを設定しているコードがある場合は、何らかの対応が必要になります。(詳細未調査)

参考1:動作確認の手順(例)

「決済で外部サービスを利用し、外部サイトからPOSTで戻ってくるサイト」を想定した場合の確認手順です。
いきなり本番環境で試すのではなく、テスト環境(ステージング環境)で試すようにしてください。

  1. こちらの手順に従って、Chromeの設定を変更する
  2. Railsアプリサイト上で商品を購入し、外部の決済サービスに遷移する
  3. 2分以上待つ(2分以上待たないとChromeがCookieを送信してしまうため)
  4. 決済を実行して、自サイトに戻る
  5. ログイン情報が維持されたまま、正常に決済が完了することを確認する(修正前は何らかの不具合が発生することも事前に確認しておく)

参考2:リクエストスペックでテストを書く(例)

SameSite=None属性が適切に付与されているかどうかを確認するリクエストスペックの記述例です。
(非SSLで接続する場合、rails_same_site_cookie gemはSameSite=None属性だけを付与し、Secure属性は付与しません)

ログイン処理はDeviseで実現されていることを前提とします。
テストコード内の_your_app_sessionは、適宜ご自身のアプリケーション名に合わせて変更してください。

spec/requests/cookies_spec.rb
require 'rails_helper'

RSpec.describe 'Cookies', type: :request do
  describe 'Cookie の SameSite 属性' do
    before do
      user = create :user
      login_as user
    end
    it 'User-Agent指定無しの場合 SameSite=None がつく' do
      get new_user_session_path
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/
    end

    it 'SameSite=Lax がデフォルトになる Chrome 80 では SameSite=None がつく' do
      mac_chrome_80 = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.42 Safari/537.36      '
      win_chrome_80 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.16 Safari/537.36'

      get new_user_session_path, headers: { 'User-Agent' => mac_chrome_80 }
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/

      get new_user_session_path, headers: { 'User-Agent' => win_chrome_80 }
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/
    end

    it 'SameSite=Noneの扱いにバグがある iOS12 Safari では SameSite=None がつかない' do
      iphone_ios12_user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1'
      get new_user_session_path, headers: { 'User-Agent' => iphone_ios12_user_agent }
      expect(response.headers['Set-Cookie']).to include '_your_app_session='
      expect(response.headers['Set-Cookie']).not_to include 'SameSite'
    end
  end
end

参考文献

本記事を書くにあたって、下記記事を参考にさせてもらいました。
どうもありがとうございました。

Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita

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

JavaScript上でObjectをRailsで取得できる形のFormDataへ変換する

概要

TypeScript上で、Fileを含んだObjectをRails APIにputやpostしたい。
→ そのままだとFileがうまく渡らないのでFormDataにする必要がある。
File以外のパラメータも含んだObjectFormDataに変換したい。

コード

/**
 * Convert object to FormData which rails can use.
 * This function is useful for uploading files.
 *
 * ex)
 * { id: 1, hero: { id: 1, name: 'NewHero' }, items: [1, 2] }
 * -> FormData with following parameters
 * id: 1
 * hero[id]: 1
 * hero[name]: NewHero
 * items[]: 1
 * items[]: 2
 *
 * @param params
 * @return FormData
 */
export const convertParamsToForm = (params: object): FormData => {
  const formData = new FormData();
  const appendParamsToForm = (variable, prefix = '') => {
    if (typeof variable !== 'object' || variable instanceof File) {
      formData.append(prefix, variable);
      return;
    }
    if (Array.isArray(variable)) {
      variable.forEach(value => appendParamsToForm(value, `${prefix}[]`));
      return;
    }
    Object.keys(variable).forEach(key => {
      appendParamsToForm(
        variable[key] || '',
        prefix ? `${prefix}[${key}]` : key
      );
    });
  };
  appendParamsToForm(params);
  return formData;
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ActiveRecordの`find_by`で大文字と小文字を区別しないで取得する方法

はじめに

find_byメソッドで値を取得する際にハマったので解決方法を探してみました。

Rails : 6.0
Ruby: 2.7
SQLite3

以上の環境しか確認していません。

検索対象と検索したい文字列を大文字に変換

保存されたタグを取得する際、大文字と小文字の違いで期待した値を取得することができない。

$ rails console 
 # 大文字と小文字を混ぜた状態で保存します
 > Tag.create(tag_name: "RuBy")
 >
 # 小文字で先程作成したタグ名を検索してみます
 > Tag.find_by(tag_name: "ruby")
 #=> nil

Tags テーブルに保存された値は"RuBy"と保存しているためfind_byメソッドで"ruby"と検索しても取得することができません。

ユーザーがタグを検索したい場合、"ruby""Ruby""RUBY"など様々な入力で検索する可能性があるため、大文字と小文字で区別されてしまうのは不便です。


大文字と小文字を区別せずfind_byメソッドで取得するためには、SQL のupper()関数を使いカラムに含まれる文字列を大文字に変換して、検索したい文字列も Ruby の upcaseメソッドで大文字に変換して取得します。

$ rails console
# 大文字と小文字を混ぜた状態で保存します
 > Tag.create(tag_name: "RuBy")
 >
 # 検索対象のカラムと検索したい文字列をそれぞれ大文字に変換
 > Tag.find_by('UPPER(tag_name) = ?', "ruby".upcase)
 #=> tag_name: "RuBy"

これで Tags テーブルの値を大文字と小文字を区別することなく取得することができました。

'UPPER()'と大文字にしているのは SQL 的な記述のためなので、特に意味はありません upper()としても動作します。

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

factory_botでモデルのenum別のtraitを一発で書く小ネタ

image.png

今回はRailsでテストデータを作成する”factroy_bot”に関する小ネタです。

image.png

TL DR;

こんな感じでイケちゃう

User.account_type.values.each do |type|
    trait :"#{type}" do
        account_type { type }
    end
end

動作環境

  • Rails: 5.2.3
  • Ruby: 2.6.5
  • factory_bot: 5.0.2
  • factory_bot_rails: 5.0.1

※今回はRubyの記法に依る部分が大きいので、上記バージョンはあまり気にしなくても良いです

コード例

想定するモデル

この記事では以下のようなUserモデルを例として考えます。プロダクトコードによくある「複数のユーザー種類をenumのカラム(今回は例としてaccount_typeとする)で持ち判別する」モデルです。

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  # 中略
  extend Enumerize
  enumerize :account_type, in: { normal: 0, admin: 1, client: 2, development: 3 }, scope: true
end

どうでもいい話ですが、色々な種類のユーザーが存在し、しかも同じユーザーが複数の種類を持つといった、ユーザー周りが複雑になるのはあるあるですが、本当にツラいですよね。AdminかEndUserかの2択ぐらいで収まれば、それぞれのユーザーから見える画面もきれいに分割できて丁度いいのですが...。

普通にfactoryを書く場合

さて、このようなenumカラムを持つUserモデルを素直にfacrotyで表現すると、このようになります。ああめんどくさい。モデルが複雑になれば、factoryが複雑になるのは当然のことですが...

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { 'test' }
    # 中略
    trait :normal do
      account_type { normal }
    end

    trait :admin do
      account_type { admin }
    end

    trait :client do
      account_type { client }
    end

    trait :development do
      account_type { development }
    end
  end
end

やりたいこと

こんな意味のないコードをチマチマ全部書くのはやりたくない。いやでも書くしか無いし、一旦Pushするか...。いや、なんかそれらしいカッコいいやり方があるはずだ。多分。ほんの数行で全部のenumのtraitを生み出すやり方が。ついでに、カラムのenum定義が増えたときにも勝手に増えるようにしてほしい。忘れそうだし。

小技

そこで小技を使うと、こんな感じで書けます。「まさか動かないだろう」と思い、冗談で書いたら動きました。Rubyってすごい。

spec/factories/user.rb
User.account_type.values.each do |type|
    trait :"#{type}" do
        account_type { type }
    end
end

User.account_type.valuesと、enumのカラムから直接traitを作っているので、enumの定義が増えたときにテストデータを作り忘れることもありません。ユーザーの氏名やアドレスを表すカラムがあれば、そこに同様にtype変数を突っ込めば、RSpecのテスタビリティも向上しそうです。

ちなみに、User.account_type.valuesの部分はこんな感じで動作しています。ハッシュでenumを取り出して、そのvalueをvaluesメソッドで配列化し、その配列の値でtraitを定義しているわけです。もちろん必要であれば、keyとvalueの両方を取り出して使うこともできます。

[1] pry(main)> User.account_type.values
=> ["normal", "admin", "client", "development"]
[2] pry(main)> User.account_type
=> #<Enumerize::Attribute:0x0000558ff649f058
 @i18n_scopes=["enumerize.user.account_type"],
 @klass=User (call 'User.connection' to establish a connection),
 @name=:account_type,
 @skip_validations_value=false,
 @value_hash=
  {"0"=>"normal",
   "1"=>"admin",
   "2"=>"client",
   "3"=>"development",
   "normal"=>"normal",
   "admin"=>"admin",
   "client"=>"client",
   "development"=>"development"},
 @values=["normal", "admin", "client", "development"]>

余談...

まあ、実際のプロダクトコードでは、各enumごとに紐付くアソシエーションも変更する必要があったりするので、なかなかこれ一つで完璧にtraitを表現することは難しいです。とはいえ、必要であれば切り出して書けばよいですし、アソシエーションが複雑になる前にスピーディーにテストデータを作りたい場合はぜひ。

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

Railsでbundle installができない

問題

bundle installをすると

Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
(略)
An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' succeeds before bundling.

と出る

指示通り
gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'
を走らせても解決しない

解決策

bundle configを実行すると

build.mysql2
Set for your local app (/Users/ユーザー名/アプリケーション名/.bundle/config): "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
と出る

そこで
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
を実行した

bundle configで確認すると
build.mysql2
Set for your local app (/Users/GO/source_code/clubru/.bundle/config): "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
となっていた

再度bundle installを実行すると無事mysqlをインストールできた

調べて出てきた結果と違ったこと

自分の場合は
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
で解決したが、調べて出てきた解決策は
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib --with-cppflags=-I/usr/local/opt/openssl@1.1/include"

bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
datta

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

【RSpec】重複のバリデーションエラーを回避するFactoryBotの書き方

ユーザー登録でemailを重複させないようにバリデーションをかけましが、テストのときにでFactoryBotで複数ユーザを予め生成する必要があったので方法を調べました。シーケンスを使ってユニークなデータを生成するやり方です。備忘録として残します。

問題点

ファクトリーで複数のユーザをセットアップする際に、emailをバリデーションでユニークなデータとして設定している場合、下記のようにするとテストコードが走る前に例外が発生します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    user_name { "tester" }
    email { "tester@example.com" }
    password { "password" }
  end
end
spec/models/user_spec.rb
  it "複数のユーザー登録" do
    user1 = FactoryBot.create(:user)
    user2 = FactoryBot.create(:user)
  end

これでテストを実行すると、当然ですが次のようなバリデーションエラーが発生します。

ターミナル
Failures:
  1) 複数のユーザー登録
     Failure/Error: user2 = FactoryBot.create(:user)

     ActiveRecord::RecordInvalid:
       バリデーションに失敗しました: メールアドレスはすでに存在します

解決策

シーケンスを使います。シーケンスはファクトリから新しいオブジェクトを作成するたびにカウンタの値を1づつ増やしながら値を設定します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    user_name { "tester" }
    sequence(:email) { |n| "tester#{n}@example.com" }
    password { "password" }
  end
end

email { "tester@example.com" }sequence(:email) { |n| "tester#{n}@example.com" }と書き換えることで、userが生成されるたびにが1づつ増えていき、tester1@example.comtester2@example.comのようにユニークで連続したemailが設定されます。

ターミナル
#見やすいように加工してあります
user1
<User id: 13, 
user_name: "tester", 
email: "tester1@example.com", 
password: "password", 
created_at: "2020-02-05 01:56:36", 
updated_at: "2020-02-05 01:56:36">
ターミナル
#見やすいように加工してあります
user2
<User id: 14, 
user_name: "tester", 
email: "tester2@example.com", 
password: "password",
created_at: "2020-02-05 01:57:21", 
updated_at: "2020-02-05 01:57:21">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】ActiveRecordで与えられた配列順にorderする

やりたいこと

ActiveRecordを与えられた配列順でorderしたい。
ids配列にユーザのidが格納されていて、このidの格納順に従って、Userモデルに対してorderしたい。

ids = [6,2,24,5,3,7]

問題

Rails5.1までは以下の書き方で配列ids順にuserをorderすることができました。

User.where(id: ids).order(['field(id, ?)', ids])

しかし、Rails5.2以降(Rails 6も)ではこのような書き方をすると
エラーが発生します。

DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "FIELD(id, ?)". Non-attribute arguments will be disallowed in Rails 6.1. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql().

Rails5.2以降は、SQLインジェクションを防ぐためにorderの引数に属性以外の文字列を取れなくなりました。今回でいうところのfieldが属性以外の文字列に当たってしまうと考えられます。

解決方法

Arel.sql()で囲めば良いので以下の書き方に変更します。

User.where(id: ids).order([Arel.sql('field(id, ?)'), ids])

また、機械的にArel.sql()で囲むのではなく、引数が安全な値であることを確認した上でArel.sql()を使いましょう。
https://qiita.com/jnchito/items/5f2f00c93c0ba68e4d31

ちなみに、この書き方どうするんだっけ?って思ってググっても出てこない時は、
Railsプロジェクトのテストコードを見ると解決することが多い。

今回の件に関してはRails6のテストコードで以下の書き方をされている。

https://github.com/rails/rails/blob/d684ca232d13abf518e184b1a8123f5438463074/activerecord/test/cases/relations_test.rb#L460-L469

  def test_finding_with_sanitized_order
    query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql
    assert_match(/field\(id, 1,3,2\)/, query)

    query = Tag.order([Arel.sql("field(id, ?)"), []]).to_sql
    assert_match(/field\(id, NULL\)/, query)

    query = Tag.order([Arel.sql("field(id, ?)"), nil]).to_sql
    assert_match(/field\(id, NULL\)/, query)
  end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

uninitialized constant ActionDispatch::Session::RedisStore (NameError)エラーを解消する

エラーの内容

Rails 5.2からはredisがビルドインされるようになりました。
それに伴いredis-rails gemが不要になったとのことでGemfileから削除しましたが、以下のエラーが発生しました。
https://railsguides.jp/5_2_release_notes.html#redis-cache-store

NameError: uninitialized constant ActionDispatch::Session::RedisStoreと言われているので、Redis関連の設定に関係していることがわかります。

NameError: uninitialized constant ActionDispatch::Session::RedisStore
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/configuration.rb:282:in `const_get'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/configuration.rb:282:in `session_store'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:61:in `block in 
/usr/local/bundle/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/stack.rb:72:in `initialize'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:15:in `new'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:15:in `build_stack'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:571:in `default_middleware_stack'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:510:in `block in app'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:508:in `synchronize'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:508:in `app'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/finisher.rb:97:in `block in <module:Finisher>'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `instance_exec'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `run'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:61:in `block in run_initializers'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:60:in `run_initializers'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:363:in `initialize!'
/app/config/environment.rb:5:in `<main>'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block _bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in ootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/usr/local/bundle/gems/zeitwerk-2.2.2/lib/zeitwerk/kernel.rb:23:in `require'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `require'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:339:in `require_environment!'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:515:in `block in run_tasks_blocks'
/usr/local/bundle/gems/rake-12.3.3/exe/rake:27:in `<top (required)>'
/usr/local/bundle/bin/bundle:23:in `load'
/usr/local/bundle/bin/bundle:23:in `<main>'
Tasks: TOP => db:migrate => db:load_config => environment
(See full trace by running task with --trace)
DEPRECATION WARNING: SourceAnnotationExtractor is deprecated! Use Rails::SourceAnnotationExtractor instead. (called from <main> at /app/config/application.rb:7)

解決方法

redis-railsgemのissueを読んでいると、どうやらSessionをredisで管理するにはredis-actionpackというgemが必要みたいです。

redis-railsのissue

there's no need to use redis-rails if you're using Rails 6, since it ships with its own cache store, :redis_cache_store. if you want to use the session management capabilities, you can just use gem 'redis-actionpack'. this gem was made to make it a bit easier to include redis-store into the app

さっそくredis-actionpackをbundle installします。

gem 'redis-actionpack'
$bundle install

これでエラーが解消できました。

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

uninitialized constant ActionDispatch::Session::RedisStore (NameError)

エラーの内容

Rails 5.2からはredisがビルドインされるようになりました。
それに伴いredis-rails gemが不要になったとのことでGemfileから削除しましたが、以下のエラーが発生しました。
https://railsguides.jp/5_2_release_notes.html#redis-cache-store

NameError: uninitialized constant ActionDispatch::Session::RedisStoreと言われているので、Redis関連の設定に関係していることがわかります。

NameError: uninitialized constant ActionDispatch::Session::RedisStore
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/configuration.rb:282:in `const_get'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/configuration.rb:282:in `session_store'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:61:in `block in 
/usr/local/bundle/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/stack.rb:72:in `initialize'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:15:in `new'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/default_middleware_stack.rb:15:in `build_stack'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:571:in `default_middleware_stack'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:510:in `block in app'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:508:in `synchronize'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/engine.rb:508:in `app'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application/finisher.rb:97:in `block in <module:Finisher>'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `instance_exec'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `run'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:61:in `block in run_initializers'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/initializable.rb:60:in `run_initializers'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:363:in `initialize!'
/app/config/environment.rb:5:in `<main>'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block _bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in ootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/usr/local/bundle/gems/zeitwerk-2.2.2/lib/zeitwerk/kernel.rb:23:in `require'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `require'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:339:in `require_environment!'
/usr/local/bundle/gems/railties-6.0.2.1/lib/rails/application.rb:515:in `block in run_tasks_blocks'
/usr/local/bundle/gems/rake-12.3.3/exe/rake:27:in `<top (required)>'
/usr/local/bundle/bin/bundle:23:in `load'
/usr/local/bundle/bin/bundle:23:in `<main>'
Tasks: TOP => db:migrate => db:load_config => environment
(See full trace by running task with --trace)
DEPRECATION WARNING: SourceAnnotationExtractor is deprecated! Use Rails::SourceAnnotationExtractor instead. (called from <main> at /app/config/application.rb:7)

解決方法

redis-railsgemのissueを読んでいると、どうやらSessionをredisで管理するにはredis-actionpackというgemが必要みたいです。

redis-railsのissue

there's no need to use redis-rails if you're using Rails 6, since it ships with its own cache store, :redis_cache_store. if you want to use the session management capabilities, you can just use gem 'redis-actionpack'. this gem was made to make it a bit easier to include redis-store into the app

さっそくredis-actionpackをbundle installします。

gem 'redis-actionpack'
$bundle install

これでエラーが解消できました。

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

Rails の Credentials を Webpacker で管理するフロント側に共有したい

賛否ありますが、Railsにベッタリの開発をしていると、なるべくRailsに寄せて考えたくなることがあります。

そんな

  • Webpacker 使ってるプロジェクト
  • 環境によって変わる値を環境変数ではなく Credentials にまとめたい。分散させたくない。
  • フロントエンドでも使いたい。たとえば Firebase の Project ID や API Key のようなDev,Stg,Prod環境ごとに違って、かつフロントエンドコードに含めて問題ないもの。

そんなときの話です。

  • webpack 4.41.5
  • webpack-dev-server 3.10.2
  • webpacker 4.2.2
  • Rails 6.0.1

Webpacker::Compiler.env は bin/webpack-dev-server を使うとうまくいかない

こんな Credentials があったとき

# bundle exec rails credentials:edit --environment=development
firebase:
  project_id: FIREBASE_PROJECT_ID
  api_key: FIREBASE_API_KEY
  auth_domain: FIREBASE_PROJECT_ID.firebaseapp.com

Rails から Webpacker に環境変数を渡すには Webpacker::Compiler.env が使えます。
Pass custom environment variables to the compiler #691

config/initializers/webpacker.rb
Webpacker::Compiler.env["FIREBASE_API_KEY"] = Rails.application.credentials.dig(:firebase, :api_key)
Webpacker::Compiler.env["FIREBASE_AUTH_DOMAIN"] = Rails.application.credentials.dig(:firebase, :auth_domain)
Webpacker::Compiler.env["FIREBASE_PROJECT_ID"] = Rails.application.credentials.dig(:firebase, :project_id)
app/javascript/src/firebase.js
import * as firebase from "firebase/app";
import "firebase/auth";

// "FIREBASE_PROJECT_ID: FIREBASE_PROJECT_ID"
console.log(`FIREBASE_PROJECT_ID: ${process.env.FIREBASE_PROJECT_ID}`);

const config = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  projectId: process.env.FIREBASE_PROJECT_ID
};

firebase.initializeApp(config);

export default firebase;

これは、Rails Server にアクセスした際にビルドする場合や、 assets:precompile でビルドする場合には動くのですが、 bin/webpack-dev-server では動作しません。

困ったことに bin/webpack-dev-server を使ったHMRの快適さに慣れると、いちいちF5なんてやってられません。

$ bin/webpack-dev-server
app/javascript/src/firebase.js
import * as firebase from "firebase/app";
import "firebase/auth";

// "FIREBASE_PROJECT_ID: undefined"
console.log(`FIREBASE_PROJECT_ID: ${process.env.FIREBASE_PROJECT_ID}`);

const config = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  projectId: process.env.FIREBASE_PROJECT_ID
};

firebase.initializeApp(config);

export default firebase;

Webpacker::Compiler.env はどう使われるか?

Webpacker::Compiler.compile

rails assets:precompile を実行したり、ビュー中で javascript_pack_tag ヘルパーを実行したりすると、 Webpacker::Compiler.compile が呼ばれます。( javascript_pack_tagwebpack-dev-server を実行していないときのみ。このへんのコードによる )

Webpacker::Compiler.compileWebpacker::Compiler.env の値を環境変数に設定した上で bin/webpack を実行します。

https://github.com/rails/webpacker/blob/6427cdbacd71412be781336277bf60b87957a8a8/lib/webpacker/compiler.rb#L68

bin/webpack-dev-server

bin/webpack-dev-server を実行したときには、 Webpacker::Compiler.compile は呼ばれません。
config/webpacker.yml から設定を読み出すなどした後、直接 node_modules/.bin/webpack-dev-server を実行します。このとき環境変数として Webpacker::Compiler.env を渡します。

https://github.com/rails/webpacker/blob/master/lib/webpacker/dev_server_runner.rb#L64

Webpacker と環境変数

通常、Webpack では、JavaScriptモジュール中の process.env.HOGE の値は未定義値となります。

Webpacker では標準で Webpack の EnvironmentPlugin を使って、これができるようになっています。
Webpacker しか知らないと参照できて当たり前のように感じてしまいますが、これは Webpacker が気を回してくれてたからなんですね。

https://github.com/rails/webpacker/blob/13756f1ea6b3393117878bb339b9dd507b801eb2/package/index.js#L21

https://github.com/rails/webpacker/blob/13756f1ea6b3393117878bb339b9dd507b801eb2/package/environments/base.js#L33

config/webpack/environment.js
const { environment } = require("@rails/webpacker"); // この environment

// こうすると EnvironmentPlugin があることがわかる
console.log(environment.plugins);

module.exports = environment;

この2つの仕組みにより Webpacker::Compiler.env に設定した値を JavaScript モジュール内で参照できるようになっています。

bin/webpack-dev-server ではなぜ Webpacker::Compiler.env が機能しないか

上記

bin/webpack-dev-server を実行したときには、 Webpacker::Compiler.compile は呼ばれません。
config/webpacker.yml から設定を読み出すなどした後、直接 node_modules/.bin/webpack-dev-server を実行します。このとき環境変数として Webpacker::Compiler.env を渡します。

の通り、 bin/webpack-dev-server でも Webpacker::Compiler.env は使っています。
しかしながら、 assets:precompilejavascript_pack_tag と違い、機能しません。

これは、 bin/webpack-dev-server では、Railsを起動しないため config/initializers/webpacker.rb に書いたコードは実行されないためです。

bin/webpack-dev-server でも Webpacker::Compiler.env を使う

A. config/initializers/webpacker.rb を実行する

bin/webpack-dev-server に次の2行を追加すると、Railsの初期化が走り、 initializers で Webpacker::Compiler.env を設定できます。
しかし「Rails.application.credentials にアクセスしたい」だけなのに、これはやり過ぎです。

require_relative '../config/application'
Rails.application.initialize!

B. config/initializers/webpacker.rbbin/webpack-dev-server でコードを共有する

Rails.application.credentials はRailsの初期化処理なしでもアクセスできます。

たとえば次のようにして共有できます。

config/webpacker_env.rb
Webpacker::Compiler.env["FIREBASE_API_KEY"] = Rails.application.credentials.dig(:firebase, :api_key)
Webpacker::Compiler.env["FIREBASE_AUTH_DOMAIN"] = Rails.application.credentials.dig(:firebase, :auth_domain)
Webpacker::Compiler.env["FIREBASE_PROJECT_ID"] = Rails.application.credentials.dig(:firebase, :project_id)
config/initializers/webpacker.rb
require_relative "../webpacker_env"
# 追加
require_relative '../config/application'
require_relative "../config/webpacker_env"

あとがき

がんばって Rails / Webpacker でなんとかするために Webpacker のコード読むぐらいなら Webpacker 捨てて Webpackの流儀でやれ、という気持ちになりましたが、一応動かすことはできました。

設定情報の二重管理をせず、手間をかけず、もっと自然に共有する方法はないものだろうか?

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