前人未踏の領域へ WEB・インフラ・プログラミング全般編

フロントエンド、バックエンド、インフラ、言語など、アプリ開発、IOT以外の記録

Rails: Cronジョブ実行時にExecJSでエラー

課題

RailsのJobを実行する処理をcrontabに記述したがExecJSでJavaScriptの実行環境が見つからないとエラーになる。

bundler: failed to load command: bin/rails (bin/rails)
ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes.
  /var/rails/milestone/shared/bundle/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect'
  /var/rails/milestone/shared/bundle/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
  /var/rails/milestone/shared/bundle/ruby/2.6.0/gems/execjs-2.7.0/lib/execjs.rb:4:in `<main>'

Nodeはインストール済みだし、ExecJSからも見えている。直接consoleからJobを実行することはできる。 なんなら別な環境では普通に動く。

原因

cronのpathに /usr/local/bin が通っていなかった。cronの初期状態のPATHは /usr/bin:/bin となっている(Ubuntuの場合)が、 Nodeを直接インストールすると /usr/local/bin/node が実行パスになる。

ちなみに、 apt経由で8.10系をインストールした場合はパッケージ名が nodejs でコマンドも node ではなくnodejsになり、 こちらの実行パスは /usr/bin/nodejs となるため、cronで発見される。 別な環境で動いたのはたまたまこちらがインストールされていたため。 ExecJSはこのどちらも探しにいくが、node が優先される。

対応

/usr/local/bin をPATHに連結させて解決。

# ~/.bash_profile
export PATH="$PATH:/usr/local/bin"

crontabに /bin/bash -lc くっつけてcronの実行者の.bash_profile を読み込ませる。

# crontab -e
0 9 0 0 0 /bin/bash -lc {command} > /var/log/cron.log 2>&1

この方法より個別にシェル書いた方が安全という記事も見つけたがそれはまた別の話ということで。

参考

GitHub チームアカウントのシート数を4以下にする

GitHubのチームアカウントは5アカウントからの購入となっており、実際に利用するユーザー数がそれに満たない場合でも 最低5ユーザー分の料金がかかる。

......と思っていたが違った。

SettingsBilling から GitHub TeamEditボタンを押下し、Remove Seats を押すと シート数を減らすことができた。

最近の値下げとも相まって月額がわずか$4に。 一人で使ってるならFreeプランでいいじゃんという気もするけど。Wikiとか使えなくなるし。

f:id:takeR:20200504210238p:plain

長いこと無駄に余分なお金払ってきてたな〜。

Rails destroyがdependent: :destroy から呼ばれたかどうかを知るには

課題

あるテーブルがすべて削除されたタイミングで親のテーブルを削除したい。 これを子テーブルのモデル側でやろうとすると、そのモデルの after_destroy で、belongs_to 関連を持つ親テーブルを 削除することになる。

これは普通に上手くいくが、楽観的排他を採用していた場合、親テーブルを直接 destory しようとした際にエラーとなる。 そこで親の dependent: :destroy によって destroy が呼ばれた場合には処理が実行されないようにしたい。どうすればよいか。

対応

destroyed_by_association を使うと削除されようとしている親のオブジェクトが返却されるので、それを利用する。

  after_destroy :destroy_parent

  private

  def destroy_parent
    # dependent: :destory 以外の場合のみ実行
    unless destroyed_by_association
       # 子が空になったら親を削除する
       self.hoge.destroy if self.hoge.children.empty?
    end
  end

参考

stackoverflow.com

apidock.com

Vue.js プロパティの変更をデータに反映する

課題

Vuexでstateを管理していて、stateの変更によって 親コンポーネントで変更した内容を子コンポーネント(state未使用)に 反映したいが変更されたプロパティがデータに反映されなかった。どうすればよいか。 ちなみに親から渡されるプロパティはオブジェクトである。

対応

汎用の子コンポーネントを使いたい場合、stateの値を使うのではなく親からプロパティ渡しで 値をもらいたい事もあるだろう。プロパティをコンポーネントで使う際にデータにセットすることができるが、 初期値として使われるだけである。

props: {
    value: {
      type: Object,
      required: false,
      default: () => ( { code: '', name: '' } )
    },
},
data() {
    return {
        searchText: value.name
    }
}

ここでvalue内のcodeやnameを更新してもstateの内容は変わるが dataは更新されず、表示が切り替わらなかった。 そこで、プロパティの値をwatchで監視してあげることにする。

props: {
    value: {
      type: Object,
      required: false,
      default: () => ( { code: '', name: '' } )
    },
},
data() {
    return {
        searchText: value.name
    }
}
watch: {
    value(v) {
        this.searchText = v.name
    }
}

こうすることでプロパティの変更をデータに伝えることができ、 データの変更を検知してビューが変更される。

Vue.js 強制的に再レンダリングする

課題

vuexのstateオブジェクトを更新したが画面に反映されない。
とりあえず現状のオブジェクトの内容で画面を更新したい。

注意

stateオブジェクトの更新が反映されなくなったら、まず最初にオブジェクトの構成が変わってしまっていることを 疑うべし。最初に生成したstateとAPIのレスポンスをそのまま突っ込んでいる場合、ネストしたオブジェクトのプロパティの数が 違っていたりすると変更を検知してくれない。してくれないどころか、 その構造のずれに気づかないでいると他の処理にまで悪影響が及ぶようになるので注意が必要。

対応

本来はちゃんと再描画されるような更新の仕方が必要なのだけど、
どうにも上手くいかない。工数的にも時間が足りない。というケースでの応急処置。

コードは参考サイトのものをほぼそのまま採用。

コンポーネントのルート要素にkeyを定義する

<template>
  <div :key="componentKey">
  </div>
</template>

再描画したいタイミングで keyを更新する

keyの値が更新された時点で小ビューの再描画が行われるので stateオブジェクトの状態が反映されることになる。

methods: {
    forceRerender() {
      this.componentKey += 1
    }
}

おまけ

完全に諦めて上記方法で実装して終わりにしようと思ったところ、
最後の最後についに問題を解決。助かった...。

参考

michaelnthiessen.com

ActiveModel::Serializer::Null with Hash

課題

gemを追加したところ、以下のようなエラーが出るようになった

Rendered ActiveModel::Serializer::Null with Hash

対応

何が原因でこの状態になるのか分からなかったが、どうやら追加したライブラリ内で使用している依存ライブラリの名前と こちらでConcernで使用しているモジュールとの名前の衝突が起きていたらしい。 仕方がないので名前空間を追加して回避。

Nginxの標準エラー画面をカスタマイズする

課題

Nginxにてベーシック認証でエラーになった場合のエラーページのHTMLを変更したい。どうすればよいか

対応

ベーシック認証でエラーになるとサーバーはエラーコード401を返す。なので401エラー時の処理を指定して あげればよい。

  • 401.html を用意する
  • 401.htmlに対するベーシック認証をoffにする

401.html を用意する

適当にエラーページを用意し、ドキュメントルートに配備。 error_page でエラーコードとファイルの対応付けを伝える

# nginx.conf
server {
    error_page 401 /401.html;
}

401.htmlに対するベーシック認証をoffにする

# nginx.conf
server {
     error_page 401 /401.html;

    location /401.html {
        auth_basic off;
    }
}

cssとかimgファイルを使いたい場合は必要に応じてlocationを追記する

location ~ ^/.*\.(css|svg)$ {
        auth_basic off;
}

あとはnginxを再起動すればOK

/* Responsive: yes */