git:stashで変更内容を退避、別ブランチに反映

ベースにしていたブランチが更新された時など、
rebaseせずに、変更内容を退避、再反映するには、以下の流れ。

# ブランチ構成が以下
# feature/baseブランチから、devブランチを切って作業中
#   master
#   feature/base
# * dev/task001

# 変更を退避(-u は --include-untrackedの略で、新規作成ファイルも退避)
git stash -u

# 変更がなくなっていることを確認
git status

# 退避した変更が登録されていることを確認
git stash list

# 以下、ブランチをベースに戻し、最新化して別ブランチ作成、移動
git checkout feature/base
git pull origin feature/base
git branch dev/task002
git checkout dev/task002

# 退避した変更が登録されていることを確認
# 番号を確認、直前であれば{0}となっているはず
git stash list

# 退避した変更を戻す
# stash@{0} を指定しないと、直近が戻る
git stash apply stash@{0}

# 変更が反映されていることを確認
git status

# 退避した変更を消す
# stash@{0} を指定しないと、直近が消える
git stash drop stash@{0}

rails:migrate実行

db:migrateの実行について。

# modelからgenerate (migrationも作成される)
rails g model staffs
bin/rails g model staffs #どっちでも

# table追加・変更だけのときはmigrationからgenerate
rails g migration AddTypeOfGenderStaffs
rails g migration CreateStaffHistorys

# migrateの実行
rails db:migrate

# 現在バージョンの確認
rails db:version

# 戻したい場合は、以下で1回分戻る
rails db:rollback
# 戻したい場合は、何回文戻すか指定可能
rails db:rollback STEP=5


# migrateの実施履歴の参照 downは未適用
rails db:migrate:status

# バージョンを指定してmigrate
rails db:migrate:up VERSION=2022mmddhhmmss

migrationファイルの書き方については別途。

SQL:条件付きCOUNT

今まで知りませんでした。
条件付きのcountがこんなに簡単にとれるとは。

select
   count(*) # 全件
  ,count(staffs.type = 1 OR NULL) as byte_count
  ,count(staffs.type = 2 OR NULL) as part_count
from
  staffs;
where
  staffs.invalid = true

参考:多謝)
【SQL】COUNT関数で条件に合致する件数を取得する - Qiita
SQLでSELECT句のCOUNTをきわめる【複数の条件指定も可能】 | ジユーズ

rails:コンソールから生SQLを実行する

以下のように、rails consoleから生SQL実行できる。
戻り値がActiveRecord::Resultなので、hash化すると見やすい。
(hash化しなくても見れる)

sql = 'SELECT staff_type, count(*) FROM staffs where is_valid = true group by staff_type order by staff_type'
ActiveRecord::Base.connection.select_all(sql).to_hash

結果0件の場合は、to_hashだとエラーになるので注意

以下でも実行できるが、クエリーの結果をのぞくには扱いにくい。

ActiveRecord::Base.connection.execute(sql)

rails:更新系処理の悲観的ロックのサンプル

掲題を実現する場合のサンプル。

処理で更新されるテーブルのモデルに、以下のようなメソッドを実装。

class BatchExecUpdateDate < ApplicationRecord
  # batch_exec_update_dates
  #
  # id        :bigint(8)  not null, primary key
  # proc_type :integer    not null
  # upated_at :datetime   not null

  enum proc_type : { daily: 0, monthly: 1}

  class << self
    def transaction_with_lock(proc_type, &block)
      target_rec = find_or_create_by(proc_type: proc_type)

      ActiveRecord::Base transacrion do
        # 行ロック取得
        lock_rec = BatchExecUpdateDate.lock('FOR UPDATE NOWAIT').find(target_rec.id)
        # 引数で渡されたブロックを処理する
        block.call
        lock_rec.update(updated_at: Time.zone.now)
      end
      true
    rescue ActiveRecord::StatementInvalid => e
      return false if e.cause&.kind_of?(PG::LockNotAvailable)

      raise e
    end
  end
end

modelでupdate_atを必須valideteかけている場合は、
明示的にupdated_atをセットしないとエラーになる
validate指定を外すと、自動でセットしてcreateしてくれる

validates :updated_at, presence: true

target_rec = find_or_create_by(proc_type: proc_type) {|beud| beud.updated_at = Time.zone.now}

以下のような処理があったとして

# 処理クラス
class AaaBatch
  class << self
    def update_process
      # 更新処理
    end
  end
end

以下のように呼び出すことで、実現できる。

# 起動するとき
BatchExecUpdateDate.transaction_with_lock(proc_type) do
  AaaBatch.update_process(0)
  BbbBatch.update_process(0)
  :
end

RSpec:よく使うmatcherメモ

付け足し付け足ししていく予定です。

インスタンスの内容のテスト

expect(section).to have_attributes(name: 'default')
expect(section.is_valid).to be_truthy
expect(section.retired_at).to be nil

件数に変更がない(unchange推奨)

expect{subject}.to change {Company.count}.by(0).and change {Section.count}.by(0)
expect{subject}.to unchange {Company.count}.and unchange {Section.count}

参考:毎度多謝)使えるRSpec入門
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita

rails:コールバック処理

あらかじめ定義しておき、必要なタイミングで自動で動く処理。
以下だと、Companyがcreateされると、直後にSectionもcreateされる。
(Section.name を仮設定しているのもコールバック)

# app/model/company.rb
class Company < ApplicationRecord
  has_many :sections

  # callback
  after_create :create_default_section

  def create_default_section
    section.create!(
      code: Section::DEFAULT_SECTION_NAME,
      company: self 
      )
  end
end

# app/model/section.rb
class Section < ApplicationRecord
  belong_to :company

  # callback
  before_create :set_name

  DEFAULT_SECTION_NAME = 'DEFAULT'.freeze

  def set_name
    self.name ||= '(temporary)' # 未設定の場合は仮名をセット
  end 
end

おまけ)create と create! の違い
例外(赤い画面)になるかならないか、が異なる。
create! はバリデーションNGになるとActiveRecord::RecordInvalid例外が発生する。
!がない場合は、保存されないが例外(赤い画面)も出ない。