Railsアプリを本番デプロイしたらsecret_key_baseが未定義っておこられたので、どういう扱いになってるのか確認してみた。
確認したRailsのバージョンは6.0.0.rc1。

はじめにまとめ


secret_key_baseの設定場所は4箇所ある:
  1. 環境変数: ENV["SECRET_KEY_BASE"]
  2. credencials: config/credentials.yml.enc
  3. secrets: config/secrets.yml (Encrypted secretsが有効ならconfig/secrets.yml.enc
  4. tmp/development_secret.txt
環境ごとの評価順:
  1. developmentかtestなら、secrets、tmp/development_secret.txtの順
  2. それ以外の環境なら、環境変数、credencials、secretsの順

secret_key_baseの取得

 Rails.applicatin.secret_key_base
#=> "bb7a320962dda4262e21262f80229946f5a3b3c05cc8968d71d1ae9e40c64b5d7afb5340c196134089590b69d8a51a05ee801217b90eb859236bf4b14f8d5a76"

secret_key_baseの定義場所

Rails::Applicationにメソッドとして定義されている。

railties/lib/rails/application.rb
 module Rails
   # snip
 
   class Application < Engine
     # snip
 
     def secret_key_base
       if Rails.env.development? || Rails.env.test?
         secrets.secret_key_base ||= generate_development_secret
       else
         validate_secret_key_base(
           ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
         )
       end
     end
   end
 end
  • 環境がdevelopmentかtestの場合
    • secrets.secret_key_baseが未設定なら、generate_development_secretを実行し結果をsecrets.secret_key_baseに保存する
    • secrets.secret_key_baseを返す
  • それ以外の場合
    • ENV["SECRET_KEY_BASE"]credentials.secret_key_basesecrets.secret_key_baseの順番で評価してバリデーションした上で結果を返す

secrets.secret_key_baseとは

Rails::Application#secretsが定義されてる。

railties/lib/rails/application.rb
     def secrets
       @secrets ||= begin
         secrets = ActiveSupport::OrderedOptions.new
         files = config.paths["config/secrets"].existent
         files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
         secrets.merge! Rails::Secrets.parse(files, env: Rails.env)
 
         # Fallback to config.secret_key_base if secrets.secret_key_base isn't set
         secrets.secret_key_base ||= config.secret_key_base
 
         secrets
       end
     end
  • config/secrets.ymlを読み込む
  • Rails 5.1で導入されたEncrypted secretsが有効ならconfig/secrets.yml.encを読み込む
  • config/secrets.ymlにsecret_key_baseが未定義ならconfigから読み込む
config/secrets.ymlはRails 4.1で導入されたようなので、それ以前はconfig/application.rbとかに書いてたのかな?
https://guides.rubyonrails.org/4_1_release_notes.html#config-secrets-yml

generate_development_secretとは

railties/lib/rails/application.rb
       def generate_development_secret
         if secrets.secret_key_base.nil?
           key_file = Rails.root.join("tmp/development_secret.txt")
 
           if !File.exist?(key_file)
             random_key = SecureRandom.hex(64)
             FileUtils.mkdir_p(key_file.dirname)
             File.binwrite(key_file, random_key)
           end
 
           secrets.secret_key_base = File.binread(key_file)
         end
 
         secrets.secret_key_base
       end
 secret_key_baseが未設定ならtmp/development_secret.txtを自動生成してその内容を返すらしい。へー。

まとめ

  •  開発中はsecret_key_baseが自動生成されるので未設定で良さそう
  •  本番環境は環境変数、credencials、secretsなど適切な方法で設定しておく

コードを書いたときにシンタックスハイライトが効くようにしてみた。

enable syntax highlighting using highlight.js · okonomi/blg@e5835bc

highlight.jsを使った。
TrixエディタのUIだとHTMLの直接編集ができないので<code class="ruby">みたいに言語を指定したりできないけど、highlight.jsは勝手に言語を判別してなんとなくいい感じに色付けしてくれるので楽。

Ruby:
class User < ApplicationRecord
  before_create :set_remember_token

  private

    def set_remember_token
      self.remember_token = generate_remember_token
    end

    def generate_remember_token
      SecureRandom.hex(20)
    end
end
HTML:
<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
  </head>
  <body>
    <p>Hello</p>
  </body>
</html>
まあ無いよりはマシかな…?

Railsサーバとwebpack-dev-serverを別コンテナで起動して、WEBPACKER_DEV_SERVER_HOSTでRailsにwebpack-dev-serverの居場所を教えてあげる形にした。

version: "3.7"

services:
  web: &app_base
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - bundle:/app/.bundle
      - node_modules:/app/node_modules
      - home:/root
    environment:
      BUNDLE_PATH: /app/.bundle
      BUNDLE_JOBS: 4
      WEBPACKER_DEV_SERVER_HOST: webpacker
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
  webpacker:
    <<: *app_base
    ports:
      - "3035:3035"
    command: bin/webpack-dev-server
  db:
    image: postgres:11.2
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/data
    environment:
      PGDATA: /data

volumes:
  bundle:
  node_modules:
  pg_data:
  home:

meta-tagsを使ってOGP対応した。

https://github.com/kpumuk/meta-tags

Chrome拡張とFacebookやTwitterの開発ツールで動作確認した。
descriptionはとりあえずサイト名を設定してるので、きちんと本文のはじめの方だけ切り出して設定するとかを後でやる。

Dockerの勉強がてら、HerokuのデプロイをDocker化(という言葉であってるのか?)した。

参考: Building Docker Images with heroku.yml | Heroku Dev Center
作業内容はこちら: Heroku container deploy by okonomi · Pull Request #152 · okonomi/blg

まずstackをcontainerに変更する。
herokuコマンドで切り替え:

heroku stack:set container -a oknmjp
Review Appはapp.jsonの指定を変更:

{
   "stack": "container"
}
次に、アプリの定義ファイルheroku.ymlを作る:

build:
  docker:
    web: Dockerfile.production
  config:
    GITHUB_OAUTH_CLIENT_KEY: xxx
    GITHUB_OAUTH_CLIENT_SECRET: xxx
    SECRET_KEY_BASE: xxx
release:
  image: web
  command:
    - bin/rails db:prepare
run:
  web: bin/rails server -p $PORT
あとはDockerfile.productionをいい感じに用意する。

ハマったこと:
  • イメージビルド時にbin/rails assets:precompileを実行しているが、SECRET_KEY_BASEを設定する必要があった
  • Railsサーバの起動ポートは$PORTで指定しないといけない
解決してないこと:
  • ビルド時にSECRET_KEY_BASEとかの漏洩しちゃいけない値はどうDockerに渡せばいいのか
    • アプリのConfig Varsはビルド時には参照できない
    • いまはとりあえずダミー値を渡してるけどあんまり良くない気がする
まだDocker化した恩恵は感じてないけど…

シンプルなRailsアプリを作って、それをDockerで動かすためのDockerfileを書いてみた。
こんなかんじ:

FROM ruby:2.6.3-alpine3.9 AS builder

ENV RAILS_ENV production

WORKDIR /app

RUN apk add --update --no-cache \
    build-base \
    libxml2-dev \
    libxslt-dev \
    sqlite-dev \
    nodejs \
    yarn \
    tzdata

# install gems
RUN gem install bundler
COPY Gemfile .
COPY Gemfile.lock .
RUN bundle install --clean --frozen --jobs $(nproc) --without development test

# install npm packages
COPY package.json .
COPY yarn.lock .
RUN yarn install --frozen-lockfile

# compile assets
COPY Rakefile .
COPY bin bin
COPY .browserslistrc .
COPY postcss.config.js .
COPY babel.config.js .
COPY config config
COPY app/assets app/assets
COPY app/javascript app/javascript
RUN bin/rails assets:precompile


FROM ruby:2.6.3-alpine3.9

ENV RAILS_ENV production
ENV RAILS_LOG_TO_STDOUT 1
ENV RAILS_SERVE_STATIC_FILES 1

WORKDIR /app

RUN apk add --update --no-cache \
    sqlite-libs \
    tzdata

COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder /app/public/assets /app/public/assets
COPY --from=builder /app/public/packs /app/public/packs
COPY . .

RUN bin/rails db:schema:load

EXPOSE 3000
ENTRYPOINT ["bin/rails"]

あと.dockerignoreはこうした:

.bundle
.dockerignore
.git
.gitignore
Dockerfile
log
node_modules
public/assets
public/packs
README.md
storage
test
tmp

  • マルチステージビルドを使って、ビルドステージではgemのインストールとassets:precompileを行う。
  • 次のステージでRailsサーバを動かすためだけの最小限のファイルをコピーする。
これでイメージサイズが160Mくらい。もう少し小さくできそうな気もする。
あとDBとか他のサーバと合わせて動かすとどうなるかちょっと分かってない。

2019-06-02 17:05

ブログ開発日記

投稿ページの<title>タグに個別のタイトルを表示するようにした。

ActionTextのリッチテキストエディタは単純な<textarea>ではなく、JSでテキスト入力をトラップして処理してる(らしい)ので、capybaraでフォームの入力をしようとしても普通のやり方だとうまくいかなかった。

どうしたらいいのかなと思ってたけど、ちょうどいいPRが出ていた。
page.execute_scriptでJSを実行して文字列をリッチテキストエディタに入力させるらしい。なるほど。
同じようにやったらテストが上手く動くようになった。

コードが汚くなってきたのでrubocopを導入したくて、調整するのが面倒なのでonkcopを入れてみた。よく調整されてるし、.rubocop.ymlに設定理由がコメントされてて勉強になる。

そのままの設定だとbin/以下のファイルで引っかかるけど自分で作ったファイルじゃないので除外設定した。
普通に設定するとonkcopで指定されてる除外設定を上書きしてしまうので、追加されるようにinherit_modeを指定する。

inherit_mode:
  merge:
    - Exclude

AllCops:
  Exclude:
    - "bin/*"
参考:

ActionTextが内部で使ってるTrixは自動的にツールバーを出してくれるけど、<code>タグの機能がなかったので付けた。
Trix.config.textAttributesに要素を追加するのと、Trix.config.toolbar.getDefaultHTMLを置き換えたらできた。
ツールバーにボタンを追加するのは現状デフォルトのHTMLを全部置き換えるしかなくて、単純にボタンを追加するというAPIがない。
Issueでリクエストが出ていたので+1しておいた。そのうち対応されたらいいなと思う。

2019-05-11 23:18

ブログ開発日記

長文を書くときに一旦保存したいなと思ったので、下書き機能をつけた。

@higakiさんのRuby初級者向けレッスン。Rubyの例外について基本と応用の説明。
rescueelseを指定できるとかensurereturnしちゃいけないとか知らなかった。

@znzさんのRubyの開発リポジトリがSubversionからGitに移行した話。
Rubyの開発はバージョン管理なし > CVS > Subversion > Git と移行していったそうで、バージョン管理の歴史だなぁと思った。

@kozo2さんのrubydownの話。
Notebookって何かなと思ったけど、ドキュメントの中にコード片とその実行結果を埋め込むためのツールがNotebookで、それの代表格がJupyter Notebookということかな。
Notebookの利用例としてアプリのログ集計とかの日次レポートを作ってメールで送信するというのがなるほどと思った。
rubydownはgemなので既存のRailsアプリに組み込んだりとかもやりやすそう。

@ogomrさんのGraphQLの発表はライブコーディングだったけどめっちゃスムーズでわかりやすかった。
モデルにドメインロジックを書かないという話があったけど、どこに書くのかっていうのがちょっとわからなかった。
GraphQL(というかgraphql gem?)を使うとコード量が増えるように思うけど、それはそれぞれのクラスに責務が分離されるから、とのこと。

序盤、プロジェクターが準備できていないというハプニングがあったけど、なんかそれも結果オーライでゆったりした雰囲気でリラックスして参加できた。次回も参加したい。

2019-05-11 19:27

ブログ開発日記

同じ日に複数投稿したら時系列がよくわからなくなるので投稿時間も表示するようにした。
<input>type="datetime-local"があるの知らなかった。
あとタイムゾーンの設定でちょっとハマってしまった。
config.time_zoneを設定しておけばよさそうと理解した。
念の為TZ環境変数も設定しておいた。

投稿にタグを付けられるようにした。タグの選択はオシャレなUIにしたかったけど手間がかかりそうだったのでとりあえず<select multiple>にした。

BLOG_TITLE環境変数で設定できるようにした。

設定ファイルとかDBで設定するのはなんか手間のわりにメリットが少ないかなあと思って環境変数にしたけど、viewファイルで環境変数を直接参照しててなんかかっこ悪い気がするけどどうなんだろう。他のプロジェクトはどうやってるんだろうか。

id:sue445さんの記事を参考にDependabotの設定ファイルを置いてみた。
こんな内容にした:

# c.f. https://dependabot.com/docs/config-file/
version: 1

update_configs:
  - package_manager: "ruby:bundler"
    directory: "/"
    update_schedule: "daily"
    allowed_updates:
      - match:
          update_type: "all"
    automerged_updates:
      - match:
          dependency_type: "development"
          update_type: "all"
      - match:
          dependency_type: "production"
          update_type: "semver:patch"
    version_requirement_updates: "off"
  - package_manager: "javascript"
    directory: "/"
    update_schedule: "daily"
    allowed_updates:
      - match:
          update_type: "all"
    automerged_updates:
      - match:
          dependency_type: "development"
          update_type: "all"
      - match:
          dependency_type: "production"
          update_type: "semver:patch"
    version_requirement_updates: "off"

READMEに"Deploy to Heroku"ボタンを付けてみた。簡単にデプロイできるようになって便利。

2019-05-03 00:00

ブログ開発日記

投稿のURLにIDが見えてるとちょっとかっこ悪いというか、ソートがID順ではないのにID順なのを期待してしまいそうでややこしいのでランダム値にした。

DBマイグレーションでカラム追加したときに既存レコードにデータをどう設定するかという問題でちょっと悩んだが、面倒になってデプロイ後にrails consoleで対応した。
migration 時にデータを操作するのは悪手か? - Qiita

2019-05-02 00:00

ブログ開発日記

このサイトをGatsby on NetlifyからRails on Herokuに移行した。

Gatsbyで手っ取り早く作ったけど、Gatsbyに慣れてないせいでかゆいところに手が届かなかった。
じゃあもう自分で作ったほうが手っ取り早いなと思ってRailsで作り直した。

Rails 6.0.0.rc1で作ってる。いまのところ特に不具合に遭遇せず普通に開発できてる。

あとAtomフィードをつけた。AtomFeedHelperで簡単にできた。
https://api.rubyonrails.org/classes/ActionView/Helpers/AtomFeedHelper.html

前から使ってみたかったbulmaを使ってみた。
https://bulma.io/

bulmaはクラス名が叙述的になっていてわかりやすい。ちょっとRSpecに近い書き味。

あとは投稿日をつけたり、ActionTextを導入したり。

ActionTextを入れたあとsystem specが一部失敗するようになってしまった。ちょっと調べたけどわからなかったので、のちほど調査することにしていまは無視することにした。

先日reviewdog organizationのメンバーになった。

Twitterを見てたらreviewdogが開発を手伝ってくれる人を募集しているとのことで、以前プルリクを投げたこともあるし、なにか手伝えることがあるかなと思って手を上げた。

Looking for maintainers · Issue \#196 · reviewdog/reviewdog

あわせてreviewdogはorganizationに移管された。今後はチームで開発していく体制になるのかな?

reviewdog

自分はGoはほとんど書けないので、いちユーザーとしての意見をあげたりドキュメントの修正とかをやっていこうかなと思っている。

Railsアプリを作るときにいつもやってることをメモしておく。

まずディレクトリ作ってそこに入って:
$ mkdir awesome-rails-app
$ cd awesome-rails-app
Gemfile作って:
$ bundle init
RailsをGemfileに追加:
$ bundle add rails --skip-install
Railsをvendor/bundle以下にインストール:
$ bundle install --path=vendor/bundle -j4
vendor/bundleにインストールしたRailsを使ってrails new実行(-fオプションでファイル上書き):
$ bundle exec rails new . -f
/vendor/bundleをGit管理から除外:
echo /vendor/bundle >> .gitignore

おわり。

Railsをグローバルにインストールしたくないのでこんなことしてる。Railsの依存でいろんなgemが入るのが嫌なので…。