Railsでこの時だけ特定のバリデーションを検証したくないときしたこと

  • 今回はRails v5.0.2を使用しています

Railsでmodelにバリデーションをつけるがこの時だけはoffにしたいという時がある。
例えば特定のバッチ走らせる時とか?
例えば今回は下記のようなバリデーションを定義していたとして

class Item < ApplicationRecord
  # nameがユニークかつ文字数が1から10文字内におさまっているか
  validates :name, uniqueness: true, length: { in: 1..10 }
end

この Item モデルのバリデーションをすべてOFFにするなら

item = Item.new(name: 'hello!')
item.save!(validate: false)

これでいける

ただ特定の条件だけ外したい場合はどうだろう? 今回の例でいうとユニークは保ちたいから文字数のチェックだけを外したい場合
今回はこのようにモデルを変更した

class Item < ApplicationRecord
  validates :name, uniqueness: true
  validates :name, length: { in: 1..10 }, unless: -> { validation_context == :hoge }
end

このように定義しなおして rails console などで試す

irb> Item.new(name: 'a' * 100).save!(context: :hoge)
true
irb> Item.new(name: 'a' * 100).save!(context: :hoge)
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken

一回目は文字数制限を外した状態でレコードは保存され、もう一度同じレコードを生成しようとするとユニークでないのでエラーになる!
意図した動きをしてくれるようになった

何が置きたのか?

railsのソースを追ってみた

save!(context: :hoge) とcallすると下記が呼ばれる

    # https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L50
    def save!(options={})
      perform_validations(options) ? super : raise_validation_error
    end

perform_validations(options) とやらがfalseをかえすとraiseでvalidation errorが出る
perform_validations(options)はすぐ下に定義されていて

    # https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L82
    def perform_validations(options={}) # :nodoc:
      options[:validate] == false || valid?(options[:context])
    end

なるほど! save!(validate: false) とした時はここで trueになるのでvalidationを評価せずに保存できるのか
今回は save!(context: :hoge) なので valid?:hoge がわたる
このvalid?は同じクラス内に定義されている

   # https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L63
   def valid?(context = nil)
      context ||= default_validation_context
      output = super(context)
      errors.empty? && output
    end

ここはnilガードとかしてcontextに値をつめたりしてるだけで実際の処理は superで親の ActiveModelvalid? が呼び出されている

   # https://github.com/rails/rails/blob/v5.0.2/activemodel/lib/active_model/validations.rb#L335
   def valid?(context = nil)
      current_context, self.validation_context = validation_context, context
      errors.clear
      run_validations!
    ensure
      self.validation_context = current_context
    end

ここで self.validation_context に引数でわたってきたcontextをつめているからmodelで validation_context が参照できたようです

Railsのログになんで色ついてるかなんて気にしたことなかった

会社でも個人でも使っているRails

macで開発をしていて log/development.log をtailしてみると綺麗に色がついたログが流れてくる

f:id:hatappi1225:20170412191932p:plain

ただこれを本番とかで使用しているLinux上で less log/development.log で見たら文字化けっぽくなってた

f:id:hatappi1225:20170412192123p:plain

解決策としては

# config/environments/production..rb
config.colorize_logging = false

のようにしてあげれば、色がつかなくなり見られるようになるが、そもそもなんでこうなるんだっけってのが分からなかったので調べた

この色がつくのは ANSIカラーシーケンスと呼ばれるもので、エスケープシーケンスという通常の文字列で表すことの出来ない文字などを特定の文字列で実現しており
今回の色とかになると

$ print "\e[34mBLUE\e[0m"

こんな感じで記載すると青色でBLUEがコンソールに出て来る 上記の文字列でいう \e[34m というのがこれ以降を青色で出してねという命令で \e[0m というのがこれ以降をリセットする命令とのこと

logrotate 備忘録

logrotate周りの設定

/work/hoge/log/*.log {
    daily
    rotate 7
    compress
}

なんかこんな感じ設定する時にあれ?これ何意味するんだけって?毎回調べてる気がするのでメモ

設定一覧

http://www.linuxcommand.org/man_pages/logrotate8.html
上記にかいてあるがよく使うであろうものをかく

daily

毎日ログローテーションする。毎週は weekly, 毎月は monthly

missingok

対象先のファイルがなくてもエラーをはかせない
逆は nomissingok

ifempty

ログファイル自体が空でもローテーションする
逆は notifempty

compress / nocompress

ローテーションされたログをgzipで圧縮するかどうか
compress は圧縮するよで nocompress は圧縮しないよ 直感的!

rotate

何世代のこすかみたいな設定
exp: rotate 5

dateext

ローテートされたファイルは hoge.log.1 みたいに保存される
これを指定することでこれを hoge.log-YYYYMMDD の形式で保存してくれる

copytruncate

logrotateのローテートさせる時の動きとしては元あるファイルをリネームして元あったファイルを生成する。
ただRailsみたいにlogをにぎったままのものは、この動きだとリネームされた方を参照してしまう

Railsを再起動させる手もあるが、 copytruncate をすると元あるファイルをコピーして元あるファイルの中身を空にします

size

ログが指定されたサイズ以上であればローテーションする
exp: size 100k

設定例

/work/log/hoge.log というログを毎日(daily), development.log-[YYYYMMDD](dateext) というフォーマットで 5世代(rotate 5)圧縮して(compress)残す
ログ自体はコピーして元あったファイルを空にする(copytruncate)
ただしファイルがない時もあるのでエラーは出さないようにしよう(missingok)

/work/log/development.log {
    daily
    dateext
    rotate 5
    missingok
    compress
    copytruncate
}

Dockerイメージを置く場所としてContainer Registryを使う

DockerのレジストリとしてはDockerHubが使われるがプライベートが良いよねーでAWSユーザーの人はECRを使うことが多いのかなと思うが GCPだってある。それがContainer Registry(GCR)

個人で開発しているものをGCP上にのせているのでGCRを使っている

この記事のゴール

FROM: Container RegistryレジストリのURL!

作って

$ docker build -t gcr_sample .

したらlocalのimage listに追加される

レジストリへのPUSH

前提として gcloud auth login はすんでいる状態とします

公式: Container Registry への push

まずlocalにbuildされたイメージがあるとします。 今回は下記のようなものをサンプルとして使用する

FROM busybox
CMD echo "GCP!!!"
$ docker build -t asia.gcr.io/[project id]/echo-gcp .

タグの名前は gcr.io/[project_id]/[image-name] にする必要がある。 gcr.ioに関してはus.gcr.io ,eu.gcr.io, asia.gcr.ioなどがあり、今回は一番近い asia.gcr.io を使用しています

後は gcloud コマンドにpush用のコマンドがついているのでそれを使う

$ gcloud docker -- push asia.gcr.io/[projrect_id]/echo-gcp
The push refers to a repository [asia.gcr.io/[projrect_id]/echo-gcp]
c0de73ac1111: Layer already exists
latest: digest: sha256:111111111111111112222222222 size: 527

確認

GCP上からContainer Registryを見ると先ほどのimage nameが表示される

f:id:hatappi1225:20170411005648p:plain

ちなみにイメージなどはGCSにバケットが自動で作られて保存されておりGCSの画面へいくと確認が出来る

PULL

これもgcloudで提供されているので

$ gcloud docker -- pull asia.gcr.io/[project_id]/echo-gcp:latest

でlocalのimageに追加される。 一旦localにない状態に戻すために docker rmi して

FROM: asia.gcr.io/[project_id]/echo-gcp:latest
CMD echo "GCP!! NEO!!"

を作って

$ docker build -t echo-gcr-neo .
Error: Status 403 trying to pull repository [project_id]/echo-gcp: "Unable to access the repository: [project_id]/echo-gcp; please verify that it exists and you have permission to access it (no valid credential was supplied)."

gcloudが頑張ってくれていた

pushやpullを gcloud docker とつけてましたが、これにより本来必要な認証周りをgcloud側で短時間のアクセストークンをわたして認証しているため、ユーザーはgcloudにログインしていれば無駄な手間なくいける。

ただこれだとDockerfileのFROMに記載するためには毎回localに一旦最新を落としてみたいなフローが必要になる。 今回はlocalになくても docker run したらリモートみにいってほしいから docker login する

$ docker login -e [メールアドレス] -u oauth2accesstoken -p "$(gcloud auth print-access-token)" https://asia.gcr.io

これでOK !! gcloud auth print-access-token によりアクセストークンが発行され、それを使って認証する 認証された情報は $HOME/.docker/config.json に入っている

これでもう一度

$ docker build -t echo-gcr-neo .
$ docker run echo-gcr-neo
GCP!! NEO!!

でた!!!