魔法を使う猫の群像劇です。平和な異世界住民の生活を描いた「ハクメイとミコチ」みたいな漫画です。
原作の書影はもっと竜っぽいです。
この表紙をみるとテメレア戦記を思い出します。
新装版が出てるんですね。今気がつきました。
料理バトル漫画の元祖みたい*1な漫画。
こんな記事を読んで、次のようなことを考えました。
アダム、1987年にはミスター味っ子が全国ネットで料理対決してたって聞いたらどんな顔するだろう / “『料理の鉄人』はずっと私に影響を与えている 著名シェフが英紙で大絶賛 | 真面目な料理対決とエンターテイメントの融合” https://t.co/2E91P0tGAt
— ぎゃばん@手洗い (@ledsun) April 17, 2022
僕の世代的には料理バトル漫画と言えば「ミスター味っ子」です。 が、93年のテレビ番組を作ったひとが読んでたとしたら、6年前の味っ子じゃなくて、20年前の味平のはずなんですよね。
おなじビッグ錠先生の「一本包丁満太郎」は床屋で読んだことがあります。 味平は未読でした。 良い機会なので読み始めました。
料理の場面がテレビ中継され、審査員がいて、実況中継される。今の料理バトルのフォーマットが描かれています。 また、この頃はまだフォーマットは固まっていなかったようで、1巻ではバトルしないのです。 普通に料理の修業をはじめるエピソードから始まります。 3巻になってようやく料理バトルが始まります。 はじめるといっても前振りで、実際の料理を作り始めるのは4巻でした。
これだけ料理バトルの舞台設定を丁寧に説明しているので、やはり、現代の料理バトルフォーマットを確立したのは味平なのかもしれません。
*1:みたいというのは 萩尾望都先生の 『ケーキ ケーキ ケーキ 』 の方が大分早いらしいです。【20世紀編】人の身体は食べ物で出来ている。 空腹時に見ると死ぬ、胃袋鷲掴みのグルメマンガの歴史を辿る! | アル
楽観的ロックシリーズの三回目です。
します。Ruby on Railsには削除時にActiveRecord::StaleObjectError
が発生することを確認するテストケースがあります。
def test_lock_destroy p1 = Person.find(1) p2 = Person.find(1) assert_equal 0, p1.lock_version assert_equal 0, p2.lock_version p1.first_name = "stu" p1.save! assert_equal 1, p1.lock_version assert_equal 0, p2.lock_version assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } assert p1.destroy assert_predicate p1, :frozen? assert_predicate p1, :destroyed? assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } end
これをWebアプリケーションにどう組み込めばいいのでしょうか?
楽観的ロックを使ったRuby on Railsアプリケーションを作って動きを確かめる - @ledsun blog の続きです。
楽観的ロックで競合が起きるとActiveRecord::StaleObjectError
が起きます。
エラー画面が表示されます。
ActiveRecord::StaleObjectError
をハンドリングして次のように、編集画面にメッセージを表示してみましょう。
つぎのように例外処理を追加します。
def update respond_to do |format| if @article.update(article_params) format.html { redirect_to article_url(@article), notice: "Article was successfully updated." } format.json { render :show, status: :ok, location: @article } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @article.errors, status: :unprocessable_entity } end end rescue ActiveRecord::StaleObjectError @article.reload.attributes = article_params.reject{ _1 == 'lock_version' } flash.now[:error] = 'Another user has made a change to that record since you accessed the edit form.' render :edit, status: :conflict end
やっていることは次の通りです。
Scaffoldで生成した編集画面ではフラッシュメッセージが表示されません。
app/views/articles/edit.html.erb
にフラッシュメッセージの表示を追加します。
<p style="color: red"><%= flash[:error] %></p> <h1>Editing article</h1> <%= render "form", article: @article %> <br> <div> <%= link_to "Show this article", @article %> | <%= link_to "Back to articles", articles_path %> </div>
一番上の行です。
なにも考えずにUpdate Article
ボタンを押すと更新されます。
別ユーザーが編集した内容を表示してあげると、より便利になりそうです。
帝国の雷撃使いと大激突。骨(表紙にいる骨のモンスター)大活躍。
暗殺者の凶刃から王女を守れるのか! 発刊ペースがやたら速くありませんか?
精神年齢8歳の遊び人の力でサッキュバスをやっつけろ! ヤンキーに色仕掛けは通じねえ!!
結論から言うと、なんのことはなくUbuntuにインストールするのと同じです。
Install Docker Engine on Debian | Docker Documentation に従って、インストールします。 普段fish-shellを使っています。 読み替えが面倒臭いので、各コマンドはbashで実行しました。
2回目のapt-get update
をすると、次のようなエラーが出ます。
Err:7 https://download.docker.com/linux/debian focal Release 404 Not Found [IP: 13.249.170.87 443]
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian focal stable
を
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu eoan stable
に変えると良さそうです。 動作確認します。
ledsun@MSI:~►sudo docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 2db29710123e: Pull complete Digest: sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
動きました!
sudo
を要らなくするおまじないをします。
sudo usermod -aG docker ledsun
シェルを起動し直さないと反映されません。
そのままのターミナルでdocker-compose
コマンドを実行して、権限エラーを出して、時間を無駄にしました。
Install Docker Compose | Docker Documentation に従って、インストールします。
docker-composeでお目当てのコンテナを起動しました。
ledsun@MSI:~/pubannotation►sudo docker-compose up Creating network "pubannotation_default" with the default driver Creating pubannotation_redis_1 ... Creating pubannotation_db_1 ... Creating pubannotation_db_1 ... error WARNING: Host is already in use by another container ERROR: for pubannotation_db_1 Cannot start service db: driver failed programming external connectivity on endpoint pubannotation_db_1 (1ac1d94f2bb6b6916c07908b0650b89686b7a91354bb2834e9e3155644fddf03): Error starting userland proxy: listen tcp 0.0.0.0:5432: bind: address already in use Creating pubannotation_elasticsearch_1 ... error Creating pubannotation_redis_1 ... done csearch_1 (66ba56873f80c2536192a2b9a22829ec83515c3759b5dca772ff08dee3afa76a): Error starting userland proxy: listen tcp 0.0.0.0:9200: bind: address already in use ERROR: for db Cannot start service db: driver failed programming external connectivity on endpoint pubannotation_db_1 (1ac1d94f2bb6b6916c07908b0650b89686b7a91354bb2834e9e3155644fddf03): Error starting userland proxy: listen tcp 0.0.0.0:5432: bind: address already in use ERROR: for elasticsearch Cannot start service elasticsearch: driver failed programming external connectivity on endpoint pubannotation_elasticsearch_1 (66ba56873f80c2536192a2b9a22829ec83515c3759b5dca772ff08dee3afa76a): Error starting userland proxy: listen tcp 0.0.0.0:9200: bind: address already in use ERROR: Encountered errors while bringing up the project.
あれあれ、起動したかったPostgreSQLもElasticSearchもインストール済みでした。 Docker入れなくてもよかったじゃん!
Ruby on Railsアプリケーションのソースコードを読もうとしました。 ソースコード読むにしてもアプリケーションを動かしたいです。 WSLにソースコードをおき、環境を作ってRuby on Railsアプリケーションを起動しました。 おもむろにVS Codeで読み始めたのですが、なんとなく興が乗りません。 RubyMineで読むことにしました。
WSL上のソースコードをRubyMineで開き、ふと公式の推奨手順を確認してみました。 なんと、面白いことが書いてあります。
For better performance, we recommend using WSL as a remote interpreter and storing your project in the Windows file system instead of WSL.
ソースコードはWindows側において置いて、WSL上のRubyで実行しなさいとあります。 VS Codeとは逆です。 手順に従って設定します。
rbenvを使っているせいか/home/ledsun/.rbenv/shims/ruby
では、RubyMineが上手く認識しませんでした。
Rubyコマンドの実体が置いてある /home/ledsun/.rbenv/versions/2.7.4/bin/ruby
を指定しました。
これでRubyMineからRuby on Railsアプリケーションを起動すると動きました。
これはこれで動くのですね。 なかなか新鮮です。
昔からある機能です。 Rails 0.9.3からの機能あったようです*1。 よくわかっていないので、説明してみます。
Railsガイドでは次のように説明されています。
Active Record クエリインターフェイス - Railsガイド
楽観的ロックでは、複数のユーザーが同じレコードを同時編集することを許し、データの衝突が最小限であることを仮定しています。この方法では、レコードがオープンされてから変更されたことがあるかどうかをチェックします。そのような変更が行われ、かつ更新が無視された場合、ActiveRecord::StaleObjectError例外が発生します。
いまいちユースケースがピンと来ません。
Railsガイド中では次のような短いサンプルがあります。
c1 = Customer.find(1) c2 = Customer.find(1) c1.first_name = "Sandra" c1.save c2.first_name = "Michael" c2.save # ActiveRecord::StaleObjectErrorが発生
これだとRuby on Railsアプリケーションの中でどう使えば良いのかよくわかりません。
画面からの編集リクエストとc1
とc2
の関係が想像できません。
GitHubのWikiが楽観的ロックを使っていそうな動作だったような、おぼろげな記憶があります。 確かめてみると、現在は次のようにフラッシュメッセージがPushされます。
記憶の中のイメージでは、Saveボタンを押したときに保存失敗していました。
想像するに、ユースケースは、ある程度長文を保持するモデルで編集内容を上書きされたくない時のようです。 便利にするなら、マージするなり衝突したDiffを表示するなりしたいところです。 そこまで凝った実装をしないで、最低限、別のユーザーからの変更があったことをユーザーに伝えるための機能です。
なんとなく用途がイメージできたので、実際にアプリケーションを作ってみます。 長文を保持するので、記事モデルを一つ持つRuby on Railsアプリケーションを作ります。
楽観的ロックはlock_version
カラムさえあれば動作します。
scaffold
するときに lock_version
カラムも指定しておきます。
bundle exec rails new -MCAJT --skip-active-job --skip-active-storage . bin/rails g scaffold Article title:string content:text lock_version:integer bin/rails db:prepare
rails
をインストールしていなければbundel init
して出来たGemfileの中のコメントを外してbundle
してください。
次のコマンドでRuby on Railsアプリケーションを起動します。
bin/rails s
http://localhost:3000/articles を開くと記事の一覧画面です。
リンクをたどって記事作成画面を表示します。
scaffold
するときに lock_version
カラムも指定したので、lock_version
が編集出来ます。
適当に記事を作ります。
記事の本文を変更してて保存してみましょう。
lock_version
を変えていないのに、勝手にインクリメントされました。
もう一度記事の本文を変更してみましょう。
今回はlock_version
も一緒に変更し、保存します。
なんか狐につままれたよう気分になりますが、Ruby on Railsアプリケーションの外側でlock_version
が変更されたらActiveRecord::StaleObjectError
が発生します。
以上が楽観的ロックの動作です。
楽観的ロックの動作はわかりました。 実際のアプリケーションでつかうにはもう少し工夫が必要そうです。
*1:https://github.com/rails/rails/blob/v0.9.3/activerecord/lib/active_record/locking.rbにlock_versionの記述があります。DHHのコミットで追加されました。
時節柄悪役のイメージが一段と強くなった主人公、魔導院の魔導士と大激突。
魔法模範演習本番、主人公の悪役令嬢と元主人公のヒロインが力を合わせて大爆発。
押し寄せる魔王軍1000匹を庵達5人で防げるのか?
PHPでTODOリストをつくる、追加まで - @ledsun blog で、タスクを追加したときの画面遷移にPRGパターンを使うと良いことに気がつきました。 次のように実装しました。
<!DOCTYPE html> <head> <title>TODOリスト</title> </head> <body> <form method="post"> <input type="text" name="name" autofocus> <button>追加</button> </form> <ul> <?php foreach (restore_todo_list() as $todo) { echo "<li>" . $todo . "</li>"; } ?> </ul> </body> <?php if ($todo = $_POST["name"]) { $todo_list = restore_todo_list(); array_unshift($todo_list, $todo); save_todo_list($todo_list); // GETリクエストするようにリダイレクトします。 header('Location: todo.php'); } function restore_todo_list() { return array_filter(explode(",", $_COOKIE["todo_list"])); } function save_todo_list($todo_list) { $todo_list_str = array_reduce($todo_list, function ($carry, $todo) { return $carry . "," . $todo; }); setcookie("todo_list", $todo_list_str); } ?>
このコードを見ているとToDoリストがデータとその操作を持つオブジェクトに見えてきました。 つまり、TodoListクラスにして、restore/saveメソッドを持たせたいです。 次はPHPでクラスを書く事に挑戦しようと思います。