Rails6.0.3.2 has been released!

Rails 6.0.3.2 has been released! | Riding Rails

security updateがreleaseされてました。
珍しい脆弱性だったのでご紹介。
急いであげる必要はないような脆弱性ですが、対応したい人は上げておきましょう。
 

内容

ここに書かれてます。
[CVE-2020-8185] Untrusted users able to run pending migrations in production

Using this issue, an attacker would be able to execute any migrations that
are pending for a Rails app running in production mode. It is important to
note that an attacker is limited to running migrations the application
developer has already defined in their application and ones that have not
already ran.

Pending Migrationが実行できるというなんとも変わった脆弱性でした。 というよりも、例外の詳細を表示させることができ、PendingMigrationがある場合は実行できるというもののようでした。

patchをみても、脆弱性の全容が全く分からなかったのですが、
ググってみると、ActionableExceptions に関するもののようで エラー画面からmigrateできる機能に関するもののようです。
こんなんあったんですね。(必要性は感じないけど)

以下に画像付きで紹介されてました。
rails commit log流し読み(2019/04/19) - なるようになるブログ

shellscriptでDBに大量データを登録する

ストアドプロシージャ使えない(面倒)、Rails Console利用できない場合にBulk Insertする例。すぐ忘れるのでメモ。

#!/bin/bash
set -eu

DB_USER="root"
DB_PASS="pass"
DB_NAME="db"
DB_HOST="host"

INSERT_NUM=100000
BREAK_NUM=1000

function execute_sql () {
  local sql=$1
  mysql -u "${DB_USER}" -p"${DB_PASS}" -h "${DB_HOST}" "${DB_NAME}" -e "${sql}"
}

function main () {
  local insert_statement="INSERT INTO some_table (some_column)"
  insert_statement+=" VALUES"

  local bulk_count=0
  for((i=0;i<INSERT_NUM;i++)); do
    if [ $bulk_count -eq 0 ]; then
      sql="${insert_statement}"
    else
      sql+=", "
    fi

    # values
    sql+="('some_value')"
    bulk_count=$(expr $bulk_count + 1)

    if [ $bulk_count -eq $BREAK_NUM ]; then
      execute_sql "${sql}"
      bulk_count=0
    fi
  done

  # 端数のinsert
  if [ $bulk_count -ne 0 ]; then
      execute_sql "${sql}"
      bulk_count=0
  fi
}

main

久しぶりにshell書くといろいろ辛く感じる。。 rubyで書けばよかった。。

Everyday Rails - RSpecによるRailsテスト入門 chapter9

最近、積読状態だった以下を読んでいます。
RSpec学ぶにはなかなか良い本だと思います。
9章の内容が良かったのでご紹介。
leanpub.com

9. 速くテストを書き、速いテストを書く

aggregate_failures

aggregate_failures を使えば、事前処理が同じようなitをまとめて書くことができます。

      it "responds successfully" do
        sign_in @user
        get :index
        aggregate_failures do
          expect(response).to be_success
          expect(response).to have_http_status "200"
        end
      end

aggregate_failures がない状態だと、どこで落ちたかの判別ができないため、従来は it を分けて書くことが推奨されてきました。
しかし、aggregate_failuresを使えば、途中でエラーとなっても、最後までexpectを実行しどのようなエラーがどこで発生したかが分かりやすくなります。

エラー例)

Failures:

  1) ProjectsController#index as an authenticated user responds successfully
     Got 2 failures from failure aggregation block.
     # ./spec/controllers/projects_controller_spec.rb:13:in `block (4 levels) in <top (required)>'

     1.1) Failure/Error: expect(response).to_not be_success
            expected `#<ActionDispatch::TestResponse:0x00007fdf6580fce0 @mon_mutex=#<Thread::Mutex:0x00007fdf6580fc68>, @mo...ders:0x00007fdf65829ca8 @req=#<ActionController::TestRequest:0x00007fdf6580fe98 ...>>, @variant=[]>>.success?` to return false, got true
          # ./spec/controllers/projects_controller_spec.rb:14:in `block (5 levels) in <top (required)>'

     1.2) Failure/Error: expect(response).to have_http_status "201"
            expected the response to have status code 201 but it was 200
          # ./spec/controllers/projects_controller_spec.rb:15:in `block (5 levels) in <top (required)>'

こりゃ使わない手はないでしょう。

doubleとinstance_double

doubleとinstance_doubleの違いについて記載があり、わかりやすい例があったのでご紹介。  
1対多の関係のこう言うモデルがあったとします。 f:id:rochefort:20200531182812p:plain

class User < ApplicationRecord
  has_many :notes

  def name
    [first_name, last_name].join(" ")
  end
end


class Note < ApplicationRecord
  belongs_to :user

  delegate :name, to: :user, prefix: true
end

Noteにuserモデルにdelegateさせるメソッドを生やしていて、これをテストする場合。 通常は、FactoryBotでデータ作成してテストします。

  it "delegates name to the user who created it" do
    user = FactoryBot.create(:user, first_name: "Fake", last_name: "User")
    note = Note.new(user: user)
    expect(note.user_name).to eq "Fake User"
  end

doubleを使うことで、DBへのアクセスを無くしてテストすることができます。

  it "delegates name to the user who created it" do
    user = double("user", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

一見良さそうに見えますが、例えば、User#name の名称が full_nameなどに変わったとしても テストは成功してしまいます。そこで登場するのが、 instance_double

  it "delegates name to the user who created it" do
    user = instance_double("User", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

これを使うと、Userモデルに定義されているかどうかまで検証してくれます。(第一引数が大文字になっています。)

See Also

aggregate_failuresやinstance_doubleについても自分のブログで少し言及していたことを忘れていました。。
RSpecのベストプラクティスとRSpec3の新機能 - rochefort's blog http://rochefort.hatenablog.com/entry/2016/02/16/080000