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

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

Rails whenever指定方法あれこれ

Wheneverとは

Rails上でcronによる定期実行バッチの処理時間を管理するためのライブラリ。crontabに記述する内容をruby言語で書ける

セットアップ

$ gem install whenever
$ bundle exec wheneverize .

コマンド

用途 コマンド
Cronを書き換えずに更新内容を確認する $ bundle exec whenever
Crontabを更新する $ bundle exec whenever --update-crontab
環境指定 $ bundle exec whenever --set environment='development'

設定ファイル(schedule.rb)

実行タイミング 記述 crontab
毎時10分 every 1.hours, at: 10 10 * * * *
毎日午前9時0分 every 1.days, at: '9:00 am' 0 9 * * *
毎週月曜日 午前9時5分 every :monday, at: '9:05 am' 5 9 * * 1

独自のjob_typeを作る

job_type とはcronが実行されるタイミングで呼ばれるコマンドのこと 通常は事前に定義されている job_typeを実行するが、自分で定義することもできる。

例えば隔週にしたいならこんな感じ。

job_type :biweekly_runner, '$(test $(expr $(date "+%W") % 2 ) -eq 0) && cd :path && bin/rails runner -e :environment ":task" :output'

のはずだったのだけど、 がwhenever内でエスケープされてしまって実行エラーになる。。隔週ってどうやるんだろう。

参考

github.com stackoverflow.com

ubuntuへのmysql2インストール時にエラーになる

課題

Railsの 5.2.4がリリースされたのでupdateしようとしたところ、mysql2のアップデートでエラーになった。

/usr/bin/ld: cannot find -lssl
/usr/bin/ld: cannot find -lcrypto
collect2: error: ld returned 1 exit status
Makefile:260: recipe for target 'mysql2.so' failed
make: *** [mysql2.so] Error 1

sslとcryptoのライブラリが足りないらしい。どうやってmysql2の 0.5.2はどうやって入れたんだ。

対応

ぐぐった感じだと libssl-dev を入れてあげれば良さそう。

$ sudo apt install libssl-dev
$ gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
$ bundle update

一応これでインストール&アップデートできた。

参考

https://github.com/znort987/blockparser/issues/38

RSpecで _before_last_saveのテストが上手く動作しない

課題

値が変更されていた場合にのみ実行されるようなテストを書いたが、FactoryBotでcreateしたオブジェクトに _before_last_saveにFactoryBotでセットしていない値がセットされていて、調べるとDBのデフォルト値だった。

原因

FactoryBotでcreateしたオブジェクト(Railsで)は before_last_saveにmigrateファイルで定義したDBのデフォルト値を持つらしい。 そのため、変更が発生していなくても before_last_saveに値が入っているため、_before_last_saveに値がセットされているかどうか を条件としたテストで期待しない動作をすることがある。

対応

デフォルト値をnil にするか、リロードして古い値をクリアすることでテストを通過するようになった。微妙だが仕方ないかな。

xxx = FactoryBot(:hoge)
xxx.reload

Nginx CORS対応で複数ドメインを許可する

課題

SPAサービスを作るにあたり、APIとWebでドメインが異なるのでCORS対応をしたい。 かつ検品環境なのでlocalホストからのアクセスも許可したい。

環境

  • nginx version: nginx/1.14.0 (Ubuntu)

対応

Access-Control-Allow-Origin に同時に複数のドメインを記述するとエラーになる。そのため、送信元のドメインに応じた 処理の記述が必要になる。

ネット上にCORS対応の記述はたくさん転がっているものの、if文を使ったものだとどうにも動作しなかった。 最終的にうまくいったのはこちら。

map $http_origin $cors_header {
    default "";
    "~^https?://(localhost:3000|hoge\.fuga\.jp)" "$http_origin";
}
server {
      location / {
          add_header Access-Control-Allow-Origin $cors_header;
      }
}

CORS対応全体としては Access-Control-Allow-Origin 以外にも記述が必要。 こうやって皆がコードの断片しか書かないから自分のとこでは上手くいかないという人が多いのだろう

参考

www.jfabre.com

曜日付きで日付を表示する

課題

Rubyで現在の日付を曜日付きのJPフォーマット 2019年11月15日(金) で返したい。 どうするのがよいか。

結論

I18nにそのものが定義されてた。

I18n.l Date.today, format: :long
=> "2019年11月15日(金)"

フォーマットは strftime に従っているので ja.yml を編集して自分で定義することも可能

対応(I18nを使わない場合)

I18n使えば良いということが分かったので以下は試行錯誤の残骸。

直接ズバリ とか返してくれる方法はなさそうだったので参考サイトの情報を元に適当に見繕うことにする。 とりあえず日付は以下で取れるので、

> Date.today.strftime '%Y年%-m月%-d日'
=> "2019年11月15日"

あとは曜日について考える。 曜日の日本語表記は自前で配列を持つ

> DAT_OF_WEEK = ["日", "月", "火", "水", "木", "金", "土"]

この配列の位置を取得するために 'Time.strftime'の'%u'と'%w'フォーマット、または Time.wday メソッドが利用できるがwdayが目的にかなってもっともシンプルだろう。

> DAT_OF_WEEK[Date.today.wday]
=> "金"

日付とくっつけておしまい。

Date.today.strftime("%Y年%-m月%-d日(#{DAT_OF_WEEK[Date.today.wday]})")
=> "2019年11月15日(金)"

参考

docs.ruby-lang.org qiita.com www.sejuku.net

バリデーションを項目ごとに1つにしたい

課題

ある項目に対して数値チェックと存在チェックを行いたい。数値チェックがNGな時点で存在チェックを満たさないのは自明なので これをスキップしたい。どうするのが良いか。

対応

バリデーションがNGだった項目は errorsにaddされるので、その存在チェックをする

validats :code, length: { is: 3 }
validats :code, numericality:true  unless errors.include?(:code)?

ここで errors[:code].blank? でも同じようなことができるのだが、errors内に:codeが作られてしまっていた。
active_model/errors.rb には include? メソッドが用意されているのでそちらを使うのが良さそう。

参考

teratail.com

異なるカラム名のモデルに対するバリデーションをConcernで共通化したい

課題

バリデーションを共通化したい。Concernを使いたいがモデルごとに違う名前を割り当ててしまった。 カスタムバリデータは大げさなので使いたくない。どうすればよいか。

対応

ActiveRecordhas_attribute を使うと項目の有無をチェックしてくれるのでそれを使う。

module EmailValidator
  extend ActiveSupport::Concern
  EMAIL_FORMAT = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  included do
    if has_attribute?(:user_mail)
      m = :contact_mail
    elsif has_attribute?(:company_mail)
      m = :mail
    end
    validates m, format: { with: EMAIL_FORMAT,
                           message: I18n.t('activerecord.errors.messages.invalid') },
              presence: false
  end
end

とりあえずできそうだけど書いてて全然イケてる感じがしないので使わないかも。

参考

https://qiita.com/hihats/items/f82b6fd99b18bf9569fd

/* Responsive: yes */