<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://u-ryo.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://u-ryo.github.io/" rel="alternate" type="text/html" hreflang="ja" /><updated>2026-08-06T15:47:50+09:00</updated><id>https://u-ryo.github.io/feed.xml</id><title type="html">u-ryo’s blog</title><subtitle>u-ryo&apos;s blog</subtitle><author><name>u-ryo</name></author><entry><title type="html">AIエージェントの調査報告は一次証拠ではない</title><link href="https://u-ryo.github.io/blog/2026/08/06/ai-agent-reports-are-not-primary-evidence/" rel="alternate" type="text/html" title="AIエージェントの調査報告は一次証拠ではない" /><published>2026-08-06T15:00:00+09:00</published><updated>2026-08-06T15:00:00+09:00</updated><id>https://u-ryo.github.io/blog/2026/08/06/ai-agent-reports-are-not-primary-evidence</id><content type="html" xml:base="https://u-ryo.github.io/blog/2026/08/06/ai-agent-reports-are-not-primary-evidence/"><![CDATA[<p>AIエージェント (Claude Code) に調査を任せる働き方を、ここ半年ほど本格的にやっています。レガシーな業務システムの影響範囲調査みたいな、SQLとJavaを何百ファイルも掘る仕事は、複数のagentに並列で投げると探索コストが桁で下がります。便利です。便利なんですが、先日ひとつやらかしまして、その反省文です。</p>

<!-- more -->

<h2 id="何が起きたか">何が起きたか</h2>

<p>バッチのSQLの結合経路を調べさせたときのことです。多段のJOINで、あるソートキー列が表から表へどう引き回されているかを追わせたところ、agentがこう報告してきました。</p>

<blockquote>
  <p>子テーブルのソートキーは親テーブルと直接は紐付いていません。間のマスタで名称一致を取り直した後の値を経由しています。単純な「親 = 子」の結合ではありません。</p>
</blockquote>

<p>具体的で、自信ありげで、「一見そう見えるが実は違う」という玄人っぽい形をしています。そしてこの報告は、検証されないままチーム共有の影響範囲ドキュメントの「未確認事項」欄に載りました。白状すると、この調査は報告の受け取りからドキュメントへの記載まで含めてAI主導の流れで回していて、人間の私はその門を素通しさせています。つまり<strong>誤読したのもAI、載せたのもAIで、人間は見ていなかった</strong>。</p>

<p>後日、レビューで指摘を受けて該当のMapper XMLを読み直したら、誤読でした。構造だけ抜き出すとこうです。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">m_group</span> <span class="k">g</span>
        <span class="k">ON</span> <span class="k">g</span><span class="p">.</span><span class="n">grp_id</span>  <span class="o">=</span> <span class="n">base</span><span class="p">.</span><span class="n">grp_id</span>
       <span class="k">AND</span> <span class="k">g</span><span class="p">.</span><span class="n">sort_no</span> <span class="o">=</span> <span class="n">base</span><span class="p">.</span><span class="n">sort_no</span>   <span class="c1">-- ここで g.sort_no は base.sort_no に固定される</span>
       <span class="k">AND</span> <span class="k">g</span><span class="p">.</span><span class="n">name_1</span>  <span class="o">=</span> <span class="n">base</span><span class="p">.</span><span class="n">name_1</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">t_detail</span> <span class="n">d</span>
        <span class="k">ON</span> <span class="n">d</span><span class="p">.</span><span class="n">grp_id</span>  <span class="o">=</span> <span class="n">base</span><span class="p">.</span><span class="n">grp_id</span>
       <span class="k">AND</span> <span class="k">g</span><span class="p">.</span><span class="n">sort_no</span> <span class="o">=</span> <span class="n">d</span><span class="p">.</span><span class="n">sort_no</span>      <span class="c1">-- 見た目はマスタ経由。実質は base.sort_no = d.sort_no</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">g.sort_no = d.sort_no</code> と書いてあると、いかにもマスタ <code class="language-plaintext highlighter-rouge">g</code> 由来の値で結合しているように見えます。でも上で <code class="language-plaintext highlighter-rouge">g.sort_no = base.sort_no</code> と縛られているので、これは <code class="language-plaintext highlighter-rouge">base.sort_no = d.sort_no</code> と同値です。マスタのJOINが実際にやっているのは階層の絞り込みと名称一致による行選択だけで、ソートキーの値は何も変えていない。<strong>5分読めば決着する話</strong>でした。</p>

<h2 id="教訓">教訓</h2>

<p>このとき自分のノートに書いた教訓が3つあります。</p>

<p><strong>その1: AIの報告は一次証拠ではない。</strong> 特に「一見そう見えるが実は違う」という形の指摘は、自分で該当行を読むまで信じてはいけません。人間の部下の報告なら、自信のなさが文面ににじむものですが、LLMの文章は間違っているときも常に流暢で具体的です。「具体的で自信ありげ」をフィルタに使う長年の癖が、AI相手では効きません。もっともらしさと正しさが無相関の相手だ、と思って扱う必要があります。</p>

<p><strong>その2: 「未確認事項」への格上げは、検証をサボることの言い換えになりうる。</strong> 未確認と書けば嘘にはならない、という安心感が罠でした。自分で5分読めば白黒つく話を、読者の宿題にしていたわけです。以来、未確認事項に書く前に「これは自分で確かめられるか?」を一度問うことにしています。</p>

<p><strong>その3 (これはAIと無関係の普遍教訓): SQLのエイリアスは値の出所を隠す。</strong> <code class="language-plaintext highlighter-rouge">g.sort_no</code> と書いてあっても <code class="language-plaintext highlighter-rouge">g</code> 由来の値とは限らない。等値条件で他の列に固定されていれば、それはただの別名です。エイリアスを目で追うのではなく、<strong>その列に効いている等値条件を全部集めてから考える</strong> — これはAIにも人間にも効く読み方だと思います。</p>

<h2 id="それでもaiに調査を任せる">それでもAIに調査を任せる</h2>

<p>じゃあAIの調査は使えないのか、というと逆で、この一件の後もほぼ全ての調査をagentに任せています。探索の大半は正しく、コストは桁違いに安い — と、ここで「8〜9割は正しい」と書きかけて、出典のない数字だと気づいてやめました。体感です。本稿の教訓は、こういうところにまで効きます。変わったのは運用ルールで、</p>

<ul>
  <li>報告には必ず<strong>出典</strong> (ファイル名・行番号・実行したコマンド) を付けさせる。裏取りのコストが「探し直す」から「その行を読む」に下がります</li>
  <li><strong>成果物に載る情報だけは、必ず一次証拠 (生のコード・生のログ・実データ) で裏取りする</strong>。全部を検証するのは無理でも、外に出る情報だけは人間が最後の門番をやる。今回の事故の急所は、調査も文書化もAIで完結していて門番が不在だったことでした。「AIの中で完結する工程」と「外に出る工程」の境目に、人間を一人置く</li>
</ul>

<p>要するに「信頼するな」ではなく「<strong>検証できる形で報告させろ</strong>」が実務解でした。</p>

<p>ところで、この「最後の門番」も結構な負担です。次回は、その検証自体をAIにやらせる話 — 書いた本人の文脈を一切与えない別セッションのAIに、成果物を敵対的にレビューさせる運用 — を書きます。</p>

<p>(この連作は、AIエージェントを業務のレガシーシステム保守で半年ほど運用して溜まった実践則を、1本1則で書いていくものです)</p>]]></content><author><name>u-ryo</name></author><category term="ai" /><category term="llm" /><category term="sql" /><summary type="html"><![CDATA[AIエージェント (Claude Code) に調査を任せる働き方を、ここ半年ほど本格的にやっています。レガシーな業務システムの影響範囲調査みたいな、SQLとJavaを何百ファイルも掘る仕事は、複数のagentに並列で投げると探索コストが桁で下がります。便利です。便利なんですが、先日ひとつやらかしまして、その反省文です。]]></summary></entry><entry><title type="html">Markdown to PDF、3年後</title><link href="https://u-ryo.github.io/blog/2026/08/05/markdown-to-pdf-3-years-later/" rel="alternate" type="text/html" title="Markdown to PDF、3年後" /><published>2026-08-05T13:00:00+09:00</published><updated>2026-08-05T13:00:00+09:00</updated><id>https://u-ryo.github.io/blog/2026/08/05/markdown-to-pdf-3-years-later</id><content type="html" xml:base="https://u-ryo.github.io/blog/2026/08/05/markdown-to-pdf-3-years-later/"><![CDATA[<p>前回 (といってももうはるか3年も前ですが)、<a href="/blog/2023/12/11/markdown-to-pdf-on-cli/">markdown to pdf on CLI</a> という記事で、pandoc/latex の docker image に <code class="language-plaintext highlighter-rouge">tlmgr install collection-langjapanese</code> で30分ほどかけて日本語PDF環境を作った話を書きました。</p>

<p>実はあの記事を書いてから割とすぐ、<a href="https://qiita.com/frozenbonito/items/10a38c5fd4ba97a9bef0">楽にMarkdownをそれっぽいPDFに変換する</a> の <code class="language-plaintext highlighter-rouge">frozenbonito/pandoc-eisvogel-ja</code> を見つけました。pandoc + Eisvogel テンプレート + 日本語フォント焼き込み済みの docker image なので、</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/data frozenbonito/pandoc-eisvogel-ja <span class="nt">-o</span> doc.pdf doc.md
</code></pre></div></div>

<p>これ一発。表紙も目次も出ます。爾来、今に至るもずっとこちらを愛用しています。以上。</p>

<p>…で終わってしまうのもなんですんで、そういえばきょうび、この界隈はどうなってるんだろう? と思って軽く見渡してみました。</p>

<h2 id="2026年の様子">2026年の様子</h2>

<p>一番の変化は <strong>Typst</strong> の台頭のようです。LaTeX 代替の組版システムで、pandoc が <code class="language-plaintext highlighter-rouge">--pdf-engine=typst</code> を直接サポートしたので、</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>pandoc typst
pandoc doc.md <span class="nt">-o</span> doc.pdf <span class="nt">--pdf-engine</span><span class="o">=</span>typst
</code></pre></div></div>

<p>で TeX 環境なしの日本語PDFが出る時代になっていました。tlmgr に22分かけた身からすると隔世の感があります。日本語の組版にこだわるなら CSS 組版の <a href="https://vivliostyle.org/ja/">Vivliostyle</a> が健在で、技術同人誌方面は大体これみたいですね。</p>

<p>あと身も蓋もない変化として、AI に「これPDFにして」と言うと agent が勝手に pandoc なり headless Chrome なりを使って出してくる、というのがあります。ツール選定という行為自体が消えつつある、のが AI 時代の今、ということでしょうか。</p>

<h2 id="3年前に動かなかった-md-to-pdf今なら動くのか">3年前に動かなかった md-to-pdf、今なら動くのか</h2>

<p>前回の記事で「どうしようもないエラーが出る」と切り捨てた <code class="language-plaintext highlighter-rouge">md-to-pdf</code>、2026年の今ならどうなのか。試してみました。</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npx <span class="nt">--yes</span> md-to-pdf sample.md
Error: Could not find Chrome <span class="o">(</span>ver. 151.0.7922.71<span class="o">)</span><span class="nb">.</span> This can occur <span class="k">if </span>either
 1. you did not perform an installation before running the script
    <span class="o">(</span>e.g. <span class="sb">`</span>npx puppeteer browsers <span class="nb">install </span>chrome<span class="sb">`</span><span class="o">)</span> or
 2. your cache path is incorrectly configured
</code></pre></div></div>

<p>一発では動かない。そこは3年前と同じなんですが、理由が進化していて、puppeteer が Chrome を自動ダウンロードしなくなったため、なんですね。エラーメッセージの指示どおりに</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npx puppeteer browsers <span class="nb">install </span>chrome
<span class="nv">$ </span>npx md-to-pdf sample.md
<span class="o">[</span>12:51:55] generating PDF from sample.md <span class="o">[</span>started]
<span class="o">[</span>12:51:59] generating PDF from sample.md <span class="o">[</span>completed]
</code></pre></div></div>

<p>今度は4秒で完了。出てきたPDFは GitHub 風スタイルで、日本語の本文・表・コードブロックとも問題なしでした。「一発では動かない」は3年経っても変わらないですが、エラーメッセージが親切になったので自己解決できる。時代は少しずつ良くなっています。</p>

<h2 id="古い-linux-でも動くのか">古い Linux でも動くのか</h2>

<p>Typst は Rust 製で musl の static build が配られていて、pandoc の公式バイナリも static link なので、原理的には glibc の古い年代物の Linux でも「置くだけ」で動くはずです。docker が使えない・使いたくない環境でこそ試す価値があります。手元の古めの環境で試してみました。</p>

<figure class="code"><div class="highlight"><pre><span></span>mkdir<span class="w"> </span>-p<span class="w"> </span>~/md2pdf/bin<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nb">cd</span><span class="w"> </span>~/md2pdf
curl<span class="w"> </span>-sLO<span class="w"> </span>https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz
tar<span class="w"> </span>xf<span class="w"> </span>typst-x86_64-unknown-linux-musl.tar.xz<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>cp<span class="w"> </span>typst-*/typst<span class="w"> </span>bin/
<span class="c1"># pandoc は GitHub releases の linux-amd64 tarball (static link) を同様に bin/ へ</span>
<span class="nb">export</span><span class="w"> </span><span class="nv">PATH</span><span class="o">=</span>~/md2pdf/bin:<span class="nv">$PATH</span>
<span class="nb">time</span><span class="w"> </span>pandoc<span class="w"> </span>sample.md<span class="w"> </span>-o<span class="w"> </span>out.pdf<span class="w"> </span>--pdf-engine<span class="o">=</span>typst<span class="w"> </span>-V<span class="w"> </span><span class="nv">mainfont</span><span class="o">=</span>IPAMincho
</pre></div></figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>real    0m5.210s
</code></pre></div></div>

<p>glibc 2.23 という年代物の環境で動きました。初回はフォントキャッシュ構築込みで5秒、以後は1秒級。日本語フォントはシステムに入っているものを typst がそのまま拾ってくれるので (<code class="language-plaintext highlighter-rouge">typst fonts</code> で一覧できます)、うちは IPAMincho を指定しただけ。無いマシンでも otf を1個置いて <code class="language-plaintext highlighter-rouge">TYPST_FONT_PATHS</code> で指せば済みます。disk 占有は typst + pandoc 合わせて210MB (ほぼ pandoc の static binary です。配布は tar.gz で30MBほどなのに、展開すると5倍に膨らむのは Haskell の static binary らしいところ)。eisvogel-ja の docker image が1.29GBなので、6分の1で済みます。出来上がりの PDF も、表紙情報・見出し・表・コードブロックとも文句なしでした。</p>

<h2 id="結論">結論</h2>

<p>今日から <code class="language-plaintext highlighter-rouge">pandoc</code> + <code class="language-plaintext highlighter-rouge">typst</code> で生きていこうと思います。</p>

<p>凄いですね、ベルリン工科大 Martin Haug さんと Laurenz Mädje さん。「LaTeX のコンパイル遅すぎ・エラー不親切すぎ」に切れて作った、と?! あの Knuth 先生に挑むというその心意気に拍手喝采です。</p>

<p>そういえば、知人が何を使って md to pdf しているのか、気になっています。今度聞いてみます。</p>]]></content><author><name>u-ryo</name></author><category term="markdown" /><category term="pdf" /><category term="pandoc" /><category term="typst" /><summary type="html"><![CDATA[前回 (といってももうはるか3年も前ですが)、markdown to pdf on CLI という記事で、pandoc/latex の docker image に tlmgr install collection-langjapanese で30分ほどかけて日本語PDF環境を作った話を書きました。 実はあの記事を書いてから割とすぐ、楽にMarkdownをそれっぽいPDFに変換する の frozenbonito/pandoc-eisvogel-ja を見つけました。pandoc + Eisvogel テンプレート + 日本語フォント焼き込み済みの docker image なので、 docker run --rm -v $(pwd):/data frozenbonito/pandoc-eisvogel-ja -o doc.pdf doc.md これ一発。表紙も目次も出ます。爾来、今に至るもずっとこちらを愛用しています。以上。 …で終わってしまうのもなんですんで、そういえばきょうび、この界隈はどうなってるんだろう? と思って軽く見渡してみました。 2026年の様子 一番の変化は Typst の台頭のようです。LaTeX 代替の組版システムで、pandoc が --pdf-engine=typst を直接サポートしたので、 brew install pandoc typst pandoc doc.md -o doc.pdf --pdf-engine=typst で TeX 環境なしの日本語PDFが出る時代になっていました。tlmgr に22分かけた身からすると隔世の感があります。日本語の組版にこだわるなら CSS 組版の Vivliostyle が健在で、技術同人誌方面は大体これみたいですね。 あと身も蓋もない変化として、AI に「これPDFにして」と言うと agent が勝手に pandoc なり headless Chrome なりを使って出してくる、というのがあります。ツール選定という行為自体が消えつつある、のが AI 時代の今、ということでしょうか。 3年前に動かなかった md-to-pdf、今なら動くのか 前回の記事で「どうしようもないエラーが出る」と切り捨てた md-to-pdf、2026年の今ならどうなのか。試してみました。 $ npx --yes md-to-pdf sample.md Error: Could not find Chrome (ver. 151.0.7922.71). This can occur if either 1. you did not perform an installation before running the script (e.g. `npx puppeteer browsers install chrome`) or 2. your cache path is incorrectly configured 一発では動かない。そこは3年前と同じなんですが、理由が進化していて、puppeteer が Chrome を自動ダウンロードしなくなったため、なんですね。エラーメッセージの指示どおりに $ npx puppeteer browsers install chrome $ npx md-to-pdf sample.md [12:51:55] generating PDF from sample.md [started] [12:51:59] generating PDF from sample.md [completed] 今度は4秒で完了。出てきたPDFは GitHub 風スタイルで、日本語の本文・表・コードブロックとも問題なしでした。「一発では動かない」は3年経っても変わらないですが、エラーメッセージが親切になったので自己解決できる。時代は少しずつ良くなっています。 古い Linux でも動くのか Typst は Rust 製で musl の static build が配られていて、pandoc の公式バイナリも static link なので、原理的には glibc の古い年代物の Linux でも「置くだけ」で動くはずです。docker が使えない・使いたくない環境でこそ試す価値があります。手元の古めの環境で試してみました。 mkdir -p ~/md2pdf/bin &amp;&amp; cd ~/md2pdf curl -sLO https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz tar xf typst-x86_64-unknown-linux-musl.tar.xz &amp;&amp; cp typst-*/typst bin/ # pandoc は GitHub releases の linux-amd64 tarball (static link) を同様に bin/ へ export PATH=~/md2pdf/bin:$PATH time pandoc sample.md -o out.pdf --pdf-engine=typst -V mainfont=IPAMincho real 0m5.210s glibc 2.23 という年代物の環境で動きました。初回はフォントキャッシュ構築込みで5秒、以後は1秒級。日本語フォントはシステムに入っているものを typst がそのまま拾ってくれるので (typst fonts で一覧できます)、うちは IPAMincho を指定しただけ。無いマシンでも otf を1個置いて TYPST_FONT_PATHS で指せば済みます。disk 占有は typst + pandoc 合わせて210MB (ほぼ pandoc の static binary です。配布は tar.gz で30MBほどなのに、展開すると5倍に膨らむのは Haskell の static binary らしいところ)。eisvogel-ja の docker image が1.29GBなので、6分の1で済みます。出来上がりの PDF も、表紙情報・見出し・表・コードブロックとも文句なしでした。 結論 今日から pandoc + typst で生きていこうと思います。 凄いですね、ベルリン工科大 Martin Haug さんと Laurenz Mädje さん。「LaTeX のコンパイル遅すぎ・エラー不親切すぎ」に切れて作った、と?! あの Knuth 先生に挑むというその心意気に拍手喝采です。 そういえば、知人が何を使って md to pdf しているのか、気になっています。今度聞いてみます。]]></summary></entry><entry><title type="html">Unicode正規化とMySQLの照合順序は別物</title><link href="https://u-ryo.github.io/blog/2026/08/05/unicode-normalization-vs-mysql-collation/" rel="alternate" type="text/html" title="Unicode正規化とMySQLの照合順序は別物" /><published>2026-08-05T12:27:00+09:00</published><updated>2026-08-05T12:27:00+09:00</updated><id>https://u-ryo.github.io/blog/2026/08/05/unicode-normalization-vs-mysql-collation</id><content type="html" xml:base="https://u-ryo.github.io/blog/2026/08/05/unicode-normalization-vs-mysql-collation/"><![CDATA[<p>データベースの検索仕様を考えていると、「正規化」という言葉が2つの意味で会議を飛び交うことがあります。RDB 設計の正規形 (第1〜第3正規形のあれ) と、Unicode の正規化 (NFC/NFD) です。名前が同じだけで完全に別物なんですけれど、話が混線したまま「DB がよしなにやってくれるのでは」という期待だけが残ることがあります。</p>

<p>結論から書きますと、MySQL は Unicode 正規化を「やってくれない」です。代わりに照合順序 (collation) が「何と何を同じとみなすか」を決めているんですけど、これは正規化の代替ではないですし、時々予想外のものまで同じにしてしまいます。手を動かして確かめたので、その記録、です。</p>

<h2 id="用語の整理">用語の整理</h2>

<ul>
  <li><strong>RDB の正規化 (normalization)</strong>: テーブル設計から冗長性を取り除く設計論。今日の話には出てきません。</li>
  <li><strong>Unicode の正規化 (normalization)</strong>: 同じ見た目の文字を同じバイト列に揃える変換。「ポ」は1文字 (U+30DD) でも「ホ + 結合半濁点」(U+30DB U+309A) でも表現でき、前者に揃えるのが NFC、後者に分解するのが NFD。macOS のファイル名経由で NFD の文字列が混入してくる、というのが実務での典型的な出会い方だと思います。</li>
  <li><strong>照合順序 (collation)</strong>: DB が文字列を比較・整列するときの「一致と順序の定義」。</li>
</ul>

<h2 id="実験">実験</h2>

<p>MySQL 8.0.46 (Docker公式image) で、NFC の「ポ」と NFD の「ポ」を各照合順序で比較してみます。文字列リテラルだとエディタやシェルが勝手に正規化しかねないので、バイト列を明示します。</p>

<figure class="code"><div class="highlight"><pre><span></span><span class="c1">-- NFC の「ポ」(U+30DD) と NFD の「ホ+結合半濁点」(U+30DB U+309A)</span>
<span class="k">SELECT</span><span class="w"> </span><span class="k">CHAR_LENGTH</span><span class="p">(</span><span class="k">CONVERT</span><span class="p">(</span><span class="n">X</span><span class="s1">&#39;E3839D&#39;</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">utf8mb4</span><span class="p">))</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">nfc_char_len</span><span class="p">,</span>
<span class="w">       </span><span class="k">CHAR_LENGTH</span><span class="p">(</span><span class="k">CONVERT</span><span class="p">(</span><span class="n">X</span><span class="s1">&#39;E3839BE3829A&#39;</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">utf8mb4</span><span class="p">))</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">nfd_char_len</span><span class="p">;</span>
</pre></div></figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+--------------+--------------+
| nfc_char_len | nfd_char_len |
+--------------+--------------+
|            1 |            2 |
+--------------+--------------+
</code></pre></div></div>

<p>文字数からして違う、正真正銘の別文字列です。これを4つの照合順序で比較しますと:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">CONVERT</span><span class="p">(</span><span class="n">X</span><span class="s1">'E3839D'</span> <span class="k">USING</span> <span class="n">utf8mb4</span><span class="p">)</span> <span class="o">=</span> <span class="k">CONVERT</span><span class="p">(</span><span class="n">X</span><span class="s1">'E3839BE3829A'</span> <span class="k">USING</span> <span class="n">utf8mb4</span><span class="p">)</span>
       <span class="k">COLLATE</span> <span class="n">utf8mb4_bin</span>        <span class="k">AS</span> <span class="n">bin_eq</span><span class="p">;</span>        <span class="c1">-- 0</span>
<span class="k">SELECT</span> <span class="p">...</span> <span class="k">COLLATE</span> <span class="n">utf8mb4_general_ci</span> <span class="k">AS</span> <span class="n">general_ci_eq</span><span class="p">;</span> <span class="c1">-- 0</span>
<span class="k">SELECT</span> <span class="p">...</span> <span class="k">COLLATE</span> <span class="n">utf8mb4_unicode_ci</span> <span class="k">AS</span> <span class="n">unicode_ci_eq</span><span class="p">;</span> <span class="c1">-- 1</span>
<span class="k">SELECT</span> <span class="p">...</span> <span class="k">COLLATE</span> <span class="n">utf8mb4_0900_ai_ci</span> <span class="k">AS</span> <span class="n">ai_ci_eq</span><span class="p">;</span>      <span class="c1">-- 1</span>
<span class="k">SELECT</span> <span class="p">...</span> <span class="k">COLLATE</span> <span class="n">utf8mb4_0900_as_cs</span> <span class="k">AS</span> <span class="n">as_cs_eq</span><span class="p">;</span>      <span class="c1">-- 1</span>
</code></pre></div></div>

<table style="border-collapse:collapse">
  <tr><th style="border:1px solid #ccc;padding:4px 8px">照合順序</th><th style="border:1px solid #ccc;padding:4px 8px">NFC「ポ」= NFD「ポ」</th></tr>
  <tr><td style="border:1px solid #ccc;padding:4px 8px"><code>utf8mb4_bin</code></td><td style="border:1px solid #ccc;padding:4px 8px">一致しない</td></tr>
  <tr><td style="border:1px solid #ccc;padding:4px 8px"><code>utf8mb4_general_ci</code></td><td style="border:1px solid #ccc;padding:4px 8px">一致しない</td></tr>
  <tr><td style="border:1px solid #ccc;padding:4px 8px"><code>utf8mb4_unicode_ci</code></td><td style="border:1px solid #ccc;padding:4px 8px">一致する</td></tr>
  <tr><td style="border:1px solid #ccc;padding:4px 8px"><code>utf8mb4_0900_ai_ci</code></td><td style="border:1px solid #ccc;padding:4px 8px">一致する</td></tr>
  <tr><td style="border:1px solid #ccc;padding:4px 8px"><code>utf8mb4_0900_as_cs</code></td><td style="border:1px solid #ccc;padding:4px 8px">一致する</td></tr>
</table>

<p>Unicode Collation Algorithm (UCA) に基づく照合順序 (<code class="language-plaintext highlighter-rouge">unicode_ci</code>、0900系) は、正規化の違いを比較時に吸収します。アクセントも大文字小文字も区別する <code class="language-plaintext highlighter-rouge">utf8mb4_0900_as_cs</code> ですら一致させるのは少し意外でした —– これは「正規化して等しいもの (正準等価) は等しい」という UCA の原則によるもので、accent-insensitivity とは別の話です。一方、旧世代の <code class="language-plaintext highlighter-rouge">utf8mb4_general_ci</code> は符号位置ごとの単純な重み比較なので吸収しません。</p>

<h2 id="では照合順序に任せてよいか">では照合順序に任せてよいか</h2>

<p>よくない、です。照合順序は「一致の定義」を丸ごと入れ替えるダイヤルであって、正規化の違いだけを吸収する装置ではありません。MySQL 8.0 のデフォルトである <code class="language-plaintext highlighter-rouge">utf8mb4_0900_ai_ci</code> は accent-insensitive、つまり「アクセント記号を無視する」のですが、日本語では濁点・半濁点がアクセント扱いになります:</p>

<figure class="code"><div class="highlight"><pre><span></span><span class="k">SELECT</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;はは&#39;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;ぱぱ&#39;</span><span class="w"> </span><span class="k">COLLATE</span><span class="w"> </span><span class="n">utf8mb4_0900_ai_ci</span><span class="p">;</span><span class="w"> </span><span class="c1">-- 1</span>
<span class="k">SELECT</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;はは&#39;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;ばば&#39;</span><span class="w"> </span><span class="k">COLLATE</span><span class="w"> </span><span class="n">utf8mb4_0900_ai_ci</span><span class="p">;</span><span class="w"> </span><span class="c1">-- 1</span>
<span class="k">SELECT</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;が&#39;</span><span class="w">   </span><span class="o">=</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;か&#39;</span><span class="w">   </span><span class="k">COLLATE</span><span class="w"> </span><span class="n">utf8mb4_0900_ai_ci</span><span class="p">;</span><span class="w"> </span><span class="c1">-- 1</span>
<span class="k">SELECT</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;はは&#39;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">_utf8mb4</span><span class="s1">&#39;ばば&#39;</span><span class="w"> </span><span class="k">COLLATE</span><span class="w"> </span><span class="n">utf8mb4_0900_as_ci</span><span class="p">;</span><span class="w"> </span><span class="c1">-- 0</span>
</pre></div></figure>

<p>母 (はは) と父 (ぱぱ)、婆 (ばば) が一致します。デフォルトのまま日本語データに UNIQUE 制約や検索を載せると、この定義で「同じ」が判定されます。逆に <code class="language-plaintext highlighter-rouge">general_ci</code> はこれらを区別しますが、<code class="language-plaintext highlighter-rouge">'ABC' = 'abc'</code> は一致させます (case-insensitive)。つまり:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">general_ci</code>: NFC/NFD は別物扱い、大小文字は同一視</li>
  <li><code class="language-plaintext highlighter-rouge">0900_ai_ci</code>: NFC/NFD は同一視、濁点も大小文字も同一視</li>
  <li><code class="language-plaintext highlighter-rouge">0900_as_cs</code>: NFC/NFD は同一視、濁点も大小文字も区別</li>
</ul>

<p>「どの差を無視してどの差を区別するか」のセットを選んでいるのであって、単体で仕様の要求と一致することはまず期待できません。</p>

<h2 id="mysqlに正規化関数は無い">MySQLに正規化関数は無い</h2>

<p>ならば比較の前に正規化すればよいのですが、MySQLにはその関数がありません:</p>

<figure class="code"><div class="highlight"><pre><span></span><span class="k">SELECT</span><span class="w"> </span><span class="n">NORMALIZE</span><span class="p">(</span><span class="n">_utf8mb4</span><span class="s1">&#39;ポ&#39;</span><span class="p">);</span>
</pre></div></figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR 1305 (42000): FUNCTION mysql.NORMALIZE does not exist
</code></pre></div></div>

<p>PostgreSQL には 13 から <code class="language-plaintext highlighter-rouge">normalize()</code> があるので、これは MySQL 側の欠落です。つまり MySQL では、<strong>正規化はデータベースに入る前 (アプリケーション層) で行うしかありません</strong>。</p>

<h2 id="まとめ">まとめ</h2>

<ul>
  <li>Unicode 正規化と照合順序は別のレイヤーの仕組み。照合順序は正規化の代替にならない</li>
  <li>保存前に NFC へ正規化する層をアプリケーション側に置く。DB には正規化済みの文字列だけを入れる</li>
  <li>検索や UNIQUE 制約の「一致」が、使っている照合順序のどの定義に当たるのかを仕様として明文化する。デフォルト (<code class="language-plaintext highlighter-rouge">0900_ai_ci</code>) は日本語では「はは = ぱぱ」であることを知った上で選ぶ</li>
  <li>検証環境: MySQL 8.0.46 (Docker)。この記事の SQL と出力は全て実行結果そのまま</li>
</ul>]]></content><author><name>u-ryo</name></author><category term="mysql" /><category term="unicode" /><category term="database" /><summary type="html"><![CDATA[データベースの検索仕様を考えていると、「正規化」という言葉が2つの意味で会議を飛び交うことがあります。RDB 設計の正規形 (第1〜第3正規形のあれ) と、Unicode の正規化 (NFC/NFD) です。名前が同じだけで完全に別物なんですけれど、話が混線したまま「DB がよしなにやってくれるのでは」という期待だけが残ることがあります。 結論から書きますと、MySQL は Unicode 正規化を「やってくれない」です。代わりに照合順序 (collation) が「何と何を同じとみなすか」を決めているんですけど、これは正規化の代替ではないですし、時々予想外のものまで同じにしてしまいます。手を動かして確かめたので、その記録、です。 用語の整理 RDB の正規化 (normalization): テーブル設計から冗長性を取り除く設計論。今日の話には出てきません。 Unicode の正規化 (normalization): 同じ見た目の文字を同じバイト列に揃える変換。「ポ」は1文字 (U+30DD) でも「ホ + 結合半濁点」(U+30DB U+309A) でも表現でき、前者に揃えるのが NFC、後者に分解するのが NFD。macOS のファイル名経由で NFD の文字列が混入してくる、というのが実務での典型的な出会い方だと思います。 照合順序 (collation): DB が文字列を比較・整列するときの「一致と順序の定義」。 実験 MySQL 8.0.46 (Docker公式image) で、NFC の「ポ」と NFD の「ポ」を各照合順序で比較してみます。文字列リテラルだとエディタやシェルが勝手に正規化しかねないので、バイト列を明示します。 -- NFC の「ポ」(U+30DD) と NFD の「ホ+結合半濁点」(U+30DB U+309A) SELECT CHAR_LENGTH(CONVERT(X&#39;E3839D&#39; USING utf8mb4)) AS nfc_char_len, CHAR_LENGTH(CONVERT(X&#39;E3839BE3829A&#39; USING utf8mb4)) AS nfd_char_len; +--------------+--------------+ | nfc_char_len | nfd_char_len | +--------------+--------------+ | 1 | 2 | +--------------+--------------+ 文字数からして違う、正真正銘の別文字列です。これを4つの照合順序で比較しますと: SELECT CONVERT(X'E3839D' USING utf8mb4) = CONVERT(X'E3839BE3829A' USING utf8mb4) COLLATE utf8mb4_bin AS bin_eq; -- 0 SELECT ... COLLATE utf8mb4_general_ci AS general_ci_eq; -- 0 SELECT ... COLLATE utf8mb4_unicode_ci AS unicode_ci_eq; -- 1 SELECT ... COLLATE utf8mb4_0900_ai_ci AS ai_ci_eq; -- 1 SELECT ... COLLATE utf8mb4_0900_as_cs AS as_cs_eq; -- 1 照合順序NFC「ポ」= NFD「ポ」 utf8mb4_bin一致しない utf8mb4_general_ci一致しない utf8mb4_unicode_ci一致する utf8mb4_0900_ai_ci一致する utf8mb4_0900_as_cs一致する Unicode Collation Algorithm (UCA) に基づく照合順序 (unicode_ci、0900系) は、正規化の違いを比較時に吸収します。アクセントも大文字小文字も区別する utf8mb4_0900_as_cs ですら一致させるのは少し意外でした —– これは「正規化して等しいもの (正準等価) は等しい」という UCA の原則によるもので、accent-insensitivity とは別の話です。一方、旧世代の utf8mb4_general_ci は符号位置ごとの単純な重み比較なので吸収しません。 では照合順序に任せてよいか よくない、です。照合順序は「一致の定義」を丸ごと入れ替えるダイヤルであって、正規化の違いだけを吸収する装置ではありません。MySQL 8.0 のデフォルトである utf8mb4_0900_ai_ci は accent-insensitive、つまり「アクセント記号を無視する」のですが、日本語では濁点・半濁点がアクセント扱いになります: SELECT _utf8mb4&#39;はは&#39; = _utf8mb4&#39;ぱぱ&#39; COLLATE utf8mb4_0900_ai_ci; -- 1 SELECT _utf8mb4&#39;はは&#39; = _utf8mb4&#39;ばば&#39; COLLATE utf8mb4_0900_ai_ci; -- 1 SELECT _utf8mb4&#39;が&#39; = _utf8mb4&#39;か&#39; COLLATE utf8mb4_0900_ai_ci; -- 1 SELECT _utf8mb4&#39;はは&#39; = _utf8mb4&#39;ばば&#39; COLLATE utf8mb4_0900_as_ci; -- 0 母 (はは) と父 (ぱぱ)、婆 (ばば) が一致します。デフォルトのまま日本語データに UNIQUE 制約や検索を載せると、この定義で「同じ」が判定されます。逆に general_ci はこれらを区別しますが、'ABC' = 'abc' は一致させます (case-insensitive)。つまり: general_ci: NFC/NFD は別物扱い、大小文字は同一視 0900_ai_ci: NFC/NFD は同一視、濁点も大小文字も同一視 0900_as_cs: NFC/NFD は同一視、濁点も大小文字も区別 「どの差を無視してどの差を区別するか」のセットを選んでいるのであって、単体で仕様の要求と一致することはまず期待できません。 MySQLに正規化関数は無い ならば比較の前に正規化すればよいのですが、MySQLにはその関数がありません: SELECT NORMALIZE(_utf8mb4&#39;ポ&#39;); ERROR 1305 (42000): FUNCTION mysql.NORMALIZE does not exist PostgreSQL には 13 から normalize() があるので、これは MySQL 側の欠落です。つまり MySQL では、正規化はデータベースに入る前 (アプリケーション層) で行うしかありません。 まとめ Unicode 正規化と照合順序は別のレイヤーの仕組み。照合順序は正規化の代替にならない 保存前に NFC へ正規化する層をアプリケーション側に置く。DB には正規化済みの文字列だけを入れる 検索や UNIQUE 制約の「一致」が、使っている照合順序のどの定義に当たるのかを仕様として明文化する。デフォルト (0900_ai_ci) は日本語では「はは = ぱぱ」であることを知った上で選ぶ 検証環境: MySQL 8.0.46 (Docker)。この記事の SQL と出力は全て実行結果そのまま]]></summary></entry><entry><title type="html">markdown to pdf on CLI</title><link href="https://u-ryo.github.io/blog/2023/12/11/markdown-to-pdf-on-cli/" rel="alternate" type="text/html" title="markdown to pdf on CLI" /><published>2023-12-11T22:11:00+09:00</published><updated>2023-12-11T22:11:00+09:00</updated><id>https://u-ryo.github.io/blog/2023/12/11/markdown-to-pdf-on-cli</id><content type="html" xml:base="https://u-ryo.github.io/blog/2023/12/11/markdown-to-pdf-on-cli/"><![CDATA[<p>markdownで快調に書いたものを、「印刷して提出せよ」ということになっていたので、「へ?」とか思って。今時ねぇ、印刷なんてねぇ、何の意味があるのか。一応evidenceってことなんだろうけど。</p>

<p>調べると、意外とないんですよね日本語markdown to pdf。popularなのはMS Visual Studio Codeの拡張機能Markdown PDF使うものですけど、たったそれだけのためにVisual Studio Code入れたかぁないですし。
nodeの<code class="language-plaintext highlighter-rouge">markdown-pdf</code>や<code class="language-plaintext highlighter-rouge">md-to-pdf</code>とかサクッと出来そうだったんですけど、古いのか、どうしようもないエラーが出ます。</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-it</span> <span class="nt">-v</span> <span class="nv">$PWD</span>:/work <span class="nt">-w</span> /work <span class="nt">-e</span> <span class="nv">https_proxy</span><span class="o">=</span>http://192.168.120.1:3128 node:alpine sh
<span class="c"># npm i -g markdown-pdf</span>
<span class="c"># markdown-pdf some.md</span>
node:events:497
      throw er<span class="p">;</span> // Unhandled <span class="s1">'error'</span> event
      ^

Error: spawn /usr/local/lib/node_modules/markdown-pdf/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT
  :
<span class="c"># npm i -g md-to-pdf</span>
<span class="c"># md-to-pdf some.md</span>
<span class="o">(</span>node:29<span class="o">)</span> <span class="o">[</span>DEP0040] DeprecationWarning: The <span class="sb">`</span>punycode<span class="sb">`</span> module is deprecated. Please use a userland alternative instead.
<span class="o">(</span>Use <span class="sb">`</span>node <span class="nt">--trace-deprecation</span> ...<span class="sb">`</span> to show where the warning was created<span class="o">)</span>

  Puppeteer old Headless deprecation warning:
    :

  ✖ generating PDF from some.md
    → TROUBLESHOOTING: https://pptr.dev/troubleshooting
ListrError: Something went wrong
    at /usr/local/lib/node_modules/md-to-pdf/node_modules/listr/index.js:102:18
     :
</code></pre></div></div>

<p>pandocもいいんですが、texの環境色々入れるのがかったるいなー。
というわけで、docker baseで。
探すと、参考文献がありました。proxyも使わないとならないので。</p>

<ul>
  <li><a href="https://qiita.com/kojix2/items/1d2db46858ce202628d2">楽にDockerで日本語Pandocする</a></li>
  <li><a href="https://qiita.com/yyamnk/items/2da2791bcee82643984f">[proxy対応]brewとtlmgrによるTeX環境構築 (ヒラギノ, times埋め込み設定)</a></li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ PROXY</span><span class="o">=</span>http://192.168.120.1:3128
<span class="nv">$ </span>docker run <span class="nt">--rm</span> <span class="nt">-it</span> <span class="nt">-e</span> <span class="nv">https_proxy</span><span class="o">=</span><span class="nv">$PROXY</span> <span class="nt">-e</span> <span class="nv">http_proxy</span><span class="o">=</span><span class="nv">$PROXY</span> <span class="nt">-e</span> <span class="nv">ftp_proxy</span><span class="o">=</span><span class="nv">$PROXY</span> <span class="nt">-e</span> <span class="nv">use_proxy</span><span class="o">=</span><span class="nb">yes</span> <span class="nt">-v</span> <span class="nv">$PWD</span>:/data <span class="nt">--entrypoint</span> sh pandoc/latex
/data <span class="c"># tlmgr install collection-langjapanese</span>
   :
tlmgr: package repository ftp://tug.org/historic/systems/texlive/2022/tlnet-final <span class="o">(</span>not verified: valid signature with expired key<span class="o">)</span>
   :
<span class="o">[</span>108/108, 22:16/22:16] <span class="nb">install</span>: collection-langjapanese <span class="o">[</span>1k]
running mktexlsr ...
   :
/data <span class="c"># pandoc some.md -o some.pdf -C --pdf-engine=lualatex -V linkcolor=blue -V documentclass=ltjsarticle -V luatexjapresetoptions=fonts-noto-cjk</span>
</code></pre></div></div>

<ul>
  <li>何か今どきftpで取りに行くので、<code class="language-plaintext highlighter-rouge">ftp_proxy</code>が必要</li>
  <li><code class="language-plaintext highlighter-rouge">use_proxy=yes</code>もないとproxy経由と認識してくれなかった</li>
  <li>working directoryはdefaultで<code class="language-plaintext highlighter-rouge">/data</code>なのでそこにmountする</li>
  <li><code class="language-plaintext highlighter-rouge">entrypoint</code>が入っているので、optionで上書きしないと<code class="language-plaintext highlighter-rouge">tlmgr</code>を動かせない</li>
  <li><code class="language-plaintext highlighter-rouge">pandoc/core</code>ではなく<code class="language-plaintext highlighter-rouge">pandoc/latex</code>であることに留意
    <ul>
      <li>500MB超とsizeも結構ある</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">tlmgr install collection-langjapanese</code>は22分かかった
    <ul>
      <li>総計30分弱はかかる</li>
    </ul>
  </li>
  <li>基本的には<code class="language-plaintext highlighter-rouge">pandoc md_file -o pdf_file</code>だが、<code class="language-plaintext highlighter-rouge">--pdf-engine</code>に<code class="language-plaintext highlighter-rouge">lualatex</code>を指定したり<code class="language-plaintext highlighter-rouge">documentclass</code>に<code class="language-plaintext highlighter-rouge">ltjsarticle</code>を指定したり、日本語特有の指定が必要</li>
  <li><code class="language-plaintext highlighter-rouge">###</code>が本文と同じ大きさの文字になるので、思っているよりlevelを1段階上げとかないと、思ったような出力にならない</li>
</ul>]]></content><author><name>u-ryo</name></author><category term="markdown" /><category term="pdf" /><category term="docker" /><summary type="html"><![CDATA[markdownで快調に書いたものを、「印刷して提出せよ」ということになっていたので、「へ?」とか思って。今時ねぇ、印刷なんてねぇ、何の意味があるのか。一応evidenceってことなんだろうけど。 調べると、意外とないんですよね日本語markdown to pdf。popularなのはMS Visual Studio Codeの拡張機能Markdown PDF使うものですけど、たったそれだけのためにVisual Studio Code入れたかぁないですし。 nodeのmarkdown-pdfやmd-to-pdfとかサクッと出来そうだったんですけど、古いのか、どうしようもないエラーが出ます。 docker run --rm -it -v $PWD:/work -w /work -e https_proxy=http://192.168.120.1:3128 node:alpine sh # npm i -g markdown-pdf # markdown-pdf some.md node:events:497 throw er; // Unhandled 'error' event ^ Error: spawn /usr/local/lib/node_modules/markdown-pdf/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT : # npm i -g md-to-pdf # md-to-pdf some.md (node:29) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. (Use `node --trace-deprecation ...` to show where the warning was created) Puppeteer old Headless deprecation warning: : ✖ generating PDF from some.md → TROUBLESHOOTING: https://pptr.dev/troubleshooting ListrError: Something went wrong at /usr/local/lib/node_modules/md-to-pdf/node_modules/listr/index.js:102:18 : pandocもいいんですが、texの環境色々入れるのがかったるいなー。 というわけで、docker baseで。 探すと、参考文献がありました。proxyも使わないとならないので。 楽にDockerで日本語Pandocする [proxy対応]brewとtlmgrによるTeX環境構築 (ヒラギノ, times埋め込み設定) $ PROXY=http://192.168.120.1:3128 $ docker run --rm -it -e https_proxy=$PROXY -e http_proxy=$PROXY -e ftp_proxy=$PROXY -e use_proxy=yes -v $PWD:/data --entrypoint sh pandoc/latex /data # tlmgr install collection-langjapanese : tlmgr: package repository ftp://tug.org/historic/systems/texlive/2022/tlnet-final (not verified: valid signature with expired key) : [108/108, 22:16/22:16] install: collection-langjapanese [1k] running mktexlsr ... : /data # pandoc some.md -o some.pdf -C --pdf-engine=lualatex -V linkcolor=blue -V documentclass=ltjsarticle -V luatexjapresetoptions=fonts-noto-cjk 何か今どきftpで取りに行くので、ftp_proxyが必要 use_proxy=yesもないとproxy経由と認識してくれなかった working directoryはdefaultで/dataなのでそこにmountする entrypointが入っているので、optionで上書きしないとtlmgrを動かせない pandoc/coreではなくpandoc/latexであることに留意 500MB超とsizeも結構ある tlmgr install collection-langjapaneseは22分かかった 総計30分弱はかかる 基本的にはpandoc md_file -o pdf_fileだが、--pdf-engineにlualatexを指定したりdocumentclassにltjsarticleを指定したり、日本語特有の指定が必要 ###が本文と同じ大きさの文字になるので、思っているよりlevelを1段階上げとかないと、思ったような出力にならない]]></summary></entry><entry><title type="html">Get Refresh Token for Google API</title><link href="https://u-ryo.github.io/blog/2023/09/10/get-refresh-token-for-google-api/" rel="alternate" type="text/html" title="Get Refresh Token for Google API" /><published>2023-09-10T15:03:00+09:00</published><updated>2023-09-10T15:03:00+09:00</updated><id>https://u-ryo.github.io/blog/2023/09/10/get-refresh-token-for-google-api</id><content type="html" xml:base="https://u-ryo.github.io/blog/2023/09/10/get-refresh-token-for-google-api/"><![CDATA[<p>何かいつの間にかGoogle APIの認証方法が変わってて、security上の理由からというので仕方ないんでしょうけど。</p>

<p><code class="language-plaintext highlighter-rouge">AUTHORIZATION_CODE</code>を得ようと、以前のように↓のURLにaccessすると、
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&amp;client_id=…apps.googleusercontent.com&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;scope=https://www.googleapis.com/auth/photoslibrary&amp;access_type=offline
<code class="language-plaintext highlighter-rouge">アクセスをブロック: ... のリクエストは無効です</code></p>

<p>と出て、あれ?!と。</p>

<blockquote>
  <p>エラー 400: invalid_request
The out-of-band (OOB) flow has been blocked in order to keep users secure. Follow the Out-of-Band (OOB) flow migration guide linked in the developer docs below to migrate your app to an alternative method.</p>
</blockquote>

<p>で、<a href="https://developers.google.com/identity/protocols/oauth2/resources/oob-migration?hl=ja">帯域外（OOB）フロー移行ガイド</a>へ誘導され。</p>

<p>このoobフローはなくなったと!? んじゃどうせぃっていうんじゃねん。</p>

<p>「デスクトップクライアント」に相当するから、<a href="https://developers.google.com/identity/protocols/oauth2/native-app?hl=ja#redirect-uri_loopback">ループバック IP アドレス（localhost または 127.0.0.1）フロー</a>に飛ばされたものの、こっちもなくなってて、<a href="https://developers.google.com/identity/protocols/oauth2/resources/loopback-migration?hl=ja">ループバック IP アドレスフローの移行ガイド</a>に飛ばされて、結局library使う方法しか書いてなくて。こちとら、shell scriptで使いたいんですけど。<code class="language-plaintext highlighter-rouge">refresh_token</code>欲しいだけなのに、なんでこんなに苦労せなかんの??🤔</p>

<p>…って嘆いても仕方ないので、library使って<code class="language-plaintext highlighter-rouge">refresh_token</code>取得だけします。</p>

<p>ちょっとめんどくさいんですけど、local環境を汚染しない形で。</p>

<p>参考:</p>

<ul>
  <li><a href="https://qiita.com/sey323/items/4c7140efaed750e690eb">PythonからGooglePhotoに画像や動画をアップロード</a></li>
  <li><a href="https://qiita.com/sey323/items/875c0ab1585044772ab2#%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BD%9C%E6%88%90">PythonからGoogleDriveにファイルをアップロード</a></li>
</ul>

<p>Google APIの<a href="https://console.cloud.google.com/apis/credentials">認証情報のpage</a>から「OAuthクライアントをダウンロード」して<code class="language-plaintext highlighter-rouge">client_secret.json</code>として保存。</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">--rm</span> <span class="nt">-it</span> <span class="nt">-v</span> <span class="nv">$PWD</span>/client_secret_....json:/root/client_secret.json python:alpine sh
<span class="c"># apk add w3m screen</span>
<span class="c"># pip install google-api-python-client google-auth-oauthlib</span>
<span class="c"># screen</span>
<span class="c"># python</span>
<span class="o">&gt;&gt;&gt;</span> from google_auth_oauthlib.flow import InstalledAppFlow
<span class="o">&gt;&gt;&gt;</span> <span class="nv">credential</span><span class="o">=</span>InstalledAppFlow.from_client_secrets_file<span class="o">(</span><span class="s1">'client_secret.json'</span>,[<span class="s2">"https://www.googleapis.com/auth/photoslibrary.appendonly"</span><span class="o">])</span>.run_local_server<span class="o">()</span>
</code></pre></div></div>

<p>ここで<code class="language-plaintext highlighter-rouge">w3m</code>が開くので、<code class="language-plaintext highlighter-rouge">Q</code>を押して閉じるとURLが表示され、そこをChromeなりで開いて、突き進んで承認します。</p>

<p>そうすると、http://localhost:8080/?state=…. へ回されて止まるので、<code class="language-plaintext highlighter-rouge">C-a</code>で別screenを開いて、そこで<code class="language-plaintext highlighter-rouge">w3m 'http://localhost:8080/?state=...'</code>とすると、<code class="language-plaintext highlighter-rouge">The authentication flow has completed. You may close this window.</code>と言われます。徐に元のscreenに戻ると<code class="language-plaintext highlighter-rouge">credential</code> objectができているので、<code class="language-plaintext highlighter-rouge">credential.refresh_token</code>とすると、漸く<code class="language-plaintext highlighter-rouge">REFRESH_TOKEN</code>を得られます。1時間有効な<code class="language-plaintext highlighter-rouge">ACCESS_TOKEN</code>は<code class="language-plaintext highlighter-rouge">credentail.token</code>で得られます。</p>

<p>これらを使うと、やっと以前のようにshell scriptで回せるようになります。ふぅ。</p>]]></content><author><name>u-ryo</name></author><category term="google" /><category term="python" /><summary type="html"><![CDATA[何かいつの間にかGoogle APIの認証方法が変わってて、security上の理由からというので仕方ないんでしょうけど。 AUTHORIZATION_CODEを得ようと、以前のように↓のURLにaccessすると、 https://accounts.google.com/o/oauth2/v2/auth?response_type=code&amp;client_id=…apps.googleusercontent.com&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;scope=https://www.googleapis.com/auth/photoslibrary&amp;access_type=offline アクセスをブロック: ... のリクエストは無効です と出て、あれ?!と。 エラー 400: invalid_request The out-of-band (OOB) flow has been blocked in order to keep users secure. Follow the Out-of-Band (OOB) flow migration guide linked in the developer docs below to migrate your app to an alternative method. で、帯域外（OOB）フロー移行ガイドへ誘導され。 このoobフローはなくなったと!? んじゃどうせぃっていうんじゃねん。 「デスクトップクライアント」に相当するから、ループバック IP アドレス（localhost または 127.0.0.1）フローに飛ばされたものの、こっちもなくなってて、ループバック IP アドレスフローの移行ガイドに飛ばされて、結局library使う方法しか書いてなくて。こちとら、shell scriptで使いたいんですけど。refresh_token欲しいだけなのに、なんでこんなに苦労せなかんの??🤔 …って嘆いても仕方ないので、library使ってrefresh_token取得だけします。 ちょっとめんどくさいんですけど、local環境を汚染しない形で。 参考: PythonからGooglePhotoに画像や動画をアップロード PythonからGoogleDriveにファイルをアップロード Google APIの認証情報のpageから「OAuthクライアントをダウンロード」してclient_secret.jsonとして保存。 $ docker run --rm -it -v $PWD/client_secret_....json:/root/client_secret.json python:alpine sh # apk add w3m screen # pip install google-api-python-client google-auth-oauthlib # screen # python &gt;&gt;&gt; from google_auth_oauthlib.flow import InstalledAppFlow &gt;&gt;&gt; credential=InstalledAppFlow.from_client_secrets_file('client_secret.json',["https://www.googleapis.com/auth/photoslibrary.appendonly"]).run_local_server() ここでw3mが開くので、Qを押して閉じるとURLが表示され、そこをChromeなりで開いて、突き進んで承認します。 そうすると、http://localhost:8080/?state=…. へ回されて止まるので、C-aで別screenを開いて、そこでw3m 'http://localhost:8080/?state=...'とすると、The authentication flow has completed. You may close this window.と言われます。徐に元のscreenに戻るとcredential objectができているので、credential.refresh_tokenとすると、漸くREFRESH_TOKENを得られます。1時間有効なACCESS_TOKENはcredentail.tokenで得られます。 これらを使うと、やっと以前のようにshell scriptで回せるようになります。ふぅ。]]></summary></entry><entry><title type="html">NHK Taiga Staff reflection</title><link href="https://u-ryo.github.io/blog/2022/03/20/nhk-taiga-staff-reflection/" rel="alternate" type="text/html" title="NHK Taiga Staff reflection" /><published>2022-03-20T21:23:00+09:00</published><updated>2022-03-20T21:23:00+09:00</updated><id>https://u-ryo.github.io/blog/2022/03/20/nhk-taiga-staff-reflection</id><content type="html" xml:base="https://u-ryo.github.io/blog/2022/03/20/nhk-taiga-staff-reflection/"><![CDATA[<p><a href="https://matomebu.com/wadai/kamakura13jiko20220313/">大河ドラマ『鎌倉殿の13人』13日放送分に一眼カメラを構えたマスクおじさんスタッフ映り込み放送事故で公式が謝罪、視聴者「タイムスクープハンターの撮影班だ」#鎌倉殿の13人</a>
たまたまぼくも本放送、再放送どちらも録画してたので、よーく見てみました。</p>

<p><img src="/kamakuradonono13nin.png" alt="kamakuradonono13nin" /></p>

<p>12fps で 3コマ約0.2秒、確かによく見付けたなぁと思います。
再放送では編集されるとの由、どうなるんだろうと注視していましたら、
北条義時の走るシーンが例の場面直前まででカットされ、
その分佐竹側の武者達が降りてくる次のシーンが若干長くなっている、
というものでした。
両者足したものを作ってみたら確かに45分を0.2秒程越えてました。</p>]]></content><author><name>u-ryo</name></author><category term="NHK" /><category term="avi" /><summary type="html"><![CDATA[大河ドラマ『鎌倉殿の13人』13日放送分に一眼カメラを構えたマスクおじさんスタッフ映り込み放送事故で公式が謝罪、視聴者「タイムスクープハンターの撮影班だ」#鎌倉殿の13人 たまたまぼくも本放送、再放送どちらも録画してたので、よーく見てみました。 12fps で 3コマ約0.2秒、確かによく見付けたなぁと思います。 再放送では編集されるとの由、どうなるんだろうと注視していましたら、 北条義時の走るシーンが例の場面直前まででカットされ、 その分佐竹側の武者達が降りてくる次のシーンが若干長くなっている、 というものでした。 両者足したものを作ってみたら確かに45分を0.2秒程越えてました。]]></summary></entry><entry><title type="html">gradlew stopped</title><link href="https://u-ryo.github.io/blog/2021/07/11/gradlew-stopped/" rel="alternate" type="text/html" title="gradlew stopped" /><published>2021-07-11T01:02:00+09:00</published><updated>2021-07-11T01:02:00+09:00</updated><id>https://u-ryo.github.io/blog/2021/07/11/gradlew-stopped</id><content type="html" xml:base="https://u-ryo.github.io/blog/2021/07/11/gradlew-stopped/"><![CDATA[<p>実に久し振りにこのblogを書こうとして、</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./grainw create-post 'some title'
</code></pre></div></div>

<p>としたら、<code class="language-plaintext highlighter-rouge">gradle-1.8.jar</code>を取りに行って404 not found食らって動きません。
この<a href="https://github.com/sysgears/grain">grain</a>って古いしもう更新止まってるし、先見の明なかったなぁということなんですが、もうちょっと本気で原因究明です。
https://services.gradle.org/distributions/ はあるのになぜ404? と思ったら、<code class="language-plaintext highlighter-rouge">http://...</code>で取りに行ってました。gradlewのgradleが古すぎる?
<code class="language-plaintext highlighter-rouge">grainw</code>のどこで止まってるか、debug print入れて割り出すと、<code class="language-plaintext highlighter-rouge">./gradlew gendeps</code>でした。これをversion 6.8.1のgradleで手動でやってみる(<code class="language-plaintext highlighter-rouge">gradle gendeps</code>)と、<code class="language-plaintext highlighter-rouge">build.gradle</code>の<code class="language-plaintext highlighter-rouge">line: 78</code>で止まります。なんで?と思って調べると、<a href="https://qiita.com/Yu-s/items/13a6a8db8dc191bb3b42">「Cannot add task ‘wrapper’ as a task with that name already exists.」が出た</a>にあった通り、でした。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>task wrapper(type: Wrapper) {
</code></pre></div></div>

<p>を</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wrapper {
</code></pre></div></div>

<p>にすれば動きました。良かった。
<code class="language-plaintext highlighter-rouge">gradle wrapper</code>で<code class="language-plaintext highlighter-rouge">./gradlew</code>を更新して、<code class="language-plaintext highlighter-rouge">./grainw</code>も復活しました。</p>]]></content><author><name>u-ryo</name></author><category term="gradle" /><category term="grain" /><summary type="html"><![CDATA[実に久し振りにこのblogを書こうとして、 ./grainw create-post 'some title' としたら、gradle-1.8.jarを取りに行って404 not found食らって動きません。 このgrainって古いしもう更新止まってるし、先見の明なかったなぁということなんですが、もうちょっと本気で原因究明です。 https://services.gradle.org/distributions/ はあるのになぜ404? と思ったら、http://...で取りに行ってました。gradlewのgradleが古すぎる? grainwのどこで止まってるか、debug print入れて割り出すと、./gradlew gendepsでした。これをversion 6.8.1のgradleで手動でやってみる(gradle gendeps)と、build.gradleのline: 78で止まります。なんで?と思って調べると、「Cannot add task ‘wrapper’ as a task with that name already exists.」が出たにあった通り、でした。 task wrapper(type: Wrapper) { を wrapper { にすれば動きました。良かった。 gradle wrapperで./gradlewを更新して、./grainwも復活しました。]]></summary></entry><entry><title type="html">ansible for docker</title><link href="https://u-ryo.github.io/blog/2021/07/11/ansible-for-docker/" rel="alternate" type="text/html" title="ansible for docker" /><published>2021-07-11T00:18:00+09:00</published><updated>2021-07-11T00:18:00+09:00</updated><id>https://u-ryo.github.io/blog/2021/07/11/ansible-for-docker</id><content type="html" xml:base="https://u-ryo.github.io/blog/2021/07/11/ansible-for-docker/"><![CDATA[<p>ansibleのlocalでの開発環境といえばvagrant+virtualboxですが、dockerでいいんじゃねぇの?と思って色々試行錯誤してみました。何度も挫けて、もう駄目かと思いましたけど、幾つもの山を越え、ついに出来ましたですよ!</p>

<p>```yaml: docker.ini
[docker_host]
localhost</p>

<p>[container]
eiger-local</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
```yaml: playbooks/docker.yml
- hosts: localhost
  connection: local
  gather_facts: no
  vars:
    ansible_python_interpreter: /Users/ryo-umetsu/.anyenv/envs/pyenv/shims/python
    # ansible_python_interpreter: /usr/bin/python3
  tasks:
    - name: deploy amazonlinux docker
      docker_container:
        image: amazonlinux
        name: eiger-local
        volumes:
          - /sys/fs/cgroup:/sys/fs/cgroup:ro
        ports: 57017:27017
        expose: 57017
        tty: yes
        detach: yes
        privileged: yes
        # capabilities:
        #   - SYS_ADMIN
        # sysctl:
        #   vm.swappiness: 1
        command: sh -c 'yum -y install procps systemd-sysv file &amp;&amp; exec /sbin/init &amp;&amp; systemctl restart'
    - name: adding host
      add_host: name="eiger-local"
    - name: wait
      wait_for:
        delay: 10
        timeout: 10

- hosts: eiger-local
  connection: docker
  vars:
    setup_mongodb_password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
          39356533306331333763353433363335343961653335373762623630653361623034646232633837
          6164306666656436653636643333363261646662356666640a636462346635323164633365376637
          39303832396638336664653439326536386235323262303733653563653766353263623032386538
          3135623166613363630a343734666666323830663838653962663032386337626635396563383230
          6266
    # setup_mongodb_password: "test"
    ansible_python_interpreter: /usr/bin/python
  roles:
    - role: "../roles/create_data_directory"
    - role: "../roles/setup_mongodb"
    - role: "../roles/configure_logrotate"
    - role: "../roles/configure_swap"
</code></pre></div></div>

<p>凄いknow-how詰まってます。</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">connection: local</code>とすると<code class="language-plaintext highlighter-rouge">ssh</code>経由じゃなく直接command叩いてくれる</li>
  <li><code class="language-plaintext highlighter-rouge">connection: docker</code>とすると<code class="language-plaintext highlighter-rouge">docker exec</code>で叩いてくれる(のでtargetに<code class="language-plaintext highlighter-rouge">authorized_keys</code>とか<code class="language-plaintext highlighter-rouge">sshd</code>とか不要)</li>
  <li><code class="language-plaintext highlighter-rouge">localhost</code>では<code class="language-plaintext highlighter-rouge">gather_facts: no</code>とする(これをしないと<code class="language-plaintext highlighter-rouge">ansible_swaptotal_mb</code>とか<code class="language-plaintext highlighter-rouge">ansible_memtotal_mb</code>といった<a href="https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_variables.html#fact">システムから検出される変数: Fact (ファクト)</a>が埋められない)</li>
  <li><code class="language-plaintext highlighter-rouge">ansible_python_interpreter: /usr/bin/python3</code>は<code class="language-plaintext highlighter-rouge">python</code> binaryを探す順序があって、<code class="language-plaintext highlighter-rouge">/usr/bin/python</code>を先に見つけてしまい、version 2を使ってしまうのを防ぐため。Macだと<code class="language-plaintext highlighter-rouge">/usr/bin/python</code>がpython2で、python3は<code class="language-plaintext highlighter-rouge">/usr/bin/python3</code>なので。ここのversionが合わないと、<code class="language-plaintext highlighter-rouge">The error was: No module named 'requests'"</code>と言われて何が悪いのかさっぱりだった。基本的には<code class="language-plaintext highlighter-rouge">pip3 install docker</code>が足りなかったのだが、その他に、このようにansibleが自分で見つけて使っているpythonのversionと自分がcommand lineで使っていて<code class="language-plaintext highlighter-rouge">pip install</code>したpythonのversionがずれていると、このように言われる、ことがわかった。その他にもinventoryで指定することもcommand line optionで指定することも出来る。command line optionで<code class="language-plaintext highlighter-rouge">-e ansible_python_interpreter=/usr/bin/python3</code>とするのが最も柔軟だが、これはplaybooks中の値を全て上書きするので、やはり注意が必要だった。pythonってversionややこしくて…</li>
  <li><code class="language-plaintext highlighter-rouge">setup_mongodb_password: !vault |</code>は<a href="https://docs.ansible.com/ansible/2.9/user_guide/playbooks_vault.html#single-encrypted-variable">Single Encrypted Variable</a>で<code class="language-plaintext highlighter-rouge">echo -n test|ansible-vault encrypt_string --vault-password-file vault_password.txt --encrypt-vault-id default --stdin setup_mongodb_password</code>のように使う(<a href="https://qiita.com/park-jh/items/91bf48848e366c79a61b#variable%E3%82%921%E9%A0%85%E7%9B%AE%E3%81%A0%E3%81%91%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%99%E3%82%8B">ansible-vaultで暗号化しよう。</a>)</li>
  <li><code class="language-plaintext highlighter-rouge">docker_container:</code>はmodule名で、古い文献だと<code class="language-plaintext highlighter-rouge">docker:</code>になっていて今は効かないので注意。中で指定できるものは<a href="https://docs.ansible.com/ansible/latest/collections/community/docker/docker_container_module.html#ansible-collections-community-docker-docker-container-module">community.docker.docker_container – manage docker containers</a>に挙がっている。<code class="language-plaintext highlighter-rouge">privileged: yes</code>は、container内で<code class="language-plaintext highlighter-rouge">sysctl vm.swappiness=1</code>(defaultは60)をされるので。<code class="language-plaintext highlighter-rouge">sysctl:</code>というoptionもあるのだけれど、そこで指定できる(whitelistにある)のは<code class="language-plaintext highlighter-rouge">net.*</code>とか<code class="language-plaintext highlighter-rouge">kernel.*</code>の一部だけだそう(<a href="https://docs.docker.jp/engine/reference/commandline/run.html#currently-supprted-sysctls">サポート中の sysctl</a>)。</li>
  <li><code class="language-plaintext highlighter-rouge">command: sh -c 'yum -y install procps systemd-sysv file &amp;&amp; exec /sbin/init'</code>は、playbook中で<code class="language-plaintext highlighter-rouge">systemctl</code>を設定するものがあるから。<code class="language-plaintext highlighter-rouge">Restart=always</code>にしたいんですよね要するに。でも、そうするとdockerに<code class="language-plaintext highlighter-rouge">systemd</code>(<code class="language-plaintext highlighter-rouge">initd</code>)が動いていねばならず、そういえばdockerって普通はそんなのなかったな、どうやるんだ? <a href="https://qiita.com/mach3/items/33f2b234babe679c759f">調べる</a>と、
    <ul>
      <li><code class="language-plaintext highlighter-rouge">systemd-sysv</code>をinstallする必要</li>
      <li><code class="language-plaintext highlighter-rouge">/sys/fs/cgroup:/sys/fs/cgroup:ro</code>のdevice mountが必要
        <ul>
          <li>macにはそんなdirectoryはないのに大丈夫なのが不思議</li>
        </ul>
      </li>
      <li>最後は<code class="language-plaintext highlighter-rouge">/sbin/init</code></li>
      <li>↑これではcontainerが数秒で倒れるので更に調べると<code class="language-plaintext highlighter-rouge">exec /sbin/init</code>(これどこで見たんだっけなぁ感動したなぁStackOverflowだった気が。他では全然書いて無くて、よく見つけたもんだ)</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">wait_for:</code>がないと、docker内にまだ<code class="language-plaintext highlighter-rouge">yum install</code>とか終わりきってないのにどんどん次に進んでしまい、<code class="language-plaintext highlighter-rouge">service.name:mongod,service.enabled:true</code>の時に「<code class="language-plaintext highlighter-rouge">mongod</code>というserviceはない」といって落ちてしまうので。ホントはちゃんと何かを待ちたかったんだけど、何を待ったらいいのか不明でした。processかなと思ったんですが、processのwaitって<a href="https://www.middlewareinventory.com/blog/ansible-wait_for-examples/">pid fileの存在有無を確認する</a>みたいな。いゃー、<code class="language-plaintext highlighter-rouge">systemd</code>というか<code class="language-plaintext highlighter-rouge">init</code>ってpid fileなんてないんですけど。network port待つ例は数多あれど。仕方ないのでただ10秒待つように。<code class="language-plaintext highlighter-rouge">timeout:10</code>は不要かと思いきや、ないとずっと待ってました。</li>
  <li>これを<code class="language-plaintext highlighter-rouge">ansible-playbook -vvv -i docker.ini playbooks/docker.yml</code>と実行するんですが、dockerでやる場合は<code class="language-plaintext highlighter-rouge">ansible/ansible-runner</code>使って、<code class="language-plaintext highlighter-rouge">docker run --rm --name=ansible -v $PWD:/work -w /work -v /var/run/docker.sock:/var/run/docker.sock ansible/ansible-runner sh -c 'pip3 install docker&amp;yum install -y yum-utils;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo;yum install -y docker-ce docker-ce-cli containerd.io;ansible-playbook -i docker.ini playbooks/docker.yml'</code>と、CentOS8 baseのansible-runnerにdockerを入れねばならず、またunix socketを共有しないとなりませんでした。CentOS8に<code class="language-plaintext highlighter-rouge">yum install docker</code>するとpodmanが入りやがるんですよね。podmanってMacじゃ動かねぇっつーの! podmanだと<code class="language-plaintext highlighter-rouge">hosts:eiger-local</code>の<code class="language-plaintext highlighter-rouge">gather_facts</code>でDocker version checkに失敗して落ちるんですよね。<a href="https://discussion.fedoraproject.org/t/podman-broken-on-silverblue-podman-complains-kernel-not-supporting-overlay-fs-backing-file-system-is-unsupported-for-this-graph-driver/25403/2">Podman broken on Silverblue? Podman complains kernel not supporting overlay fs (backing file system is unsupported for this graph driver)</a>見て<code class="language-plaintext highlighter-rouge">sed -i "s/#mount_program/mount_program/" /etc/containers/storage.conf</code>加えても、<code class="language-plaintext highlighter-rouge">Error: mount /var/lib/containers/storage/overlay:/var/lib/containers/storage/overlay, flags: 0x1000: operation not permitted</code>。うーん。逆に、docker containerにansibleを入れる方がsimpleでした。即ち、<code class="language-plaintext highlighter-rouge">docker run --rm --name=ansible -v $PWD:/work -w /work -v /var/run/docker.sock:/var/run/docker.sock docker sh -c 'apk add ansible py3-pip;pip3 install docker;ansible-playbook -i docker.ini playbooks/docker.yml'</code>ですね。</li>
</ul>

<h2 id="参考サイト">参考サイト</h2>

<ul>
  <li><a href="https://techblog.gmo-ap.jp/2016/09/23/docker%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92ansible%E3%81%A7%E3%83%97%E3%83%AD%E3%83%93%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%97%E3%81%9F/">ansibleを使ってDockerコンテナをプロビジョニングしたお話</a></li>
  <li><a href="https://qiita.com/gosarami/items/e1600292aa260444d3df">ansible-playbookでdockerコンテナをプロビジョニングしてみた</a></li>
  <li><a href="https://inamuu.com/ansible%E3%81%A7docker%E3%81%B8%E3%83%97%E3%83%AD%E3%83%93%E3%82%B8%E3%83%A7%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B/">Ansibleでdockerへプロビジョニングする</a></li>
  <li><a href="https://techblog.gmo-ap.jp/wp-content/uploads/2016/09/77a5ba13cea567e2af8323cbfa5a2286.pdf">ansibleを使ってDockerコンテナをプロビジョニングする</a></li>
  <li><a href="https://docs.ansible.com/ansible/latest/collections/community/docker/docker_container_module.html#ansible-collections-community-docker-docker-container-module">community.docker.docker_container – manage docker containers</a></li>
  <li><a href="https://rheb.hatenablog.com/entry/ansible_interpreter_discovery">Ansible2.8の新機能1 - Interpreter Discovery</a></li>
  <li><a href="https://qiita.com/muddydixon/items/d2982ab0846002bf3ea8">Docker privileged オプションについて</a></li>
  <li><a href="https://qiita.com/osamunmun/items/1786aac5904439522d72">Dockerコンテナ上でsysctlでカーネルパラメータを変更する方法</a></li>
  <li><a href="https://docs.docker.jp/engine/reference/commandline/run.html#currently-supprted-sysctls">サポート中の sysctl</a></li>
  <li><a href="https://qiita.com/ma2shita/items/f1a68a3f909c5cee7869">Dockerコンテナ内のnet.core.somaxconnをいじる方法</a></li>
  <li><a href="https://blog.ymyzk.com/2017/01/docker-compose-sysctls/">Docker Compose でカーネルパラメータを設定する</a></li>
  <li><a href="https://moznion.hatenadiary.com/entry/2018/09/01/224626">Linux Kernel 4.12以前であればhostマシンのsysctlの値がDocker container環境に引き継がれるかどうか検証した</a></li>
  <li><a href="https://qiita.com/hiroyuki_onodera/items/398c1331b157e5b4b588">Ansible: ターゲットノードでpython3を使用するansible.cfg設定は、ansible_python_interpreterでは無くinterpreter_python</a></li>
  <li><a href="https://kamatama41.hatenablog.com/entry/2019/06/17/001009">DockerのUbuntuコンテナでsystemdを動かす</a></li>
  <li><a href="https://qiita.com/mach3/items/33f2b234babe679c759f">Docker amazonlinux2 で systemctl を使いたい人生だった</a></li>
  <li><a href="https://qiita.com/kawanet/items/d856b7b9eb8ceccac4d7">Docker for MacでAmazon Linux 2でsystemd</a></li>
  <li><a href="https://www.middlewareinventory.com/blog/ansible-wait_for-examples/">Ansible wait_for module examples</a></li>
</ul>]]></content><author><name>u-ryo</name></author><category term="docker" /><category term="ansible" /><summary type="html"><![CDATA[ansibleのlocalでの開発環境といえばvagrant+virtualboxですが、dockerでいいんじゃねぇの?と思って色々試行錯誤してみました。何度も挫けて、もう駄目かと思いましたけど、幾つもの山を越え、ついに出来ましたですよ! ```yaml: docker.ini [docker_host] localhost [container] eiger-local ```yaml: playbooks/docker.yml - hosts: localhost connection: local gather_facts: no vars: ansible_python_interpreter: /Users/ryo-umetsu/.anyenv/envs/pyenv/shims/python # ansible_python_interpreter: /usr/bin/python3 tasks: - name: deploy amazonlinux docker docker_container: image: amazonlinux name: eiger-local volumes: - /sys/fs/cgroup:/sys/fs/cgroup:ro ports: 57017:27017 expose: 57017 tty: yes detach: yes privileged: yes # capabilities: # - SYS_ADMIN # sysctl: # vm.swappiness: 1 command: sh -c 'yum -y install procps systemd-sysv file &amp;&amp; exec /sbin/init &amp;&amp; systemctl restart' - name: adding host add_host: name="eiger-local" - name: wait wait_for: delay: 10 timeout: 10 - hosts: eiger-local connection: docker vars: setup_mongodb_password: !vault | $ANSIBLE_VAULT;1.1;AES256 39356533306331333763353433363335343961653335373762623630653361623034646232633837 6164306666656436653636643333363261646662356666640a636462346635323164633365376637 39303832396638336664653439326536386235323262303733653563653766353263623032386538 3135623166613363630a343734666666323830663838653962663032386337626635396563383230 6266 # setup_mongodb_password: "test" ansible_python_interpreter: /usr/bin/python roles: - role: "../roles/create_data_directory" - role: "../roles/setup_mongodb" - role: "../roles/configure_logrotate" - role: "../roles/configure_swap" 凄いknow-how詰まってます。 connection: localとするとssh経由じゃなく直接command叩いてくれる connection: dockerとするとdocker execで叩いてくれる(のでtargetにauthorized_keysとかsshdとか不要) localhostではgather_facts: noとする(これをしないとansible_swaptotal_mbとかansible_memtotal_mbといったシステムから検出される変数: Fact (ファクト)が埋められない) ansible_python_interpreter: /usr/bin/python3はpython binaryを探す順序があって、/usr/bin/pythonを先に見つけてしまい、version 2を使ってしまうのを防ぐため。Macだと/usr/bin/pythonがpython2で、python3は/usr/bin/python3なので。ここのversionが合わないと、The error was: No module named 'requests'"と言われて何が悪いのかさっぱりだった。基本的にはpip3 install dockerが足りなかったのだが、その他に、このようにansibleが自分で見つけて使っているpythonのversionと自分がcommand lineで使っていてpip installしたpythonのversionがずれていると、このように言われる、ことがわかった。その他にもinventoryで指定することもcommand line optionで指定することも出来る。command line optionで-e ansible_python_interpreter=/usr/bin/python3とするのが最も柔軟だが、これはplaybooks中の値を全て上書きするので、やはり注意が必要だった。pythonってversionややこしくて… setup_mongodb_password: !vault |はSingle Encrypted Variableでecho -n test|ansible-vault encrypt_string --vault-password-file vault_password.txt --encrypt-vault-id default --stdin setup_mongodb_passwordのように使う(ansible-vaultで暗号化しよう。) docker_container:はmodule名で、古い文献だとdocker:になっていて今は効かないので注意。中で指定できるものはcommunity.docker.docker_container – manage docker containersに挙がっている。privileged: yesは、container内でsysctl vm.swappiness=1(defaultは60)をされるので。sysctl:というoptionもあるのだけれど、そこで指定できる(whitelistにある)のはnet.*とかkernel.*の一部だけだそう(サポート中の sysctl)。 command: sh -c 'yum -y install procps systemd-sysv file &amp;&amp; exec /sbin/init'は、playbook中でsystemctlを設定するものがあるから。Restart=alwaysにしたいんですよね要するに。でも、そうするとdockerにsystemd(initd)が動いていねばならず、そういえばdockerって普通はそんなのなかったな、どうやるんだ? 調べると、 systemd-sysvをinstallする必要 /sys/fs/cgroup:/sys/fs/cgroup:roのdevice mountが必要 macにはそんなdirectoryはないのに大丈夫なのが不思議 最後は/sbin/init ↑これではcontainerが数秒で倒れるので更に調べるとexec /sbin/init(これどこで見たんだっけなぁ感動したなぁStackOverflowだった気が。他では全然書いて無くて、よく見つけたもんだ) wait_for:がないと、docker内にまだyum installとか終わりきってないのにどんどん次に進んでしまい、service.name:mongod,service.enabled:trueの時に「mongodというserviceはない」といって落ちてしまうので。ホントはちゃんと何かを待ちたかったんだけど、何を待ったらいいのか不明でした。processかなと思ったんですが、processのwaitってpid fileの存在有無を確認するみたいな。いゃー、systemdというかinitってpid fileなんてないんですけど。network port待つ例は数多あれど。仕方ないのでただ10秒待つように。timeout:10は不要かと思いきや、ないとずっと待ってました。 これをansible-playbook -vvv -i docker.ini playbooks/docker.ymlと実行するんですが、dockerでやる場合はansible/ansible-runner使って、docker run --rm --name=ansible -v $PWD:/work -w /work -v /var/run/docker.sock:/var/run/docker.sock ansible/ansible-runner sh -c 'pip3 install docker&amp;yum install -y yum-utils;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo;yum install -y docker-ce docker-ce-cli containerd.io;ansible-playbook -i docker.ini playbooks/docker.yml'と、CentOS8 baseのansible-runnerにdockerを入れねばならず、またunix socketを共有しないとなりませんでした。CentOS8にyum install dockerするとpodmanが入りやがるんですよね。podmanってMacじゃ動かねぇっつーの! podmanだとhosts:eiger-localのgather_factsでDocker version checkに失敗して落ちるんですよね。Podman broken on Silverblue? Podman complains kernel not supporting overlay fs (backing file system is unsupported for this graph driver)見てsed -i "s/#mount_program/mount_program/" /etc/containers/storage.conf加えても、Error: mount /var/lib/containers/storage/overlay:/var/lib/containers/storage/overlay, flags: 0x1000: operation not permitted。うーん。逆に、docker containerにansibleを入れる方がsimpleでした。即ち、docker run --rm --name=ansible -v $PWD:/work -w /work -v /var/run/docker.sock:/var/run/docker.sock docker sh -c 'apk add ansible py3-pip;pip3 install docker;ansible-playbook -i docker.ini playbooks/docker.yml'ですね。 参考サイト ansibleを使ってDockerコンテナをプロビジョニングしたお話 ansible-playbookでdockerコンテナをプロビジョニングしてみた Ansibleでdockerへプロビジョニングする ansibleを使ってDockerコンテナをプロビジョニングする community.docker.docker_container – manage docker containers Ansible2.8の新機能1 - Interpreter Discovery Docker privileged オプションについて Dockerコンテナ上でsysctlでカーネルパラメータを変更する方法 サポート中の sysctl Dockerコンテナ内のnet.core.somaxconnをいじる方法 Docker Compose でカーネルパラメータを設定する Linux Kernel 4.12以前であればhostマシンのsysctlの値がDocker container環境に引き継がれるかどうか検証した Ansible: ターゲットノードでpython3を使用するansible.cfg設定は、ansible_python_interpreterでは無くinterpreter_python DockerのUbuntuコンテナでsystemdを動かす Docker amazonlinux2 で systemctl を使いたい人生だった Docker for MacでAmazon Linux 2でsystemd Ansible wait_for module examples]]></summary></entry><entry><title type="html">cut pdf and convert to png</title><link href="https://u-ryo.github.io/blog/2020/12/23/cut-pdf-and-convert-to-png/" rel="alternate" type="text/html" title="cut pdf and convert to png" /><published>2020-12-23T01:07:00+09:00</published><updated>2020-12-23T01:07:00+09:00</updated><id>https://u-ryo.github.io/blog/2020/12/23/cut-pdf-and-convert-to-png</id><content type="html" xml:base="https://u-ryo.github.io/blog/2020/12/23/cut-pdf-and-convert-to-png/"><![CDATA[<p>「pdf fileの10ページ目を切り取ってpngにconvertする」やり方。
<code class="language-plaintext highlighter-rouge">pdftk</code>で<code class="language-plaintext highlighter-rouge">10</code>ページ目を<code class="language-plaintext highlighter-rouge">cat</code>して標準出力に渡し、それをpipeで繋げて<code class="language-plaintext highlighter-rouge">pdftoppm</code>で標準入力から読み込みredirectして<code class="language-plaintext highlighter-rouge">-png</code>で変換してfileへ。<code class="language-plaintext highlighter-rouge">-png</code>がないと<code class="language-plaintext highlighter-rouge">.ppm</code>になるので注意。</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pdftk nenga2021.pdf <span class="nb">cat </span>10 output -|pdftoppm <span class="nt">-png</span> - <span class="o">&gt;</span> nenga2021.png
</code></pre></div></div>]]></content><author><name>u-ryo</name></author><category term="imagemagick" /><category term="pdf" /><category term="tkpdf" /><summary type="html"><![CDATA[「pdf fileの10ページ目を切り取ってpngにconvertする」やり方。 pdftkで10ページ目をcatして標準出力に渡し、それをpipeで繋げてpdftoppmで標準入力から読み込みredirectして-pngで変換してfileへ。-pngがないと.ppmになるので注意。 pdftk nenga2021.pdf cat 10 output -|pdftoppm -png - &gt; nenga2021.png]]></summary></entry><entry><title type="html">learned on ruby and vue, angularjs</title><link href="https://u-ryo.github.io/blog/2020/03/15/learned-on-ruby-and-vue-angularjs/" rel="alternate" type="text/html" title="learned on ruby and vue, angularjs" /><published>2020-03-15T21:15:00+09:00</published><updated>2020-03-15T21:15:00+09:00</updated><id>https://u-ryo.github.io/blog/2020/03/15/learned-on-ruby-and-vue-angularjs</id><content type="html" xml:base="https://u-ryo.github.io/blog/2020/03/15/learned-on-ruby-and-vue-angularjs/"><![CDATA[<p>お仕事でRuby on RailsとVueをやっております。
これまではずっとJavaやAngularな人生だったので、Duck Typingと格闘中です。
新規機能開発で様々な山を超えてきた(知見を得た)ので、忘れないうちに。</p>

<ul>
  <li>破壊的method(Bang Method)って、その響きから何かあんまり良くないのかと無意識に思っていたのですけれども、調べてみると特に忌避すべきというものは見つかりませんでした。内部logicを考えると中で変数を付け替えていると思われますが、bangしないと両方分memoryに持ってなきゃならないから?→そうしたらrubocopに、<code class="language-plaintext highlighter-rouge">result[:key]=... if ...</code>と書けと言われました。ナルホドです!</li>
  <li>手元では<code class="language-plaintext highlighter-rouge">yarn test:vue</code>通るのにCircleCIでは<code class="language-plaintext highlighter-rouge">7 fails</code>と言われて通らないので悩みました。どう目を凝らしてもerrorらしき出力はなく。よく見るとCircleCIの最後に<code class="language-plaintext highlighter-rouge">Too long with no output (exceeded 10m0s): context deadline exceeded</code>とあり、かかった時間が10:57とか。えー!?そんなにかかっているの?? 手元で<code class="language-plaintext highlighter-rouge">time</code>で計測するも、1分ちょっと。CircleCI再実行して観察すると、test自体は1分程で終わり、その後ずっとだんまりで、10分程してTimeoutします。何だろう? 手元では再現しないのでとにかく厄介です。考えてみたら、チェックじゃなくて「1)」とか「2)」になっているところが失敗したところなんですね。確かに7)までありますわ。でも、そこがどう間違っているのかが全然出力されないので皆目わかりません。一箇所、<code class="language-plaintext highlighter-rouge">describe('...', async () =&gt; {</code>ってなっていたから、これか! と消して勇躍試してみたものの、症状変わらず。<code class="language-plaintext highlighter-rouge">mount</code>を<code class="language-plaintext highlighter-rouge">shallow</code>にしてみたり削れるだけの<code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code>を削っても。何故か<code class="language-plaintext highlighter-rouge">describe</code>の囲みを解くとtestが失敗します。期待してるobjectのpropsがないと。でももう一つ別のpropsはある。もぅわけわかんない!ですよ。CircleCIにsshで入ったりして色々と調べてみると、特定の2 filesが先にあると失敗する様子。何それ。どちらかがない、もしくはどちらかより先に実行されれば上手く行く。もっと詳しく見てみると、具体的には、<code class="language-plaintext highlighter-rouge">shallow</code>した直後の<code class="language-plaintext highlighter-rouge">wrapper.html()</code>の中身が全然違うという。どうして? そんなとこどうにもなんないじゃん。…あぁ、ぃゃ、test対象Vue Classできちんとcomponentを定義すると(e.g.<code class="language-plaintext highlighter-rouge">components: { 'el-button': Button, 'el-dialog': Dialog }</code>)warningが消えてcomponentとして見えるようになることがありますが(特にElementUIは<code class="language-plaintext highlighter-rouge">el-...</code>とclass nameが合わないので…(- -;)、そういうわけでもなく。何せ2 filesが組み合わさるとtestにコケるというのが謎です。試しにダメにするspec fileを一つ消してから全体的に<code class="language-plaintext highlighter-rouge">npm run test:vue</code>してみると、またコケるので、その2 filesはat leastであってもっと他にも組み合わせの悪いのがあるということです。i18nかなぁ? 確かに一方はenで他方はjaでしたが、大して使ってないのでcomment outしても変わらないですし… localでは全体的に通しても通っている、CircleCI上でも単体や相性の悪いものの後でなければ動く、でも全体的に通すとtestに失敗する、というのはとても悔しく、厄介です。結局心折れて、componentが見つからなければtest codeをthroughするようにしました。端から<code class="language-plaintext highlighter-rouge">describe.skip(...</code>よりはマシでしょう。それにしても何でやねん。大したtestしてるわけでもないclasses(methodがcallされたかどうかをassertしてる程度)に阻まれて、ちゃんと作ったspecのtest(条件がどうならどういう表示状態、<code class="language-plaintext highlighter-rouge">input</code>要素をclickしたらどういう文言のdialogが出て、等)を実行できないというのは何とも残念でなりません。</li>
  <li>Vueのtest、ムズカシイです。child componentの扱い、ですね。何気なく使っているであろう<code class="language-plaintext highlighter-rouge">el-table</code>とか<code class="language-plaintext highlighter-rouge">el-dialog</code>、そのままだとそんなcomponent知らないよとwarningが出まくります(<code class="language-plaintext highlighter-rouge">[Vue warn]: Unknown custom element: &lt;el-table-column&gt; - did you register the component correctly? For recursive components, make sure to provide the "name" option.</code>)。<code class="language-plaintext highlighter-rouge">wrapper</code>からはHTML Tagとして扱えるので最後はそうするのですけど、出来ればちゃんとcomponentとして扱いたいところです(でないとtable等でdataをloopさせて表示を作るcodeだった時のdata部分がそっくりないのでtest出来ない)。spec classで<code class="language-plaintext highlighter-rouge">shallow</code>(or<code class="language-plaintext highlighter-rouge">mount</code>)する時に<code class="language-plaintext highlighter-rouge">stubs:</code>するのかと思ってた(前それで頑張った気がした)んですけどそうではなく、spec対象元classでちゃんと定義されていれば?、否、ElementUIなんかは丸っと読み込まれているのでいいのかなぁ?ともあれ、spec classで<code class="language-plaintext highlighter-rouge">localVue.use(Table)</code>とした<code class="language-plaintext highlighter-rouge">localVue</code>を<code class="language-plaintext highlighter-rouge">shallow</code>(or<code class="language-plaintext highlighter-rouge">mount</code>)時に読み込んでやる必要がありました。でそのcomponentを<code class="language-plaintext highlighter-rouge">find</code>して<code class="language-plaintext highlighter-rouge">.props()</code>して中身を<code class="language-plaintext highlighter-rouge">expect</code>していくという流れですね。最初は<code class="language-plaintext highlighter-rouge">console.log(wrapper.html())</code>で何が捕れるのかよく観察すると。でも上記のように、何かが変わるとすぐ取れるcomponentが変わるようなので、怖いです。その辺りの機微がよくわからないので…</li>
  <li>Vue testにおけるtickの待ち方。<code class="language-plaintext highlighter-rouge">it(..., async () =&gt; { ... await Vue.nextTick(); ...});</code> 2 ticks待たないと値が変わらないことも。</li>
  <li><code class="language-plaintext highlighter-rouge">wrapper.findAll('.cell')</code>で取って来た値はElement Objectなので、見た目上は空でも<code class="language-plaintext highlighter-rouge">expect(cell).to.be.empty</code>とは出来ません。<code class="language-plaintext highlighter-rouge">expect(cell.isEmpty()).to.be.true</code></li>
  <li>Vue componentって、初期値<code class="language-plaintext highlighter-rouge">props</code>と<code class="language-plaintext highlighter-rouge">data</code>は分けねばならない? 当初、<code class="language-plaintext highlighter-rouge">data</code>に初期値を渡そうとしてどうしても出来ず、途方に暮れました。<code class="language-plaintext highlighter-rouge">data</code>の初期値は<code class="language-plaintext highlighter-rouge">data</code>部に書いた最初に返す値でそれは何でもよく、tagの方で渡す値で上書きされるのかと思ったら違うんですね。では<code class="language-plaintext highlighter-rouge">data</code>ではなく<code class="language-plaintext highlighter-rouge">props</code>を使うのかと思うとそうでもなく、<code class="language-plaintext highlighter-rouge">props</code>を後から変更しようとすると怒られますし。Stackoverflowにあった解決策は、初期値<code class="language-plaintext highlighter-rouge">props</code>と<code class="language-plaintext highlighter-rouge">data</code>を使い分けること。無駄な変数増えて何だかなぁとは思いますれど、そういうもの?</li>
  <li><code class="language-plaintext highlighter-rouge">slot-scope</code>って、<code class="language-plaintext highlighter-rouge">="scope"</code>ではなく<code class="language-plaintext highlighter-rouge">={row}</code>とやればいいんですね。本で知りました。</li>
  <li><code class="language-plaintext highlighter-rouge">...mapGetters</code>がなかなか効かなくて苦労しました。<code class="language-plaintext highlighter-rouge">computed:</code>に入れる、storeのmodule構造に合わせて<code class="language-plaintext highlighter-rouge">module(file)名/method名</code>にする、とやったんですが、<code class="language-plaintext highlighter-rouge">undefined</code>って言われて。<code class="language-plaintext highlighter-rouge">store</code>経由で<code class="language-plaintext highlighter-rouge">this.store.value_name</code>とかってやると取れるのに。でも<a href="https://uncle-javascript.com/vuex-getters">State を直接参照せず、getter 経由で参照することを強く推奨する</a>というので頑張りました。一回は諦めたんですけど、その後retryしたらうまくいくように。結局どうしてうまくようになったのか、決め手が何だったのかよく分からずじまいです。</li>
  <li>確認dialogを出すというんですが、非同期のJavascript/Vueにあってはこれが意外とムズカシイです。確認dialogというからには処理を止めなければなりません。よくやるように「flagで表示を制御」ではmain処理止まらないんですよね。そもそもsleepだとて簡単には行かないですよねJavascriptで。とても苦労しました。<code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code>を導入してやっと止めました。要は「dialogを作っているところを<code class="language-plaintext highlighter-rouge">new Promise</code>でくるんで<code class="language-plaintext highlighter-rouge">return</code>、それを<code class="language-plaintext highlighter-rouge">await</code>、しているfunctionを<code class="language-plaintext highlighter-rouge">async</code>」ですかね。</li>
  <li>dialogって自分で<code class="language-plaintext highlighter-rouge">$destory()</code>出来るんですね。生成と削除は外からやらないとダメかな、と思っていた(ので<code class="language-plaintext highlighter-rouge">$emit</code>して親で受けて<code class="language-plaintext highlighter-rouge">dialog.$destory()</code>してた)んですけど、実際生成は外から<code class="language-plaintext highlighter-rouge">$mount()</code>呼ばないとならないですけど(<code class="language-plaintext highlighter-rouge">created:</code>や<code class="language-plaintext highlighter-rouge">mounted:</code>はcallbackですよね)、「ボタン押したら消滅」で自己<code class="language-plaintext highlighter-rouge">$destroy()</code>は出来るんですねやってみたら。いちいち面倒くさいけどやってみないとわからんもんですね。</li>
  <li>dialogを出す必要に迫られたんですけど、よくあるようにdialogを予めDOMに仕込んでflagでappear/disposeとするには、2階層上のVue componentに書かないと、loopで大量生産されてしまうことが判明。Vue component間は親との対話だったら<code class="language-plaintext highlighter-rouge">$emit</code>と、戻しは何だ??を使えば出来ますが、2階層上は面倒です。情報渡すだけならまだしも、その後の処理を孫でやるならdialogの結果を2階層戻してもらわないとならないですし、じゃぁ処理もgrandparentに渡す? 処理に必要な情報も渡さないとだし、grandparentで知らなくてもいいことを知らなきゃいけないなんて。ということで、何かないかなーと探すと、揮発性dialogというものが(<a href="https://speakerdeck.com/uhck/hui-fa-xing-falsegao-ikonponentowozuo-ruhua">揮発性の高いコンポーネントを作る話</a>)。探すと似たようなことしてる人割と多く。これを理解して、そのままではなく2 filesで済むようにして自分のにapplyするのも頭を使いました。これ結構いいような。Vueのdialogも一般的にこれでいいような。初からDOMについているのではなくって。</li>
  <li>既述のように、<code class="language-plaintext highlighter-rouge">await</code>もうまく行ってよし、じゃぁtestを書くぞ、と試したら、いきなりわけのわからないerrorに見舞われてそもそもtestが動かなくなってしまいました。なんでだろー?! 自分以前では確かに動いていたので、自分のせいです。でもどのfileが悪いとかも出て来ないのでまるで取っ掛かりがなく、途方に暮れました。error messageで検索すると、どうやらなんと<code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code>を使った場合に出る<code class="language-plaintext highlighter-rouge">bable</code>/<code class="language-plaintext highlighter-rouge">polyfill</code>系のerrorだとか。えー!! 折角苦労して<code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code>でdialog止めたのに! 代替手段なんて思い付かないよー! ということで、何とか突破すべく頑張りました。<code class="language-plaintext highlighter-rouge">babel</code>の線で色々と調べてみると、こうしたら直った、ああしたら、とか種々書いてあるんですね。新たなgem install以外は全部試しましたけど、うまく行かず。でも<a href="https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined/">この記事</a>を参考にvueの<code class="language-plaintext highlighter-rouge">setup.js</code>冒頭に<code class="language-plaintext highlighter-rouge">require("babel-polifill");</code>と書いたら!! 動いてくれました!! 省みれば僅か1行の追加ですけど、この1行には数時間もの汗と涙が詰まっているのです。</li>
  <li>APIの試験をするのに、<code class="language-plaintext highlighter-rouge">curl</code>やPostmanを使うのはよくありますけど、cookieが何か長くてcopyが面倒だなーと思ったので、Chrome Developer toolのConsoleにて<code class="language-plaintext highlighter-rouge">fetch</code>でやる手法を。<code class="language-plaintext highlighter-rouge">fetch(｀/apis/endpoint/＄｛id｝｀,{method:method,credentials:'include',headers:{'X-CSRF-Token':token}}).then((response)=&gt;console.log(response))</code>とすればBrowserの持っている認証情報をそのまま使ってくれます。<code class="language-plaintext highlighter-rouge">credentials:'include'</code>が肝です。どうせ使い捨てcommandですから。</li>
  <li>Railsは自動的にCSRF対策がなされている、ということで、何してるんだろーと思いきや、session毎に予測困難な<code class="language-plaintext highlighter-rouge">X-CSRF-Token</code>が付くというものでした。request毎に違うものでなくてもいいのか? いいのか…予測困難なら…</li>
  <li>Railsの日付、Timezone気を付けないといけないのですけど(AWSはUTCなので)、<code class="language-plaintext highlighter-rouge">Time.current</code>や<code class="language-plaintext highlighter-rouge">Date.current</code>のように<code class="language-plaintext highlighter-rouge">.current</code> methodを使えばTimezoneを意識してくれる模様。<code class="language-plaintext highlighter-rouge">.now</code>より<code class="language-plaintext highlighter-rouge">.current</code>がということでやはり「ナウい」は死語ということですか。</li>
  <li>Ruby、共通処理は継承関係作って親クラスに、と思っていたんですけどそれってJava的発想? OOP的というか。RubyではむしろMixin? MixinってAOPかと思ってたんですけど。Duck Typingだからそもそもclass的には無関係でいいんですね。Java屋さんとしては何だかなぁと。</li>
  <li>Rubyのstream(<code class="language-plaintext highlighter-rouge">map</code>や<code class="language-plaintext highlighter-rouge">select</code>や<code class="language-plaintext highlighter-rouge">reject</code>や<code class="language-plaintext highlighter-rouge">each</code>等)、もうfoolproofとして<code class="language-plaintext highlighter-rouge">lazy...force</code>を付けるのがいいんじゃないか、と思ったんですけど、そうでもないんですねこれ!! ってかそもそもどうして<code class="language-plaintext highlighter-rouge">lazy</code>がdefaultじゃないんだろう? ってJava屋さんとしては思ってたんですけど、<a href="https://bugs.ruby-lang.org/issues/6183#note-2">銀の弾丸ではない</a>という話があって衝撃でした何を今更ですけど(古い話のようで今は<a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/77130">多少performance上がった</a>そう)。というわけで、<code class="language-plaintext highlighter-rouge">lazy</code>は「省メモリ」か「後でかなりの数<code class="language-plaintext highlighter-rouge">take</code>することがわかっている場合」でないと効果なさそうなんですね。<a href="https://stackoverflow.com/questions/47653131/rubys-lazy-less-performant-than-allocating-huge-lists">元ネタ</a></li>
  <li>最近何か安易に大量のlogをslackに投げていたので、そうじゃないでしょ基本的にはlog fileでしょ、それもdefaultのfileでいいよね、ということで、<code class="language-plaintext highlighter-rouge">Logger.new(...)</code>ってやったんですけどどうして毎回file nameを指定しなきゃいけないのか、しかもfile名指定して<code class="language-plaintext highlighter-rouge">new</code>すると都度file作りやがる、のでappend modeでopenして、とかってやらないとならないの?! なんか面倒臭くね? と思ってたら、<code class="language-plaintext highlighter-rouge">Rails.logger</code>でいいんですね。なぁんだ。<code class="language-plaintext highlighter-rouge">@logger ||= Rails.logger</code></li>
  <li><code class="language-plaintext highlighter-rouge">controller</code>で最終的?に全てのerrorを拾うには<code class="language-plaintext highlighter-rouge">rescue_from error_class名, with: :method名</code>、事前validationを定義するには<code class="language-plaintext highlighter-rouge">before_action :method名</code></li>
  <li>決まった名前をclass methodにするか定数にするか、は、定数の方が良いようです。ref. <a href="https://stackoverflow.com/questions/15903835/class-method-vs-constant-in-ruby-rails">Class method vs constant in Ruby/Rails</a></li>
  <li>Rubyで「objectの同一性」とは、Javaと違って<code class="language-plaintext highlighter-rouge">==</code>、<code class="language-plaintext highlighter-rouge">eql?</code>、<code class="language-plaintext highlighter-rouge">hash</code>の3 methodあるんですね。うち<code class="language-plaintext highlighter-rouge">==</code>はArrayで、残る2つはSetで使われる模様。Arrayでは比較通るのにSetでは違うと判定され悩みました。Setではobject hashを取っているからなんですね。でも結局、<code class="language-plaintext highlighter-rouge">.to_json</code>すれば同一性methods定義しなくても行けました。何だかなぁですけどまぁunit testなのでいっかー。</li>
  <li>すっかり忘れてましたけど<code class="language-plaintext highlighter-rouge">routes</code>で<code class="language-plaintext highlighter-rouge">resources</code>を使えばendpoint定義をまとめられますね。path parameterも積極的に使うべきでしょう(その存在を強制できるので)。parameter nameは基本<code class="language-plaintext highlighter-rouge">id</code>ですけど変えられるんですね。</li>
  <li>ActiveRecordで、MySQLみたいに「あれば取得、なければ作る(recordも入れる)」なんてあるかなーと思ったら<code class="language-plaintext highlighter-rouge">.find_or_create_by</code>がありました。更にforeign key制約をつけて、その制約に反したらerrorにして欲しい場合は、<code class="language-plaintext highlighter-rouge">.find_or_create_by!</code>でした。</li>
  <li>RubyでVO(Value Object)を作るのは、どうしたらいいんですかね。<code class="language-plaintext highlighter-rouge">object = Struct.new(:variables, ...)</code>とobjectとして使えば良い、という記述があったので、今度試してみます。←ここでいうobjectってclasなんですね。</li>
  <li>RubyにもJavadocのようなcomment流儀あるんですね。<a href="http://yardoc.org">YARD</a>ですか。<code class="language-plaintext highlighter-rouge">@param [引数のクラス] 引数名 引数の説明</code>、<code class="language-plaintext highlighter-rouge">@return [戻り値のクラス] 戻り値の説明</code></li>
  <li>配列からhash mapを作る手法、色々ありますよね。よくある「<code class="language-plaintext highlighter-rouge">[key, value]</code>の配列の配列にしてから<code class="language-plaintext highlighter-rouge">.to_set</code>」はloopを2回回すので何だかなぁと思っていました。Rubyにもある<code class="language-plaintext highlighter-rouge">reduce(inject)</code>を使えば出来る!とあったのでやったのですけど、そうしたらrubocopに「そういう時は<code class="language-plaintext highlighter-rouge">each_with_object</code>を使え」と言われました。ナルホド、それなら確かにblockの最後に<code class="language-plaintext highlighter-rouge">hash</code>をreturnせずともよいわけですね! というわけで、<code class="language-plaintext highlighter-rouge">list.each_with_object({}){|value,hash|hash[key]=value}</code>、更に一気にhashのhashを作るには、<code class="language-plaintext highlighter-rouge">list.each_with_object(Hash.new{|hash,key|hash[key]={}){|value,hash|hash[key1][key2]=value}</code></li>
  <li>同様に、一気に<code class="language-plaintext highlighter-rouge">Set</code>を作るには、<code class="language-plaintext highlighter-rouge">list.each_with_object(Set.new){|value,set|set&lt;&lt;value}</code>の方が、最後に<code class="language-plaintext highlighter-rouge">.to_set</code>よりよいでしょう。</li>
  <li>reekに「<code class="language-plaintext highlighter-rouge">log.debug</code>を複数回使っているからまとめよ」と言われて弱りました。単純にまとめると、毎回引数を評価しちゃうじゃないですか。でもloggingなので遅延評価が必要でしょう。というわけで、blockを残せるようにmethod定義しました。<code class="language-plaintext highlighter-rouge">def debug(&amp;block) Rails.logger.debug(&amp;block); end</code>使う時は<code class="language-plaintext highlighter-rouge">debug{...}</code>です。</li>
  <li><code class="language-plaintext highlighter-rouge">rescue</code>で<code class="language-plaintext highlighter-rouge">=&gt; e</code>とせずとも<code class="language-plaintext highlighter-rouge">$!</code>でerrorを参照できるんですね。<code class="language-plaintext highlighter-rouge">rescue</code>で<code class="language-plaintext highlighter-rouge">retry</code>とすれば<code class="language-plaintext highlighter-rouge">begin</code>からまたやってくれるんですね! ただ、上限設けないとずっとretryし続けるので注意ですかね。</li>
  <li>Railsでmail送信は、configが終わっているのであとは、<code class="language-plaintext highlighter-rouge">ApplicationMailer</code>を継承したclassを作成、<code class="language-plaintext highlighter-rouge">app/views/class名/method名.html.haml</code>といったtemplateを用意、という感じでした。html mailだけだとspam判定されやすいとのことなので、<code class="language-plaintext highlighter-rouge">app/views/class名/method名.txt.erb</code>も用意しました。classのinstance fieldをmail template中で参照できます。</li>
  <li>ActiveRecordでの取得はなるべく<code class="language-plaintext highlighter-rouge">.pluck</code>にした方が速いし軽い、ということでそうしてますが、でもそうすると何のためのDTOなのかと。model定義の意義が少し薄れるような。modelがfatなのが悪い? <code class="language-plaintext highlighter-rouge">.pluck</code>でない場合はせめて<code class="language-plaintext highlighter-rouge">.select</code>で選択するfieldを減らしてやって生成されるobjectを少しでも軽くすると。ただActiveRecordからObjectを生成するcostが高いとのことなので、<code class="language-plaintext highlighter-rouge">.pluck</code>の方が推奨されています。…<code class="language-plaintext highlighter-rouge">.select</code>って、modelにないfieldもselectできるんですね。そしてdynamicにmethodが生えると。なんかそこまで行くと気持ち悪い気がするのは、やはりJava出身だから??</li>
  <li><code class="language-plaintext highlighter-rouge">yield</code>はよく使いました。<code class="language-plaintext highlighter-rouge">if block_given?</code>も併せて。</li>
  <li>instanceからclass variableへの参照は<code class="language-plaintext highlighter-rouge">instance.class::VARIABLE</code>でした。</li>
  <li>rspecで<code class="language-plaintext highlighter-rouge">allow(Rails).to receive(:logger).and_return(spy('logger'))</code>とするとloggingが一律空回しになります。</li>
  <li>method chainを<code class="language-plaintext highlighter-rouge">allow</code>するには<code class="language-plaintext highlighter-rouge">receive_message_chain</code>(e.g.<code class="language-plaintext highlighter-rouge">expect(SomeClass).to receive_message_chain(:joins, :distinct, :where, :pluck)</code>) <code class="language-plaintext highlighter-rouge">.with(...)</code>を複数書いても、それぞれのmethodの引数指定ではなく、最後の一つしか評価されていない?</li>
  <li>rspecで、違ったparameterで同じobjectのmethodをcallするのをstubするには、<code class="language-plaintext highlighter-rouge">allow(...).to receive(:same_method).with(...)</code>を並列で何度も書けばよいです。<code class="language-plaintext highlighter-rouge">with(...)</code>をmethod chainさせるとかではないんですね。</li>
  <li>rspecでinstance doubleに引数を連ねるとmethod stubになります。e.g. <code class="language-plaintext highlighter-rouge">double('user', id: 1)</code>→<code class="language-plaintext highlighter-rouge">user.id==1</code>(<code class="language-plaintext highlighter-rouge">double</code>の最初の引数は任意名)</li>
  <li>調べればすぐ出てくることではありますが、Railsのmigrationの<code class="language-plaintext highlighter-rouge">create_table</code>時にprimary keyを自動で出来る<code class="language-plaintext highlighter-rouge">id</code>以外にしたい場合は<code class="language-plaintext highlighter-rouge">create_table</code>の行に<code class="language-plaintext highlighter-rouge">primary_key: key名</code>と書きます。更にforeign key constraintを付け加えたい場合は、その下に<code class="language-plaintext highlighter-rouge">add_foreign_key :table名, :foreign_table名</code>と書けば大丈夫でした。</li>
  <li>RubyでTime format時、<code class="language-plaintext highlighter-rouge">.strftime('%X')</code>とすると一文字で<code class="language-plaintext highlighter-rouge">15:37:21</code>に、<code class="language-plaintext highlighter-rouge">.strftime('%F')</code>とすると<code class="language-plaintext highlighter-rouge">2020-03-01</code>になるので便利です。また、<code class="language-plaintext highlighter-rouge">.iso8601</code>とすると<code class="language-plaintext highlighter-rouge">T</code>を挟んでTimezone付きで<code class="language-plaintext highlighter-rouge">2020-03-01T15:39:10+09:00</code>というようにしてくれるのでこれも便利です。</li>
  <li><a href="https://daily.belltail.jp/?p=2035">嫁の顔忘れてもTimecop.returnは忘れないでねという話</a>にあるように、<code class="language-plaintext highlighter-rouge">Timecop.travel(Time.parse('2020/3/1 0:01:03')) do ... end</code>で囲うのはいいですね。<code class="language-plaintext highlighter-rouge">Timecop</code>も<code class="language-plaintext highlighter-rouge">.travel</code>(特定時刻にしてから時を刻む)と<code class="language-plaintext highlighter-rouge">.freeze</code>(特定時刻にして時を止める)では違うので注意です。</li>
  <li>Rubyで文字列の連結は<code class="language-plaintext highlighter-rouge">&lt;&lt;</code>が<a href="https://qiita.com/Kta-M/items/c7c2fb0b61b11d3a2c48">いいみたい</a>ですね。C++みたいですね。</li>
  <li>ActiveRecordでN+1問題を回避するには、なるべく<code class="language-plaintext highlighter-rouge">joins</code>した方が良いようです。が、図に乗って巨大tableをjoinするとそれはcostがでかいので、分割して引いた方がいいんでしょう。<code class="language-plaintext highlighter-rouge">joins</code>するにはmodelに<code class="language-plaintext highlighter-rouge">has_many</code>とかの関係が書いてないとダメでした。2つ先の関連も書けて、<code class="language-plaintext highlighter-rouge">has_many :second_table, through: :first_table</code>と<code class="language-plaintext highlighter-rouge">through:</code>で繋げられました。</li>
  <li>と思ったのは幻想でした。書けちゃいますけど、joinされたものはkeyで結びついたものではなく、雑に合わさったもので、<code class="language-plaintext highlighter-rouge">where</code>で引いてもそうでないものまでついてきていて、bugを引き起こしていました。ちゃんとやらないとダメですね。</li>
  <li>「ちゃんとやる」とは、<a href="https://qiita.com/TeruhisaFukumoto/items/007ad22cc170d297dbcc">Railsでモデルを4段階joinする方法で、もう一度理解するjoinsとmerge</a>にあるように、ActiveRecordの<code class="language-plaintext highlighter-rouge">joins</code>で繋ぐ時に<code class="language-plaintext highlighter-rouge">joins(first:[second: :third])</code>とすること。これでちゃんとjoinされました。</li>
  <li>controllerで、APIっぽくheaderだけ返して中身は不要という場合、<code class="language-plaintext highlighter-rouge">head :created</code>(201)とか<code class="language-plaintext highlighter-rouge">head :unauthorized</code>(401)と書けます。</li>
  <li>Rubyのhere documentはもう<code class="language-plaintext highlighter-rouge">&lt;&lt;~</code>でよくないすか? <code class="language-plaintext highlighter-rouge">&lt;&lt;</code>だと左端にひっつくので。</li>
  <li>Rubyの<code class="language-plaintext highlighter-rouge">reduce</code>は<code class="language-plaintext highlighter-rouge">each_with_object</code>に取って代わられるのですが、<code class="language-plaintext highlighter-rouge">reduce(&amp;:method)</code>で済むところに活路があるようです。使えたのは<code class="language-plaintext highlighter-rouge">Set</code>のmergeですね。<code class="language-plaintext highlighter-rouge">[Set.new([1,2,3]),Set.new([2,3,4])].reduce(&amp;:merge)==Set.new([1,2,3,4])</code></li>
  <li>複数回<code class="language-plaintext highlighter-rouge">.include?</code>するなら絶対<code class="language-plaintext highlighter-rouge">Set</code>にすべきですよ。</li>
  <li>定数文字列もなるべく<code class="language-plaintext highlighter-rouge">.freeze</code>すべきでしょうか。magic comment <code class="language-plaintext highlighter-rouge"># frozen_string_literal: true</code>で(が?)よいのでしょうか。</li>
  <li>controllerのrspecで、<code class="language-plaintext highlighter-rouge">before_action</code>のtestを書くことになったのですが、色々と悩みました。どこに書くか? 書いたclass? 使うclass? Javaでabstract classのtestだとconcrete classに書きますが、controllerは使う側に書くと多岐にわたるので、<code class="language-plaintext highlighter-rouge">application_controller_spec.rb</code>に書きました。</li>
  <li>curlでtest clientとしてJSONを<code class="language-plaintext highlighter-rouge">-d</code>でPOSTしたところ、どうもうまく行かなくて、調べるとserver側に<code class="language-plaintext highlighter-rouge">"{\"key\":\"value\"}"</code>なんて渡っていました。どうしてー?! HTTP Request Headerに<code class="language-plaintext highlighter-rouge">Contetnt-Type: application/json</code>が必要なんですね! そういえば。</li>
  <li>今更ですがAngularJSのtestで、<code class="language-plaintext highlighter-rouge">window.location.href</code>をassertionしようとして四苦八苦しました。<a href="https://stackoverflow.com/questions/20252382/how-to-mock-window-location-replace-in-angularjs-unit-test">How to mock $window.location.replace in AngularJS unit test?</a>等から得たことは、<code class="language-plaintext highlighter-rouge">window.location.href</code>だとtest(jasmin)で拾えないので<code class="language-plaintext highlighter-rouge">$window.location.href</code>にする、responseがemptyだとどうしてもtestの方でcatchしてくれないのでdummy値を<code class="language-plaintext highlighter-rouge">response</code>代入する、testでは<code class="language-plaintext highlighter-rouge">$window</code>がemptyで悩みました。<code class="language-plaintext highlighter-rouge">$provide.value('$window',</code>でprovideして<code class="language-plaintext highlighter-rouge">beforeEach(inject(function($injector, $rootScope, $httpBackend, $window) {</code>でinjectしつつ<code class="language-plaintext highlighter-rouge">window=$window;</code>と付け替えて、assertionのところで<code class="language-plaintext highlighter-rouge">window</code>を使う、という。</li>
  <li><code class="language-plaintext highlighter-rouge">diff</code>で相違は出ますが、共通行を抽出するには? <code class="language-plaintext highlighter-rouge">comm -1 -2 file1 file2</code>(但しfilesはsort済であること) ref.<a href="https://qiita.com/mekagazira/items/1a1791a42e435cefd5f6">２つのファイルの共通行を抽出する方法</a></li>
  <li>MySQLでindexがついたかどうかを確認するには、<code class="language-plaintext highlighter-rouge">describe table名</code>では<code class="language-plaintext highlighter-rouge">Mul</code>と付くだけでよくわからず。<code class="language-plaintext highlighter-rouge">show create table table名</code>でCREATE文を出すことで。</li>
  <li>controllerのrspecで、sessionってどう表すのかと。<code class="language-plaintext highlighter-rouge">get :index,params:{aaa:'AA'}</code>だから<code class="language-plaintext highlighter-rouge">get :index,params{...},session:{...}</code>かと思いきや、実際そう書いてあるところもあるんですが、それではうまく行かず、<code class="language-plaintext highlighter-rouge">get :index,{param:'something'},{session:'something'}</code>と。ref. <a href="https://stackoverflow.com/questions/22451969/rspec-set-session-object">RSpec set session object</a></li>
  <li>rspecでprivate variableにsetするには、<code class="language-plaintext highlighter-rouge">some_instance.instance_variable_set('variable_name', 'some value')</code>、getするには`some_instance.instance_variable_get(‘variable_name’)</li>
  <li>visitor patternを適用しようと思っていたのですけど、諦めました。RubyってDuck Typingだからあんまり効果を見出だせず。<a href="https://passingloop.tumblr.com/post/13835569972/rarely-used-visitor-pattern-in-ruby">Ruby で Visitor パターンをあまり使わない理由，Ruby で Visitor パターンを使うとき</a>という投稿もあり。</li>
</ul>

<hr />
<ul>
  <li>
    <p><a href="https://abicky.net/2018/12/03/104228/">upstart で start した job の設定を変更するには一度 stop する必要がある</a>
へーそうなんだ! ってぃぅか何のための<code class="language-plaintext highlighter-rouge">restart</code>っすか? <code class="language-plaintext highlighter-rouge">restart</code>あるんだから機能すると思いますよねぇ…orz</p>
  </li>
  <li><code class="language-plaintext highlighter-rouge">1.hour.ago</code>があるのに<code class="language-plaintext highlighter-rouge">.later</code>や<code class="language-plaintext highlighter-rouge">.after</code>はないのでどうするんだろう? と思ったら、<code class="language-plaintext highlighter-rouge">1.hour.from_now</code>っていうんですね。</li>
  <li>controllerのrspec書いていて、やっと単体で通るようになったので、いざ全体的にrspecかけてみると、コケます。確かに自分の書き足したところ。でも通る時もある。どうして?→そういう、rspecの結果が不安定な時は、実行順序依存性があります。これを紐解かねばなりません。それが厄介でした。といいますか、rspecって全体的にかけると、といいますかspec fileを指定しないでdirectoryだけ指定して実行すると、spec filesの実行順序がrandomなんですね。それはその方がいいですね。なので、色んな手を使って順序依存性を解明していきます。ref. <a href="https://qiita.com/ShunjiKato/items/c95e6cb0fb3523aa693a">RSpecコマンドのオプションまとめ</a> rspecに<code class="language-plaintext highlighter-rouge">--order random:NNNNN</code>を付けてrandom seedを固定、<code class="language-plaintext highlighter-rouge">-f d</code>として出力formatにclass名を出すようにし、<code class="language-plaintext highlighter-rouge">--no-color</code>でescape sequenceを抑制します。<code class="language-plaintext highlighter-rouge">-o filename.log</code>でfileに出力、それをうまく行ったときと失敗した時で保存して、class名を比較しました。当該classのtest以前だけが大事なので、そこだけ出して<code class="language-plaintext highlighter-rouge">sort</code>して<code class="language-plaintext highlighter-rouge">diff</code>取ります。失敗事例が複数取れれば、それらの共通classを<code class="language-plaintext highlighter-rouge">comm -1 -2 &lt;(...) &lt;(...)</code>で抽出するのが良いです。
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose <span class="nb">exec </span>rails bash <span class="nt">-c</span> <span class="s1">'RAILS_ENV=test bundle exec rspec --order random:32510 -f d -o 32510.log --no-color'</span>
diff &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> controllers2.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span> &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> 32510.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span>|ag <span class="s1">'&lt;'</span>
<span class="nb">comm</span> <span class="nt">-1</span> <span class="nt">-2</span> &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> controllers2.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span> &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> 4627.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span>
<span class="nb">comm</span> <span class="nt">-1</span> <span class="nt">-2</span> &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> controllers2.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span> &lt;<span class="o">(</span><span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'^[A-Za-z]'</span> <span class="nt">-e</span> <span class="s1">'^  Apis::'</span> 48327.log|awk <span class="s1">'{if(/ApplicationController/){exit}print}'</span>|sort<span class="o">)</span>|grep <span class="nt">-r</span> <span class="nt">-l</span> <span class="nt">-f</span> /dev/stdin spec/controllers|sort <span class="nt">-u</span>|tr <span class="nt">-d</span> <span class="s1">'\r'</span>
</code></pre></div>    </div>
  </li>
  <li>単体では通るのに全体だと時々こけるというのはどうしてもよくわからないので、何度も何度も実行して観察してみると、コケる時のFの出る位置がまちまちなんですね。あぁ、これって毎回test順序違うのか、とその時初めて思い至りました。それで、rspecにおけるrandom seedの固定方法や出力形式の指定の仕方を調べ、何とか実行順を成功時と失敗時で比較して、怪しいものを試して、辿り着いたのでした。</li>
  <li>controllerのtestでは、<code class="language-plaintext highlighter-rouge">before_action</code>に指定したprivate methodのtest且つ利用するchild classでのtestではなくbase classでのtestなので、当初は<code class="language-plaintext highlighter-rouge">contorller.send('method_name')</code>とかやってたんですけどこれは本来的ではないな、ということで、Anonymous Controller使いました。といっても<code class="language-plaintext highlighter-rouge">describe</code>の中で<code class="language-plaintext highlighter-rouge">controller do ... end</code>して、そこで<code class="language-plaintext highlighter-rouge">before_action :method_name</code>しただけのものです。それでその<code class="language-plaintext highlighter-rouge">describe</code>内のcontrollerは定義したものになるという、お手軽な。でもここでエラー(<code class="language-plaintext highlighter-rouge">Error</code>をthrow、じゃないRubyだからraiseですか、する筈が正常に終了してしまう)が出て、悩みました。結局、わかってみればどうということはないのですが、他のspec classで、<code class="language-plaintext highlighter-rouge">ApplicationController.skip_before_filter :login_required</code>を<code class="language-plaintext highlighter-rouge">before do...</code>でやっていて、でも<code class="language-plaintext highlighter-rouge">after do...</code>で元に戻していなかったから、というのが原因でした。</li>
  <li>そもそも、<code class="language-plaintext highlighter-rouge">ApplicationController.skip_before_filter</code>なんてせずに、単に<code class="language-plaintext highlighter-rouge">allow(controller).to receive(:method)</code>とmockすれば十分です。そうしたら<code class="language-plaintext highlighter-rouge">after do...</code>だって不要ですし。ref. <a href="https://qiita.com/yuutetu/items/a1586339d716aad04098">before_filterを分離してテストする方法(Rspec)</a> 別段<code class="language-plaintext highlighter-rouge">true</code>返す必要もないんですねこの<code class="language-plaintext highlighter-rouge">before_action</code>って。<code class="language-plaintext highlighter-rouge">before_filter</code>は古いんだそう。</li>
  <li>いつも<code class="language-plaintext highlighter-rouge">schedule</code>の確認をするのに。<code class="language-plaintext highlighter-rouge">bundle exec whenever</code></li>
  <li>AngularJSでのredirect? になるのかな? は、routesの<code class="language-plaintext highlighter-rouge">$stateProvider.state('...')</code>に定義されたところへ、<code class="language-plaintext highlighter-rouge">$state.go('...')</code>で飛びます。ref. <a href="https://qiita.com/nogson/items/63d8f007fe987e2dfe9e">Angularjs UI Routerの使い方</a> でも結局、AngularJSのrouting範囲外に飛ばしたかったので、<code class="language-plaintext highlighter-rouge">$window.location.href = ...</code>で飛ばしました。</li>
  <li>ActiveRecordの<code class="language-plaintext highlighter-rouge">scope</code>に引数を取る場合には、<code class="language-plaintext highlighter-rouge">scope scope_name -&gt;(arg1, arg2) { ... }</code> 矢印とカッコはくっつけないとreekに怒られます。</li>
  <li>Rubyで<code class="language-plaintext highlighter-rouge">String</code>の<code class="language-plaintext highlighter-rouge">select</code>を正規表現でするなら<code class="language-plaintext highlighter-rouge">grep(/.../)</code>、<code class="language-plaintext highlighter-rouge">-v</code>の場合は<code class="language-plaintext highlighter-rouge">grep_v</code>(<a href="https://stackoverflow.com/questions/4165971/ruby-equivalent-to-grep-v/33881808">Ruby equivalent to grep -v</a>)</li>
  <li>AngularJSのroutingってURLについたanchor(<code class="language-plaintext highlighter-rouge">#</code>以下)に出るのですが、それってそもそもbrowserがserver側に送信しないんですね…</li>
  <li>ActiveRecordで選択して保存を1行で。例えば<code class="language-plaintext highlighter-rouge">User.find(1).update(name:'new name')</code></li>
  <li>rspecでActiveRecordのtest dataってfixtureやFactoryGirlを使うものかと思っていたのですけど、普通に<code class="language-plaintext highlighter-rouge">.create</code>とか<code class="language-plaintext highlighter-rouge">.update</code>とかして大丈夫なんですね。大丈夫、というのは、後腐れがない、んですね。</li>
  <li>RSpecで<code class="language-plaintext highlighter-rouge">allow</code>でmockした時に、<code class="language-plaintext highlighter-rouge">.and_return</code>だけでなく処理(<code class="language-plaintext highlighter-rouge">sleep(1)</code>)を入れたかったんですけどどうしたら? と思ったら、単にblockでいいんですね。blockの最終評価値がreturnになる、というなら<code class="language-plaintext highlighter-rouge">.and_return</code>って不要なの?</li>
</ul>

<!--
```sh
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/adset?campaign_id=6119375093538&date_preset=customize&advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/campaign?campaign_id=6119375093538&advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/adset?adset_id=6119375094338&advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/adset?adset_id=6119375094338&type=daily&advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/adgroup?adset_id=6119375094338&advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/stats/adgroup?type=daily&advertiser_id=3&adgroup_id=6119375096938'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/adgroups/show/6119375096938?advertiser_id=3'
curl -v -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/adgroups/reach_estimateand_description?adgroupid=6119375096938&advertiser_id=3'
curl -v -H 'content-type:application/json' -d '{"creative_id":"6119375095738"}' -H "JWT-Authorization:$JWT" -H "X-CSRF-Token:$CSRF" -H "Cookie:$COOKIE" 'http://dev.ibiza.jp:13000/apis/ads/preview?advertiser_id=3'
```
-->]]></content><author><name>u-ryo</name></author><category term="ruby" /><category term="vue" /><category term="angularjs" /><summary type="html"><![CDATA[お仕事でRuby on RailsとVueをやっております。 これまではずっとJavaやAngularな人生だったので、Duck Typingと格闘中です。 新規機能開発で様々な山を超えてきた(知見を得た)ので、忘れないうちに。 破壊的method(Bang Method)って、その響きから何かあんまり良くないのかと無意識に思っていたのですけれども、調べてみると特に忌避すべきというものは見つかりませんでした。内部logicを考えると中で変数を付け替えていると思われますが、bangしないと両方分memoryに持ってなきゃならないから?→そうしたらrubocopに、result[:key]=... if ...と書けと言われました。ナルホドです! 手元ではyarn test:vue通るのにCircleCIでは7 failsと言われて通らないので悩みました。どう目を凝らしてもerrorらしき出力はなく。よく見るとCircleCIの最後にToo long with no output (exceeded 10m0s): context deadline exceededとあり、かかった時間が10:57とか。えー!?そんなにかかっているの?? 手元でtimeで計測するも、1分ちょっと。CircleCI再実行して観察すると、test自体は1分程で終わり、その後ずっとだんまりで、10分程してTimeoutします。何だろう? 手元では再現しないのでとにかく厄介です。考えてみたら、チェックじゃなくて「1)」とか「2)」になっているところが失敗したところなんですね。確かに7)までありますわ。でも、そこがどう間違っているのかが全然出力されないので皆目わかりません。一箇所、describe('...', async () =&gt; {ってなっていたから、これか! と消して勇躍試してみたものの、症状変わらず。mountをshallowにしてみたり削れるだけのasync/awaitを削っても。何故かdescribeの囲みを解くとtestが失敗します。期待してるobjectのpropsがないと。でももう一つ別のpropsはある。もぅわけわかんない!ですよ。CircleCIにsshで入ったりして色々と調べてみると、特定の2 filesが先にあると失敗する様子。何それ。どちらかがない、もしくはどちらかより先に実行されれば上手く行く。もっと詳しく見てみると、具体的には、shallowした直後のwrapper.html()の中身が全然違うという。どうして? そんなとこどうにもなんないじゃん。…あぁ、ぃゃ、test対象Vue Classできちんとcomponentを定義すると(e.g.components: { 'el-button': Button, 'el-dialog': Dialog })warningが消えてcomponentとして見えるようになることがありますが(特にElementUIはel-...とclass nameが合わないので…(- -;)、そういうわけでもなく。何せ2 filesが組み合わさるとtestにコケるというのが謎です。試しにダメにするspec fileを一つ消してから全体的にnpm run test:vueしてみると、またコケるので、その2 filesはat leastであってもっと他にも組み合わせの悪いのがあるということです。i18nかなぁ? 確かに一方はenで他方はjaでしたが、大して使ってないのでcomment outしても変わらないですし… localでは全体的に通しても通っている、CircleCI上でも単体や相性の悪いものの後でなければ動く、でも全体的に通すとtestに失敗する、というのはとても悔しく、厄介です。結局心折れて、componentが見つからなければtest codeをthroughするようにしました。端からdescribe.skip(...よりはマシでしょう。それにしても何でやねん。大したtestしてるわけでもないclasses(methodがcallされたかどうかをassertしてる程度)に阻まれて、ちゃんと作ったspecのtest(条件がどうならどういう表示状態、input要素をclickしたらどういう文言のdialogが出て、等)を実行できないというのは何とも残念でなりません。 Vueのtest、ムズカシイです。child componentの扱い、ですね。何気なく使っているであろうel-tableとかel-dialog、そのままだとそんなcomponent知らないよとwarningが出まくります([Vue warn]: Unknown custom element: &lt;el-table-column&gt; - did you register the component correctly? For recursive components, make sure to provide the "name" option.)。wrapperからはHTML Tagとして扱えるので最後はそうするのですけど、出来ればちゃんとcomponentとして扱いたいところです(でないとtable等でdataをloopさせて表示を作るcodeだった時のdata部分がそっくりないのでtest出来ない)。spec classでshallow(ormount)する時にstubs:するのかと思ってた(前それで頑張った気がした)んですけどそうではなく、spec対象元classでちゃんと定義されていれば?、否、ElementUIなんかは丸っと読み込まれているのでいいのかなぁ?ともあれ、spec classでlocalVue.use(Table)としたlocalVueをshallow(ormount)時に読み込んでやる必要がありました。でそのcomponentをfindして.props()して中身をexpectしていくという流れですね。最初はconsole.log(wrapper.html())で何が捕れるのかよく観察すると。でも上記のように、何かが変わるとすぐ取れるcomponentが変わるようなので、怖いです。その辺りの機微がよくわからないので… Vue testにおけるtickの待ち方。it(..., async () =&gt; { ... await Vue.nextTick(); ...}); 2 ticks待たないと値が変わらないことも。 wrapper.findAll('.cell')で取って来た値はElement Objectなので、見た目上は空でもexpect(cell).to.be.emptyとは出来ません。expect(cell.isEmpty()).to.be.true Vue componentって、初期値propsとdataは分けねばならない? 当初、dataに初期値を渡そうとしてどうしても出来ず、途方に暮れました。dataの初期値はdata部に書いた最初に返す値でそれは何でもよく、tagの方で渡す値で上書きされるのかと思ったら違うんですね。ではdataではなくpropsを使うのかと思うとそうでもなく、propsを後から変更しようとすると怒られますし。Stackoverflowにあった解決策は、初期値propsとdataを使い分けること。無駄な変数増えて何だかなぁとは思いますれど、そういうもの? slot-scopeって、="scope"ではなく={row}とやればいいんですね。本で知りました。 ...mapGettersがなかなか効かなくて苦労しました。computed:に入れる、storeのmodule構造に合わせてmodule(file)名/method名にする、とやったんですが、undefinedって言われて。store経由でthis.store.value_nameとかってやると取れるのに。でもState を直接参照せず、getter 経由で参照することを強く推奨するというので頑張りました。一回は諦めたんですけど、その後retryしたらうまくいくように。結局どうしてうまくようになったのか、決め手が何だったのかよく分からずじまいです。 確認dialogを出すというんですが、非同期のJavascript/Vueにあってはこれが意外とムズカシイです。確認dialogというからには処理を止めなければなりません。よくやるように「flagで表示を制御」ではmain処理止まらないんですよね。そもそもsleepだとて簡単には行かないですよねJavascriptで。とても苦労しました。async/awaitを導入してやっと止めました。要は「dialogを作っているところをnew Promiseでくるんでreturn、それをawait、しているfunctionをasync」ですかね。 dialogって自分で$destory()出来るんですね。生成と削除は外からやらないとダメかな、と思っていた(ので$emitして親で受けてdialog.$destory()してた)んですけど、実際生成は外から$mount()呼ばないとならないですけど(created:やmounted:はcallbackですよね)、「ボタン押したら消滅」で自己$destroy()は出来るんですねやってみたら。いちいち面倒くさいけどやってみないとわからんもんですね。 dialogを出す必要に迫られたんですけど、よくあるようにdialogを予めDOMに仕込んでflagでappear/disposeとするには、2階層上のVue componentに書かないと、loopで大量生産されてしまうことが判明。Vue component間は親との対話だったら$emitと、戻しは何だ??を使えば出来ますが、2階層上は面倒です。情報渡すだけならまだしも、その後の処理を孫でやるならdialogの結果を2階層戻してもらわないとならないですし、じゃぁ処理もgrandparentに渡す? 処理に必要な情報も渡さないとだし、grandparentで知らなくてもいいことを知らなきゃいけないなんて。ということで、何かないかなーと探すと、揮発性dialogというものが(揮発性の高いコンポーネントを作る話)。探すと似たようなことしてる人割と多く。これを理解して、そのままではなく2 filesで済むようにして自分のにapplyするのも頭を使いました。これ結構いいような。Vueのdialogも一般的にこれでいいような。初からDOMについているのではなくって。 既述のように、awaitもうまく行ってよし、じゃぁtestを書くぞ、と試したら、いきなりわけのわからないerrorに見舞われてそもそもtestが動かなくなってしまいました。なんでだろー?! 自分以前では確かに動いていたので、自分のせいです。でもどのfileが悪いとかも出て来ないのでまるで取っ掛かりがなく、途方に暮れました。error messageで検索すると、どうやらなんとasync/awaitを使った場合に出るbable/polyfill系のerrorだとか。えー!! 折角苦労してasync/awaitでdialog止めたのに! 代替手段なんて思い付かないよー! ということで、何とか突破すべく頑張りました。babelの線で色々と調べてみると、こうしたら直った、ああしたら、とか種々書いてあるんですね。新たなgem install以外は全部試しましたけど、うまく行かず。でもこの記事を参考にvueのsetup.js冒頭にrequire("babel-polifill");と書いたら!! 動いてくれました!! 省みれば僅か1行の追加ですけど、この1行には数時間もの汗と涙が詰まっているのです。 APIの試験をするのに、curlやPostmanを使うのはよくありますけど、cookieが何か長くてcopyが面倒だなーと思ったので、Chrome Developer toolのConsoleにてfetchでやる手法を。fetch(｀/apis/endpoint/＄｛id｝｀,{method:method,credentials:'include',headers:{'X-CSRF-Token':token}}).then((response)=&gt;console.log(response))とすればBrowserの持っている認証情報をそのまま使ってくれます。credentials:'include'が肝です。どうせ使い捨てcommandですから。 Railsは自動的にCSRF対策がなされている、ということで、何してるんだろーと思いきや、session毎に予測困難なX-CSRF-Tokenが付くというものでした。request毎に違うものでなくてもいいのか? いいのか…予測困難なら… Railsの日付、Timezone気を付けないといけないのですけど(AWSはUTCなので)、Time.currentやDate.currentのように.current methodを使えばTimezoneを意識してくれる模様。.nowより.currentがということでやはり「ナウい」は死語ということですか。 Ruby、共通処理は継承関係作って親クラスに、と思っていたんですけどそれってJava的発想? OOP的というか。RubyではむしろMixin? MixinってAOPかと思ってたんですけど。Duck Typingだからそもそもclass的には無関係でいいんですね。Java屋さんとしては何だかなぁと。 Rubyのstream(mapやselectやrejectやeach等)、もうfoolproofとしてlazy...forceを付けるのがいいんじゃないか、と思ったんですけど、そうでもないんですねこれ!! ってかそもそもどうしてlazyがdefaultじゃないんだろう? ってJava屋さんとしては思ってたんですけど、銀の弾丸ではないという話があって衝撃でした何を今更ですけど(古い話のようで今は多少performance上がったそう)。というわけで、lazyは「省メモリ」か「後でかなりの数takeすることがわかっている場合」でないと効果なさそうなんですね。元ネタ 最近何か安易に大量のlogをslackに投げていたので、そうじゃないでしょ基本的にはlog fileでしょ、それもdefaultのfileでいいよね、ということで、Logger.new(...)ってやったんですけどどうして毎回file nameを指定しなきゃいけないのか、しかもfile名指定してnewすると都度file作りやがる、のでappend modeでopenして、とかってやらないとならないの?! なんか面倒臭くね? と思ってたら、Rails.loggerでいいんですね。なぁんだ。@logger ||= Rails.logger controllerで最終的?に全てのerrorを拾うにはrescue_from error_class名, with: :method名、事前validationを定義するにはbefore_action :method名 決まった名前をclass methodにするか定数にするか、は、定数の方が良いようです。ref. Class method vs constant in Ruby/Rails Rubyで「objectの同一性」とは、Javaと違って==、eql?、hashの3 methodあるんですね。うち==はArrayで、残る2つはSetで使われる模様。Arrayでは比較通るのにSetでは違うと判定され悩みました。Setではobject hashを取っているからなんですね。でも結局、.to_jsonすれば同一性methods定義しなくても行けました。何だかなぁですけどまぁunit testなのでいっかー。 すっかり忘れてましたけどroutesでresourcesを使えばendpoint定義をまとめられますね。path parameterも積極的に使うべきでしょう(その存在を強制できるので)。parameter nameは基本idですけど変えられるんですね。 ActiveRecordで、MySQLみたいに「あれば取得、なければ作る(recordも入れる)」なんてあるかなーと思ったら.find_or_create_byがありました。更にforeign key制約をつけて、その制約に反したらerrorにして欲しい場合は、.find_or_create_by!でした。 RubyでVO(Value Object)を作るのは、どうしたらいいんですかね。object = Struct.new(:variables, ...)とobjectとして使えば良い、という記述があったので、今度試してみます。←ここでいうobjectってclasなんですね。 RubyにもJavadocのようなcomment流儀あるんですね。YARDですか。@param [引数のクラス] 引数名 引数の説明、@return [戻り値のクラス] 戻り値の説明 配列からhash mapを作る手法、色々ありますよね。よくある「[key, value]の配列の配列にしてから.to_set」はloopを2回回すので何だかなぁと思っていました。Rubyにもあるreduce(inject)を使えば出来る!とあったのでやったのですけど、そうしたらrubocopに「そういう時はeach_with_objectを使え」と言われました。ナルホド、それなら確かにblockの最後にhashをreturnせずともよいわけですね! というわけで、list.each_with_object({}){|value,hash|hash[key]=value}、更に一気にhashのhashを作るには、list.each_with_object(Hash.new{|hash,key|hash[key]={}){|value,hash|hash[key1][key2]=value} 同様に、一気にSetを作るには、list.each_with_object(Set.new){|value,set|set&lt;&lt;value}の方が、最後に.to_setよりよいでしょう。 reekに「log.debugを複数回使っているからまとめよ」と言われて弱りました。単純にまとめると、毎回引数を評価しちゃうじゃないですか。でもloggingなので遅延評価が必要でしょう。というわけで、blockを残せるようにmethod定義しました。def debug(&amp;block) Rails.logger.debug(&amp;block); end使う時はdebug{...}です。 rescueで=&gt; eとせずとも$!でerrorを参照できるんですね。rescueでretryとすればbeginからまたやってくれるんですね! ただ、上限設けないとずっとretryし続けるので注意ですかね。 Railsでmail送信は、configが終わっているのであとは、ApplicationMailerを継承したclassを作成、app/views/class名/method名.html.hamlといったtemplateを用意、という感じでした。html mailだけだとspam判定されやすいとのことなので、app/views/class名/method名.txt.erbも用意しました。classのinstance fieldをmail template中で参照できます。 ActiveRecordでの取得はなるべく.pluckにした方が速いし軽い、ということでそうしてますが、でもそうすると何のためのDTOなのかと。model定義の意義が少し薄れるような。modelがfatなのが悪い? .pluckでない場合はせめて.selectで選択するfieldを減らしてやって生成されるobjectを少しでも軽くすると。ただActiveRecordからObjectを生成するcostが高いとのことなので、.pluckの方が推奨されています。….selectって、modelにないfieldもselectできるんですね。そしてdynamicにmethodが生えると。なんかそこまで行くと気持ち悪い気がするのは、やはりJava出身だから?? yieldはよく使いました。if block_given?も併せて。 instanceからclass variableへの参照はinstance.class::VARIABLEでした。 rspecでallow(Rails).to receive(:logger).and_return(spy('logger'))とするとloggingが一律空回しになります。 method chainをallowするにはreceive_message_chain(e.g.expect(SomeClass).to receive_message_chain(:joins, :distinct, :where, :pluck)) .with(...)を複数書いても、それぞれのmethodの引数指定ではなく、最後の一つしか評価されていない? rspecで、違ったparameterで同じobjectのmethodをcallするのをstubするには、allow(...).to receive(:same_method).with(...)を並列で何度も書けばよいです。with(...)をmethod chainさせるとかではないんですね。 rspecでinstance doubleに引数を連ねるとmethod stubになります。e.g. double('user', id: 1)→user.id==1(doubleの最初の引数は任意名) 調べればすぐ出てくることではありますが、Railsのmigrationのcreate_table時にprimary keyを自動で出来るid以外にしたい場合はcreate_tableの行にprimary_key: key名と書きます。更にforeign key constraintを付け加えたい場合は、その下にadd_foreign_key :table名, :foreign_table名と書けば大丈夫でした。 RubyでTime format時、.strftime('%X')とすると一文字で15:37:21に、.strftime('%F')とすると2020-03-01になるので便利です。また、.iso8601とするとTを挟んでTimezone付きで2020-03-01T15:39:10+09:00というようにしてくれるのでこれも便利です。 嫁の顔忘れてもTimecop.returnは忘れないでねという話にあるように、Timecop.travel(Time.parse('2020/3/1 0:01:03')) do ... endで囲うのはいいですね。Timecopも.travel(特定時刻にしてから時を刻む)と.freeze(特定時刻にして時を止める)では違うので注意です。 Rubyで文字列の連結は&lt;&lt;がいいみたいですね。C++みたいですね。 ActiveRecordでN+1問題を回避するには、なるべくjoinsした方が良いようです。が、図に乗って巨大tableをjoinするとそれはcostがでかいので、分割して引いた方がいいんでしょう。joinsするにはmodelにhas_manyとかの関係が書いてないとダメでした。2つ先の関連も書けて、has_many :second_table, through: :first_tableとthrough:で繋げられました。 と思ったのは幻想でした。書けちゃいますけど、joinされたものはkeyで結びついたものではなく、雑に合わさったもので、whereで引いてもそうでないものまでついてきていて、bugを引き起こしていました。ちゃんとやらないとダメですね。 「ちゃんとやる」とは、Railsでモデルを4段階joinする方法で、もう一度理解するjoinsとmergeにあるように、ActiveRecordのjoinsで繋ぐ時にjoins(first:[second: :third])とすること。これでちゃんとjoinされました。 controllerで、APIっぽくheaderだけ返して中身は不要という場合、head :created(201)とかhead :unauthorized(401)と書けます。 Rubyのhere documentはもう&lt;&lt;~でよくないすか? &lt;&lt;だと左端にひっつくので。 Rubyのreduceはeach_with_objectに取って代わられるのですが、reduce(&amp;:method)で済むところに活路があるようです。使えたのはSetのmergeですね。[Set.new([1,2,3]),Set.new([2,3,4])].reduce(&amp;:merge)==Set.new([1,2,3,4]) 複数回.include?するなら絶対Setにすべきですよ。 定数文字列もなるべく.freezeすべきでしょうか。magic comment # frozen_string_literal: trueで(が?)よいのでしょうか。 controllerのrspecで、before_actionのtestを書くことになったのですが、色々と悩みました。どこに書くか? 書いたclass? 使うclass? Javaでabstract classのtestだとconcrete classに書きますが、controllerは使う側に書くと多岐にわたるので、application_controller_spec.rbに書きました。 curlでtest clientとしてJSONを-dでPOSTしたところ、どうもうまく行かなくて、調べるとserver側に"{\"key\":\"value\"}"なんて渡っていました。どうしてー?! HTTP Request HeaderにContetnt-Type: application/jsonが必要なんですね! そういえば。 今更ですがAngularJSのtestで、window.location.hrefをassertionしようとして四苦八苦しました。How to mock $window.location.replace in AngularJS unit test?等から得たことは、window.location.hrefだとtest(jasmin)で拾えないので$window.location.hrefにする、responseがemptyだとどうしてもtestの方でcatchしてくれないのでdummy値をresponse代入する、testでは$windowがemptyで悩みました。$provide.value('$window',でprovideしてbeforeEach(inject(function($injector, $rootScope, $httpBackend, $window) {でinjectしつつwindow=$window;と付け替えて、assertionのところでwindowを使う、という。 diffで相違は出ますが、共通行を抽出するには? comm -1 -2 file1 file2(但しfilesはsort済であること) ref.２つのファイルの共通行を抽出する方法 MySQLでindexがついたかどうかを確認するには、describe table名ではMulと付くだけでよくわからず。show create table table名でCREATE文を出すことで。 controllerのrspecで、sessionってどう表すのかと。get :index,params:{aaa:'AA'}だからget :index,params{...},session:{...}かと思いきや、実際そう書いてあるところもあるんですが、それではうまく行かず、get :index,{param:'something'},{session:'something'}と。ref. RSpec set session object rspecでprivate variableにsetするには、some_instance.instance_variable_set('variable_name', 'some value')、getするには`some_instance.instance_variable_get(‘variable_name’) visitor patternを適用しようと思っていたのですけど、諦めました。RubyってDuck Typingだからあんまり効果を見出だせず。Ruby で Visitor パターンをあまり使わない理由，Ruby で Visitor パターンを使うときという投稿もあり。 upstart で start した job の設定を変更するには一度 stop する必要がある へーそうなんだ! ってぃぅか何のためのrestartっすか? restartあるんだから機能すると思いますよねぇ…orz 1.hour.agoがあるのに.laterや.afterはないのでどうするんだろう? と思ったら、1.hour.from_nowっていうんですね。 controllerのrspec書いていて、やっと単体で通るようになったので、いざ全体的にrspecかけてみると、コケます。確かに自分の書き足したところ。でも通る時もある。どうして?→そういう、rspecの結果が不安定な時は、実行順序依存性があります。これを紐解かねばなりません。それが厄介でした。といいますか、rspecって全体的にかけると、といいますかspec fileを指定しないでdirectoryだけ指定して実行すると、spec filesの実行順序がrandomなんですね。それはその方がいいですね。なので、色んな手を使って順序依存性を解明していきます。ref. RSpecコマンドのオプションまとめ rspecに--order random:NNNNNを付けてrandom seedを固定、-f dとして出力formatにclass名を出すようにし、--no-colorでescape sequenceを抑制します。-o filename.logでfileに出力、それをうまく行ったときと失敗した時で保存して、class名を比較しました。当該classのtest以前だけが大事なので、そこだけ出してsortしてdiff取ります。失敗事例が複数取れれば、それらの共通classをcomm -1 -2 &lt;(...) &lt;(...)で抽出するのが良いです。 docker-compose exec rails bash -c 'RAILS_ENV=test bundle exec rspec --order random:32510 -f d -o 32510.log --no-color' diff &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' 32510.log|awk '{if(/ApplicationController/){exit}print}'|sort)|ag '&lt;' comm -1 -2 &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' 4627.log|awk '{if(/ApplicationController/){exit}print}'|sort) comm -1 -2 &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' controllers2.log|awk '{if(/ApplicationController/){exit}print}'|sort) &lt;(grep -e '^[A-Za-z]' -e '^ Apis::' 48327.log|awk '{if(/ApplicationController/){exit}print}'|sort)|grep -r -l -f /dev/stdin spec/controllers|sort -u|tr -d '\r' 単体では通るのに全体だと時々こけるというのはどうしてもよくわからないので、何度も何度も実行して観察してみると、コケる時のFの出る位置がまちまちなんですね。あぁ、これって毎回test順序違うのか、とその時初めて思い至りました。それで、rspecにおけるrandom seedの固定方法や出力形式の指定の仕方を調べ、何とか実行順を成功時と失敗時で比較して、怪しいものを試して、辿り着いたのでした。 controllerのtestでは、before_actionに指定したprivate methodのtest且つ利用するchild classでのtestではなくbase classでのtestなので、当初はcontorller.send('method_name')とかやってたんですけどこれは本来的ではないな、ということで、Anonymous Controller使いました。といってもdescribeの中でcontroller do ... endして、そこでbefore_action :method_nameしただけのものです。それでそのdescribe内のcontrollerは定義したものになるという、お手軽な。でもここでエラー(Errorをthrow、じゃないRubyだからraiseですか、する筈が正常に終了してしまう)が出て、悩みました。結局、わかってみればどうということはないのですが、他のspec classで、ApplicationController.skip_before_filter :login_requiredをbefore do...でやっていて、でもafter do...で元に戻していなかったから、というのが原因でした。 そもそも、ApplicationController.skip_before_filterなんてせずに、単にallow(controller).to receive(:method)とmockすれば十分です。そうしたらafter do...だって不要ですし。ref. before_filterを分離してテストする方法(Rspec) 別段true返す必要もないんですねこのbefore_actionって。before_filterは古いんだそう。 いつもscheduleの確認をするのに。bundle exec whenever AngularJSでのredirect? になるのかな? は、routesの$stateProvider.state('...')に定義されたところへ、$state.go('...')で飛びます。ref. Angularjs UI Routerの使い方 でも結局、AngularJSのrouting範囲外に飛ばしたかったので、$window.location.href = ...で飛ばしました。 ActiveRecordのscopeに引数を取る場合には、scope scope_name -&gt;(arg1, arg2) { ... } 矢印とカッコはくっつけないとreekに怒られます。 RubyでStringのselectを正規表現でするならgrep(/.../)、-vの場合はgrep_v(Ruby equivalent to grep -v) AngularJSのroutingってURLについたanchor(#以下)に出るのですが、それってそもそもbrowserがserver側に送信しないんですね… ActiveRecordで選択して保存を1行で。例えばUser.find(1).update(name:'new name') rspecでActiveRecordのtest dataってfixtureやFactoryGirlを使うものかと思っていたのですけど、普通に.createとか.updateとかして大丈夫なんですね。大丈夫、というのは、後腐れがない、んですね。 RSpecでallowでmockした時に、.and_returnだけでなく処理(sleep(1))を入れたかったんですけどどうしたら? と思ったら、単にblockでいいんですね。blockの最終評価値がreturnになる、というなら.and_returnって不要なの?]]></summary></entry></feed>