Gitの書籍に出てこないようなGitの使い方を2つ紹介

Twitterの140文字だと説明しづらいので、ブログに書く。

ケース1: 過去のコミットを部分的に戻す

概要

a001から分岐してb001-003まで3つコミットをした後、b001のコミットを修正したいケース。

f:id:sinsoku:20200211234320p:plain

よくある方法

git rebase -i を使って、b001のコミットを edit にする。

詳細はググれば出てくるので省略。

便利: 相殺コミットをぶつける方法

b001の一部を修正したコミットb004を用意します。

f:id:sinsoku:20200211235128p:plain

git rebase -i a001 すると以下のような状態でエディタが開く。

pick a001 hello
pick b001 foo
pick b002 bar
pick b003 buz
pick b004 fix_foo

これの順番を入れ替えて、b004をfixupに変える。

pick a001 hello
pick b001 foo
fixup b004 fix_foo
pick b002 bar
pick b003 buz

保存してエディタを閉じると、b001のコミットにb004のコミットを混ぜる(一部を戻す)ことができる。

メリット

  • 途中でコンフリクトしたときにgit rebase --abort で戻れる
  • commit --fixup と組み合わせるとより便利
  • 修正コミットは git checkout <sha1> -- <path> などで作っても良い

過去のツイート

ケース2: 作業中のファイルを一時的に退避する

a001から分岐したfeatureブランチでb001のコミットをした後、別の作業を依頼されたケース。

f:id:sinsoku:20200212002030p:plain

よくある方法

別ブランチをa001から切って、作業を進める。

便利: 一時的にリバートする

とりあえずb001をリバート(b001')して、別の作業を済ませて c001 のコミットを作ります。

f:id:sinsoku:20200212002638p:plain

次にc001だけを持つブランチを作ります。

$ git switch -c fix_bug
$ git rebase -i a001
# エディタが開くので、c001 のコミットだけ残す

fix_bugのブランチでプルリクを作ったら、元のブランチに戻ってリバートコミットを取り除きます。

$ git switch feature_1
$ git rebaes -i a001
# エディタが開くので、b001'とc001のコミットを取り除く

最終的に以下のようなグラフになって、b001から作業を再開できる。

f:id:sinsoku:20200212002647p:plain

メリット

  • ブランチの切り替えが減る
  • DBのスキーマを変えずに済む
    • テストデータなども消えない
  • ライブラリのバージョンを変えずに済む
    • masterでgemが更新されていると bundle-install に時間がかかるけど、これを回避できる
  • コンフリクトするコミットだけをリバートするだけなので簡単
    • ブランチの全コミットをリバートする必要はない

過去のツイート

まとめ

ブログに書いてみたけど、やっぱり分かりづらい。

別のディレクトリにある Gemfile をsystemメソッドで "bundle check" する

タイトルの通りなのですが、普通にやると意図した動きしなかったのでメモ。

具体的な例

factory_bot が分かりやすいです。

具体的なコマンドは以下の通り。

$ git clone --depth 1 https://github.com/thoughtbot/factory_bot.git
$ cd factory_bot
$ bundle install
$ bundle exec ruby -e 'system({ "BUNDLE_GEMFILE" => "6.0.gemfile" }, "bundle check", chdir: "gemfiles/")'
Could not find zeitwerk-2.1.9 in any of the sources
Run `bundle install` to install missing gems.

動くコマンド

bundler経由で実行すると RUBYOPT="-r/usr/local/lib/ruby/2.7.0/bundler/setup" がセットされるので、これを上書きすると動く。

$ bundle exec ruby -e 'system({ "BUNDLE_GEMFILE" => "6.0.gemfile", "RUBYOPT" => ""}, "bundle check", chdir: "gemfiles/")'
The following gems are missing
 * zeitwerk (2.1.9)
 * childprocess (2.0.0)
 * aruba (0.14.11)
 * simplecov (0.17.0)
Install missing gems with `bundle install`

bundle install する前なので、正しく 6.0.gemfile を参照してる挙動になった。

追記

Bundler.with_clean_system を使う方法を Twitter で教えてもらった。

こっちの方がシンプルで、良いですね!

PostgreSQL 12でpg_hint_planをインストールする方法

brew install postgresql でインストールした postgres で pg_hint を使えるようにする方法が分からなかったのでメモ。

URL一覧

インストール方法

OSDN の v1.3.4 の rpm に含まれているのは Postgres 11 用らしく、 Postgres 12 で動くか不安だったので使うのは避けた。

そして、 v1.3.4 のソースも Postgres 11 用なのか、ビルドはエラーになった。

$ make
clang -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -Wno-unused-command-line-argument -O2  -I. -I./ -I/usr/local/include/postgresql/server -I/usr/local/include/postgresql/internal -I/usr/local/Cellar/icu4c/64.2/include -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/readline/include -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libxml2   -c -o pg_hint_plan.o pg_hint_plan.c
pg_hint_plan.c:20:10: fatal error: 'nodes/relation.h' file not found
#include "nodes/relation.h"
         ^~~~~~~~~~~~~~~~~~
1 error generated.
make: *** [pg_hint_plan.o] Error 1

GitHubのmasterブランチ

GitHub からソースを取得し、 22a770c で make すると、今度は別のエラーが出た。

$ make
clang -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -Wno-unused-command-line-argument -O2  pg_hint_plan.o -L/usr/local/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/readline/lib  -Wl,-dead_strip_dylibs  -Wl,--build-id  -bundle -bundle_loader /usr/local/Cellar/postgresql/12.1/bin/postgres -o pg_hint_plan.so
ld: unknown option: --build-id
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [pg_hint_plan.so] Error 1

よく分からないけど --build-id のオプションがダメらしいので、消してみたら make と make install が実行できた。

$ git diff
diff --git a/Makefile b/Makefile
index de9f5c5..bde9cdc 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ TARSOURCES = Makefile *.c  *.h COPYRIGHT* \
        doc/* expected/*.out sql/*.sql sql/maskout.sh \
        data/data.csv input/*.source output/*.source SPECS/*.spec

-LDFLAGS+=-Wl,--build-id
+LDFLAGS+=-Wl

 installcheck: $(REGRESSION_EXPECTED)

これで pg_hint_plan は使えるようになったけど、これが正式なやり方かは謎。

銀座Railsで「Railsアプリの設計」というタイトルで発表しました #ginzarails

12/13(金)にあった銀座Rails#16で登壇しました。

スライド

speakerdeck.com

話したかったこと

  • 1〜3年目のRailsエンジニアに参考になる話がしたい
    • 7年やって私が覚えたことは、たぶん何かしら参考になるだろう
    • 来週の仕事ですぐ参考になる話がしたかった
  • 懇親会で他のシニアエンジニアの考えを聞きたかった

補足: なぜクラス内クラスを使うのか?

スライドの最後の方が説明不足だったので、ブログで補足を書いておきます。

👮‍♀️RuboCop の取締りは厳しい

RuboCop の標準ルールを守ろうとすると、以下を行うことになる。

  1. 行数が長くなると、変数を抽出する
  2. 変数代入によって AbcSize が増えるので、プライベートメソッドを抽出する
  3. メソッド追加によって ClassLength が増える

対策方法は2つある

  • Module を抽出する
  • Class を抽出する

しかし、Module はプライベートメソッドを使えない。(スライドを参照)

Class を抽出する

  • 普通にクラスを作る
  • 内部クラス(クラス内クラス)を作る

内部クラスだと僅かにですが使用箇所を制限できる。

例えば、スライド例で挙げた User::Ranking を他のクラス(例えば Blog )で使うのは避けるはず。

これにより User::Ranking を消すときに調べる範囲を減らして、消しやすくしている。

結論

このような思考過程で内部クラス(クラス内クラス)を利用したクラス設計を好んで使っています。

ActiveRecord::Base.unscope の呼び出しでネストが深くなっているのを浅くする

仕事で見かけたコードを汎用化した内容にして、個人ブログにメモっておく。*1

多段ネストを reverse して Proc でラップしたり、それを RSpec で検証する方法はブログ書いてないと忘れる自信がある。

モデルの定義

class User < ApplicationRecord
  default_scope { where(deleted_at: nil) }
end

class Article < ApplicationRecord
  default_scope { where.not(published_at: nil) }
end

元コード

User.unscoped do
  Article.unscoped do
      assert_equal 'SELECT "users".* FROM "users"', User.all.to_sql
      assert_equal 'SELECT "articles".* FROM "articles"', Article.all.to_sql
  end
end

書き換えたあと

module ScopeHelper
  def self.unscoped(models:, &block)
    models.reverse
      .inject(block) { |result, model| -> { model.unscoped(&result) } }
      .call
  end
end

ScopeHelper.unscoped(models: [User, Article]) do
  assert_equal 'SELECT "users".* FROM "users"', User.all.to_sql
  assert_equal 'SELECT "articles".* FROM "articles"', Article.all.to_sql
end

RSpecで書いたテスト

RSpec.describe ScopeHelper do
  describe '.unscoped' do
    it do
      expect(User).to receive(:unscoped).and_call_original
      block = -> {}
      expect(Article).to receive(:unscoped) do |&args|
        expect(args).to eq block
      end

      ScopeHelper.unscoped(models: [User, Article], &block)
    end
  end
end

*1:default_scopeとか絶対使わないけど、他に汎用化した例は思い浮かばなかった

2019年11月現在のRailsのissueに関するメモ #heiseirb

平成.rb #10Railsのissueに取り組むきっかけが分からない人向けのメモです。

個人的に気になっているissueについて簡単にまとめました。

注意)特に初心者向けとかではありません。

STIhas_secure_password の組合せで、親クラスでvalidationsを切り替えたい?

Trouble using has_secure_password validations attribute with STI models https://github.com/rails/rails/issues/37755

  • 再現コードなし
  • 登録者のコードは意図通りじゃなさそう
    • self.classClass を返すから常に true

scope + new でエラーが出る

raise_on_type_mismatch! in ActiveRecord::Associations::BelongsToAssociation https://github.com/rails/rails/issues/37752

  • 再現コードなし
  • コミッターの反応あり

ignored_columns + from で意図しないカラムが無視される

Ignored columns is ignoring virtual columns selected by .from https://github.com/rails/rails/issues/37745

  • 再現コードなし

through と polymorphic の組合せで外部キーがnilになる

Assigning with a through association and a self-ref polymorphic association leaves a nil foreign key https://github.com/rails/rails/issues/37758

  • 再現コードなし

request.variant の挙動が5.2と6.0で違う

Rails 6.0.0 chooses variants differently from 5.2 when variants are using different template engines https://github.com/rails/rails/issues/37021

  • 再現コードなし
  • リグレッション
    • 5.2 の挙動がバグっていた可能性もある
  • stale ラベル

enum で文字列を代入したらエラーになる

Revisiting the inconsistency in enums: raises ArgumentError when making an assignment with invalid value, but returns wrong results when querying https://github.com/rails/rails/issues/37630

  • 再現コードなし

accepts_nested_attributes_for でモデルを削除してもメモリ上に残る

Removing association with dependent: :destroy, through nested attributes leaves entity from intermediate table in memory https://github.com/rails/rails/issues/37649

  • 再現コードあり

DateTime#advance の挙動が5.2と6.0で違う?

DateTime#advance() expats integer params https://github.com/rails/rails/issues/37425

  • 再現コードなし

joins と pluck をあわせて使うと型が変わる

Unexpected Type Casting in ActiveRecord::Calculations#pluck https://github.com/rails/rails/issues/28044

  • 再現コードあり
  • 直すのは難しそう

テーブルない状態で order を呼ぶと5.2からエラー?

Calling "order" crashes when the model isn't represented by a database table https://github.com/rails/rails/issues/37741

  • 再現コードあり
  • 仕様かバグかの判断から必要そう

belongs_to と scope の組合せでsaveに失敗する

belongs_to with scope cause racord save fails https://github.com/rails/rails/issues/36990

  • 再現コードなし
  • stale ラベル

Rustでwebアプリを実装して勉強 その5 - テンプレートを使ってhtmlを返す

Rust初心者が勉強したことを記録する備忘録。

今日やった事

今日はRustやる時間が少ないので、handlebarsを使ってhtmlを返すところを少しだけ試した。

github.com

handlebars の使い方

actix-webのexamplesを参考にして、そのままコピペした。

// Handlebars uses a repository for the compiled templates. This object must be
// shared between the application threads, and is therefore passed to the
// Application Builder as an atomic reference-counted pointer.

サンプルコードのコメントに書いてあるように、Handlebars のインスタンスは各スレッドで共有するために web::Data を使う。

html を検証する

html を assert_eq! で検証するのは大変なので、部分一致で確認するように修正した。

- assert_eq!(test::read_body(resp), Bytes::from_static(b"Hello world!"));
+ let html = str::from_utf8(&test::read_body(resp))
+    .expect("Could not convert response to UTF8")
+     .to_string();
+ assert!(html.contains("<h1>Hello world!</h1>"));

& をつけたり、str や String の関係がよく分かってないが、とりあえずこんな感じで書くことで部分一致を検証できた。

次にやりたいこと

試したいことはだいたいできたので、次はコードを整理したい。

  • SQLに関する処理をModelに移す(もしくはRepositoryを作る)
  • テストコードを書く
  • jsonを返すように直す
  • テンプレートエンジンを使ってhtmlを返す
  • テスト環境の改善(コードのリファクタリングトランザクション周りの設定など)
  • Dockerで動くように直す
  • Herokuで動かす