texta.fm - 2. The Power of Constraints を聴いたぞ

podcasts.google.com

メモ(きになったところだけ)

  • composed_ofだとfreezeされる
  • immutableなオブジェクトのデメリットはメモリ消費が多いこと
    • これは処理系レベルで解決されるべき問題
  • コンテナ数をスケールアウトすることが容易になってきたので、1コンテナの中でのプロセス数を増やすことは(少なくとも一般的なwebアプリケーションでは)目指さなくてよいのでは
  • (契約による設計において)assertion errorとexceptionは異なる
    • assertion errorは、ありえないこと、以下のときに投げる
      • 仲間内の利用者が約束を守っていなかったとき
    • exceptionは、呼び出し側が約束を守っていたとしても発生するかもしれないこと
      • 外部(APIとか)の利用者が約束を守っていなかったとき
      • 利用者は約束を守っているけど問題が発生したとき
  • assertionはそんなにいっぱい書かなくていいんじゃないの、という話
    • お互いに全てを疑ってガード節を並べるのではなく、約束を設けて、呼び出し側のことを信頼する
    • コントローラで不適切な入力はちゃんと弾いて、適切な引数で呼んでくれているのだろうから、モデルではいちいちガード節は書かない、と。
    • そうすると全体最適になる
  • VOはコンストラクタでvalidationを書く
    • そもそもinvalidなオブジェクトは存在しえないようにする
    • これはinvalidなオブジェクトが存在しうるRailsのモデルとは大きく異なる
  • なぜRailsではinvalidなオブジェクトの存在を許しているのか
    • Railsでは、ユーザーからの入力をいったん受け入れるのをモデルでやっちゃうから
    • モデルより外側にvalidationをする場所があるなら、モデルはvalidなものしか存在しえない世界にできる
    • Railsはその雑さと引き換えにレイヤーの少なさ=単純性を実現している
    • モデルに書くことによって、外部/内部からの入力に対するvalidationを一箇所に書けるようになっている

おもったこと

VOはimmutableな方がいいよね、というだけで、mutableでもありうるよな

JavaのStringはimmutableらしいけど、Rubyは。

str1 = "foo"
str1.object_id  # => 180
str2 = "foo"
str2.object_id  # => 200

# VOである
str1 == str2  # => true

# mutableである
str1.concat("bar")  # => "foobar"
str1  # => "foobar"
str1.object_id  # => 180

しかしmutableにすることのメリットがいまいちわからん。 メモリ消費の抑制というのはそこまでの理由ではない気がする。 なぜmutableにしたのだろうか?

正規表現で直前/直後の文字をフィルタしたいときは行頭/行末の可能性を明示する

VS Code正規表現による検索をかけることがある。

きわめて初歩的ながら仕様を知らずに検索漏れが発生しそうになってしまったのでメモする。


たとえばいまcoolというメソッドの呼び出し箇所を調べていたとする。

/cool/だとこうだ。

cool  # <= hit

cool.strong  # <= hit

cool?  # <= hit

cool?という別のメソッドもひっかかるわけだが、これは除外したいとする。

そこで/cool[^\?]/とすると、こうだ。

cool  # <= not hit

cool.strong  # <= hit

cool?  # <= not hit

一行目はhitするものかとおもっていたら、一行目もhitしなくなってしまった。

仕様を確認する。

Use regular expressions - Visual Studio | Microsoft Docs

Match any character that is not in a given set of characters.

ふむ、/[^abc]/はcharacterに一致するのであって、改行とか行末には一致しないのか。

ということで必要だった表現は/cool($|[^\?])/ということみたいです。

cool  # <= hit

cool.strong  # <= hit

cool?  # <= not hit

省略するが行頭でもまったく同じ話なので、例えば/(^|[^_])cool/のように書く必要がある。

react-dndでドラッグアンドドロップ

HTML5のDrag and Drop APIを直接操作することも可能そうだが、ブラウザ間のinconsistencyで苦しむ羽目になりそうだ。 そこらへんをよしなに抽象化して差を吸収してくれるreact-dndというライブラリがあるということで触ってみた。

https://codesandbox.io/s/adoring-butterfly-r82k4

(ファイルアップロードのやつはsandboxのiframeだと機能しないけど別タブで開くと機能する。...?)

『Linuxのしくみ』読書メモ

*1

ざっくり

CPU/メモリ/ストレージデバイスという構成とか、プロセススケジューリングとかスワッピングとかのトピック自体は知っているものも多かったけど、全体的に解像度がぐっと上がった気がしてよかった。

書いてあって知ったこと

  • CPUのモード

  • システムコール

  • 各種コマンド

    • sar(system admin reporter)でいろいろ
    • freeでメモリ
    • df(disk free)でディスク
  • プロセスにnice(2)で優先度をつけると、プロセススケジューリングにおいてより多くの/少ないCPU時間を割り当てることができる

  • 仮想メモリと物理メモリがある

    • プロセス(カーネルの外、ユーザーの世界)から物理メモリに直接アクセスする方法はない
    • 仮想メモリと物理メモリの紐付けはページテーブルによって管理される
    • 仮想メモリと物理メモリを分離することのメリットは以下
      • 物理メモリ上で断片化せざるを得なかったとしても、仮想アドレス上では連続しているので大きなひとかたまりのデータをもつことが可能
      • 「各プロセスは、そのプロセスのページテーブルに記載された物理アドレスにしかアクセスできない」というルールを導入して、各プロセスが自身と関係ない物理メモリにアクセスすることを防げる
      • プログラムファイルはコード領域とデータ領域をメモリ上のどこにどれだけ確保するかという情報をもつ。物理メモリしかない場合、同じプログラムを複数のプロセスで実行すると、物理メモリ上のある領域を複数のプロセスがそれぞれ勝手につかおうとして困ってしまう。仮想メモリを挟むことによって、物理メモリとしては異なる領域を参照させ、干渉を避けることができる。
    • 仮想メモリの割当はmmap(2)によって行い、割り当てた時点では物理メモリは割り当てられない。メモリへのアクセスが発生して「対応する物理メモリないじゃん!」でページフォールトが起きたら、カーネルページフォールトハンドラが物理メモリの割当(とページテーブルの書き換え)を行う。
    • ページテーブルをコンパクトにするため以下の仕組みがある。
      • 階層化して、対応する物理アドレスがない範囲については、その範囲に対応する下層のテーブルをそもそも作成しない
      • 仮想/物理メモリの対応関係が複数ページにわたって連続している場合、それらページをまとめて一単位(ヒュージページ)として対応関係を保持することでテーブルサイズを小さくする
  • キャッシュメモリにも階層がある

  • キャッシュメモリとページキャッシュは「書き込みがあったらキャッシュを書き換えてダーティー状態にしといて、後でライトバック」とかの点で同型

  • ハイパースレッドは以下の仕組み

    • CPUと(キャッシュ)メモリとの間でのデータ転送待ち時間の分CPUをアイドルにしないために
    • 1つの物理CPUに、プロセスAのデータ転送待ちの間にプロセスBを処理させる
    • これを「論理CPU XがプロセスAを、論理CPU YがプロセスBを引き受けている」状態として表現する
  • バイスはファイルとして表現され(/dev/配下など)、以下の2種類がある

    • キャラクタデバイス: 読み書きできるがランダムアクセスできない。端末やキーボードなど
    • ブロックデバイス: 読み書き及びランダムアクセスできる。ストレージデバイスなど
  • ブロックデバイスにアクセスするには以下2つの方法がある(どちらもカーネルを介する)

  • ブロックデバイスのIOパフォーマンスを良くするために、カーネル内のブロックデバイス層が以下の機能を提供する

    • 先読み: ある領域にアクセスするとつづけて後続の領域が必要になることがおおいので、言われなくてもついでに後続の領域を読み込んでおく
    • IOスケジューラ: IOの指示をちょっとためて、アクセスする領域についてソートしたり連続部分をマージしたりしてからまとめてアクセスする
  • ブロックデバイスのIOパフォーマンスをよくするには

    • HDDでもSSDでも、シーケンシャルアクセスの場合は先読みでかなりパフォーマンスが上げられるので、なるべくシーケンシャルアクセスしよう
    • (どうせその領域全体が必要になるなら)細切れではなくなるべくまとめてアクセスしよう

書いてなくて知ったこと

  • macOS(Mojave)にはtop, dfはあるけどsar, freeはない

  • バイナリの置き場所

    • /bin: 非常時(シングルユーザーモード)でも利用できる必要があるコマンド
    • /usr/bin: RPMdebなどのパッケージ管理システムによって、システムに管理されるコマンド(apt-get, yum
    • /usr/local/bin: RPMdebなどのパッケージ管理システムによってシステムに管理されないコマンド(homebrew)
    • いずれもsbinはシステムから叩くコマンド
  • 標準ライブラリは、コンパイラがデフォルトでリンクしてくれるから「標準」

  • Rubyのコードは以下の手順でシステムコールを呼び出す

次は

ここらへん読みたいな

*1:この要約には違法性はないと考える。https://www.bengo4.com/c_1015/c_17/c_1263/b_341980/

ActiveRecordでnew => build => save! するとどうなる

関連付けのあるモデルにおいて、親レコードをnew => 子レコードをbuild => 親レコードをsave! したときの挙動が複雑な気がしたのでメモ。

(理解力が不足しているだけかもしれない。)

検証環境: ActiveRecord 6.0.2.1

追記

さんざん書いてから気づいたのだけど、has_oneにもhas_manyにもvalidateというオプションが存在する。 これのデフォルトがhas_oneだとfalse, has_manyだとtrueになっているため本記事の挙動となっていただけで、ここを指定すればいいだけの話だった...。

結論を言葉で

  • has_oneでは親レコードと子レコードのvalidityは独立している。
  • has_manyでは子レコードが1つでもinvalidなら親レコードもinvalidになる。

親レコードをsave!したとき、

  • 親レコードがinvalidならraiseする
  • 親レコードも子レコードもvalidな場合、insertがtransactionで囲まれて実行される
  • 親レコードがvalidで子レコードがinvalidな場合(上記の性質によりhas_oneでのみありうる)、親レコードのinsert処理のみが実行される

実験

サンプルは以下。

migrations:

class CreateTables < ActiveRecord::Migration[6.0]
  def change
    create_table :customers do |t|
    end

    create_table :banks do |t|
    end

    create_table :merchants do |t|
    end

    create_table :bank_accounts do |t|
      t.references :customer, foreign_key: true, null: false
      t.references :bank, foreign_key: true, null: false
      t.string :account_number, null: false
    end

    create_table :orders do |t|
      t.references :customer, foreign_key: true, null: false
      t.references :merchant, foreign_key: true, null: false
      t.integer :price, null: false
    end
  end
end

models:

class Customer < ApplicationRecord
  has_one :bank_account
  has_many :orders
end

class Bank < ApplicationRecord
end

class Merchant < ApplicationRecord
end

class BankAccount < ApplicationRecord
  belongs_to :customer
  validates :account_number, presence: true
end

class Order < ApplicationRecord
  belongs_to :customer
  validates :price, presence: true
end

実験するケースは以下。

  • 1 has_one
    • 1-1 invalid
    • 1-2 valid & insertable
    • 1-3 valid & uninsertable
  • 2 has_many
    • 2-1 all invalid
    • 2-2 all valid & insertable
    • 2-3 all valid & uninsertable
    • 2-4 valid&savable + valid & uninsertable
    • 2-5 invalid + valid & uninsertable
    • 2-6 invalid + valid & insertable

1-1 invalid

irb(main):118:0> customer = Customer.new
irb(main):119:0> customer.build_bank_account
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: nil, created_at: nil, updated_at: nil>
irb(main):120:0> customer.valid?
=> true
irb(main):121:0> customer.bank_account.valid?
=> false
irb(main):122:0> customer.bank_account.errors.full_messages
=> ["Account number can't be blank"]
irb(main):123:0> customer.save!
=> true
irb(main):125:0> customer.persisted?
=> true
irb(main):126:0> customer.bank_account.persisted?
=> false

1-2 valid & insertable

irb(main):156:0> customer = Customer.new
irb(main):157:0> customer.build_bank_account(account_number: "1234", bank_id: 1)
=> #<BankAccount id: nil, customer_id: nil, bank_id: 1, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):158:0> customer.valid?
=> true
irb(main):159:0> customer.bank_account.valid?
=> true
irb(main):160:0> customer.save!
=> true
irb(main):161:0> customer.persisted?
=> true
irb(main):162:0> customer.bank_account.persisted?
=> true

1-3 valid & uninsertable

irb(main):138:0> customer = Customer.new
irb(main):139:0> customer.build_bank_account(account_number: "1234")
=> #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: "1234", created_at: nil, updated_at: nil>
irb(main):140:0> customer.valid?
=> true
irb(main):141:0> customer.bank_account.valid?
=> true
irb(main):142:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'bank_id' doesn't have a default value)
irb(main):143:0> customer.persisted?
=> false
irb(main):144:0> customer.bank_account.persisted?
=> false

2-1 all invalid

irb(main):176:0> customer = Customer.new
irb(main):177:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):178:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):187:0> customer.valid?
=> false
irb(main):188:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):191:0> customer.orders.map {|order| order.valid? }
=> [false, false]
irb(main):193:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], ["Price can't be blank"]]
irb(main):196:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)

2-2 all valid & insertable

irb(main):001:0> customer = Customer.new
irb(main):002:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):004:0> customer.orders.build(price: 200, merchant_id: 2)
=> #<Order id: nil, customer_id: nil, merchant_id: 2, price: 200, created_at: nil, updated_at: nil>
irb(main):005:0> customer.valid?
=> true
irb(main):006:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):007:0> customer.save!
=> true
irb(main):008:0> customer.persisted?
=> true
irb(main):009:0> customer.orders.map {|order| order.persisted? }
=> [true, true]

2-3 all valid & uninsertable

irb(main):016:0> customer = Customer.new
irb(main):017:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):018:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):019:0> customer.valid?
=> true
irb(main):020:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):021:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):024:0> customer.persisted?
=> false
irb(main):025:0> customer.orders.map {|order| order.persisted? }
=> [false, false]

2-4 valid&savable + valid & uninsertable

irb(main):035:0> customer = Customer.new
irb(main):036:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):037:0> customer.orders.build(price: 200)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil>
irb(main):038:0> customer.valid?
=> true
irb(main):039:0> customer.orders.map {|order| order.valid? }
=> [true, true]
irb(main):040:0> customer.save!
ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value)
irb(main):041:0> customer.persisted?
=> false
irb(main):042:0> customer.orders.map {|order| order.persisted? }
=> [false, false]

2-5 invalid + valid & uninsertable

irb(main):044:0> customer = Customer.new
irb(main):045:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):046:0> customer.orders.build(price: 100)
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil>
irb(main):047:0> customer.valid?
=> false
irb(main):048:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):049:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):050:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):051:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)

2-6 invalid + valid & insertable

irb(main):059:0> customer = Customer.new
irb(main):060:0> customer.orders.build
=> #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):061:0> customer.orders.build(price: 100, merchant_id: 1)
=> #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil>
irb(main):062:0> customer.valid?
=> false
irb(main):063:0> customer.errors.full_messages
=> ["Orders is invalid"]
irb(main):064:0> customer.orders.map {|order| order.valid? }
=> [false, true]
irb(main):065:0> customer.orders.map {|order| order.errors.full_messages }
=> [["Price can't be blank"], []]
irb(main):066:0> customer.save!
ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)