<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: m-yoshimo</title>
    <description>The latest articles on Forem by m-yoshimo (@m_yoshimo).</description>
    <link>https://forem.com/m_yoshimo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F355525%2F28758397-853d-43b7-91bc-9fa6f4644105.jpeg</url>
      <title>Forem: m-yoshimo</title>
      <link>https://forem.com/m_yoshimo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/m_yoshimo"/>
    <language>en</language>
    <item>
      <title>既存の xxenv を anyenv に移行する時は install し直したほうが良い</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:46:42 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/xxenv-anyenv-install-1igm</link>
      <guid>https://forem.com/m_yoshimo/xxenv-anyenv-install-1igm</guid>
      <description>&lt;p&gt;python、ruby、go、node、haskell ... と複数の言語で開発しています。&lt;br&gt;
各言語ごとに xxenv をインストールしていたのですが、さすがに home 直下も汚くなるし、統合的に管理したほうが何かと便利だと思って anyenv を入れようと思って嵌った。&lt;/p&gt;
&lt;h1&gt;
  
  
  結論
&lt;/h1&gt;

&lt;p&gt;先に結論から。&lt;br&gt;
既存の .xxenv を .anyenv/env/xxenv に mv しても上手く行きません！&lt;br&gt;
anyenv に移行する人は、安直せずに anyenv で改めて各言語・ライブラリをインストールし直しましょう。&lt;/p&gt;
&lt;h1&gt;
  
  
  どうして上手くいかないのか
&lt;/h1&gt;

&lt;p&gt;移行時の環境はこんな状態。&lt;br&gt;
anyenv にはまだ何も入れていません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$  ls -la | grep env
drwxrwxrwx 1 m-yoshimo m-yoshimo   4096  4月 24 21:51 .anyenv
drwxrwxrwx 1 m-yoshimo m-yoshimo   4096  4月 17 15:05 .pyenv
drwxrwxrwx 1 m-yoshimo m-yoshimo   4096  6月 18  2018 .rbenv
$ anyenv versions
$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;試しに pyenv を移行しようと思いました。&lt;br&gt;
さほどライブラリをインストールしているわけではないですが、python2 系と python3 系の両方を利用していたので、言語をインストールし直すのは面倒と思って、安直に mv したわけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pyenv versions
  system
  2.7.16
* 3.7.3 (set by /home/m-yoshimo/.python-version)
$ mv .pyenv .anyenv/envs/pyenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;すると、anyenv と python は見事に認識してくれました！&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ anyenv versions
pyenv:
  system
  2.7.16
* 3.7.3 (set by /home/m-yoshimo/.python-version)
$ which python
/home/m-yoshimo/.anyenv/envs/pyenv/shims/python
$ python --version
Python 3.7.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;しかし、これはぬか喜びでした。&lt;br&gt;
pip を動かそうとすると。。。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip list
/home/m-yoshimo/.anyenv/envs/pyenv/pyenv.d/exec/pip-rehash/pip: /home/m-yoshimo/.anyenv/envs/pyenv/versions/3.7.3/bin/pip: /home/m-yoshimo/.pyenv/versions/3.7.3/bin/python3.7: 誤ったインタプリタです: そのようなファイルやディレクトリはありません
$ pyenv rehash
$ pip list
/home/m-yoshimo/.anyenv/envs/pyenv/pyenv.d/exec/pip-rehash/pip: /home/m-yoshimo/.anyenv/envs/pyenv/versions/3.7.3/bin/pip: /home/m-yoshimo/.pyenv/versions/3.7.3/bin/python3.7: 誤ったインタプリタです: そのようなファイルやディレクトリはありません
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;rehash しても init しなおしても勿論ダメ。&lt;br&gt;
どうも、直接ハードコードされているので、これを解消しないとダメな模様&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ grep -rn '\.pyenv' .anyenv/envs/pyenv/versions/ | head -n 10
.anyenv/envs/pyenv/versions/2.7.16/bin/2to3:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/aws:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/aws_completer:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/easy_install:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/easy_install-2.7:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/futurize:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/idle:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/jp.py:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/pasteurize:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
.anyenv/envs/pyenv/versions/2.7.16/bin/pip:1:#!/home/m-yoshimo/.pyenv/versions/2.7.16/bin/python2.7
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;python ファイルにハードコードされているということは。。。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ grep -rn '\.pyenv' .anyenv/envs/pyenv/versions/2.7.16/lib/ | head -n 10
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/libpython2.7.a に一致しました
.anyenv/envs/pyenv/versions/2.7.16/lib/pkgconfig/python-2.7.pc:1:prefix=/home/m-yoshimo/.pyenv/versions/2.7.16
.anyenv/envs/pyenv/versions/2.7.16/lib/pkgconfig/python-2.7.pc:3:libdir=/home/m-yoshimo/.pyenv/versions/2.7.16/lib
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/BaseHTTPServer.pyc に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/BaseHTTPServer.pyo に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/Bastion.pyc に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/Bastion.pyo に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/CGIHTTPServer.pyc に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/CGIHTTPServer.pyo に一致しました
バイナリファイル .anyenv/envs/pyenv/versions/2.7.16/lib/python2.7/ConfigParser.pyc に一致しました
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;もちろんコンパイル済みの方にもハードコードされているわけです。&lt;/p&gt;

&lt;p&gt;上記に見えるのは一部でしかなく、これらをすべて sed で置換するのも大変面倒なので、それするくらいなら各言語のバージョンとライブラリをインストールし直したほうが安全かつ楽ということでした。&lt;/p&gt;

&lt;p&gt;試しに rbenv も mv でやってみましたが、同様の状況になったので諦めました。&lt;/p&gt;

&lt;p&gt;おしまい&lt;/p&gt;

</description>
      <category>anyenv</category>
      <category>pyenv</category>
      <category>rbenv</category>
    </item>
    <item>
      <title>Ubuntu 18.04 にあげたら Rails で libMagickCore.so.2 が見つからない</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:45:57 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/ubuntu-18-04-rails-libmagickcore-so-2-6n5</link>
      <guid>https://forem.com/m_yoshimo/ubuntu-18-04-rails-libmagickcore-so-2-6n5</guid>
      <description>&lt;p&gt;先日、WSL の Ubuntu 環境を 16.04 から 18.04 にアップグレードしたのですが、いざ rails を起動しようとすると libMagicCore.so.2 が見当たらないとのこと。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails s
rails aborted!$
LoadError: libMagickCore-6.Q16.so.2: cannot open shared object file: No such file or directory - /home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/rmagick-2.16.0/lib/RMagick2.so$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/rmagick-2.16.0/lib/rmagick_internal.rb:12:in `&amp;lt;top (required)&amp;gt;'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `block in require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:in `load_dependency'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:283:in `require'$
/home/m-yoshimo/bundle/MyApp/ruby/2.4.0/gems/rmagick-2.16.0/lib/rmagick.rb:1:in `&amp;lt;top (required)&amp;gt;'$
/mnt/c/work/repositories/MyApp/config/application.rb:10:in `&amp;lt;top (required)&amp;gt;'$
/mnt/c/work/repositories/MyApp/Rakefile:4:in `require'$
/mnt/c/work/repositories/MyApp/Rakefile:4:in `&amp;lt;top (required)&amp;gt;'$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;libMagicCore を探しても見当たらない&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls /usr/lib/libMagic*
ls: '/usr/lib/libMagic*' にアクセスできません: そのようなファイルやディレクトリはありません
$ ls /usr/local/lib/libMagi*
ls: '/usr/local/lib/libMagi*' にアクセスできません: そのようなファイルやディレクトリはありません
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;と思ったら、/usr/lib/x86_64-linux-gnu に移動してました&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls /usr/lib/x86_64-linux-gnu/libMagick*
/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.a         /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.a
/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.la        /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.la
/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so        /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.so
/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so.3      /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.so.3
/usr/lib/x86_64-linux-gnu/libMagickCore-6.Q16.so.3.0.0  /usr/lib/x86_64-linux-gnu/libMagickWand-6.Q16.so.3.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;とりあえず PATH を通してあげて&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH=/usr/lib/x86_64-linux-gnu:${PATH}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;rmagick はインストール時にロードする libMagicCore のファイルパスを記憶するようなので、一度 rmagick を削除してから、再度 bundle install しなおす&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec gem uninstall rmagick

Select gem to uninstall:
 1. rmagick-2.16.0
 2. rmagick-3.0.0
 3. All versions
&amp;gt; 3
Successfully uninstalled rmagick-2.16.0
Successfully uninstalled rmagick-3.0.0

$ bundle install
...
Fetching rmagick 2.16.0
Installing rmagick 2.16.0 with native extensions
...
Bundle complete! 80 Gemfile dependencies, 375 gems now installed.
Bundled gems are installed into `/home/m-yoshimo/bundle/MyApp`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;これで無事に起動できるようになりましたとさ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails s
=&amp;gt; Booting Puma
=&amp;gt; Rails 5.2.0 application starting in development
=&amp;gt; Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.4 (ruby 2.4.3-p205), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ここまで書いて気付いたのですが、普通に過去に同じ経験した人がいたようです。&lt;br&gt;
&lt;a href="https://qiita.com/aiyu427/items/9231aab85c3b3b8ac227"&gt;https://qiita.com/aiyu427/items/9231aab85c3b3b8ac227&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>wsl</category>
    </item>
    <item>
      <title>debuild で一度に複数のパッケージ作る時のメモ</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:45:01 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/debuild-2o4a</link>
      <guid>https://forem.com/m_yoshimo/debuild-2o4a</guid>
      <description>&lt;p&gt;&lt;a href="https://twitter.com/m_yoshimo"&gt;@yoshimo&lt;/a&gt; です。&lt;br&gt;
debuild で一度に複数のパッケージを作ることが多いので、メモ代わりに残します。&lt;/p&gt;
&lt;h1&gt;
  
  
  概要
&lt;/h1&gt;

&lt;p&gt;ちょっとした HTTP Server アプリ作った時に、ポート番号や URI を production や staging といった環境で変えたい。&lt;br&gt;
chef や ansible といったオーケストレーションツールだったり、Cloud インスタンス側の環境変数を使えば良いんだけど、オーケストレーションツール使うほどでもなく、でも変更履歴くらいは管理したい。&lt;/p&gt;

&lt;p&gt;お手軽に config パッケージで管理する程度で良いが、git の repository は増やしたくない。&lt;/p&gt;

&lt;p&gt;ということで、複数のパッケージを一度の debuild で作れるようにしておけば良いやってことで、備忘録的に最低限何を記述すれば出来るのか記載しておきたいと思います。&lt;/p&gt;
&lt;h1&gt;
  
  
  前提
&lt;/h1&gt;

&lt;p&gt;debian/rules の記載は CDBS を使うことを前提にしています。&lt;br&gt;
CDBS については、有用な記事がいっぱい転がっているので、そちらを探してください。&lt;br&gt;
&lt;a href="https://eng-entrance.com/linux-package-deb-create"&gt;こことか&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  configs
&lt;/h1&gt;

&lt;p&gt;環境毎の設定ファイルは configs というディレクトリに置くことにします。&lt;br&gt;
例えばこんな感じ。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -l configs/*
-rwxr-xr-x 1 m-yoshimo m-yoshimo 136  4月  5 15:26 configs/config.develop.yml
-rwxr-xr-x 1 m-yoshimo m-yoshimo 135  4月  5 15:26 configs/config.prod.yml
-rwxr-xr-x 1 m-yoshimo m-yoshimo 132  4月  5 15:26 configs/config.stg.yml

$ cat configs/config.prod.yml
port: 8080

$ cat configs/config.stg.yml
port: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  debian/control
&lt;/h1&gt;

&lt;p&gt;debian/control で Package を複数定義します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source: my-http-server
Section: non-free
Priority: optional
Maintainer: Masanori Yoshimoto &amp;lt;masanori.yoshimoto@zenkigen.jp&amp;gt;
Build-Depends: debhelper (&amp;gt;= 9), cdbs
Standards-Version: 4.0.0

Package: my-http-server
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: My HTTP server

Package: my-http-server-conf
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Configuration of my-http-server for production

Package: my-http-server-conf-staging
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Configuration of my-http-server for staging

Package: my-http-server-conf-development
Architecture: all
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Configuration of my-http-server for development
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  debian/rules
&lt;/h1&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/make -f

include /usr/share/cdbs/1/rules/debhelper.mk

DEB_DESTDIR_app = "debian/my-http-server"
DEB_DESTDIR_conf = "debian/my-http-server-conf"
DEB_DESTDIR_conf_stg = "debian/my-http-server-conf-staging"
DEB_DESTDIR_conf_dev = "debian/my-http-server-conf-development"
TARGET_DIR = "/opt/zenkigen/my-http-server"
SYSTEMD_PATH = "/etc/systemd/system"

install/my-http-server::
    install -pd $(DEB_DESTDIR_app)$(TARGET_DIR)
    install -pm 755 bin/my-http-server $(DEB_DESTDIR_app)$(TARGET_DIR)
    install -pd $(DEB_DESTDIR_app)$(SYSTEMD_PATH)
    install -pm 644 my-http-server.service $(DEB_DESTDIR_app)$(SYSTEMD_PATH)

install/my-http-server-conf::
    install -pd $(DEB_DESTDIR_conf)$(TARGET_DIR)
    install -pm 755 configs/config.prod.yml $(DEB_DESTDIR_conf)$(TARGET_DIR)/config.yml

install/my-http-server-conf-staging::
    install -pd $(DEB_DESTDIR_conf_stg)$(TARGET_DIR)
    install -pm 755 configs/config.stg.yml $(DEB_DESTDIR_conf_stg)$(TARGET_DIR)/config.yml

install/my-http-server-conf-development::
    install -pd $(DEB_DESTDIR_conf_dev)$(TARGET_DIR)
    install -pm 755 configs/config.develop.yml $(DEB_DESTDIR_conf_dev)$(TARGET_DIR)/config.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;install を列挙するだけですね。&lt;br&gt;
複数のパッケージがある場合は、debian ディレクトリ以下にパッケージ毎のディレクトリが生成されて、その下にインストール先が展開されます。&lt;br&gt;
そのため、自動で生成される $(DEB_DESTDIR) 定数では対応できないので、各パッケージのディレクトリ ("debian/app" など) を指定します。&lt;/p&gt;
&lt;h1&gt;
  
  
  rules と control 以外
&lt;/h1&gt;

&lt;p&gt;1つのパッケージを作る時と変更はしなくてよいはず。&lt;br&gt;
postinst とか使っている場合は注意が必要かもしれません。&lt;br&gt;
試してないので後日試そうかと思います。&lt;/p&gt;
&lt;h1&gt;
  
  
  debuild 成果物
&lt;/h1&gt;

&lt;p&gt;こんな感じでパッケージが生成されます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ debuild -us -uc
(長いので省略。Warning とか出たら頑張って直して)

$ ls -la ../my-http-server*.deb
-rw-r--r-- 1 m-yoshimo m-yoshimo    1400  4月  5 16:47 my-http-server-conf-development_1.0.0_all.deb
-rw-r--r-- 1 m-yoshimo m-yoshimo    1398  4月  5 16:47 my-http-server-conf-staging_1.0.0_all.deb
-rw-r--r-- 1 m-yoshimo m-yoshimo    1386  4月  5 16:47 my-http-server-conf_1.0.0_all.deb
-rw-r--r-- 1 m-yoshimo m-yoshimo 3761490  4月  5 16:47 my-http-server_1.0.0_amd64.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  まとめ &amp;amp; デメリット
&lt;/h1&gt;

&lt;p&gt;同じ repository で一度の debuild で app と conf のパッケージが作れるのでお手軽。&lt;br&gt;
しかし、debian/changelog が共通なので、conf を修正するだけで app (ここでいう my-http-server_1.0.0) の version も上げないといけなくなります。&lt;br&gt;
これは非常に良くないデメリットです。&lt;br&gt;
また、バージョンが破綻することにもなります。&lt;/p&gt;

&lt;p&gt;chef や ansible といったツールを使った方が良いでしょう。&lt;br&gt;
Kubernetes なら ConfigMap といった環境変数使った方が良いと思います。&lt;/p&gt;

</description>
      <category>debuild</category>
    </item>
    <item>
      <title>Slack に投稿した日報を API で取得して、ReleaseNote 風にする (じぶんリリースノートの準備)</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:43:59 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/slack-api-releasenote-2l3d</link>
      <guid>https://forem.com/m_yoshimo/slack-api-releasenote-2l3d</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;最近、じぶんリリースノートなるものが流行りだそうで、少しのってみることにしました。&lt;br&gt;
&lt;a href="https://blog.a-know.me/entry/2019/02/02/214612"&gt;https://blog.a-know.me/entry/2019/02/02/214612&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;【※注意】4/21 にやろうと思ったばかりなので、まだじぶんリリースノートは作れていません。来月作るための簡単な準備をしたので、それを共有します。&lt;/p&gt;

&lt;p&gt;そもそも、フリーランス時代に色々と自分のスキルだったり実績を整理していたんだけど、これがまぁまぁ大変で、抜け漏れはあるわ・アップデート忘れるわで結構面倒だったので、じぶんリリースノート作れば良いんじゃないかなって思ったのがはじまり。&lt;/p&gt;

&lt;p&gt;ただ、いきなり過去を振り返るのは大変だし、今月振り返るだけでも中々に時間が取られそう&lt;/p&gt;

&lt;p&gt;というわけで、じぶんリリースノートを作る上で、こうやると良いかもなぁっていうお試し方法をまとめてみた。&lt;br&gt;
4/21 に始めたので、来月あたりじぶんリリースノート作った時にまた改善すると思います。&lt;/p&gt;
&lt;h1&gt;
  
  
  何するの
&lt;/h1&gt;

&lt;p&gt;特別なことはしません。&lt;br&gt;
Slack に日報を投稿して、それを API で取得して、Qiita に貼りやすいように Markdown 形式で出力するだけです。&lt;/p&gt;
&lt;h1&gt;
  
  
  準備するもの
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Slack のアカウントと適当なチャンネル

&lt;ul&gt;
&lt;li&gt;個人ワークスペースを作って、jibun_release_note みたいなチャンネルを作ると良いと思います&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Slack の API 準備

&lt;ul&gt;
&lt;li&gt;API のアクセストークンの取得方法はこちらを参考に

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dkrk-blog.net/slack/slack_api01"&gt;https://www.dkrk-blog.net/slack/slack_api01&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;スコープ設定時に channels.history を選択すること (メッセージ取得権限)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Slack への投稿
&lt;/h1&gt;

&lt;p&gt;例えばこんな感じ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4rimb6Xh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44638/b2fe4e17-59c8-c80e-20f2-56773fd9c7db.png" class="article-body-image-wrapper"&gt;&lt;img width="722" alt="SnapCrab_NoName_2019-4-22_22-2-34_No-00.png" src="https://res.cloudinary.com/practicaldev/image/fetch/s--4rimb6Xh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44638/b2fe4e17-59c8-c80e-20f2-56773fd9c7db.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;社外秘など、あとで公開したくない情報には "[confidential]" をつける&lt;/p&gt;
&lt;h1&gt;
  
  
  Slack メッセージ取得
&lt;/h1&gt;

&lt;p&gt;コードはこんな感じ。(日付処理まわりとか雑なのは許して)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env python3

from datetime import datetime
import calendar
import requests
import json
import os
import sys

url = 'https://slack.com/api/channels.history'
token = os.environ['SLACK_API_TOKEN']
channel_id = os.environ['SLACK_CHANNEL_ID']
date_yy_mm = os.environ['HISTORY_REQUEST_YY_MM']

def main():
    year = int(date_yy_mm.split('-')[0])
    month = int(date_yy_mm.split('-')[1])
    _, lastday = calendar.monthrange(year, month)

    oldest = datetime.strptime(date_yy_mm + "-01", '%Y-%m-%d').timestamp()
    latest = datetime.strptime(date_yy_mm + "-" + str(lastday), '%Y-%m-%d').timestamp()
    payload = {
        'token': token,
        'channel': channel_id,
        'latest': str(latest),
        'oldest': str(oldest)
    }
    r = requests.get(url, params=payload)
    if r.status_code != 200:
        print('Failed to get response (' + r.status_code + ')')
        sys.exit(1)

    json = r.json()
    ok = json['ok']
    if ok is False:
        print('Failed to get messages')
        print(json['error'])
        sys.exit(1)
    else:
        msgs = json['messages']
        for msg in msgs:
            date = datetime.fromtimestamp(int(float(msg['ts'])))
            message = msg['text'].replace('\u2022', '+')
            out = []
            for line in message.splitlines():
                if line.find('[confidential]') &amp;gt; 0:
                    continue
                out.append(line)
            print("### " + str(date.date()))
            print('\n'.join(out))
            print()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;実行結果はこうなります。&lt;br&gt;
※ Slack のチェンネル ID の取得方法はこちら (&lt;a href="https://qiita.com/YumaInaura/items/0c4f4adb33eb21032c08"&gt;https://qiita.com/YumaInaura/items/0c4f4adb33eb21032c08&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export SLACK_API_TOKEN='xoxp-XXXXXXX...'
$ export SLACK_CHANNEL_ID=CHxxxxxxx
$ export HISTORY_REQUEST_YY_MM=2019-04
$ ./history.py
### 2019-04-22
+ mysqldump-parser (python) の施策
+ rubocop と rails_best_practice を適用してみた (rubocop のエラー多すぎるので MaxLineLength あたり緩和
+ ruby の auto-complete が遅い問題調査 (deoplete で omni の pattern 弄るのやめたらマシになった)
+ python 3.7.3 を pyenv に install
+ vim で python lsp をどう使うか調査
+ slack channel history API 使ってみた

### 2019-04-21
+ react の eslint 設定見直し
+ vim に eslint を本格導入 (自動整形は一時的に off)
+ ローカルの yarn を 1.15.2 にアップデート
+ global の node_modules を整理 (最低限必要な eslint-cli と yarn だけに変更)

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ちゃんと confidential も抜けてますね。&lt;/p&gt;

&lt;p&gt;あとは Qiita なり、CHANGELOG.md なり、ReleaseNote.md なりに貼るだけ！&lt;br&gt;
じぶんリリースノートのレポジトリを github に作って、ReleaseNote.md に &amp;gt;&amp;gt; で&lt;/p&gt;

&lt;h1&gt;
  
  
  貼ってみた
&lt;/h1&gt;

&lt;h3&gt;
  
  
  2019-04-22
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;mysqldump-parser (python) の施策&lt;/li&gt;
&lt;li&gt;rubocop と rails_best_practice を適用してみた (rubocop のエラー多すぎるので MaxLineLength あたり緩和&lt;/li&gt;
&lt;li&gt;ruby の auto-complete が遅い問題調査 (deoplete で omni の pattern 弄るのやめたらマシになった)&lt;/li&gt;
&lt;li&gt;python 3.7.3 を pyenv に install&lt;/li&gt;
&lt;li&gt;vim で python lsp をどう使うか調査&lt;/li&gt;
&lt;li&gt;slack channel history API 使ってみた&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2019-04-21
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;react の eslint 設定見直し&lt;/li&gt;
&lt;li&gt;vim に eslint を本格導入 (自動整形は一時的に off)&lt;/li&gt;
&lt;li&gt;ローカルの yarn を 1.15.2 にアップデート&lt;/li&gt;
&lt;li&gt;global の node_modules を整理 (最低限必要な eslint-cli と yarn だけに変更)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>slack</category>
    </item>
    <item>
      <title>WSL 上で Ubuntu 16.04 -&gt; 18.04 にあげてみた</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:36:52 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/wsl-ubuntu-16-04-18-04-2097</link>
      <guid>https://forem.com/m_yoshimo/wsl-ubuntu-16-04-18-04-2097</guid>
      <description>&lt;p&gt;Windows Subsystem for Linux の Ubuntu 16.04 LTS をずっと使っていましたが、そろそろ環境を一新したいと思い、Ubuntu 18.04 LTS にアップグレードすることにしました。&lt;/p&gt;

&lt;p&gt;その時の作業ログと嵌った内容をメモしていきます。&lt;/p&gt;

&lt;h1&gt;
  
  
  環境
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;PC OS: Windows 10 Pro&lt;/li&gt;
&lt;li&gt;作業 OS: Ubuntu 16.04 LTS (Windows Subsystem for Linux)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  パッケージ更新
&lt;/h1&gt;

&lt;p&gt;18.04 LTS にあげる前に既存のパッケージを 16.04 LTS の最新にアップデートしていきます。&lt;br&gt;
コマンドはこんな感じ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get update -y
$ sudo apt-get upgrade -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  mysql-server 5.7 アップデートのエラー対応
&lt;/h1&gt;

&lt;p&gt;パッケージ更新前の mysql version はメモしてなかったので忘れてしまったのですが、Xenial 標準なので 5.7 のはず。&lt;br&gt;
そのため、特に問題は起きないだろうと思っていたら、いきなり嵌りました。&lt;/p&gt;
&lt;h2&gt;
  
  
  /var/run/mysql/mysqld.sock に繋がらないというエラー
&lt;/h2&gt;

&lt;p&gt;エラーはこんな感じ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’ (2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;touch コマンドで mysqld.sock 作成したり、/var/run/mysqld の権限変更しても全くダメで、/etc/mysql/mysql.conf.d/mysqld.conf の sock パスを /tmp/mysql/mysqld.sock などにしてみたけど駄目でした。&lt;/p&gt;

&lt;p&gt;全く原因がつかめなかったので、一度 mysql-server と mysql-server-5.7 を uninstall することにしました。&lt;br&gt;
uninstall は purge 付きでまっさらに。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get remove --purge mysq-server-5.7 mysql-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  post-installation スクリプトエラー
&lt;/h2&gt;

&lt;p&gt;改めて mysql を再インストールすると、こんどは post-installation スクリプトでエラーが発生。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get install mysql-server-5.7
mysql-server-5.7 (5.7.25-0ubuntu0.16.04.2) を設定しています ...
invoke-rc.d: could not determine current runlevel
 * Stopping MySQL database server mysqld                                                                                                                [ OK ]
invoke-rc.d: could not determine current runlevel
invoke-rc.d: could not determine current runlevel
 * Stopping MySQL database server mysqld                                                                                                                [ OK ]
dpkg: パッケージ mysql-server-5.7 の処理中にエラーが発生しました (--configure):
 サブプロセス インストール済みの post-installation スクリプト はエラー終了ステータス 1 を返しました
処理中にエラーが発生しました:
 mysql-server-5.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;/var/lib/dpkg/info/mysql-server-5.7.postinst を覗いた感じだと、バージョンアップに伴う migrate なども実行している模様。&lt;br&gt;
というわけで /var/log/mysql/error.log を見ると&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2019-04-07T11:37:22.245232Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2019-04-07T11:37:22.249995Z 0 [Note] mysqld (mysqld 5.7.25-0ubuntu0.16.04.2) starting as process 9312 ...
2019-04-07T11:37:22.261040Z 0 [Note] InnoDB: PUNCH HOLE support available
2019-04-07T11:37:22.261120Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2019-04-07T11:37:22.261133Z 0 [Note] InnoDB: Uses event mutexes
2019-04-07T11:37:22.261145Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used for memory barrier
2019-04-07T11:37:22.261156Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.8
2019-04-07T11:37:22.261167Z 0 [Note] InnoDB: Using Linux native AIO
2019-04-07T11:37:22.261586Z 0 [Note] InnoDB: Number of pools: 1
2019-04-07T11:37:22.261764Z 0 [Note] InnoDB: Using CPU crc32 instructions
2019-04-07T11:37:22.267945Z 0 [ERROR] InnoDB: Linux Native AIO interface is not supported on this platform. Please check your OS documentation and install appropriate binary of InnoDB.
2019-04-07T11:37:22.267982Z 0 [Note] InnoDB: You can disable Linux Native AIO by setting innodb_use_native_aio = 0 in my.cnf
2019-04-07T11:37:22.267996Z 0 [Warning] InnoDB: Linux Native AIO disabled.
2019-04-07T11:37:22.270010Z 0 [Note] InnoDB: Initializing buffer pool, total size = 128M, instances = 1, chunk size = 128M
2019-04-07T11:37:22.282543Z 0 [Note] InnoDB: Completed initialization of buffer pool
2019-04-07T11:37:22.289178Z 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread priority can be changed. See the man page of setpriority().
2019-04-07T11:37:22.303956Z 0 [Note] InnoDB: Highest supported file format is Barracuda.
2019-04-07T11:37:22.307061Z 0 [Note] InnoDB: Log scan progressed past the checkpoint lsn 2643657451
2019-04-07T11:37:22.307115Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 2643657470
2019-04-07T11:37:22.307143Z 0 [ERROR] InnoDB: Ignoring the redo log due to missing MLOG_CHECKPOINT between the checkpoint 2643657451 and the end 2643657470.
2019-04-07T11:37:22.307162Z 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error
2019-04-07T11:37:22.608051Z 0 [ERROR] Plugin 'InnoDB' init function returned error.
2019-04-07T11:37:22.608108Z 0 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2019-04-07T11:37:22.608126Z 0 [ERROR] Failed to initialize builtin plugins.
2019-04-07T11:37:22.608137Z 0 [ERROR] Aborting

2019-04-07T11:37:22.608155Z 0 [Note] Binlog end
2019-04-07T11:37:22.608686Z 0 [Note] mysqld: Shutdown complete
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;[ERROR] InnoDB: Ignoring the redo log due to missing MLOG_CHECKPOINT between the checkpoint 2643657451 and the end 2643657470.&lt;/p&gt;

&lt;p&gt;が出ていて、おそらく ib_logfile が大きくなりすぎたのだろうと予想。&lt;br&gt;
対処方法はいくつかあると思いますが、開発環境のデータベースで、いつでも壊して作り直せる状態にしていたので、今回は mysql datadir ごと消しました&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo rm /var/lib/mysql/*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;こういう怖いコマンドは、本来は dump したりしてからすべきですが、rails の db/seed.rb からいつでもデータを復旧させられるので、消しちゃった。&lt;/p&gt;

&lt;p&gt;REDO LOG のエラーが出た時は、下記のページなどを参考にしたほうが正しいやり方に近いのではないでしょうか。&lt;br&gt;
&lt;a href="https://yoku0825.blogspot.com/2019/01/blog-post.html"&gt;https://yoku0825.blogspot.com/2019/01/blog-post.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;REDO LOG 消しちゃっても良いよっていう人は、下記のページ参考に (REDO LOG 消してよいと判断できる人はエラー見て対応しちゃいそうですが)&lt;br&gt;
&lt;a href="https://4to.pics/article/post/121"&gt;https://4to.pics/article/post/121&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  do-release-upgrade
&lt;/h1&gt;

&lt;p&gt;(---ここから 2019/04/07 追記 ---)&lt;br&gt;
結果的に source.list 更新 &amp;amp; dist-upgrade することになってしまった上、do-release-upgrade で中途半端な対応を行ったことで dist-upgrade 時に依存が壊れてしまって解決に時間が掛かったので、do-release-upgrade はやらない方が良いかもしれません。&lt;br&gt;
source.list 更新および dist-upgrade のやり方は、こちら参考にして下さい。&lt;br&gt;
&lt;a href="https://linuxconfig.org/how-to-upgrade-to-ubuntu-18-04-lts-bionic-beaver#h9-how-to-upgrade-ubuntu-the-debian-way"&gt;https://linuxconfig.org/how-to-upgrade-to-ubuntu-18-04-lts-bionic-beaver#h9-how-to-upgrade-ubuntu-the-debian-way&lt;/a&gt;&lt;br&gt;
(---ここまで-----------------------)&lt;/p&gt;

&lt;p&gt;WSL 上で何が起こるか分からないのが、この do-release-upgrade だったのですが、かなりの時間を要しものの基本放置で問題なかったです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo do-release-upgrade -d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;無事に OS 自体はアップデートできました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.2 LTS
Release:        18.04
Codename:       bionic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;唯一上手くアップデートできなかったパッケージがこちら&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo dpkg -a --configure
dpkg: 依存関係の問題により anthy の設定ができません:
 anthy は以下に依存 (depends) します: libanthyinput0 ...しかし:
  パッケージ libanthyinput0 はまだインストールされていません。

dpkg: パッケージ anthy の処理中にエラーが発生しました (--configure):
 依存関係の問題 - 設定を見送ります
処理中にエラーが発生しました:
 anthy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;知らないパッケージだったのですが、これはどうも日本語のかな漢字変換ソフトウェアらしく、WSL のテキストインプット周りは Windows さんのお仕事なので不要そうってことで放置。&lt;br&gt;
&lt;a href="https://ja.wikipedia.org/wiki/Anthy"&gt;https://ja.wikipedia.org/wiki/Anthy&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt --fix-broken install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;上記で anthy を削除して完了。&lt;/p&gt;

&lt;h1&gt;
  
  
  (ここから追記 2019/04/07)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  source.list 更新
&lt;/h1&gt;

&lt;p&gt;do-release-upgrade の完了時に anthy でエラーが出ていたためか、source.list が xenial のままでした。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tail -n 10 /etc/apt/sources.list
# deb-src http://archive.canonical.com/ubuntu xenial partner

deb http://security.ubuntu.com/ubuntu/ xenial-security main restricted
# deb-src http://security.ubuntu.com/ubuntu/ xenial-security main restricted
deb http://security.ubuntu.com/ubuntu/ xenial-security universe
# deb-src http://security.ubuntu.com/ubuntu/ xenial-security universe
deb http://security.ubuntu.com/ubuntu/ xenial-security multiverse
# deb-src http://security.ubuntu.com/ubuntu/ xenial-security multiverse
deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
# deb-src [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;不安になって mysql の version 見てみたら 16.04 のままっぽい&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dpkg -l | grep mysql-server
ii  mysql-server                         5.7.25-0ubuntu0.16.04.2                    all          MySQL database server (metapackage depending on the latest version)
ii  mysql-server-5.7                     5.7.25-0ubuntu0.16.04.2                    amd64        MySQL database server binaries and system database setup
ii  mysql-server-core-5.7                5.7.25-0ubuntu0.16.04.2                    amd64        MySQL database server binaries
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;というわけで、bionic に変更して改めて update &amp;amp; dist-upgrade します (涙&lt;/p&gt;

&lt;h1&gt;
  
  
  dist-upgrade
&lt;/h1&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo sed -i 's/xenial/bionic/g' /etc/apt/sources.list
$ sudo apt update &amp;amp;&amp;amp; sudo apt -y dist-upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;source.list 編集してアップデートするのであれば、do-release-upgrade 意味なかったのではなかろうか。。。&lt;/p&gt;

&lt;h2&gt;
  
  
  再び anthy
&lt;/h2&gt;

&lt;p&gt;dist-upgrade に伴って再度 anthy の更新を行うとするようで、改めて dist-upgrade に失敗する&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;以前に未選択のパッケージ libanthy1:amd64 を選択しています。
(データベースを読み込んでいます ... 現在 94988 個のファイルとディレクトリがインストールされています。)
.../libanthy1_1%3a0.3-6ubuntu1_amd64.deb を展開する準備をしています ...
libanthy1:amd64 (1:0.3-6ubuntu1) を展開しています...
.../anthy_1%3a0.3-6ubuntu1_amd64.deb を展開する準備をしています ...
anthy (1:0.3-6ubuntu1) で (9100h-25ubuntu1 に) 上書き展開しています ...
dpkg: 警告: 古いディレクトリ '/var/lib/anthy' を削除できません: ディレクトリは空ではありません
以前に未選択のパッケージ m17n-db を選択しています。
.../m17n-db_1.7.0-2_all.deb を展開する準備をしています ...
m17n-db (1.7.0-2) を展開しています...
以前に未選択のパッケージ libotf0:amd64 を選択しています。
.../libotf0_0.9.13-3build1_amd64.deb を展開する準備をしています ...
libotf0:amd64 (0.9.13-3build1) を展開しています...
以前に未選択のパッケージ libm17n-0:amd64 を選択しています。
.../libm17n-0_1.7.0-3build1_amd64.deb を展開する準備をしています ...
libm17n-0:amd64 (1.7.0-3build1) を展開しています...
dpkg: libuim-data: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: libuim-data (&amp;gt;= 1:1.8.6-15).
 uim-utils は以下に依存 (depends) します: libuim-data (&amp;gt;= 1:1.8.6-15).
 uim-fep は以下に依存 (depends) します: libuim-data (&amp;gt;= 1:1.8.6-15).
 uim-xim は以下に依存 (depends) します: libuim-data (&amp;gt;= 1:1.8.6-15).

(データベースを読み込んでいます ... 現在 95596 個のファイルとディレクトリがインストールされています。)
libuim-data (1:1.8.6-15) を削除しています ...
dpkg: uim-common: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15) ...しかし:
  パッケージ uim-common は削除されようとしています。
 libuim-scm0:amd64 は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15).
 libuim-custom2:amd64 は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15).
 uim-utils は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15) ...しかし:
  パッケージ uim-common は削除されようとしています。
 libuim8:amd64 は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15).
 uim-fep は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15) ...しかし:
  パッケージ uim-common は削除されようとしています。
 uim-xim は以下に依存 (depends) します: uim-common (&amp;gt;= 1:1.8.6-15).

uim-common (1:1.8.6-15) を削除しています ...
dpkg: `uim-data' のインストール によって壊れた uim-anthy の設定削除を考えています ...
dpkg: 問題ありません。(uim-data によって壊れた) uim-anthy の設定削除をします
(データベースを読み込んでいます ... 現在 95385 個のファイルとディレクトリがインストールされています。)
.../uim-data_1%3a1.8.6+gh20180114.64e3173-2build2_all.deb を展開する準備をしています ...
uim-anthy (1:1.8.6-15) を設定削除しています ...
Error: in load: file "/usr/share/uim/lib/sigscheme-init.scm" not found
dpkg: アーカイブ /var/cache/apt/archives/uim-data_1%3a1.8.6+gh20180114.64e3173-2build2_all.deb の処理中にエラーが発生しました (--unpack):
 installed uim-anthy package pre-removal script subprocess returned error exit status 1
処理中にエラーが発生しました:
 /var/cache/apt/archives/uim-data_1%3a1.8.6+gh20180114.64e3173-2build2_all.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$sudo apt --fix-broken install
...
uim-anthy (1:1.8.6-15) を設定削除しています ...
Error: in load: file "/usr/share/uim/lib/sigscheme-init.scm" not found
dpkg: アーカイブ /var/cache/apt/archives/uim-data_1%3a1.8.6+gh20180114.64e3173-2build2_all.deb の処理中にエラーが発生しました (--unpack):
 installed uim-anthy package pre-removal script subprocess returned error exit status 1
処理中にエラーが発生しました:
 /var/cache/apt/archives/uim-data_1%3a1.8.6+gh20180114.64e3173-2build2_all.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;uim-anthy 削除しようとしてるけど、/usr/share/uim/lib/sigscheme-init.scm なくて怒られる&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get remove --purge uim-anthy
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
これらを直すためには 'apt --fix-broken install' を実行する必要があるかもしれません。
以下のパッケージには満たせない依存関係があります:
 anthy : 依存: libanthyinput0 しかし、インストールされようとしていません
 libuim-custom2 : 依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
 libuim-scm0 : 依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
 libuim8 : 依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
 uim-fep : 依存: libuim-data (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
           依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
 uim-utils : 依存: libuim-data (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
             依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
 uim-xim : 依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
           依存: libuim-data (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
E: 未解決の依存関係です。'apt --fix-broken install' を実行してみてください (または解法を明示してください)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;uim-anthy 単体で削除しようとしたが、依存がぶっ壊れている&lt;br&gt;
仕方がないので、依存関係のあるパッケージを一度削除してみる&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get remove --purge uim-anthy uim-common anthy libanthyinput0 libuim-custom2 uim-common libuim-scm0 libuim8 uim-fep libuim-data uim-utils uim-xim
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
パッケージ 'uim-common' はインストールされていないため削除もされません
パッケージ 'uim-common' はインストールされていないため削除もされません
パッケージ 'libanthyinput0' はインストールされていないため削除もされません
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  anthy-common im-config libanthy1 libgcroots0 libm17n-0 libotf0 m17n-db
これを削除するには 'sudo apt autoremove' を利用してください。
以下のパッケージは「削除」されます:
  anthy* libuim-custom2* libuim-data* libuim-scm0* libuim8* uim-anthy* uim-fep* uim-utils* uim-xim*
アップグレード: 0 個、新規インストール: 0 個、削除: 9 個、保留: 390 個。
5 個のパッケージが完全にインストールまたは削除されていません。
この操作後に 1,063 kB のディスク容量が解放されます。
続行しますか? [Y/n] y
(データベースを読み込んでいます ... 現在 95385 個のファイルとディレクトリがインストールされています。)
uim-anthy (1:1.8.6-15) を削除しています ...
Error: in load: file "/usr/share/uim/lib/sigscheme-init.scm" not found
dpkg: パッケージ uim-anthy の処理中にエラーが発生しました (--remove):
 installed uim-anthy package pre-removal script subprocess returned error exit status 1
libuim-custom2:amd64 (1:1.8.6-15) を削除しています ...
uim-xim (1:1.8.6-15) を削除しています ...
uim-fep (1:1.8.6-15) を削除しています ...
dpkg: libuim-scm0:amd64: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: libuim-scm0 (&amp;gt;= 1:1.5.7).
 uim-utils は以下に依存 (depends) します: libuim-scm0 (&amp;gt;= 1:1.5.7).
 libuim8:amd64 は以下に依存 (depends) します: libuim-scm0 (&amp;gt;= 1:1.5.7).

libuim-scm0:amd64 (1:1.8.6-15) を削除しています ...
dpkg: anthy: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: anthy ...しかし:
  パッケージ anthy は削除されようとしています。

anthy (1:0.3-6ubuntu1) を削除しています ...
dpkg: uim-utils: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: uim-utils (&amp;gt;= 1:1.8.6-15) ...しかし:
  パッケージ uim-utils は削除されようとしています。

uim-utils (1:1.8.6-15) を削除しています ...
dpkg: libuim8:amd64: 依存関係に問題があります。しかし要求に従い削除しています:
 uim-anthy は以下に依存 (depends) します: libuim8 (&amp;gt;= 1:1.5.7) ...しかし:
  パッケージ libuim8:amd64 は削除されようとしています。

libuim8:amd64 (1:1.8.6-15) を削除しています ...
処理中にエラーが発生しました:
 uim-anthy
E: Sub-process /usr/bin/dpkg returned an error code (1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;やはり uim-anthy でエラーになる&lt;br&gt;
なので、libuim8 を先に消すようにしないとダメっぽい&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo apt-get remove --purge libuim8
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
パッケージ 'libuim8' はインストールされていないため削除もされません
これらを直すためには 'apt --fix-broken install' を実行する必要があるかもしれません。
以下のパッケージには満たせない依存関係があります:
 uim-anthy : 依存: libuim-scm0 (&amp;gt;= 1:1.5.7) しかし、インストールされようとしていません
             依存: libuim8 (&amp;gt;= 1:1.5.7) しかし、インストールされようとしていません
             依存: uim-utils (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
             依存: libuim-data (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
             依存: uim-common (&amp;gt;= 1:1.8.6-15) しかし、インストールすることができません
             依存: anthy しかし、インストールされようとしていません
E: 未解決の依存関係です。'apt --fix-broken install' を実行してみてください (または解法を明示してください)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;依存壊れる。。。が、これである程度、削除フラグは立っているはずなので、--fix-broken で直るか試してみる&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ sudo apt --fix-broken install
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
依存関係を解決しています ... 完了
以下の追加パッケージがインストールされます:
  anthy libanthyinput0 libdouble-conversion1 libegl-mesa0 libegl1 libegl1-mesa libevdev2 libgbm1 libglvnd0 libinput-bin libinput10 libmtdev1 libqt5core5a
  libqt5dbus5 libqt5gui5 libqt5network5 libqt5svg5 libqt5widgets5 libqt5x11extras5 libuim-custom2 libuim-scm0 libuim8 libwam-bin libwacom-common libwacom2
  libwayland-client0 libwaylan1-mesa libwayland-server0 libxcb-dri3-0 libxcb-icccm4 libxcb-imd0 libinput-bin libinput10 libmtdev1 libqt5core5a libqt5dbus5 lbqt5network5 libqt5svg5 libqt5widgets5 libqt5x11extras5 libuimlibwacom-bin libwacom-common libwacom2 libwayland-egl1
  libuild2         amd64        Universal Input Method - uim-custo  Universal Input Method - GTK+2.x front end
ii  uim-gtk2.0-i          1:1.8.6+gh20180114.64e3173-2build2         amd64    lugins:amd64                    1:1.8.6+gh20180114.64e3173-2bu.7.25-0ubuntu0.18.04.2            all          MySQL databasee depending on the latest version)
ii  mysql-server-5.7      アップデートはこれまで。

# まとめ

意外と嵌ってしまって時間を要しましたが、ひとまずはアップデート出来て良かったです。
細かい部分で問題がないかの確認して、新しく環境を作り直したほうが早かった
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

</description>
      <category>wsl</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>WebRTC Chrome で映像ビットレートが音声に食われる件</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:31:36 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/webrtc-chrome-58m8</link>
      <guid>https://forem.com/m_yoshimo/webrtc-chrome-58m8</guid>
      <description>&lt;p&gt;(2019/07/16:追記) 考察と結論をアップデートしました。&lt;/p&gt;

&lt;p&gt;モバイル環境でも WebRTC を利用する場合、回線が細かったり、キャリアの通信量上限などで映像・音声の配信ビットレートは結構重要なウェイトを占めると思います。&lt;/p&gt;

&lt;p&gt;色々とビットレート制御を調べているうちに、Chrome では指定した映像ビットレート通りに配信されないという事象を見つけました。&lt;br&gt;
どうも、音声ビットレートに食われてしまっているようなので、配信ビットレートの制御について説明しつつ、どういう事象が起きるか説明したいと思います。&lt;/p&gt;

&lt;h1&gt;
  
  
  WebRTC の配信ビットレートの制御
&lt;/h1&gt;

&lt;p&gt;WebRTC では配信側のフレームサイズ、フレームレートおよび圧縮コードによって配信ビットレートが決まる。&lt;br&gt;
フレームサイズやフレームレートは getUserMedia の constraints などで指定し、圧縮コードは SDP で指定することになる。&lt;/p&gt;

&lt;p&gt;フレームサイズやフレームレートは、カメラデバイスのスペックに依存するため、constraints の exact を使って厳密に指定することは現実的ではないため、max/min などで幅を持たせることになる。&lt;/p&gt;

&lt;p&gt;その際、ウェブブラウザの実装に応じてフレームサイズやフレームレートが最適化されて、配信ビットレートが決まる。&lt;/p&gt;

&lt;h1&gt;
  
  
  SDP による映像配信ビットレート制御
&lt;/h1&gt;

&lt;p&gt;WebRTC では SDP の m=video 内で TIAS および AS を指定することでビットレートを制御できるで配信ビットレートを指定することができる。&lt;br&gt;
映像の場合、下記のようにすることで 512 kbps に制限することを伝えている。&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

m=video 9 UDP/TLS/RTP/SAVPF 120 116 117 96 97
b=TIAS:512000
b=AS:512
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;SDP で配信ビットレートを指定すると、ブラウザが上記のビットレートに収まるように、フレームサイズやフレームレートを上手く調整してくれる。&lt;/p&gt;

&lt;p&gt;例えば、getUserMedia の constraints で下記を指定して、配信ビットレートの変えてみよう。&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ideal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ideal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;facingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  AS:512 を指定した場合
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F72554989-1c6d-1ca2-ed5f-3812f1621b20.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_13-28-16_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F72554989-1c6d-1ca2-ed5f-3812f1621b20.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;解像度 720p のフレームを 20fps でエンコードしており、55513 Bytes/s = 444kbps で配信していることがわかる。&lt;br&gt;
配信ビットレートはフレームの大きさで変わるため、少し上下する。&lt;br&gt;
少し眺めていた感じでは、52000 ~ 60000 Byts/s あたりに収まっていた。&lt;/p&gt;
&lt;h2&gt;
  
  
  AS:256 を指定した場合
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fa2c1c2bd-6519-ce00-e97e-00110b65feb3.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_13-30-39_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fa2c1c2bd-6519-ce00-e97e-00110b65feb3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;解像度が 960x540 に変わり、配信ビットレートが 28225 Bytes/s = 225kbps になっている。&lt;br&gt;
フレームレートは変わっていないので、フレームサイズを変更して指定された配信ビットレートに合わせようとしていることがわかる。&lt;/p&gt;
&lt;h1&gt;
  
  
  音声配信ビットレートの指定
&lt;/h1&gt;

&lt;p&gt;音声も同様に SDP で maxaveragebitrate を指定することで音声配信ビットレートを制御できる。&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

m=audio 9 UDP/TLS/RTP/SAVPF 109
a=fmtp:109 maxplaybackrate=48000;maxaveragebitrate=256000;stereo=1;sprop-stereo=1;minptime=10;useinbandfec=1
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Chrome の場合、6000 ~ 510000 bps の範囲でサポートされているようだ。&lt;br&gt;
&lt;a href="https://chromium.googlesource.com/external/webrtc/+/HEAD/api/audio_codecs/opus/audio_encoder_opus_config.h#28" rel="noopener noreferrer"&gt;https://chromium.googlesource.com/external/webrtc/+/HEAD/api/audio_codecs/opus/audio_encoder_opus_config.h#28&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Chrome の映像配信ビットレートの指定がうまく行かない件
&lt;/h1&gt;

&lt;p&gt;前述のとおり、配信ビットレートを指定することで、フレームサイズやフレームレートはブラウザが調整してくれる。&lt;br&gt;
上記は Chrome の例だったが、Firefox でも同様である (Safari や Edge は知らない)。&lt;/p&gt;

&lt;p&gt;さて、本題。&lt;br&gt;
上記の例では audio を disable にしていた。&lt;br&gt;
ここで audio を true にして、映像・音声の配信ビットレートをそれぞれ指定して chrome://webrtc-internals を見てみる。&lt;/p&gt;

&lt;h2&gt;
  
  
  映像：512kbps、音声：32kbps の場合
&lt;/h2&gt;

&lt;h3&gt;
  
  
  映像まわりの stats
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F437d736b-882c-46fd-40c5-166028df3f54.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-47-42_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F437d736b-882c-46fd-40c5-166028df3f54.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc7d2b5a9-41a9-4615-c22b-332fe474909c.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-48-29_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc7d2b5a9-41a9-4615-c22b-332fe474909c.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F7c899e80-6193-048b-3747-3cc5db947e03.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-48-57_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F7c899e80-6193-048b-3747-3cc5db947e03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  音声まわりの stats
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fd9a431e1-4777-f320-cf9e-40d209e33814.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-48-1_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fd9a431e1-4777-f320-cf9e-40d209e33814.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F9ab2d0a8-2fdf-372a-08c4-bdf781aa83c5.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-48-42_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F9ab2d0a8-2fdf-372a-08c4-bdf781aa83c5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  映像：512kbps、音声：256kbps の場合
&lt;/h2&gt;

&lt;h3&gt;
  
  
  映像まわりの stats
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F79a7cc37-0c4e-4e37-b56f-500906712ac0.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-51-18_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F79a7cc37-0c4e-4e37-b56f-500906712ac0.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffeb314e0-0763-b0ac-4109-8adc507f01be.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-51-56_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffeb314e0-0763-b0ac-4109-8adc507f01be.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffe5c3aa3-5cd6-cf1a-e61b-5d0bb9ac49e1.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-52-13_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffe5c3aa3-5cd6-cf1a-e61b-5d0bb9ac49e1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  音声まわりの stats
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F524ea6b3-b029-bf2e-bfcd-0b55fe5a6311.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-51-40_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2F524ea6b3-b029-bf2e-bfcd-0b55fe5a6311.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc8d3aacd-64c3-26b5-0a61-3e5af342a143.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-52-46_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc8d3aacd-64c3-26b5-0a61-3e5af342a143.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  映像ビットレートが音声ビットレートに食われてる？
&lt;/h1&gt;

&lt;p&gt;比較しやすいように映像の bitrate を並べてみる&lt;br&gt;
左が音声 32kbps で右が 256kbps である。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc7d2b5a9-41a9-4615-c22b-332fe474909c.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-48-29_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Fc7d2b5a9-41a9-4615-c22b-332fe474909c.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffeb314e0-0763-b0ac-4109-8adc507f01be.png" class="article-body-image-wrapper"&gt;&lt;img alt="SnapCrab_NoName_2019-7-9_14-51-56_No-00.png" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fm-yoshimo.github.io%2Fdev-images%2Fwebrtc_chrome%2Ffeb314e0-0763-b0ac-4109-8adc507f01be.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;画像が小さくて申し訳ないが、明らかに映像の bitrate が下がっている。&lt;br&gt;
この事象は Firefox では発生しない。&lt;br&gt;
スクショが取れていないが、音声ビットレートの値を 192kbps などの大きい値にしたり、マルチストリーム環境で配信者を複数に増やすと、この差が顕著に見受けられる。&lt;/p&gt;

&lt;h1&gt;
  
  
  考察
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://tools.ietf.org/html/rfc4566#section-5.8" rel="noopener noreferrer"&gt;RFC4566&lt;/a&gt; や &lt;a href="https://tools.ietf.org/html/rfc3890#section-6.2" rel="noopener noreferrer"&gt;RFC3890&lt;/a&gt; を読む限り、RTP session 毎なので m=video の下に書いていれば映像にだけ影響すると思っているのだが、AS の略が &lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;I&lt;/strong&gt;ndicated bandwidth なので、映像・音声を含めた全体を示すのかもしれない。&lt;/p&gt;

&lt;p&gt;また、Chrome でこのような事象が発生して、Firefox では発生していないが、Firefox ではそもそも音声ビットレート制御 (maxaveragebitrate の指定) が効かないようなので、Chrome の方が WebRTC の挙動として正しい可能性がある。&lt;/p&gt;

&lt;h1&gt;
  
  
  結論
&lt;/h1&gt;

&lt;p&gt;映像のビットレートを指定することは出来ない。&lt;br&gt;
SDP において、AS に指定する値は映像・音声ビットレートを足し合わせた値を指定し、音声ビットレートを maxaveragebitrate で指定することで、映像のビットレートを間接的に指定する方法が良さそう。&lt;/p&gt;

&lt;p&gt;注意事項として、maxaveragebitrate は Firefox では効いていないため、Chrome と Firefox で映像ビットレートは異なるので注意 (全体のビットレートは同じはず)。&lt;/p&gt;

</description>
      <category>webrtc</category>
    </item>
    <item>
      <title>redux-form の validation を関数合成使って見やすくしてみる</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:07:29 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/redux-form-validation-3lh4</link>
      <guid>https://forem.com/m_yoshimo/redux-form-validation-3lh4</guid>
      <description>&lt;p&gt;私は React + redux-form でフォーム画面を作ることが多いのですが、redux-form の validation がやや冗長になりやすい傾向にあります。&lt;/p&gt;

&lt;p&gt;これを関数合成使って、見やすくメンテナンスしやすいコードに出来ないかなと思って試してみました。&lt;/p&gt;

&lt;p&gt;※ 基本的な redux-form の知識が必要です。&lt;/p&gt;

&lt;h1&gt;
  
  
  どのあたりが冗長なの？
&lt;/h1&gt;

&lt;p&gt;例えば、このようなフォームを実装するとします。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fKc3CGPe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44638/743f49b8-b24b-b6da-8e75-fb6e2ba9e05a.png" class="article-body-image-wrapper"&gt;&lt;img width="616" alt="SnapCrab_NoName_2019-6-27_11-50-56_No-00.png" src="https://res.cloudinary.com/practicaldev/image/fetch/s--fKc3CGPe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44638/743f49b8-b24b-b6da-8e75-fb6e2ba9e05a.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;redux-form で validate 関数を定義していくと、こんな風に書くと思います。&lt;br&gt;
(共通化などは一旦考えないで書いた場合)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;option_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;option_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;必須入力です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;必須入力です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;必須入力です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;EMAIL_VALIDATION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;正しいメールアドレスを入力して下さい&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone_number&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;PHONE_NUMBER_MIN_LENGTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;PHONE_NUMBER_MAX_LENGTH&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;携帯電話は10桁以上20桁以下で入力できます。&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birth_day&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;BIRTHDAY_PATTERN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birth_day&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birth_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;不正な値です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calcAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birth_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ADULT_AGE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggreement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aggreement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;未成年者は保護者の同意が必要です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;綺麗なコードには見えませんが、これぐらいなら許容範囲かもしれません。&lt;br&gt;
しかし、例えば性別や生年月日といった項目が追加されるとどうでしょうか。かなり長くなってしまいます。&lt;/p&gt;

&lt;p&gt;また、名前やメールアドレスといった必須入力かどうかを判定するロジックは同じ仕組みなので共通化したいですね。&lt;/p&gt;
&lt;h1&gt;
  
  
  どんな風に書けると嬉しい？
&lt;/h1&gt;

&lt;p&gt;共通化するにあたって、どういう書き方ができると綺麗かなと考えてみました。&lt;br&gt;
ここは個人的な思想なども入ると思うので一概に正しいとも言えませんが、今回は下記のように書けると良いかなと思います。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;validateRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validateRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validateRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validateEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validatePhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone_number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validateBirthday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;birth_day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;validateUnderCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;calcAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birth_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ADULT_AGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;validateRequired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aggreement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;どうでしょうか？&lt;br&gt;
それなりに見やすくなったと思います。&lt;/p&gt;

&lt;p&gt;validates 関数は高階関数で、values と errors の初期値(ここでは空の Hash) をとり、列挙された関数を順次実行してくれる関数です。&lt;/p&gt;

&lt;p&gt;validateRequired など関数は、redux-form の values が hash であることから、指定された key に対して validate を行う関数です。&lt;br&gt;
validateRequired であれば、その key が存在するかどうかといったチェックですね。&lt;/p&gt;

&lt;p&gt;validateUnderCondition のみ特別で引数を複数取ります。第二引数に validateRequired といった validation 関数を指定するのですが、第一引数にそれを実行する条件を指定できるようにしてます。&lt;br&gt;
今回では、未成年の場合のみ同意が必要といった validation を表現するために使います。&lt;/p&gt;
&lt;h1&gt;
  
  
  validates 関数
&lt;/h1&gt;

&lt;p&gt;では、validates 関数を組んでいきます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;fns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nextFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;prevFn&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prevFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prevFn&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;高階関数にしつつ、どちらの引数もリストで取るようにしました。&lt;br&gt;
fns は関数のリストで、args は values や errors の初期値といった前提条件となる値のリストです。&lt;/p&gt;

&lt;p&gt;reduce を使って関数合成しつつも、apply で動的引数である args を適用しています。&lt;br&gt;
また、fns が一つの場合でも実行できるようにしています。&lt;br&gt;
複数の関数を合成するだけであれば不要ですが、項目が 1 つだけの form もあり得ると思うので条件分岐しています。&lt;/p&gt;

&lt;p&gt;残念なところは、6 行目あたりで prevFn が function かどうかの判定が必要になってしまっているところです。&lt;br&gt;
後述しますが、apply を使っているためか、prevFn が関数ではなく prefFn の返り値そのものになってしまってうまく関数合成ができませんでした。&lt;/p&gt;

&lt;p&gt;redux-form 以外でも利用できるようにするために apply 使っていたのですが、redux-form の validation に限れば、args を動的引数で取る必要性はないので、apply を辞めるという手もあります。&lt;br&gt;
もし、もっと良い方法ご存知の方は教えて欲しいです。&lt;/p&gt;
&lt;h1&gt;
  
  
  各 validation 関数
&lt;/h1&gt;

&lt;p&gt;各々の validation 関数は特別なことはしていません。&lt;br&gt;
全て列挙すると長くなるので代表的なものを載せます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateRequired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;必須入力です&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateUnderCondition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;errors&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validateFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;特に難しいことはしていません。&lt;br&gt;
redux-form の values が hash なので、高階関数の一つ目の引数に key を取って、それを使って値を割り出しています。&lt;br&gt;
validation 関数に関しては、redux-form の構造にどっぷりっといった感じですが、values も errors もただの hash なので redux-form 以外でも使い回すことは出来そうです。&lt;/p&gt;

&lt;h1&gt;
  
  
  まとめ
&lt;/h1&gt;

&lt;p&gt;この記事ですが、実はあまり redux-form には関係なかったりします。&lt;br&gt;
元は関数合成使って、もう少しうまく書けないかなぁと思ったのが、たまたま redux-form の validate だっただけです。&lt;br&gt;
記事にするにあたって、もっと汎用的に直してから書こうかと思いましたが、むしろイメージしづらいのではと思って、そのまま redux-form の validate を題材にしました。&lt;br&gt;
もちろん、ここにあげているバリデーション以外にも redux-form の FieldArray を使った場合の validation 関数等々、色々と定義はしていますが、validates 関数のおかげで集約しつつ簡単にフォーム側の validate を書けるので重用してます。&lt;/p&gt;

&lt;p&gt;また、redux-form 以外でも各 validation 関数はもちろん利用しています。&lt;br&gt;
ただ共通化しただけの関数ですからね。&lt;/p&gt;

&lt;p&gt;残念な部分もあるので、もっと改善していきたいと思います。&lt;/p&gt;

&lt;p&gt;(書いた後に思いましたが、記事的に validates 関数と validation 関数があって、何が何やらって感じですね。。。表現力磨きたい。&lt;/p&gt;

</description>
      <category>redux</category>
      <category>javascript</category>
    </item>
    <item>
      <title>gcloud コマンドで複数アカウントで異なるプロジェクトを設定する</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:05:59 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/gcloud-1il0</link>
      <guid>https://forem.com/m_yoshimo/gcloud-1il0</guid>
      <description>&lt;p&gt;個人と会社のアカウントなど、複数のアカウントで異なるプロジェクトを設定する方法について。&lt;/p&gt;

&lt;h1&gt;
  
  
  gcloud config configurations でアカウントとプロジェクトを追加する
&lt;/h1&gt;

&lt;p&gt;gcloud config 管理には configurations という単位でアカウントとプロジェクトをまとめて管理できる。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud config configurations list
NAME           IS_ACTIVE  ACCOUNT                           PROJECT           DEFAULT_ZONE       DEFAULT_REGION
default        True       m-yoshimoto@company.xxx.jp        product-A         asia-northeast1-b  asia-northeast1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;リスト表示すると最初に作成した configurations が [default] として表示されている。&lt;br&gt;
ここに configurations を追加していく。&lt;br&gt;
やることは至って簡単で下記で出来る。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud config configurations create {configurations-name}
$ gcloud config set project {project-name}
$ gcloud config set account {email-address}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;実際に追加した時のコマンド実行結果がこちら。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud config configurations create private-study

$ gcloud config set project private-1
Updated property [core/project].

$ gcloud config set account m-yoshimo-new@gmail.com
Updated property [core/account].

$ gcloud config list
[core]
account = m-yoshimo-new@gmail.com
disable_usage_reporting = True
project = private-1

Your active configuration is: [private-study]

$ gcloud config configurations list
NAME           IS_ACTIVE  ACCOUNT                         PROJECT           DEFAULT_ZONE       DEFAULT_REGION
default        True       m-yoshimoto@company.xxx.jp      product-A         asia-northeast1-b  asia-northeast1
private-study  True       m-yoshimo-new@gmail.com         private-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;zone や region は適宜、&lt;br&gt;
&lt;br&gt;
&lt;code&gt;gcloud config set compute/zone {zone-name}&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 等で設定すればよい。&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>gcloud</category>
    </item>
    <item>
      <title>Rails migration から ridgepole に移行した</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Thu, 26 Mar 2020 00:04:56 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/rails-migration-ridgepole-5gbg</link>
      <guid>https://forem.com/m_yoshimo/rails-migration-ridgepole-5gbg</guid>
      <description>&lt;p&gt;rails migration から ridgepole に移行したので移行時のメモ&lt;/p&gt;

&lt;h1&gt;
  
  
  Ridgepole 導入の理由
&lt;/h1&gt;

&lt;p&gt;rails migration では migration の度に migration ファイルの作成して、実行後は db/schema.rb が自動更新される。&lt;/p&gt;

&lt;p&gt;システムが未成熟なこともあり、ガンガン migration を行うので、下記のような問題が出てきた。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;migration ファイルが大量に出来てしまって管理が大変&lt;/li&gt;
&lt;li&gt;db/schema.rb を github に上げ忘れること多発&lt;/li&gt;
&lt;li&gt;db/schema.rb でコンフリクト多発&lt;/li&gt;
&lt;li&gt;db:rollback 使うことがほとんどなかった&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;無駄な作業の煩わしさから解放されるために、ridgepole という gem が色々な記事でオススメされていたので、導入してみようと思います。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a ref="https://qiita.com/akishin/items/4612ae2a6f7a5173ff7f"&gt;Ridgepole を使ってみる
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a ref="https://qiita.com/ayies128/items/c84db2ea41210eae0577"&gt;Rails マイグレーションの廃止とRidgepoleの導入・利用方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techlife.cookpad.com/entry/2014/08/28/194147"&gt;クックパッドにおける最近のActiveRecord運用事情&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Ridgepole インストール
&lt;/h1&gt;

&lt;p&gt;Gemfile に以下を追加する&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'ridgepole'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;bunlder でインストール&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle install --path vendor/bundle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  導入手順
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/winebarrel/ridgepole#usage"&gt;https://github.com/winebarrel/ridgepole#usage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ここに書いてある通りなので簡単に移行できるが、今後を考えてテーブル毎に Schema ファイルを分割して運用することを念頭に移行を進めてみる。&lt;/p&gt;

&lt;h1&gt;
  
  
  Schemafile の作成
&lt;/h1&gt;

&lt;p&gt;まず、現状のデータベースから ridgepole 用の Schemafile を作るのだが、今後の管理を考えてテーブル毎にファイルを分割して作成する。&lt;br&gt;
試験用に既存データベースとして、users と posts というテーブルを用意した。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec ridgepole -c config/database.yml --export --split -o db/Schemafile
Export Schema
  write `db/posts.schema`
  write `db/users.schema`
  write `db/Schemafile`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;中身を見てみるとこんな感じ。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'posts.schema'
require 'users.schema'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create_table "posts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["name"], name: "index_posts_on_name"
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# -*- mode: ruby -*-
# vi: set ft=ruby :
create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED", force: :cascade do |t|
  t.string "name"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["name"], name: "index_users_on_name"
end

add_foreign_key "posts", "users", column: "user_id"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;注意したいのは、外部制約キー (add_foreign_key) が users にあること。&lt;br&gt;
どうも、&lt;a href="https://qiita.com/ayies128/items/dc1b6f4b18b95f5b425b"&gt;こちらの記事&lt;/a&gt;によると、アルファベット順で一番最後のファイルに集約される模様。&lt;/p&gt;

&lt;p&gt;ここは好みによると思いますが、add_foreign_key は db/posts.schema に移しました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema ファイルを git で管理するので、export することは稀&lt;/li&gt;
&lt;li&gt;posts のスキーマを確認する際に、posts.schema に書かれていないと存在を忘れる &amp;amp; 再定義してしまい、ミスに繋がる&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  適用 &amp;amp; 確認
&lt;/h1&gt;

&lt;p&gt;Schemafile を適用してみて変更がないことを確認する。&lt;br&gt;
ローカルの開発環境でテストをするので、-E development を付ける。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec ridgepole -c config/database.yml -E development -f db/Schemafile --apply
Apply `db/Schemafile` (dry-run)
No change
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  rake task 作成
&lt;/h1&gt;

&lt;p&gt;毎回、上記のようなコマンドを実行するのは大変なので rake task で簡単にします。&lt;br&gt;
&lt;a href="https://qiita.com/lhside/items/0dcad79d9b801e34bc7c"&gt;こちらの記事&lt;/a&gt;を参考に作成しました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace :ridgepole do
  desc "Apply ridgepole schemafile"
  task apply: :environment do
    ridgepole('--apply')
  end

  desc "Export ridgepole schemafile"
  task export: :environment do
    ridgepole('--export')
  end

  private
  def config_file
    if Rails.env.development?
      'config/database.yml'
    elsif Rails.env.staging?
      'config/database.staging.yml'
    elsif Rails.env.production?
      'config/database.production.yml'
    else
      raise 'no configuration specified'
    end
  end

  def ridgepole(*options)
    command = ['bundle exec ridgepole --file db/schemas/Schemafile', "-c #{config_file}", "-E #{Rails.env}"]
    system (command + options).join(' ')
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;これで環境に関わらず、下記のコマンドで Schemafile を適用できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails ridgepole:apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  db:seed の代用 rake task
&lt;/h1&gt;

&lt;p&gt;ridgepole でスキーマを更新した場合、db:seed による rake task を実行しても、下記のように pending migration による警告が発生して実行できません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails db:seed
You have 2 pending migrations:
  20190703015353 CreateUsers
  20190704105400 CreatePosts
Run `rails db:migrate` to update your database then try again.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;そのため、ridgepole:apply と同様に rake task を定義しておくと良いと思います。&lt;br&gt;
db/seeds.rb は ruby スクリプトで定義していることが多いので、そのまま移植するだけで済むことが多いと思います。&lt;/p&gt;
&lt;h1&gt;
  
  
  production 環境への適用
&lt;/h1&gt;

&lt;p&gt;手動で production 環境に migration していた場合は下記のコマンドを実行する。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rails ridgepole:apply RAILS_ENV=production
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;CI/CD 連携している場合は、スクリプト等でこれまで&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;bundle exec rails db:migrate&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 をしていた箇所を&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;bundle exec rails ridgepole:apply&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 に差し替えれば良いと思います。&lt;/p&gt;

&lt;h1&gt;
  
  
  ゴミ掃除
&lt;/h1&gt;

&lt;p&gt;db/schema.rb や db/migrations 以下の migration ファイルを削除しておきましょう&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ridgepole</category>
    </item>
    <item>
      <title>react-device-detect による User Agent 判定</title>
      <dc:creator>m-yoshimo</dc:creator>
      <pubDate>Wed, 25 Mar 2020 23:58:03 +0000</pubDate>
      <link>https://forem.com/m_yoshimo/react-device-detect-user-agent-56nj</link>
      <guid>https://forem.com/m_yoshimo/react-device-detect-user-agent-56nj</guid>
      <description>&lt;p&gt;User Agent の判定って自前で全部やろうとすると想定してないケースも多くて大変ですよね。&lt;br&gt;
そこで、React 使っている人は &lt;a href="https://github.com/duskload/react-device-detect"&gt;react-device-detect&lt;/a&gt; というライブラリを使っている人も多いかと思います。&lt;/p&gt;

&lt;p&gt;とはいえ、実際にどういった判定になるのか、想定してなかったみたいなことがあるとこまるので、いくつかの User Agent でどのような判定になるのか確認してみました。&lt;/p&gt;

&lt;p&gt;判定は Chrome の developer console 使って、ちまちま User Agent いじって手元で確認しました。&lt;/p&gt;

&lt;h1&gt;
  
  
  利用した User Agent
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://developers.whatismybrowser.com/useragents/explore/"&gt;https://developers.whatismybrowser.com/useragents/explore/&lt;/a&gt;&lt;br&gt;
ここから探してピックアップしてみました。&lt;/p&gt;

&lt;p&gt;プラットフォーム (OS とか) とブラウザの組み合わせをなるべく作ったつもり。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;UserAgent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;IE&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:62.0) Gecko/20100101 Firefox/62.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surface Pro 3&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;IE&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Android 8.0.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Opera Mini&lt;/td&gt;
&lt;td&gt;Opera/9.80 (Android; Opera Mini/20.0.2254/117.37; U; fr) Presto/2.12.423 Version/12.16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android Tablet&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 8.1.0; SM-T580) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Android 7.0; Tablet; rv:62.0) Gecko/62.0 Firefox/62.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 7.0; SM-T580 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.123 Safari/537.36 EdgA/42.0.0.2529&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Samsung Browser&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-T719 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/6.2 Chrome/56.0.2924.87 Safari/537.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/70.0.3538.75 Mobile/15E148 Safari/605.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;MobileSafari&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/14.0b12646 Mobile/16B92 Safari/605.1.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A366&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Opera&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) OPiOS/9.1.0.86723 Mobile/12B440 Safari/9537.53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPad&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/70.0.3538.75 Mobile/15E148 Safari/605.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;MobileSafari&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPad; CPU OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/14.0b12646 Mobile/16B92 Safari/605.1.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (iPad; CPU OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A404&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  確認する react-device-detect の関数
&lt;/h1&gt;

&lt;p&gt;良く使うであろうやつをピックアップ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Device.isMobile&lt;/li&gt;
&lt;li&gt;Device.isTablet&lt;/li&gt;
&lt;li&gt;Device.isMobileOnly&lt;/li&gt;
&lt;li&gt;Device.isIOS&lt;/li&gt;
&lt;li&gt;Device.isAndroid&lt;/li&gt;
&lt;li&gt;Device.isWinPhone&lt;/li&gt;
&lt;li&gt;Device.isSafari&lt;/li&gt;
&lt;li&gt;Device.isMobileSafari&lt;/li&gt;
&lt;li&gt;Device.isChrome&lt;/li&gt;
&lt;li&gt;Device.isFirefox&lt;/li&gt;
&lt;li&gt;Device.isIE&lt;/li&gt;
&lt;li&gt;Device.isEdge&lt;/li&gt;
&lt;li&gt;Device.isBrowser&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  確認結果
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;isMobile&lt;/th&gt;
&lt;th&gt;isTablet&lt;/th&gt;
&lt;th&gt;isMobileOnly&lt;/th&gt;
&lt;th&gt;isIOS&lt;/th&gt;
&lt;th&gt;isAndroid&lt;/th&gt;
&lt;th&gt;isSafari&lt;/th&gt;
&lt;th&gt;isMobileSafari&lt;/th&gt;
&lt;th&gt;isChrome&lt;/th&gt;
&lt;th&gt;isFirefox&lt;/th&gt;
&lt;th&gt;isIE&lt;/th&gt;
&lt;th&gt;isEdge&lt;/th&gt;
&lt;th&gt;isBrowser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;IE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surface Pro 3&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;IE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;Platform&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobile&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isTablet&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileOnly&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIOS&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isAndroid&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isChrome&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isFirefox&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIE&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isEdge&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isBrowser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Opera Mini&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android Tablet&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Samsung Browser&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;Platform&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobile&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isTablet&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileOnly&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIOS&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isAndroid&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isChrome&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isFirefox&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIE&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isEdge&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isBrowser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;MobileSafari&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Opera&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPad&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;MobileSafari&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TRUE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;Platform&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobile&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isTablet&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileOnly&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIOS&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isAndroid&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isMobileSafari&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isChrome&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isFirefox&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isIE&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isEdge&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;span&gt;isBrowser&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  まとめ
&lt;/h1&gt;

&lt;p&gt;react-device-detect で User Agent がどう判定されるのか確認してみました。&lt;br&gt;
気になるところとしては、Tablet は isMobileOnly 判定が false になるところでしょうか。&lt;br&gt;
また、isMobileSafari は、isMobile と isSafari の AND なので、実際には使うケース少ないかもしれません (Android 考慮すると先に isMobile 判定するから)。&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
