Quantcast
Channel: Rubyの記事一覧|TechRacho by BPS株式会社
Viewing all 1079 articles
Browse latest View live

ruby-packerでRubyコードをシングルバイナリにコンパイルしてみた

$
0
0

ruby-packerとは

@pmq20さん作のruby-packerは、Rubyコードをシングルバイナリに変換して、Rubyがない環境でも実行できるようにするコンバーターです。

Evil Martiansのdipツール↓にも、ruby-packerでビルドした各種シングルバイナリ版がありますので実績はありますね。なおmacOS向けのdipのバイナリサイズは14MBでした。

docker-composeを便利にするツール「dip」を使ってみた

特徴

ruby-packerは、RubyとCのコードが半々近くで、他にもyaccやObjective-Cなどのコードをわずかに含んでいます。以下はREADMEからの引き写しです。

  • Linux、Mac、Windows向けのバイナリをそれぞれ生成できる
  • requireloadをネイティブでサポート(相対パスでもloadできる)
  • 自動アップデート機能
  • ネイティブC拡張をフルサポート
  • Railsアプリもフルサポート

試していませんが、ビルド時に----auto-update-url=でURLを指定するとオートアップデートもできるようです。

変換に必要なもの

Command Line Toolsは普段からMacに入れていますが、あのバカでかいXcodeも必要とは…😢

やってみた環境

  • macOS Catalina
  • Xcode 11.5
  • Command Line Tools for Xcode 11.5

LinuxとWindowsではやっていません。

ruby-packerをインストールする

とりあえずmacOS環境でインストールしてみました。

  • Homebrewでsquashfsをインストール
brew install squashfs
  • ruby-packerをインストール
curl -L http://enclose.io/rubyc/rubyc-darwin-x64.gz | gunzip > rubyc
chmod +x rubyc

./rubyc --help # 動作確認

自分はrubyc~/bin/の下に置きました。

単純なRubyコードファイルを変換する

こっ恥ずかしいことこの上ありませんが、大昔に練習で書いた単純なCLI Rubyコードを試しに変換してみます。gemではない単独のRubyファイルで、requireloadもありません。

試しにCommand Line ToolsのみでXcodeなしでやれるかどうかやってみました。

$ rubyc iscandar.rb
-> Project root not supplied, /Users/hachi8833/deve/ruby/iscandar assumed.
Ruby Compiler (rubyc) v0.4.0
- entrance: /Users/hachi8833/deve/ruby/iscandar/iscandar.rb
- options: {:make_args=>"-j4", :output=>"/Users/hachi8833/deve/ruby/iscandar/a.out", :tmpdir=>"/var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc"}

-> mkdir -p /var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc
-> cp -r "/__enclose_io_memfs__/local/vendor/zlib" "/var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc/zlib"
-> cd /var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc/zlib
-> Running [{"CI"=>"true", "ENCLOSE_IO_USE_ORIGINAL_RUBY"=>"1", "CFLAGS"=>" -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe ", "LDFLAGS"=>""}, "./configure --static"]
Checking for gcc...
Building static library libz.a version 1.2.11 with gcc.
Checking for size_t... Yes.
Checking for off64_t... No.
Checking for fseeko... Yes.
Checking for strerror... Yes.
Checking for unistd.h... Yes.
Checking for stdarg.h... Yes.
Checking whether to use vs[n]printf() or s[n]printf()... using vs[n]printf().
Checking for vsnprintf() in stdio.h... Yes.
Checking for return value of vsnprintf()... Yes.
Checking for attribute(visibility) support... Yes.
-> Running [{"CI"=>"true", "ENCLOSE_IO_USE_ORIGINAL_RUBY"=>"1", "CFLAGS"=>" -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe ", "LDFLAGS"=>""}, "make -j4"]
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN -I. -c -o example.o test/example.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o adler32.o adler32.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o crc32.o crc32.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o deflate.o deflate.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o infback.o infback.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o inffast.o inffast.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o inflate.o inflate.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o inftrees.o inftrees.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o trees.o trees.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o zutil.o zutil.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o compress.o compress.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o uncompr.o uncompr.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o gzclose.o gzclose.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o gzlib.o gzlib.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o gzread.o gzread.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN  -c -o gzwrite.o gzwrite.c
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN -I. -c -o minigzip.o test/minigzip.c
libtool -o libz.a adler32.o crc32.o deflate.o infback.o inffast.o inflate.o inftrees.o trees.o zutil.o compress.o uncompr.o gzclose.o gzlib.o gzread.o gzwrite.o
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN -o example example.o -L. libz.a
gcc -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe  -DHAVE_HIDDEN -o minigzip minigzip.o -L. libz.a
-> cd /Users/hachi8833/deve/ruby/iscandar
-> cp -r "/__enclose_io_memfs__/local/vendor/openssl" "/var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc/openssl"
-> cd /var/folders/3x/sfj972cx6vnddfqpk5yv36m00000gn/T/rubyc/openssl
-> Running [{"CI"=>"true", "ENCLOSE_IO_USE_ORIGINAL_RUBY"=>"1", "CFLAGS"=>" -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe ", "LDFLAGS"=>""}, "./config"]
Operating system: x86_64-apple-darwinDarwin Kernel Version 19.4.0: Wed Mar 4 22:28:40 PST 2020; root:xnu-6153.101.6~15/RELEASE_X86_64
WARNING! If you wish to build 32-bit library, then you have to
         invoke 'KERNEL_BITS=32 ./config '.
         You have about 5 seconds to press Ctrl-C to abort.
"glob" is not exported by the File::Glob module
Can't continue after import errors at ./Configure line 17.
BEGIN failed--compilation aborted at ./Configure line 17.
"glob" is not exported by the File::Glob module
Can't continue after import errors at ./Configure line 17.
BEGIN failed--compilation aborted at ./Configure line 17.
This system (darwin64-x86_64-cc) is not supported. See file INSTALL for details.
-> Running [{"CI"=>"true", "ENCLOSE_IO_USE_ORIGINAL_RUBY"=>"1", "CFLAGS"=>" -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe ", "LDFLAGS"=>""}, "make -j4"]
make: *** No targets specified and no makefile found.  Stop.
Failed running [{"CI"=>"true", "ENCLOSE_IO_USE_ORIGINAL_RUBY"=>"1", "CFLAGS"=>" -fPIC -O3 -fno-fast-math -ggdb3 -Os -fdata-sections -ffunction-sections -pipe ", "LDFLAGS"=>""}, "make -j4"]

やはりXcodeもないとダメか…。

空き容量を増やしてApp StoreでXcodeをインストールし(8GB必要)、Command Line Toolsも最新版を入れ直しました。この作業の方が時間かかりました😢

気を取り直して再チャレンジ。READMEには「どんなコードでもコンパイルに5分はかかる」とあります。

$ rubyc -c iscandar.rb
#(めちゃめちゃ長いので省略)
compiling ext/extinit.o
make[2]: Nothing to be done for `libencs'.
generating enc.mk
compiling enc/encinit.c
linking ruby
ld: warning: could not create compact unwind for _ffi_call_unix64: does not use RBP or RSP based frame
-> cp "ruby" "/Users/hachi8833/deve/ruby/iscandar/a.out"
-> cd /Users/hachi8833/deve/ruby/iscandar

細かなエラーは出ていますが、Macbookのファンが唸りをあげて10分ほどで無事焼き上がりました。
焼き上がったa.outは11MBでした。早速実行してみます。

$ ./a.out
■イスカンダルのトーフ屋ゲーム■ (外部仕様より再現)
Copyright (C) 1978-2013 by N.Tsuda
Reference: http://vivi.dyndns.org/tofu/tofu.html
背景: あなたはイスカンダル星で遭難し、帰りの費用を稼ぐためにトーフをなるべくたくさん売ってお金を稼がなければならない。
最初に所持金1000円が与えられる。
30000円儲けることができれば、めでたくイスカンダルから脱出することができる。
トーフは製造に一個あたり10円かかり、一個あたり12円で売ることができる。
トーフは晴れの日は100個、曇りの日は50個、雨の日は10個売れる。
売れなかった分は損失となる。
あなたは天気予報を見て、明日いくつのトーフを製造するかを決めねばならない。

A Tofu vendor surviving in Iscandar
Copyright (C) 1978-2013 by N.Tsuda
Reference: http://vivi.dyndns.org/tofu/tofu.html
Background: You are a castaway in planet Iscandar in outer space, and you have to gain money by making and selling Tofu in order to go back to your mother planet.
Initially you have 1,000 yen. The goal is to gain 30,000 yen for your traveling fee.
One Tofu costs 10 yen for production, and the unit price is 12 yen.
The sales of Tofu depends on weather: you can sell 100 Tofu on a fine day, 50 on a cloudy day, and 10 on a rainy day.
Watch weather forecast and determine the quantity of Tofu you are going to make.

Now you have 1000 yen.

--------------------------------------------------------------------------------
Weather Forecast
Probability: Fine: 86%, Cloudy: 13%, Rainy: 1%.
--------------------------------------------------------------------------------

Enter the quantity of Tofu:

動きました😋。11MBならGo言語のバイナリサイズと大して変わらない感じです。よくここまで作ったと思います🙇

気づいたこと

Ruby 2.4.1ベース

最新リリースのバイナリ版ruby-packerで使われているRubyのバージョンは2.4.1です。リポジトリはまめに更新されていますが、バイナリ版は3年前のものです。

$ rubyc --ruby-version
2.4.1

試しにruby-packerをgit cloneしてソースからビルドすると、最新のRuby 2.7.1でビルドされるようになりましたが、その代わり先のRubyコードをこれで変換すると、バイナリサイズが200MBを超えてしまいました。

-rwxr-xr-x 1 hachi8833 staff  11M  5 27 16:35 iscandar
-rwxr-xr-x 1 hachi8833 staff 216M  5 28 15:04 ruby2.7.1_iscandar

2.7.1だからこんなに大きくなるのかどうかはわかりませんが、バイナリ版のリリースをあえて更新してないのはそれが理由なのかもしれないと推測しました。

Macでのビルドにハマった

最初のビルドはすっと通りましたが、いろいろ試しているうちにビルドがコケるようになってきました😇

とりあえずの対策としては、rubyc -cのように-cオプションを付けて一時ディレクトリを削除するか、それでもダメならビルドメッセージに出てくる/var/folders/3x/あたりに置かれる一時ディレクトリをsudo rm -rfで削除します。

他のgemやRailsのビルドはまだやれていません😢

思いつく使いみち

一番ありそうなのは、Rubyをインストールできない環境でどうしてもRubyコードを実行したいときや、Rubyのないユーザー環境にRubyコードを配布したいときかなと思います。

ruby-packerでは事実上CRubyをコンパイルしているので、CRubyのビルド経験がないとハマりそうです。

Railsのシングルバイナリもやれるとのことですが、RDB接続やらWebpackerやらがちゃんと動くかどうかなど突破しないといけないことが多そうなので、自分はやらないつもりです😅

まだ試していませんが、Dockerでruby-packerを試せるようにした方がいます↓。


週刊Railsウォッチ(20200615前編)`rails new`に`minimal`がマージ、ARの共通集合を取る`and`、RubyMine+Dockerチュートリアル動画ほか

$
0
0

こんにちは、hachi8833です。オードリー・タンさんのPodcast聞いちゃいました😋


つっつきボイス:「Rebuild昨日やってたんですね」「どちらも英語のレベルも話のレベルも高くて脱帽でした🎩」「やはりリモートで対談」「見出しの『モナドは自分の仕事で必要というわけでもない』というあたりしか覚えてませんが😆」「こういう人のジョブがどんなのか興味あるな〜😋

参考: モナド (プログラミング) - Wikipedia

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Rails: 先週の改修(Rails公式ニュースより)

⚓ホストのcookieドメイン選択のマッチを厳密にした

# actionpack/lib/action_dispatch/middleware/cookies.rb#L442
        def handle_options(options)
          if options[:expires].respond_to?(:from_now)
            options[:expires] = options[:expires].from_now
          end
          options[:path]      ||= "/"
          options[:same_site] ||= request.cookies_same_site_protection
          if options[:domain] == :all || options[:domain] == "all"
            # If there is a provided tld length then we use it otherwise default domain regexp.
            domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
            # If host is not ip and matches domain regexp.
            # (ip confirms to domain regexp so we explicitly check for ip)
            options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
              ".#{$&}"
            end
          elsif options[:domain].is_a? Array
-           # ホストが、ドットが前に付いていないドメイン名のいずれかにマッチする場合
-           options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
+           # hostが、渡されたドメインのいずれかにマッチする場合
+           options[:domain] = options[:domain].find do |domain|
+             domain = domain.delete_prefix(".")
+             request.host == domain || request.host.end_with?(".#{domain}")
+           end
          end
        end

つっつきボイス:「どれどれ、以前はexample.comを指定してた場合にexample.com.aumyexample.comにまでマッチしてたのか!」「それはイカン😅」「『cookieでは元々おかしなドメイン名は無視するから互換性はそんなに心配いらないだろうけど』とコメントにありますね」「ブラウザ側の実装上は一応大丈夫ということなのかな?🤔」「いずれにしろ正しい挙動にするのがいいですね👍」「行儀が悪いのを直したと」

⚓新機能: Active Recordでリレーション名.andをサポート

集合論で言う「共通集合」を取れるそうです。

参考: 共通部分 (数学) - Wikipedia


つっつきボイス:「@kamipoさんのプルリクでした」「おぉ、今度はandが入った🎉」「なるほど、サンプルコードのようにdavid_and_mary.and↓と書けるようになった😋」「今までだとwhere文のところにSQLを書かないとできなかったヤツですね」「なるほど!」「こういうクエリを書きたいときはあるので、Active Recordの機能でやれるようになったのはいいことですね👍

# 同PRより
david_and_mary = Author.where(id: [david, mary])
mary_and_bob   = Author.where(id: [mary, bob]) # => [bob]

david_and_mary.merge(mary_and_bob) # => [mary, bob]

david_and_mary.and(mary_and_bob) # => [mary]
david_and_mary.or(mary_and_bob)  # => [david, mary, bob]

「これいいな〜、いつ使えるようになるんですか?😋」「マージされたから次のリリースで入るのかな〜😋」「早く欲しいよ〜😂」「😆」「😆

⚓委譲をやめて属性アクセスを15%高速化

# activemodel/lib/active_model/attributes.rb#L135
      def read_attribute(attr_name)
        name = attr_name.to_s
        name = self.class.attribute_aliases[name] || name

-       _read_attribute(name)
+       @attributes.fetch_value(name)
      end

つっつきボイス:「こちらも@kamipoさんでした」「read_attribute周りの修正か」「『たった1行のメソッドに委譲する意味はないので避けた』と書かれてますね」「こういう修正の見当が付くのがスゴい💪」「メソッド自動生成がらみはわけわからなくなりがちですし😭」「@kamipoさん止まらないですね」

わずか1行のメソッドに委譲するほどの価値はない。委譲を避けることでread_attributeを15%高速化できた。
同コミットより大意

Warming up --------------------------------------
read_attribute('id')   165.744k i/100ms
read_attribute('name')
                       162.229k i/100ms
fast_read_attribute('id')
                       192.543k i/100ms
fast_read_attribute('name')
                       191.209k i/100ms
Calculating -------------------------------------
read_attribute('id')      1.648M (± 1.7%) i/s -      8.287M in   5.030170s
read_attribute('name')
                          1.636M (± 3.9%) i/s -      8.274M in   5.065356s
fast_read_attribute('id')
                          1.918M (± 1.8%) i/s -      9.627M in   5.021271s
fast_read_attribute('name')
                          1.928M (± 0.9%) i/s -      9.752M in   5.058820s

⚓同じカラムをmergeしたときの条件の扱いをRails 6.2で統一

従来の振る舞いは非推奨になるとのことです。

同じカラムでのmergeで両方の条件を維持しないようになり、Rails 6.2では常に後者の条件で置き換えられるようになる。
Rails 6.2の振る舞いに移行するにはrelation.merge(other, rewhere: true)を使うこと。
Changelogより

# Changelogより
# Rails 6.1 (IN句はマージする側の等価条件で置き換えられる)
Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]

# Rails 6.1 (両者の条件に矛盾が存在する: 非推奨化済み)
Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => []

# Rails 6.1 で`rewhere`を用いることでRails 6.2の振る舞いに移行する
Author.where(id: david.id..mary.id).merge(Author.where(id: bob), rewhere: true) # => [bob]

# Rails 6.2 (IN句と同じ振る舞い、マージされる側の条件は常に置き換えられる)
Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]
Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [bob]

つっつきボイス:「この間から@kamipoさんがmerge周りに手を加えてましたね」「この間のrewhere: trueあたりとか(ウォッチ20200525)」「今後振る舞いが少し変わるのか🤔」「今回はdeprecatino warningを出すようにしたんですね😋

関連PR: #39250と#39236
今回の変更の目的は、マージの振る舞いがこれまで一貫していなかったのを統一すること。
現在は、マージされる側(mergee)の条件がマージする側(merger)によって置き換えられるのは、双方のarelノードが等価条件またはIN句の場合のみだった。
言い換えると、マージされる側の条件が等価条件でもIN句でもない場合(betweengtltなど)、双方の条件は同じカラムであっても維持されていた。
これではマージの振る舞いに熟知していないと予測が難しい。
元々自分は、この振る舞いは意図したものではなく単なる実装上の問題だと推測していた。理由は、mergeより後に導入されたunscoperewhereの挙動はmergeよりも一貫性が高くなっているため。
等価条件やIN句は条件のほとんどを占めるのが普通なので、この問題を踏んだことのある人はほとんどいないだろうと推測しているが、一貫性に欠ける現在の振る舞いを非推奨化し、将来のUXを改善するために今後完全に統一したいと思う。
同PRより大意


「ところでRails 6.2っていつ頃出るんでしょう?🤔」「どうでしょう、新し目の機能もだいぶたまってきた感ありますし、そんなに先の話じゃないのかな?」「さっきのmerge周りとかは挙動が変わるので、今のうちにテスト書いておきたいですね☺」「今作ってる機能が月末リリースなので早いとこRailsのバージョン上げたいです〜😅」「今月だと絶妙に微妙そう😆

後でRailsのマイルストーンを見てみました。

  • マイルストーン: 6.1.0 Milestone — 現時点で92件中残り21件
  • マイルストーン: 6.2 Milestone — 現時点で3件中残り2件

「そういえばmergeの変更はコンフィグで現状維持できるんでしたっけ?🤔」「コンフィグあったかな…😅」「もしかすると今後コンフィグを追加するのかも?」「リリースまではまだ多少時間もありますし、そうするかもしれませんね☺」「breaking changeならコンフィグ欲しいかも」

⚓続報: rails new--minimalオプション機能がマージ(Ruby Weeklyより)


つっつきボイス:「この間の#39444--interactiveが入るかと思ったらこちらが先でした」「minimumじゃなくてminimalとは😆」「ほぼスッピンの形でrails newやれるようになるそうです」

# railties/lib/rails/generators/rails/app/app_generator.rb#300
+       if options[:minimal]
+         self.options = options.merge(
+           skip_action_cable: true,
+           skip_action_mailer: true,
+           skip_action_mailbox: true,
+           skip_action_text: true,
+           skip_active_job: true,
+           skip_active_storage: true,
+           skip_bootsnap: true,
+           skip_dev_gems: true,
+           skip_javascript: true,
+           skip_jbuilder: true,
+           skip_spring: true,
+           skip_system_test: true,
+           skip_webpack_install: true,
+           skip_turbolinks: true).tap do |option|
+             if option[:webpack]
+               option[:skip_webpack_install] = false
+               option[:skip_javascript] = false
+             end
+           end.freeze
+       end

「ミニマルにするとAction Mailerもスキップされるのか😆」「Active Strage、最初はなくてもいいかな😋」「JBuilderは今となってはなくてもいいでしょう😆」「Webpackも飛ばせるとは、まさしくミニマル😳」「すっぽんぽん🤣」「普通のAPIモードよりさらに禁欲的というか🤣」「システムテストも飛ばしてる🤣」「スースーしそう🤣

参考: Rails による API 専用アプリケーション - Railsガイド

「『いいね👍』付けてる人がめちゃ多い」「嬉しい人は嬉しいでしょうね😋」「Railsに慣れた人がうんとささやかなAPIサーバーを作りたいときなんかはミニマルだとありがたいかも☺」「JavaScriptも切るあたりがAPIを想定してるっぽいかも😋

「インタラクティブなrails newはそれはそれでやるのかな?」「Railsの場合、機能同士の依存関係なんかもあるので、何を外すべきかを考えるのって割と難しいところはありますね😅」「あ、そうですね😳」「ミニマルで作っておいて、機能が必要になったら後から足す方が理にかなってそうですし🧐

「そもそもどの機能を外したらいいのかが自分に見当が付かないという😆」「デフォルトだと機能が山盛りだからなおさら😆」「これとこれは競合するとか、これはこれに依存するとか」「機能を外したつもりなのに外れてなかったこともあった気がします😅」「Action TextがActive Storageに依存してるんでしたっけ、だとするとActive Storageを外したつもりでもAction Textを使うとまた入ってきたり😆

⚓Rails

⚓RailsアプリをJS抜きで同じに作り直してみた


つっつきボイス:「この記事こないだ読んだんですけど意図がよくわからなかったかも😅」「前に作ったRailsアプリを、機能を変えずにJS抜きで作り直したのかな?🤔」「サーバーサイドだけのアプリケーションにしてみたという感じかも」

# 同記事より
# app/views/todos/_todo.html.erb

<div id="<%= dom_id(todo) %>" class="ToDoItem">
  <p class="ToDoItem-Text"><%= todo.name %></p>
  <%= button_to "-", todo_path(todo.id),
      method: :delete,
      remote: true,
      class: "ToDoItem-Delete"
     %>
</div>

「こんなふうに↑前はJSでDOM制御していた画面を、あえてサーバーサイドスクリプトを通して実行するようにAction Cableで書く、みたいな😆」「JavaScriptがキライなのが伝わってきそう😆」「断捨離というか😆

「以下のhtml: render_to_stringのあたりなんかはHTMLでレンダリングしてますね: しかも書き換えは[:html]を指定してHTML置換してますし☺」「男らしい💪」「カッコイイ✨」「フロントエンドの人たちから何か言われそうですけど😆」「想像できます😆

# 同記事より
# app/controllers/todos_controller.rb

def create
  todo = Todo.new(todo_params)

  if todo.save
    cable_ready[TODOS_CHANNEL].insert_adjacent_html(
      selector: "#todo-list",
      position: "afterbegin",
      html: render_to_string(partial: "todos/todo", locals: {todo: todo}, formats: [:html])
    )
    cable_ready[TODOS_CHANNEL].set_value(
      selector: "#todo_name",
      value: ""
    )
    cable_ready[TODOS_CHANNEL].remove(
      selector: ".error"
    )
    cable_ready.broadcast

    return render(plain: "", status: :created)
  end

  cable_ready[TODOS_CHANNEL].insert_adjacent_html(
    selector: "#todo_name",
    position: "afterend",
    html: "<p class='error'>#{todo.errors[:name].first}</p>"
  )
  cable_ready.broadcast

  render json: {errors: todo.errors.to_h}, status: :unprocessable_entity
end

「まあ昔はこういう書き方が結構使われてましたね: Railsでも今はなきRJSとかで、JSを含むパーシャルとHTMLを含むパーシャルをサーバーサイドでいい感じに返して、それを使ってDOMを書き換えるようなコードは自分も書いてましたし🧐」「自分も当時そういうコードいっぱい書いてたら後の時代にフロントエンドの人に怒られました😅」「まあ治安の悪さを考えれば、この書き方がなくなったのもワカル😆

参考: RubyOnRails を使ってみる 【第 7 回】 RJS を使ってみる

「でもあの当時はjQueryが一般的な時代でしたし」「そうですよね」「あの時代はJSフレームワークと言うと他にBackbone.jsぐらいしか見当たりませんでしたし、当時はいろいろしょうがないと思います😆」「時代が違うのでご勘弁を😆


記事見出しより:

  • Action Cableをセットアップする
  • JavaScriptコードを消し去る
  • JavaScript抜きで機能を再実装する
  • 締めくくり

⚓ActiveModel::AttributeAssignmentとは


つっつきボイス:「今日のWebチーム内発表で話題に出た機能です」「そうそう☺

「Railsのフォームに標準で入ってくるDate/Timeセレクタって、そのままだと『年』『月』『日』『時』『分』『秒』という6つのセレクトボックスができるんですけど、それをそのままPOSTすると当然ながら6つのパラメータに分解されてから送信されるので、それを組み立てる仕事をやってるのがこれだそうです」「へぇ〜😳」「hashアトリビュートになっている値を渡すとよしなにやってくれるらしいです😆

# 同APIより
class Cat
  include ActiveModel::AttributeAssignment
  attr_accessor :name, :status
end

cat = Cat.new
cat.assign_attributes(name: "Gorby", status: "yawning")
cat.name # => 'Gorby'
cat.status # => 'yawning'
cat.assign_attributes(status: "sleeping")
cat.name # => 'Gorby'
cat.status # => 'sleeping'

「勉強会のお題ではActive Recordを使わないでActive Modelだけでやってたので、この機能を明示的に使う必要があったんだそうです」「へ〜、assign_attributes()ってここに実装されてるのね😳」「好きな人が多い機能でしたっけ」「assign_attributes()は普通によく使うヤツですね☺

後で調べると、assign_attributes()はRails 3.1のときにActive Recordに入ってたんですね。APIdockを見た感じでは、5.0のときにActive Modelに引っ越したようです。

参考: Rails 3.1: assign_attributesメソッド - Rails 雑感 - Ruby on Rails with OIAX
参考: assign_attributes (ActiveModel::AttributeAssignment) - APIdock

「そうそう、Qiitaの記事↓でいうとPOSTされたときはこんな感じデータになってたのを、このメソッドでいい感じにRailsのTimeWithZoneに変換してやってくれたということで」「Rails標準だとこういう形になるよねという話」

参考: 【Tips】Rails の assign_attributes は分割されたパラメータを飲み込む - Qiita

# Qiita記事より
params
=> {
    "name"=>"ダミー名前",
    "reserved_at(1i)"=>"2020",
    "reserved_at(2i)"=>"3",
    "reserved_at(3i)"=>"2",
    "reserved_at(4i)"=>"00",
    "reserved_at(5i)"=>"00"
  }

「記事はこの動作に興味を持って追ったんですね」「実際、POSTでやってくるこの謎パラメーターに一度は首を傾げますし😆」「1iとか2iとか😆

⚓geared_pagenation: 速度可変のページネーション(Ruby Weeklyより)


つっつきボイス:「Basecampが直々に出してきたgemのようです」「gearedというと回るギアの?⚙」「自動車の変速装置というかトランスミッションみたいな動作をイメージしてるのかな🤔

# 同リポジトリより
class MessagesController < ApplicationController
  def index
    set_page_and_extract_portion_from Message.order(created_at: :desc)
  end
end
# app/views/messages/index.html.erb

Showing page <%= @page.number %> of <%= @page.recordset.page_count %> (<%= @page.recordset.records_count %> total messages):

<%= render @page.records %>

<% if @page.last? %>
  No more pages!
<% else %>
  <%= link_to "Next page", messages_path(page: @page.next_param) %>
<% end %>

「なるほど、人間の性格として、ページネーションのあるページをオートスクロールするときなんかだと、2ページ目ではそんなにたくさん表示しなくてもいいけど、2ページ目まで開いた人なら3ページ目はどうせ開くだろうし、もっとたくさん表示して欲しいと思うでしょうから」「ふむふむ」「READMEにも書いてますけど、たとえばページのelementsは1ページ目なら15個でいいけど、2ページ目なら30個、3ページ目なら50個、4ページ目なら100個…みたいにだんだん増やしていく、というのをgeared pagenationと呼んでるんでしょうね😋」「な〜るほど!」

「実際ページのスレッドを追いかけて次々にページをめくっていると、どうせなら先に進むに連れて多めに読み込んで欲しいって思うことありますし☺」「ときにはページネーションなしで一気に全ページ出して欲しいと思うときもありますけど😆」「それもわかる😆」「マウスホイールをゆっくり回したときと勢いよく回したときでスクロールの距離が違うみたいな🐭」「そんな感じ」

「なるほど〜という感じのgemですけど、これをサーバーサイドでやるのかという気持ちはちょっとありますね😆」「😆

⚓Railsでメモ化しない方がいい場合(RubyFlowより)

# 同記事より
def slow_method
  @result ||= perform_slow_method
end

つっつきボイス:「メモ化を使わないとき」「記事にもありますけど、いつも言ってる『それはメモ化してもセーフなのか?』というヤツ😆」「あ、なるほど」「クラス変数やクラスインスタンス変数をメモ化すると競合が発生する可能性がありますし、スレッドで使ったときもそうですし🧐

「メモ化ってそんなに好きというほどじゃないかも😆」「もちろん何でもメモ化するのはよくありませんけど、Active Recordで毎回pluckで取ってきたりすると遅くなるデータもあるので、自分は状況に応じてメモ化を使いますね☺

Rails: pluckでメモリを大幅に節約する(翻訳)

⚓Railsマイグレーションのup_only


つっつきボイス:「めちゃめちゃ短い記事なんですけど、こんなのあるって知らなかったので😳」「up_onlyですって😳」「で探してみたらkoicさんの少し前の記事が見つかりました↓」「up_onlyが当初RuboCopでアラート出たからCopに書き足してくれたのね😋

up_onlyというとdownはやらないということでしょうか?」「でしょうね、データを更新するマイグレーションなんかだとdownしたくないこともありますし☺

⚓その他Rails


つっつきボイス:「JetBrainsの動画記事です」「RubyMineは前からDocker Composeをサポートしてるけど新しい機能でも増えたのかなと思ったらチュートリアル動画ね☺」「コメント欄でjnchitoさんが『これは素晴らしい!』と激賞していますね」「RubyMineとDocker Composeか〜😋

⚓JetBrains IDEのDocker Composeインテグレーション

「ところでRubyMineというかJetBrains IDEのDocker Composeインテグレーションは、右クリックでexecできたりしますし、なかなかよくできてますよ❤」「おぉ😍

「記事からリンクされてるこれ↑もいい機能ですし🥰」「リモートインタプリタ?」「つまりローカル環境にRubyがなくても、Docker Composeの中で動かすDockerコンテナの上にあるRubyをリモートインタプリタとして指定できます👍」「そしたらローカルにわざわざいろんなバージョンのRuby入れんでもええよねと😋」「そうそう、OpenSSH 1.0.いくつを使わないとコンパイルできないような古〜いRubyでもDockerコンテナに乗ってれば作業できますし😆」「この間の古いRubyコンパイル話っすね😆


前編は以上です。

おたより発掘

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200609後編)Rubyにカスタマイズ可能な軽量fiberスケジューラを実験導入、RailsとGraphQL、DBについて知って欲しいことほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20200616後編)本番環境をFullstaq Rubyに換えた理由、CSRF発生フローチャート、DBのトランザクション分離レベル比較ほか

$
0
0

こんにちは、hachi8833です。風向きが変わりつつある今日このごろですね。


morimorihoge注:本トピックに関するコメントとして、初出時人種差による不平等を容認・揶揄するようにも取れる内容がありましたので当該部分を削除させていただきました。
Twitter上でのご指摘を受けて社内ヒアリングを行ったところ、私を含む参加メンバーで主題の認識違いがあり、主題のすれ違いの中話していた内容をテキスト化した際に公開記事として掲載するにあたって不適切な内容となってしまっていたことが判明いたしました。不快に感じられた方にはこの場を借りてお詫びいたします。
※BPS株式会社は会社・組織として人種その他先天的・後天的な要因による差別を容認・支持する立場にはありません(弊社代表にも確認済み)
  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

Ruby

Rubyが自動化に向いている理由(Hacklinesより)


つっつきボイス:「比較の対象がJSやJavaやPythonになってます」「まJavaでやるのはね😆」「自動化という感じじゃないですね😆」

「向いている理由1がリフレクションとブレークポイント?」「Cucumberをbreakしてデバッグ、今ならJSにもこういうのは普通にありますけど😆」

「記事書いている人はRubyが好きだからRubyでやりたい感😆」「まあこういうふうに書ける↓のはたしかにRuby以外ではあんまり見かけませんけど☺️」

# 同記事より
class LoginPage < SitePrism::Page
  set_url '/login.html'

  element :un, 'input#username'
  element :pw, 'input#password'
  element :submit, 'input#submit'
end

「Cucumberでこういうふうに書ける↓なんてのもそうですし」「そうですね〜」

# 同記事より
Given("I login with valid credentials") do
  $LoginPage.load
  $LoginPage.un.set 'jeff@amzn.corp'
  $LoginPage.pw.set ENV['TESTPW']
  $LoginPage.submit.click
  $LoginPage.should_not have_error
  $Dashboard.should be_displayed
end

「Rubyだと人間による操作をキレイにコード化しやすいという気持ちは何となくわかりますね😋」

site-prism

「ところでSitePrismとかいうCapybara支援DLSがあるみたい↓」「おや👀」

参考: RubyでPageObjectsパターンを実装できる SitePrism のご紹介 - Qiita
参考: CapybaraとSitePrismを使ってみる - Qiita

piperator: Elixir風パイプライン演算子(Ruby Weeklyより)


つっつきボイス:「何て読むんだろか?」「パイペレーター?」「パイプライン演算子(pipe operator)のもじり?😆」

参考: パイプライン演算子 - Wikipedia

# 同リポジトリより
Piperator.
  pipe(->(values) { values.lazy.map { |i| i * 3 } }).
  pipe(->(values) { values.sum }).
  call([1, 2, 3])
# => 18

「何をするんでしょうね?😆」「きっと普通にパイプラインというかストリーム処理をやるんでしょう😆」「内部でスレッドになってたりしそう」「クローラーなんかだとこういうパイプライン的な処理やりますね」「パイプライン処理をサポートするツールがあれば、メモリも節約できるでしょうし😋」

「そういえばRubyにもパイプライン演算子|>が入りかかった後でrevertされてましたね↑」「その辺は、本当の意味でのパイプラインになっているかどうかという問題もありますし😆」「それもそうですね😅」「パイプラインっぽく見えるのとパイプライン処理をできるというのはまったく別の話なので☺️」「ですよね☺️」「詳しくは見てませんが、このpiperatorはlazyとか使ってたりしますし、たぶん処理としてパイプラインになっているんじゃないかな〜😋」「超巨大な円周率データファイルだって処理できますよ、なんて😆」

Rubyを含むパイプライン演算子の流れについては以下の記事が詳しいです。

参考: パイプライン演算子の歴史 - まめめも

Fullstaq Rubyに乗り換えた理由(Hacklinesより)


つっつきボイス:「Evil Martiansの記事に続いてFullstaq Rubyに乗り替えたところが出ましたね」「お〜、最初から使える案件ならFullstaq Rubyはもう使ってみてもいいよね❤️」「使ってはいけない理由もありませんし😋」「上の記事でもまさに同じことを言ってました」

Fullstaq Rubyの第一印象とDocker/Kubenetes Rubyアプリとの統合(翻訳)

「途中からFullstaq Rubyに乗り換えるのはしんどそうだけど、最初から使えるならもう別に使ってもいいでしょう😆」「Dockerで使えば環境も関係なくなるし😋」「結局違いはjemallocを使うところですし」「mallocよりいいと言われているjemallocですね」

参考: jemalloc について調べたのでまとめた - zonomasaの日記

Rubyとjemallocの関係については以下の記事もどうぞ。

Ruby: mallocでマルチスレッドプログラムのメモリが倍増する理由(翻訳)

test-bench: 語彙を絞り込んだテストフレームワーク

# 同リポジトリより
context "Some Context" do
  context "Some Inner Context" do
    test "Some test" do
      # ...
    end
  end
end

つっつきボイス:「まだ★は少ないんですが、ボキャブラリーを増やしすぎないところがちょっといいかなと思って」「ボキャブラリー少ないのは重要😆」「普通にアサーションするタイプのテストフレームワークという感じ☺️」「こうやって車輪が再発明されていくのかなと🚗」

# 同リポジトリより
assert(true)               # Passes
assert(false)              # Fails
assert(1 == 1)             # Passes
assert(some_object.nil?)   # Passes if some_object is nil
assert(1 > 1)              # Fails

refute(true)               # Fails
refute(false)              # Passes
refute(1 != 1)             # Passes
refute(!some_object)       # Passes if some_object is *not* nil

「そういえばrefuteって何だっけ?」「あ〜ど忘れ😅」「見るからにassertの逆なんでしょうけど😆」「今辞書を引いたら『論破する』でした」「そうそう、『そうでないこと』を主張するヤツ😆」「言われてみると普通のアサーションだと冗長になりそうなときとかにrefuteがたまに欲しくなるかも」「アサーションに!付けたくないときとか」「語彙を増やさないなら!でもよかったりして?😆」「equalityチェックみたいに単純なものならいいんですけど、!付けるだけだと表しにくいものもありますし🧐」「refuteの方がシンプルに書けますよね、わかります😋」


RubyのMiniTestにもrefuteがあるのですが↓、そういえばRSpecでは聞いたことありませんね。

参考: module MiniTest::Assertions - Documentation for Ruby 2.1.0

「ところで公式サイトのドメイン名がtest-bench.softwareですし↓」「ドットsoftwareドメイン?😳」「そんなドメイン名使えるんだ😆」

DB

コンカレントシステムにおけるデータベース一貫性モデル


つっつきボイス:「何だか見慣れない図😅」「図のピンクはネットワーク障害によっては利用できなくなる可能性があるけど青はできるみたいな感じだそうです」「モデルというのはデータベースを含むシステムのモデリングの話なのね☺️」


同記事より

「それぞれの要素はだいたいわかるけど、全体としては何を表してるんだろう?🤔」「よくわかんな〜い😆」「この記事はどこから?」「この間話題にした『データベースについて知っておきたかったこと17(英語)』記事からここにリンクされているのを見つけました(ウォッチ20200609)」「なるほど、そういう文脈ね☺️」

「ざっと見た感じ、データベース屋さんとか分散システム屋さん向けのsurvey paperをまとめたものっぽい: リンク先は最終的に論文のリファレンスになってますし🧐」「たしかに図のピンクやオレンジや青のブロックをクリックするとその用語の説明や関連論文を説明するページが開きますね😳」「ほんとだ」

「情報は新しいのかな?」「このサイトにはコピーライト以外に日付情報が見当たらなくてよくわかりませんけど😅」「新しめの論文も参照してるみたい😋」「たぶんデータベース一貫性モデルのsurvey情報としてはいいサイトなんだろうなと思います👍」

RDBMSごとのトランザクション分離レベルの違い


つっつきボイス:「これも同じく『データベースについて知っておきたかったこと17(英語)』記事からのリンクで知りました」「PostgreSQLやMySQLとかが載ってる↓👀」


同リポジトリより

参考: トランザクション分離レベル - Wikipedia

「見た感じ、SerializableやRead Uncommitedのようないわゆる一般的なトランザクションレベル↑の用語が、実際にはRDBMSごとにどういう用語で表されてどう実装されているのかというのをまとめたんでしょうね🧐」「ふむふむ」「たとえば同じRepeatable Readでも、ぽすぐれの場合とMySQLの場合ではそれぞれこうなってるよ、みたいに」「あ、そういうことか😋」「ちょっと見えてきた😋」「そういえばリンク元記事でも『トランザクション分離レベルはRDBMSごとに違う』という話の中で引用してました」

「G0とかG1aとか何だろうと思ったらページの下に略語の意味が書いてあった😅」「厳密に追っていくと、本当はRDBMSごとにこれだけ細かく違ってるということなんでしょうね」「ここまで理解しているWeb系エンジニアっているんでしょうか?😭」「あんまりいないでしょう🤣」

「Oracleやってる人ならそれなりに知ってるのかな?🤔」「Oracleはほんのちょっと触ったけどMSSQLわかんない😆」「MSSQLの分離レベルがなぜか他より細かく分かれてる😳」「最後のFDB SQL Layerって何ですかこれ?😆」「全然知りませんけど😆、ググったらこんなの出てきましたね↓」「FoundationDBって知らな〜い😆」「分散データベースに特化してるっぽいことが書いてある👀」

「お、コード例も載ってる↓👍」「検証用のテストコードもあるんですね😋」「これも研究屋さんまたはデータベースの専門家が作った資料っぽいですね🧐」

-- 同リポジトリより(PostgreSQLの場合)
begin; set transaction isolation level read committed; -- T1
begin; set transaction isolation level read committed; -- T2
update test set value = 11 where id = 1; -- T1
update test set value = 12 where id = 1; -- T2, BLOCKS
update test set value = 21 where id = 2; -- T1
commit; -- T1. This unblocks T2
select * from test; -- T1. Shows 1 => 11, 2 => 21
update test set value = 22 where id = 2; -- T2
commit; -- T2
select * from test; -- either. Shows 1 => 12, 2 => 22

「資料末尾のリファレンスも、VLDBとかSIGMODみたいなトップ会議がずらっと並んでいるし、データベースの専門家ならこの辺のリファレンスは常識的に知ってるということなんでしょう、たぶん🧐」「本来ならここまで勉強せいと😅」「リポジトリが6年前だからあんまり新しくないのかと思ってました😅」「新しい古いというよりは研究系のサーベイですし☺️」

「ところで、こういうサーベイって大学院の情報系学科なんかで課題に出そう🎓」「こんな課題出るんですか〜?😅」「CS系だとあってもおかしくないですヨ☺️」「ちょっと新しい世界を垣間見たかも😆」「こんな世界もあるんだな〜😅」

クラウド/コンテナ/インフラ/Serverless

IPA-DN-EasyBgpStarterKit


つっつきボイス:「はてブでもバズってました」「そうそう、さんの作ったこの資料がとてもよくできてるって知り合いとも話題になりましたし😆」「何だかパワーワードっぽい言葉がいっぱいありますね😆」「読んでいくうちにだんだんエクストリームな言い回しに気がつくという😆」「おぉ〜おもしれ〜😆」「真ん中辺の図の『上流ISPにうまいこと言ってタダで貰ってきた1GbpsのBGP接続ポート』なんていうパワーワードも🤣」「『あっ、あのけしからんソフトイーサ社だな』とか文章のレベル高い🤣」

「オモシロイけど途中からだんだん難しくてわかんなくなる😅」「お、この辺のインフラ方面はあんまりやらない感じですか?」「まあ😅」「要はAS番号取ってBGPで完全二重冗長のネットワークを組んでみたぜという感じなので、やりたいことはよくわかります: IPv4のフルルートを取るところとか楽しそう😆」

参考: BGP(Border Gateway Protocol)とは

「よく見たらすっごく長い😅」「チュートリというには長いですけど手順がっつり明らかにしてますし☺️」「『友達のプロバイダー仲間に馬鹿にされないように、自宅の BGP ルータとして Cisco CRS-3 や ASR9000 などを利用する場合の設定サンプル』なんてのもある🤣」「BPSルーター持ってなくても馬鹿にされませんし普通😆」「いろいろ常人を超えてる🤣」「あなたの自宅にハイエンドBGPバックボーンルータを設置したくなるでしょうって、なりませんよ🤣」「まあいつもの登さん節ですね😆」「やっぱり有名な方なんですね?」「この道では普通に有名です☺️」「本気出した天才プログラマー肌ですね」(以下延々)

物理レイヤの障害


つっつきボイス:「これは?」「サメが海底の通信ファイバーケーブルをかじって通信障害が発生した瞬間を捉えた貴重な記録映像だそうです📽」「ぶほっ😇」「え?え?」「かじりやがった…こいつめ😭」「本気でかじってますよこの子😳」「まさにシャークバイト」「これでデータ不整合とか起きたら報告書に何て書くんでしょうね😆」「サメのせい🦈」

「一説にはケーブルの電磁波に誘われたのかもって」「それあるらしいですね😆」「ケーブルに歯型が残ってたとかならありそうですけど、よくその瞬間を撮影できたな〜😳」「どうやって撮ったんでしょ?」「障害調査中か、はたまた通常メンテ中にたまたま見つけたのかな?🤔」

「今さらですけど、ぼくらの快適なネット生活をこういう海底ケーブルが支えてくれてるんですね: 感謝しときます🙏」「ちなみにNetFlixなんから海底ケーブルに相当投資してますね💵」「へぇ〜、ねとふりって海底ケーブルにも手を出してるんですか😳」「彼らのビジネスへの影響が大きいインフラですし、凄い額の投資が必要ですし: こういった企業が海底ケーブルの主導権を巡って熾烈な争いを繰り広げているという記事も何かで見たことありますヨ☺️」

参考: 海底ケーブル - Wikipedia

その他インフラ

CSS/HTML/フロントエンド/テスト/デザイン

CSRFフローチャート


同記事より


つっつきボイス:「CSRFはこうやって起きるの図だそうです」「いいな〜、こういうの知りたかった😍」

参考: クロスサイトリクエストフォージェリ - Wikipedia

「CSRFって防がなきゃと思いつつどうなってるのかなって思ってました😅」「これだけだとあんまりわかりやすいとは思えないけど😆、チェックシート的に使えそうなのと、ちゃんと解決方法のパスも示されてるのがいいですね😋」「お、図の下にはちゃんとPOC(proof-of-concept)のサンプルコードまである↓のがエライ!❤️」「これなら実際に動かしてCSRFを確かめられる😋」「有能👍」

// 同記事より
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://www.example.com/api/setrole");
xhr.withCredentials = true;
//application/json is not allowed in a simple request. text/plain is the default
xhr.setRequestHeader("Content-Type", "text/plain");
//You will probably want to also try one or both of these
//xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.send('{"role":admin}');

その他HTML


つっつきボイス:「NginxがHTTP/3をクイックサポートするという話、出てましたね😋」「HTTP/3、ついにやるのか〜」

参考: Webサイトの表示速度をさらに高速化!「HTTP/3」とは? | さくらのSSL

# 同記事より
    $ hg clone -b quic https://hg.nginx.org/nginx-quic
    $ cd nginx-quic
    $ ./auto/configure --with-debug --with-http_v3_module       \
                       --with-cc-opt="-I../boringssl/include"   \
                       --with-ld-opt="-L../boringssl/build/ssl  \
                                      -L../boringssl/build/crypto"
   $ make
   $ #sudo make install ##面倒くなければ install してしまっても良い

--with-http_v3_moduleを付けてコンパイルするのね↑」「ChromeのDevToolsでh2-27とドラフトの番号表示されてる👀」「ChromeもCanaryを--enable-quic --quic-version=h3-27で起動しないといけないと」「あ、クライアントもか😳」「クライアントも対応しないとできませんし😆」

NginxのHTTP/3対応版が公開されました。実際に動かしていきます。(なお、現在サポートしているのはHTTP/3 draft 27版です)
同記事より

「HTTP/2のときにもnginx-buildとか使って頑張って対応したの思い出しました😭」

参考: nginx-build〜nginxのビルドプロセスを自動化〜 - Mercari Engineering Blog

言語/ツール/OS/CPU

迷うとき


つっつきボイス:「方向性の似ているツイートをまとめて貼りました」「1つ目のツイート、関数型言語にも戻り値の配置が普通と逆になるものがあったような?🤔」「C言語だと戻り値の型を最初に書きますもんね😋」「そうそう😆」「mattnさんのツイートだから『関数名の後に戻り値の型を書いて』はGo言語ですね😆」

「2つ目は久々にCoffeeScriptやったらハマった話」「こ〜ひ〜すくりぷとか〜😆」「if文なのにendいらないよ〜って😆」「インデントで考えるところで頭おかしくなりそう😆」「CoffeeScriptで書かれている以上やるしかない😅」

参考: CoffeeScript - Wikipedia

「実はCoffeeScript好きだったんだけどな〜😅」「あの頃は数行しか書かないみたいなところがありましたよね: ちょっと複雑になってくるとインデントレベルを追えなくなる😇」「それはあります😆」

「3つ目は、1つの記号を2つ以上の意味で使わないということなのかなと」「プログラミングを初心者に教える立場としてはその方がありがたい面はあるでしょうね☺️」「C言語がいいっていう人たちは『言語仕様が小さいからいい』って言ってたりしますけど🤣」「まあそうなんですが😅」「難しさって人によってもレベルによっても違ってくるのが悩ましいですよね: 言語仕様は小さいけどポインタみたいな難しい概念がある言語がいいのか、予約語はいっぱいあるけど、1つのやり方には1つの書き方しかない言語がいいのか、みたいに☺️」「人生いろいろ、言語もいろいろ」

その他

用語をどうするか問題


つっつきボイス:「GitHub CLIのブランチ名をmaster以外の名前に変えようというissueが出てて、trunkとかどうだ?みたいな話が出てました😆」「trunkにしようというのはウケる😆」「以前のRubyはSubversionでリビジョン管理してたからメインブランチがtrunkでしたね😆」

参考: Apache Subversion - Wikipedia

「issueに『generally problematic term』ってあるから、やっぱり今米国で騒ぎになっているあの問題がらみなんでしょうね」「え〜、こんなところにまで…😅」「masterだとslaveを連想させるときがあるから云々で」「と言われても…😅」「大昔のハードディスクなんかによくあったマスター/スレーブもプライマリ/セカンダリに変わったりしてましたね」(以下延々)

つっつき中は気づきませんでしたが、後でこのissueを見ると本当にこのツールのmasterがtrunkに変更されていました😳。

番外

聞こえてますか…?


つっつきボイス:「プログラミングのときに活性化する部分が、どうやら会話のときに音声を理解する場所らしいと」「誰かの声って、自分の中のもうひとりの自分なのかもって思いました」「バグを発見すると右脳前頭部の活動が瞬間的に跳ね上がるって😆」

「皆さんもやっぱり聞こえてるんでしょうか?」「自分の実感としては、プログラミングの最中にかける音楽に歌が入ってると作業効率ががっくり落ちますね📉」「それすごくわかる!」「歌入りはアカン😆」「なのでだいたいゲーム音楽のサントラみたいなインスト音楽になっちゃいます🎶」「歌ものが作業中に合わないって言う人、たしかに割と見かけます」

「無意識のうちに脳が言葉を処理しようとして動き出しちゃうからでしょうね☺️」「それがコーディング作業とバッティングするというのは、まあわからなくもないかも🤔」「あと脳内デバッグというか脳内で処理を流しているときなんかも」「よくわかんないけどわかる感じ😆」


後編は以上です。

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200615前編)`rails new`に`minimal`がマージ、ARの共通集合を取る`and`、RubyMine+Dockerチュートリアル動画ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly

Hacklines

Hacklines

Ruby: 配列要素の平均値を取るときのコツ(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Ruby: 配列要素の平均値を取るときのコツ(翻訳)

Rubyは、要素がintegerの配列の平均値を生成するネイティブのメソッドを提供していません。Mathライブラリは、より複雑な計算メソッドに注力しており、Array組み込みの#averageメソッドや#meanメソッドはありません。

つまり、これらのメソッドを独自に作成する余地がある分、自分の足を撃ち抜く可能性も生じます。

以下のようにすること

要素がintegerの配列の平均値を算出するには、以下のようにArray#sumを用いる。

a = [1, 2, 3, 4, 5, 6, 7, 8]

a.sum(0.0) / a.size
#=> 4.5

そうすべき理由

Array#sumは、injectよりもずっとずっと高速です。

Array#sumが導入されたのはRuby 2.4からでした。Array#sum以外のやり方(訳注: injectを使う方法)がネット上に山ほど落ちている理由は、これです。

benchmark-ips gemで実装ごとのパフォーマンスの違いを比較できます。

require "benchmark/ips"

# 要素が1,000個の配列を生成する
a = Array.new(1000) { |_| rand(1000) }

Benchmark.ips do |x|
  x.report("sum(0.0) / size") do
    a.sum(0.0) / a.size
  end
  x.report('inject(0.0) / size') do
    a.inject(0.0) { |result, i| result + i } / a.size
  end
  x.compare!
end

私のノートPCでは、ネイティブの#sumを用いると50倍も速くなりました。このメソッドは、まさにこうしたパフォーマンス向上のためにRubyに組み込まれたのです。

Calculating -------------------------------------
   sum(0.0) / size  680.425k (± 6.5%) i/s -  3.432M in 5.06s
inject(0.0) / size   13.513k (± 5.0%) i/s - 67.586k in 5.01s

Comparison:
   sum(0.0) / size: 680425.2 i/s
inject(0.0) / size:  13512.7 i/s - 50.35x  slower

訳注: 訳者のMacbook Pro (2019)では69倍の違いが生じました。

#sumしてから#sizeするのと同様の手法は他にもいろいろありますが、パフォーマンス上の寄与が最も大きいのは、最初にネイティブの#sumメソッドを用いることです。

さらに多くの実装でベンチマークしてみましょう。

require "benchmark/ips"

# 要素が1,000個の配列を生成する
a = Array.new(1000) { |_| rand(1000) }

Benchmark.ips do |x|
  x.report("sum(0.0) / size") do
    a.sum(0.0) / a.size
  end
  x.report("sum.to_f / size") do
    a.sum.to_f / a.size
  end
  x.report("sum / size.to_f") do
    a.sum / a.size.to_f
  end
  x.report("sum.fdiv(size)") do
    a.sum.fdiv(a.size)
  end
  x.report('inject(0.0, :+) / size') do
    a.inject(0.0, :+) / a.size
  end
  x.report('inject(0.0) / size') do
    a.inject(0.0) { |result, i| result + i } / a.size
  end
  x.report('inject(0).to_f / size') do
    a.inject(0) { |result, i| result + i }.to_f / a.size
  end
  x.report('inject(0) / size.to_f') do
    a.inject(0) { |result, i| result + i } / a.size.to_f
  end
  x.report('inject(0).fdiv(size)') do
    a.inject(0) { |result, i| result + i }.fdiv(a.size)
  end
  x.compare!
end

結果は以下のとおりです。

Comparison:
       sum(0.0) / size: 668222.4 i/s
       sum / size.to_f: 660291.4 i/s - same-ish
       sum.to_f / size: 655929.1 i/s - same-ish
        sum.fdiv(size): 621960.0 i/s - same-ish
inject(0.0, :+) / size:  30823.6 i/s - 21.68x slower
 inject(0) / size.to_f:  18740.7 i/s - 35.66x slower
 inject(0).to_f / size:  18320.1 i/s - 36.47x slower
  inject(0).fdiv(size):  18082.6 i/s - 36.95x slower
    inject(0.0) / size:  15264.0 i/s - 43.78x slower

他の方法はあるか

RailsのActive Recordには#averageメソッドがあります。このメソッドは、データベース上の数値カラムを直接SQLで計算します。

求めているユースケースがそれであれば、ぜひこの#averageを使うべきです。Active Recordモデルをインスタンス化してからRubyの配列をイテレートすると、ほぼ確実に遅くなります。

関連記事

Ruby 2.4〜の`#clamp`で最大・最小値をまとめて制約(翻訳)

週刊Railsウォッチ(20200622前編)AR attributes周りの高速化進む、Active RecordでUNIONクエリを書く、Cable Ready gemほか

$
0
0

こんにちは、hachi8833です。GitHub Marketplaceの無料カテゴリを覗いてみたらいろいろありすぎてまごつきました。


つっつきボイス:「GitHub Marketplaceをちゃんと見たのは初めてでした」「GitHubリポジトリと連携できるアプリとかアクションとかですね👀」「IDEまであるのね」「エコシステムがあるのはありがたい🙏」「人気も表示して欲しいかなとちょっと思いました」「人気の指標次第では殴り合いが始まりそうですけど😆」「Chrome拡張みたいに😆」「誰かマーケットプレースをキュレーションしてくれるといいな」「やりたい人ならいくらでもいそうですけど」

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Rails: 先週の改修(Rails公式ニュースより)

以下のコミットリストのChangelogを中心に見繕いました。

⚓新機能: immutable_strings_by_default設定がActiveRecord::Baseに追加

全stringカラムをデフォルトでイミュータブルにできるそうです。Rails 4のときにマージされずにcloseした#29938↓に、MySQL向けの修正を加えたそうです。


つっつきボイス:「@kamipoさんによる修正です」「Active Recordのattribute:immutable_stringを指定できるようになったのか👀」「コンフィグにもimmutable_strings_by_defaultが入るのね😋」「この改修でちょっと速くなるみたい🚅

Warming up --------------------------------------
           user.name    34.811k i/100ms
      user.fast_name    39.505k i/100ms
Calculating -------------------------------------
           user.name    343.864k (± 3.6%) i/s -      1.741M in   5.068576s
      user.fast_name    384.033k (± 2.7%) i/s -      1.936M in   5.044425s

29938にMySQLのbooleanシリアライズの修正を加えたもの。
attributeのパフォーマンス改善を長年手掛けてきたが、(ミュータブルな)string型キャストはattributeで効率のよくない部分のひとつだ。
インプレースの改変を検出するため、string型は値の読み込みや代入をすべてdupすることで元の値を改変から保護するが、(ミュータブルな)string型の効率が低い理由がこれ。
イミュータブルなstring型は、読み込まれたり代入されたりする値をすべてfreezeすることでdupを回避でき、それによってfrozenな値はインプレース改変できないようになる。
string型attributeひとつと、そのイミュータブルなstring型のブランチによるシンプルなベンチマークは以下のとおり。
同PRより大意

# 同PRより
ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :name
    t.string :fast_name
  end
end

class User < ActiveRecord::Base
  attribute :fast_name, :immutable_string
end

user = User.new

Benchmark.ips do |x|
  x.report("user.name") do
    user.name = "foo"
    user.name_changed?
  end
  x.report("user.fast_name") do
    user.fast_name = "foo"
    user.fast_name_changed?
  end
end

「これが既存のコードに影響を与えることはまずなさそう: Active Recordで取ってきたstringのインスタンス自体を破壊的に変更することは普通ないだろうなと思うのでこれでいいでしょうし」「速い方がいいですよね😋

⚓Springをboot.rbに移動


つっつきボイス:「bundle exec spring binstub--allが要らなくなった」「#39622でspringがコケてた問題を修正したそうです」

1. `gem 'spring', group: :development`を`Gemfile`に追加
2. `bundle install`でspringをインストール
-3. `bundle exec spring binstub --all`でbinstubをspring化する
+3. `bundle exec spring binstub`でspring binstubを生成する

「DHHの元のプルリク↓も少し変えてるみたい」

「spring gemはいつの間にか動いててよくわからない😅」「マイグレーションがspringでつっかかったことはときどきあります😢」「割と謎だけど、Dockerコンテナで動かしている分には関係なくなることも多いのであまり気にならないかな」「springがあれば起動速くなりますし😋

⚓Rails 5より前の古いYAMLの読み込みを非推奨化

古いYAMLを使いたい人はLegacyYamlAdapterでやって欲しいそうです。


つっつきボイス:「古いyamlでdeprecation warningが出ると」「そろそろやっていい時期でしょうね」

# activerecord/lib/active_record/legacy_yaml_adapter.rb#L4
- module LegacyYamlAdapter
+ module LegacyYamlAdapter # :nodoc:
    def self.convert(klass, coder)
      return coder unless coder.is_a?(Psych::Coder)

      case coder["active_record_yaml_version"]
      when 1, 2 then coder
      else
+       ActiveSupport::Deprecation.warn(<<-MSG.squish)
+         YAML loading from legacy format older than Rails 5.0 is deprecated
+         and will be removed in Rails 6.2.
+       MSG
        if coder["attributes"].is_a?(ActiveModel::AttributeSet)
          Rails420.convert(klass, coder)
        else
          Rails41.convert(klass, coder)
        end
      end
    end

「その名もlegacy_yaml_adapter.rbなんてのがある↑」「Rails420とかRails41みたいに、書式が変わるたびにここに追加してたのね」「yamlのコンフィグファイルを使うテストで後方互換性をこうやって維持しているのか」「こういう実装は参考になる👍」「なるほど〜」「古い書式が必要ならLegacyYamlAdapterを使えばやれますし😋

⚓Arelで使われていないEquality#operatorを削除

# activerecord/lib/arel/nodes/equality.rb
module Arel # :nodoc: all
  module Nodes
    class Equality < Arel::Nodes::Binary
-     def operator; :== end
-
      def equality?; true; end

operatorメソッドは87b6856で追加され、Active Recordの12b3ecaでこのメソッドを参照している。
既にHomogeneousInが追加されているが、これにはInノードのようなoperatorはないので、operatorは以前のような互換性目的では動作しなくなっている。
これをやりたいときは代わりにequality?メソッドを使うべき。
同PRより大意


つっつきボイス:「operatorっていうメソッドがArelにあったんですね」「不要になったら消されるのが常」「homogeneousって何でしたっけ?」「『同じ』的な意味っぽいけど🤔」「辞書見ると『同種の』という堅苦しい意味でした」

⚓各種高速化


つっつきボイス:「#39612によるとModel.find(1)はいいけど、Model.find(1).attr_nameLazyAttributeHashがあっても遅かったのね」「findはlazyになってたけどattributeがlazyになってなかったので修正したとかそういう感じでしょうね☺」「なるほど!」「今ちょうどこういう感じのベンチマーク書いてるので参考になる↓🥰

「ところでプルリクに貼られてるこのgist↑、折り畳めるようになってるんですけどmarkdownでどう書くんだろう?」「GitHubのgistだからGitHubだと書くだけで折りたたみ表示になるとか?🤔」「それっぽいですね」

生のデータベース値を元にattributes hashをインスタンス化するのはattribute周りで遅い部分のひとつだった。
これはattributeの改変検出で必要だったためで、言い換えれば改変が発生するまでは不要ということになる。
0f29c21で導入されたLazyAttributeHashはattributeに最初にアクセスするまでインスタンス化をlazyにする(つまりModel.find(1)は遅くならなくなったがModel.find(1).attribute名が遅かった)
このPRはLazyAttributeSetによるattributeのインスタンス化をよりlazyにし、attributeに最初に代入またはdirtyチェックするときまでattributeをインスタンス化しないようになる(つまりModel.find(1).attribute名は遅くなくなる)。
これによってreaonly(改変なし)のattributeアクセスがおよそ35%改善される。
同PR冒頭より


「#39645はdefine_methodを置き換えて速くしたそうです」「実はdefine_methodでメソッドを動的生成する必要がなかった感😆」「呼び出すメソッドが決まってれば動的にやらなくてもよかったと」「なるほど」

# activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb#L4
  module Type
    module Helpers # :nodoc: all
      class AcceptsMultiparameterTime < Module
-       def initialize(defaults: {})
+         define_method(:serialize) do |value|
        module InstanceMethods
          def serialize(value)
            super(cast(value))
          end

-         define_method(:cast) do |value|
+         def cast(value)
            if value.is_a?(Hash)
              value_from_multiparameter_assignment(value)
            else
              super(value)
            end
          end

-         define_method(:assert_valid_value) do |value|
+         def assert_valid_value(value)
            if value.is_a?(Hash)
              value_from_multiparameter_assignment(value)
            else
              super(value)
            end
          end

-         define_method(:value_constructed_by_mass_assignment?) do |value|
+         def value_constructed_by_mass_assignment?(value)
            value.is_a?(Hash)
          end
...
Before:
Warming up --------------------------------------
type.serialize(time)    12.899k i/100ms
Calculating -------------------------------------
type.serialize(time)    131.293k (± 1.6%) i/s -    657.849k in   5.011870s

After:
Warming up --------------------------------------
type.serialize(time)    14.603k i/100ms
Calculating -------------------------------------
type.serialize(time)    145.941k (± 1.1%) i/s -    730.150k in   5.003639s

「Active Record、着々と速くなってますね💪」「Rubyのdefine_methodって便利なのでときどき使いたくなりますけどね😋

⚓セキュリティ修正: show_detailed_exceptionsをオンにしないとActionableErrorsを表示しないようにした


つっつきボイス:「こちらは今日(6/18)出した記事↓で扱ったセキュリティ修正です」「ああ、pendingしたマイグレーションに絡むヤツでしたっけ」「う、そうでしたか😅

セキュリティアップデート: Rails 6.0.3.2および一部のgem

「このリンク先↓ちょっとだけ眺めたんですけど」「お、リンク切れ直ってますね」「pendingしたマイグレーションを実行できてしまうらしいんですけど、なぜexceptionを見ることで実行できたのかが今のところ謎なんですよね🤔」「あら〜」「productionで本当に実行できるとしたらエグいといえばエグいけど、実行できるのはpending中の定義済みマイグレーションだけみたいなので、ただちに大事になったりはしないんじゃないかな」「問題につながるのは、pendingマイグレーションが残った形でデプロイしたときなんでしょうし🤔」「pendingマイグレーションを実行できちゃうのはたしかにイケてませんし」「後で読んでみます👀

信頼されていないユーザーがproduction環境でpendingマイグレーションを実行できる問題
これはRails 6.0.3.2より前のバージョンにおける脆弱性で、信頼されていないユーザーが、production環境で任意のpendingマイグレーションを実行できてしまう。
(中略)
影響
攻撃者はこのissueを用いて、productionモードで動くRailsアプリで任意のpending中マイグレーションを実行できるようになる可能性がある。ただし攻撃者が実行できるマイグレーションは、アプリケーションで定義済みであり、かつ未実行のものに限られる点が重要である。
groups.google.comより抜粋・大意

追記(2020/06/23)

y-yagiさんブログでこのissueがActionableErrorに関連していることを解説しているそうです。ありがとうございます!

⚓Rails

⚓Active RecordでUNIONクエリを書く(Hacklinesより)


つっつきボイス:「Active RecordでUNIONクエリですか」「生SQL使ったりいろいろ苦心してますね」「まあBase.connection.execute()でやれますけど」「ActiveRecordExtendedというgemも使ってみてる👀」「同じモデルをUNIONするのは比較的わかりやすいかな☺

# 同記事より
result = ActiveRecord::Base.connection.execute("SELECT users.* FROM users WHERE users.active = 't' UNION SELECT users.* FROM users WHERE users.manager = 't'")
result.to_a #=> Collection of users from the union query
# 同記事より
User.where.not(updated_at: nil).merge(
  User.union(
    User.where(name: "Jon")
  ).union(
    User.where(active: true)
  )
).order(start_date: :desc)

「結局unionヘルパーを自力で書いてる↓」

# 同記事より
module ActiveRecordUnion
  extend ActiveSupport::Concern

  class_methods do
    def union(*relations)
      mapped_sql = relations
        .map(&:to_sql)
        .join(") UNION (")

      unionized_sql = "((#{mapped_sql})) #{table_name}"

      from(unionized_sql)
    end
  end
end

ActiveRecord::Base.send(:include, ActiveRecordUnion)

.join(") UNION (")という無理やりな書き方↓がちょっと微笑ましい」「あるある😆

# 同記事より
mapped_sql = relations
  .map(&:to_sql)
  .join(") UNION (")

参考: PostgreSQL12 7.4. 問い合わせの結合

「普段からActive Recordを使っていると、SQLのINTERSECTとかUNIONってそれほど使う機会なさそうな気はしますけどね」「pluckでフェッチして差分取って検索し直すぐらいだったらSQLのINTERSECTで一発でキメたいときはあるといえばありますし」「まあこんなに苦労してやるぐらいだったら、記事冒頭みたいにBase.connection.execute()使うか(データベース)ビューを実行したくなりますね☺」「わかります」

「ただBase.connection.execute()は返ってくる値が使いにくいので、できればビューを使いたいかな: でもビューだと今度は動的なことがやれなくなるのが痛し痒し…」「そうなんですよね😅

RDBMSのVIEWを使ってRailsのデータアクセスをいい感じにする【銀座Rails#10】

⚓parallel_tests: RSpecやTest::Unitをパラレル化(Ruby Weeklyより)

# 同記事より
rake parallel:test          # Test::Unit
rake parallel:spec          # RSpec
rake parallel:features      # Cucumber
rake parallel:features-spinach       # Spinach

rake parallel:test[1] --> force 1 CPU --> 86 seconds
rake parallel:test    --> got 2 CPUs? --> 47 seconds
rake parallel:test    --> got 4 CPUs? --> 26 seconds
...

つっつきボイス:「parallel_tests、見たことある気がする👁」「ウォッチでまだ扱ったことがなかったので入れてみました」「使ったことないけど歴史長そう🏯」「RSpec用に似たようなgemって他にもあったと思いますし」

後で調べるとinitial commitは2009年でした。

「今は更新されなくなって無料になったRailscasts↓へのリンクがREADMEに貼ってあるぐらいなので相当昔からあるのはたしか」「ほんとだ」「これも今は使われなくなったzeus gemとかがあった時代ですし」「なつかしい」「間違いなく老舗ですね☺

「このparallel_tests gemとか、クックパッドさんが出してるrrrspecというRSpec並列化gem↓あたりが老舗かな」「なるほど」

⚓RaiilsのActiveModel::Errorで各バリデーションエラーをカプセル化


つっつきボイス:「この記事ではRails自体の機能でやってるんですが、これも知らなかったので😅」「どれどれ、errorsの構造がこういう配列になったのね↓」

user.errors.where(:contact_number)
=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>"abcdefghijk}>]

「どっかで見たな〜これ」「記事によるとこの#32313は昨年入ってたそうです↓」「ですよね、最近こうなった覚えがありますし😋」「ActiveModel::ErrorsオブジェクトからActiveModel::Errorオブジェクトの配列に変わった」

# 同記事より
user.errors.add(:contact_number, :too_short, count: 10)
=> <#ActiveModel::Error attribute=contact_number, type=too_short, options={:count=>10}>

user.errors.where(:contact_number)
=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>nil}>, <#ActiveModel::Error 
attribute=contact_number, type=too_short, options={:count=>10}>]

user.errors.added?(:contact_number, :too_short, count: 10)
=> true

user.errors.added?(:contact_number, :too_short)
=> false

user.errors.delete(:contact_number, :too_short, count: 10)

user.errors.where(:contact_number)
=> [<#ActiveModel::Error attribute=contact_number, type=not_a_number, options={:value=>nil}>]

user.errors.match?(:contact_number, :not_a_number)
=> true

user.errors.match?(:contact_number, :too_long)
=> false

「データモデルとしてはこの方が本来望ましい形ですね👍」「おぉ😍

⚓delete_allの挙動にびっくりした話(Hacklinesより)


つっつきボイス:「delete_allしてみたらUPDATEクエリが走ってびっくりしたそうです↓」「ああ、has_manyのときは参照のidがNULLでUPDATEされるみたいなヤツですね」「記事のAPIドキュメントにもそう書かれてますね😳


同記事より

:dependentオプションで指定した戦略に応じてコレクションのレコードをすべて削除する。:dependentが指定されていない場合はデフォルトの戦略に従う。
has_many関連付けの場合、デフォルトの削除戦略は:nullifyになる。これは外部キーをNULLに設定する。

同記事で引用したdelete_allのAPIドキュメントより

「そういえばdelete_alldestroy_allだと、前者はコールバックしないんだったっけ?」「そうそう、destroy_allはコールバックを実行するんだった↓」

destroy_allはコールバックがある分、削除するレコード数が増えると当然めちゃ遅くなる可能性がある」「コールバック動かすにはeachすることになりますし」「ですよね」「delete_allは1つのSQLクエリでやるからコールバックはできない分、当然速い🚅」「delete_alldestroy_allの2つのメソッドがあるのには理由があるということで☺

メモ: レコードごとのインスタンス化、コールバック実行、削除は、多数のレコードを一気に削除しようとすると時間がかかることがある。1レコードにつき少なくともSQLのDELETEクエリがひとつ発行される(コールバックでさらに発行される可能性もある)。多数の行を短時間で削除したい場合、関連付けやコールバック関連の懸念がないのであればdelete_allを使う。

同記事で引用したdestroy_allのAPIドキュメントより

⚓Cable Readyでブラウザをリアルタイム更新(Ruby Weeklyより)


つっつきボイス:「Screencastsによる手順解説ですけど、最近Cable Readyを割と見かけるなと思って」「Action Cableといい感じに連携するヤツでしたっけ」


同リポジトリより

cable_readyっていうメソッド名なのね」「こういう書き方するのか〜」「include CableReady::Broadcasterしてるし」「チラ見した限りでは、Action CableがJSで出してきたブロードキャストを受けていい感じにリアルタイム更新してくれそう😋

# サンプルコードより
class Card < ApplicationRecord
  include CableReady::Broadcaster

  after_update do
    cable_ready["cards"].morph(
      selector: "#" + ActionView::RecordIdentifier.dom_id(self),
      html: ApplicationController.render(self)
    )
    cable_ready.broadcast
  end
end

「JSのmorph処理をモデルに書くとか、フロントエンジニアに睨まれそうな書き方😆」「morph?」「具体的には知りませんけど、名前からしていわゆるモーフィング的な動きをやれるヤツでしょうね」「あ、モーフィングですか」「引数がselectorhtmlですし、おそらく当該セレクタの内容をこの内容でモーフィング的に書き換えるんだろうな〜と推測しました」「なるほど!」

「まあApplicationRecordを継承したモデルにこういう処理を書くのはちょっと考えちゃいますけど😅


前編は以上です。

おたより発掘

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200616後編)本番環境をFullstaq Rubyに換えた理由、CSRF発生フローチャート、DBのトランザクション分離レベル比較ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

週刊Railsウォッチ(20200623後編)Bootstrap 5 alphaリリース、Lambda FunctionsとEFS、DB設計で気をつけていることほか

$
0
0

こんにちは、hachi8833です。つい先ほどこのニュース↓を見て一人でどよめいてます。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Ruby

⚓mrubyとC言語とRust


つっつきボイス:「『C言語はなんか好き』いいですね〜」「『Rustはヤバい』も」「Cって長いこと書いてないな〜」「Cを最後に真面目に読んだのはLinuxカーネル周りでしたし」「書いたのもLinuxのドライバぐらいでしたし」

参考: C言語 - Wikipedia
参考: The Linux Kernel: Linux カーネルソース

「Rustならちょっとやってみたいかも」「私も!」「今からCを一生懸命やるよりRustの方が楽しそうですし😍」「BPS社内のRust勢に教わりたい」「OSみたいなものを書くにはRustがいいでしょうね〜」「記事の『マルチスレッドやメモリ管理の問題が、Rustの世界では言語仕様の段階で解決されている』ってまさに夢の世界」「俺たちの欲しかったもの」「Go言語もそこまでやってませんし」

参考: Rust (プログラミング言語) - Wikipedia

Rustのドキュメント、どれを見るべきなのかという話

⚓Rubyで素数アルゴリズムを書く(Hacklinesより)

# 同記事より: 改良版素数アルゴリズム
def is_prime_number(item)
  return false if item == 1
  (2..(item - 1)).each do |number|
    if item % number == 0
      return false
    end
  end
  return true
end

つっつきボイス:「美しくない素数アルゴリズムと改良した素数アルゴリズムの二本立てです」「こういう基本的なアルゴリズムをステップバイステップで丁寧に説明してくれるのはいいですね👍


「ところで、こういう初心者向けの丁寧な教材って、不思議に日本語圏にあまりありませんよね」「たしかに!」「英語圏だと結構よく見かけるんですけど」「コードを書くときの意図をみっちり解説してくれる教材って何気に貴重✨

「強い人が初心者目線で書く時間ってなかなか取れなさそうですけど」「こういう丁寧な教材を書くのは、必ずしも強い人でなくてもいいんですよ」「あ、そうか!」「コードを書くのが得意な人と、教えるのが上手い人というのは別なこともよくありますので」「もちろん強いものを作るには強い人がいないと解説すらできませんが、世の中には初心者にレクチャーするのが得意な人も確実にいますし」「そういう教材は常に求められてるんですね…」

「ところでこのDZone.comっていうサイト、前も見た気がする」「トップページ開くといろんな言語がどっさり表示されてますね」「英語アレルギーがなければ、初心者の人はこういうサイトでみっちり学ぶのもよいと思います👍

調べてみるとDZoneは過去のウォッチにもときどき登場してますね。

⚓deep_dupdupの違い(Hacklinesより)


つっつきボイス:「非常に短い記事です」「cloneの話が載ってませんけど😆」「タイトルにUpdatedとあるので別記事の続きかな?」「どうやらこっち↓が本編ですね」「そのようでした😅

「そうそう、Rubyのcloneはshallow copy(浅いコピー)、dupもshallow copyだけどfrozenとかは無視する」「そしてRailsのdeep_dupはArrayとHashについてはdeep copy(深いコピー)するけど、それ以外はdupを呼ぶと」「frozenされているとselfを返すと」

「『deep_dupは銀の弾丸ではない』とありますね」「deep copyは中に何が入っているかわからないので難しいでしょう」「データ構造に応じて処理を変えないといけないので、一般的なdeep copyは作りようがないと思います😆」「よしなにやってくれるdeep_dupが仮にあったとしても、処理の末端でdeep_dupを呼ぶはずがありませんよね😆」「初めて使う人は、この記事を読んで『一般的なdeep copyは無理なんだな』と理解してから使いましょうということで」

⚓oxidized: Ruby製ネットワークデバイス設定バックアップツール(GitHub Trendingより)


つっつきボイス:「ネットワーク機器の、コンフィグバックアップツール?」「RANCIDのオルタナってあるけど、RANCIDって何だろうか?」「初めて見ました😳」「rancidでググると関係ないものがいっぱい出てくる😅」「これか↓、CISCOのルーターみたいなネットワーク機器のコンフィグ管理らしい」

「でoxidizedは何をするツールなのかな?」「★はいっぱい付いてますね」「RANCIDの説明を見た感じでは、ルーターとかにログインしてコマンド実行してコンフィグを取ってきて差分を表示したりするようだ」「こんなのあるんですね」「oxidizedはRESTful APIも提供しているところが新しそう」

# 同リポジトリより
source:
  default: csv
  csv:
    file: ~/.config/oxidized/router.db
    delimiter: !ruby/regexp /:/
    map:
      name: 0
      model: 1

「もしかするとCISCO以外のLinuxサーバーのコンフィグも扱えるのかな?」「サポートOSのリストを見た感じだとLinux系はないみたいなので、このツールはLinuxとかでデーモン的にも動かせるけど対象はあくまでネットワーク機器ということかなと🤔」「なるほど、oxidizedをRESTful APIで制御できるあたりとか、どうもそんな感じみたいですね」「欲しい人は欲しそうなツールということで☺


もしかするとrancidの意味をもじってoxidized(酸化した)と付けたのかもしれませんね。

rancid: {形} : (腐ったような)悪臭のする、嫌な匂いのする、鼻を突く

⚓その他Ruby


つっつきボイス:「こちらのSpringin’はサイトのページをスクロールするとMatzが推薦文を書いてます」「iOSアプリなのね」「名前にアポストロフィがあるあたりが日本っぽくなさそう」「ビジュアルプログラミングだけど、コードを書くというより、絵を描いて動かしたり音を出したりするのが中心みたい」「こういうのは楽しいですね😋」「楽しい😋


「ところでSpringin’見てて思い出したものがあるんだけど、あれ何て名前だったかな…昔すぎて思い出せない😅」「ビジュアルプログラミングですか?」「タブレットなんですけどビジュアルプログラミングもできるというヤツ」「うーん何でしょう?」「…EnchantMOONだ!」「あぁ!」

「EnchantMOON、すごく興味あったんですけど知ったときには販売終了してたのを思い出しました😢 」「当時としてはスペックが足りなかったんでしょうね」「iPadはスペックが統一されているところありがたいですね、高いけど💰」「廉価版でも高いです😭」「Chromebookの方が安いくらいですし」「Chromebookぐらいの値段でiPadが出てくれるといいんですけど」(以下延々)


enchantMOONのshi3zさんが今も更新しているブログがありました↓。

参考: enchantMOONの遺産|shi3z|note

⚓DB

⚓データベース設計の際に気をつけていること


つっつきボイス:「はてブで上がっていた記事です」「自分もざっと読みましたけど、現場の目線で書かれた感じで、取りあえずおかしな部分は見当たりませんでしたね👍」「身体で学んだんでしょうね」

「『activeというカラムではなく別途payment_historyなどのテーブルに移す』あたりはベストプラクティスのひとつ」

「『三角関係のリレーションを持つテーブルは(できるだけ)作らない』もそうで、テーブルとテーブルの間に中間テーブルがある状態では、中間テーブルを飛ばしてテーブル同士をリレーションするのは、できるだけやりたくないですね」

「『一時的なレコードと永続化が必要なレコードを同じテーブルに入れない』も同意: 記事にも書いてますけどSpreeはカートに入れた注文と実際の受注データが同じテーブルに入るんですよ」「ありゃ😅

⚓「適切な」が難しい

「単にベストプラクティスをかき集めたというより、自分で罠を踏んで自力で乗り越えてきたノウハウ集に近いと思います👍」「おぉ」「その分、データベースの初心者が初めて読んでもすぐにはピンとこないかもしれませんけど」「それはありますね」

「たとえば『適切なデータ型を使う』とか『インデックスを適切に貼る』と言ったとき、その『適切』の部分こそ難しいし経験が必要で、だからこそその部分が本当のノウハウだよなという気持ちはありますね」「経験者の価値はそういうところにあるんだろうなと思います☺

「『正規化が必要なところを見極める』も、その『見極める』が難しいわけですし😆」「ほんとにそう😆

⚓MySQLのJOIN

「あと記事ではMySQLのJOINの遅さについて書かれているけど、最近はそこまで遅いことはだいぶなくなったと思います🧐」「ふむふむ」「MySQLの場合テンポラリテーブルができると重くなりがちで、JOINしているデータがバッファプールからあふれるとファイルに書き出す機能がMySQLに標準であるんですけど、それが始まると遅くなりますね」「なるほど」

参考: MySQL - 一時テーブルの作成 | mysql Tutorial

「MySQLでそれが起きていなければ、最近はよほど変なクエリを投げない限りそこまで遅くはならないと思うんですけど」「@kamipoさんのツイートも同じ意見ですね↓」「そうそう、たしかに集合関数を変に混ぜると遅くなることがあります」

「今の時代にMySQLのJOINが遅くなるとしたらテンポラリテーブルのファイル書き出しぐらいなんじゃないかな〜🤔」「ふむふむ」「クエリがまともに設計されていればそうそう遅くならないはずですし、仮に遅くなってもインデックス周りを調整すれば基本的にはイケると思うんですけど」「まずはDB設計をちゃんとやりましょうということですね」

「RailsだとActive Recordの使い方がよくなくて遅くなる方が多いと思いますし、そういうときの方が極端に遅くなりやすい気はします」「ですね」


「@kamipoさんの一連のツイートで一番イイのはやっぱりこの『Rails新しいのが出たら絶対バージョンアップしてくれよな』でしょう↓」「まさに😋

⚓クラウド/コンテナ/インフラ/Serverless

⚓AWS Lambda FunctionsでEFSが使えるようになった


つっつきボイス:「BPSの社内Slackに貼っていただいた情報です」「Lambda FunctionsがEFSをマウントできるようになったのはアツい👍」「関連記事みたいに早くも喜びの声があちこちで上がってるみたいですね」

「EFSをマウントできるようになったことで、何かするのにS3からいちいち持ってこなくてよくなるのが嬉しいですね: S3だと遅いし、ファイル全部が必要じゃない場合でもS3だと端から取ってくるしかないので」「そうでしたか!」「S3はパーシャルで一部だけを持ってくることも一応できますけど、それでもファイルシステムAPIで取れるEFSの方が圧倒的に使いやすいですし」「なるほど〜」

⚓AWS EFS

「Lambda Functionsの場合、たとえばログを単純にEFSに出せるようになるのが便利ですね😋: まあEFSにそういうログの出し方をするのは本当はあんまりよくないんですけど、CloudWatchに出すと取り出すのが面倒なものなんかはこうやってサクッとEFSに出せるのはありがたい」「おぉ」

「ただEFSはIOPS(Input Output Per Second)があふれると死ぬことがあるので、そこは気をつけないといけませんけど」「なるほど」

参考: Amazon EFS のパフォーマンス - Amazon Elastic File System

「あとEFSにはバーストクレジットというものがあって、IOの量が制限値を超えると途端に遅くなるんですよ」「ありゃ😅」「これが起きるとホントつらいので、バーストクレジットはちゃんと監視しておかないとヤバい」

参考: Amazon EFS バーストクレジットを理解する

「Lambda FunctionsからEFSを使う場合、バーストさせたいとかスケールさせたいことが割とあると思うんですけど、でかいファイルとか細かいファイルが大量にあるところにアクセスする要件ではたぶん使わない方がいいんじゃないかなと思います」「ふ〜む」「それよりは、さっきのように単純にログに吐き出すみたいな使い方の方がおそらくキレイにやれるだろうと思いますね☺」「なるほど!」

⚓その他インフラ


つっつきボイス:「これこれ、Chrome OSでWindowsアプリが動くようになるらしい🎉」「みんなが欲しかったのってこれなのでは?という気持ちになりました」「教育現場でCeleron並の半端なPCを使わせるぐらいなら、もうChrome OSでいいのでは?って思いますし」

「ちなみにうちの中2男子も今学校でChromebookで一日置きにリモート授業やってます」「ほほぅ」「最高なんですけど、リモート授業やってるところを子どもが見せてくれなくて😅」「まあそのお年頃ではしょうがないでしょう🤣

「まあChromebookで動くWindowsアプリは相当遅いかもしれませんけど」「Windowsアプリが動くのが便利なのはわかるんですけど、何に使うんでしょうね?ブラウザでできちゃう時代なのに🤔」「職業訓練的にExcelを体験させたいときとか?」(以下延々)

⚓JavaScript

⚓Bootstrap 5 alphaがリリース: jQuery依存がなくなる

CSS変数(カスタムプロパティ)も使い始めているそうです。


つっつきボイス:「ついにBootstrap 5のalphaが出た」「ちなみに今授業ではBootstrap 4を教えてますけど、昔に比べるとBootstrapを積極的に使う理由は減った感じはありますね」「この前もお話ししてましたね」「今だとデザイナーなしで管理画面を作るときとか、そのぐらいかな〜」「今はFlexboxが使えるようになってきましたし、Bootstrapみたいにユーティリティまで包含するようなフレームワークだと余計なものが入ってきますし」

参考: A Complete Guide to Flexbox | CSS-Tricks

「Bootstrapを使うなら、完全にBootstrapに身を任せる方がいいでしょうね: 今のBootstrapにはたとえばほんのちょっとだけmarginやpaddingするみたいな機能↓があったりするんですけど、そういうのも含めてBootstrapを使うかどうか決心しないと」

参考: Spacing · Bootstrap v4.5

// getbootstrap.comより
.mt-0 {
  margin-top: 0 !important;
}

.ml-1 {
  margin-left: ($spacer * .25) !important;
}

.px-2 {
  padding-left: ($spacer * .5) !important;
  padding-right: ($spacer * .5) !important;
}

.p-3 {
  padding: $spacer !important;
}

「こんな細かい機能があったとは😳」「mt-0(margin top 0)とかml-1(margin left 1)みたいなのがいろいろあるんですよ」「!importantまで付いてる😆」「Bootstrapは元々CSSを一行も書かずに画面を作りたい人のためのものですし、それに合う要件なら使う方がいいと思います」


追いかけボイス「IEサポートなしだと使える場面が限られるけどjquery依存消えるのはありがたいし、custom propertiesでコンパイル無しで切り替え増やせるのも良い感じ👍」「脱jQueryは素直にありがたいですよね」

⚓CSS/HTML/フロントエンド/テスト/デザイン

⚓CSSの:is():where()

:is()は元々:matches()という名前だったんですね。


つっつきボイス:「なるほど、:is()を使うとこういうSCSSっぽい書き方↓ができるのね」

// 同記事より
/* BEFORE */
.embed .save-button:hover,
.attachment .save-button:hover {
  opacity: 1;
}

/* AFTER */
:is(.embed, .attachment) .save-button:hover {
  opacity: 1;
}

参考: とほほのSass入門 - とほほのWWW入門

:where()もこういうことができると↓」

// 同記事より
/* sanitize.css */
svg:where(:not([fill])) {
  fill: currentColor;
}

/* author stylesheet */
.share-icon {
  fill: blue; /* 詳細度がより高いため、上書きされる */
}

「CSSってこういう方向に進んでるのか〜、ブラウザ自体がSASSとかSCSSをサポートする方向には行かないのかな?🤔」「必要以上に高機能にしたくないのかもしれませんね」「まあSASSやSCSSの外部ファイルインクルード機能まであるとバンドルしにくいのかもしれませんし」「ブラウザも重くなりそうですけど、SASSやSCSSをブラウザがサポートする未来があってもいいかなとちょっとだけ思いました☺

⚓言語/ツール/OS/CPU

⚓wrk: マルチコアCPUで高負荷を出せるHTTPベンチマークツール


つっつきボイス:「Rubyで書かれてるかと思ったらC言語でした😅」「Apache Benchに近そうな雰囲気👀」「こういうツール昔からありそうですね」「いっぱいありますよ〜」

参考: ab - Apache HTTP server benchmarking tool - Apache HTTP Server Version 2.4

「wrkはマルチスレッドになってて、epollやkqueueみたいなLinuxの新しめのシステムコールで並列性とか遅延実行とかを効果的に回せるというのが特徴のようですね」「おぉ」「要するに高速でぶん回せる💪

「ベンチマークソフトは測定対象の負荷をあふれさせることが重要なので、ベンチマークソフトの能力が足りなくて十分な負荷をかけられないのが一番よくないんですよ」「なるほど」

「ちなみに以下のLinuxコマンドツールトップテン記事↓の中で知らなかったのがこのwrkでした」

参考: Top Command Line Tools for Development and Fun – Pavel Timofeev – On software development, startups, and other thingsHacklinesより)

⚓書籍『プロフェッショナルIPv6』ダウンロード可能


つっつきボイス:「IPv6か〜、今の時期だとIPv4の方が圧倒的にお金になるというのもありますし、IPv6の知識がアプリケーションエンジニアにとってどのぐらいメリットがあるかは悩ましいところなんですけど、IPv6の設計周りはとても参考になりますね👍」「お〜」「私v6はさっぱりです😅

「IPv6使ってても、閉じたネットワークの中ではIPv4が使われることも多いですよね」「本来IPv6はありとあらゆるデバイスに割り当てたいという夢から出発しているのに、プロバイダをIPv6で契約してもIPv6のグローバルアドレスがアサインされないことが多いのもちょっと悲しい😢」「そうですよね…」

「エイリアスIPアドレスなんかはIPv6で当たり前に使えるので、それによってIPv4でも使えるようになってきたところもあるのかなと思います」「おぉ?」「IPv6は、何もしなくてもアドレスを3つ持ったりするんですよ: リンクローカルアドレスと、ユニークローカルアドレスと、グローバルアドレス」「へ〜」

参考: IPv6アドレス - ユニキャストアドレス(グローバル、リンクローカル、サイトローカル)

「昔のIPv4は1つのネットワークインターフェイスに1つのアドレスしか持たなかったんですけど、IPv6でリンクローカルやグローバルやらのアドレスが付くようになって、その影響でIPv4でも普通にエイリアスIPアドレスが使えるようになってきたような印象がありますね、実際はどちらが先なのかはわかりませんけど」


「家の中をIPv6にしてみようかな〜😋」「私の家はルーターはIPv6の有効フラグは立ててますけど、そこ止まりです😆」「今だと、IPv6に対応したプロバイダーと契約してIP over Ethernet(IPoE)でつなぐのが現時点で最速のインターネット接続ですよね」「お〜そうでしたか!」「そういえば最近その話をよく目にしますね」「理論上はですけど😆」「逆にIPv6をオンにし忘れて遅くなった話も聞きますね」

参考: 【初心者でも分かる】PPPoE方式とIPoE方式の違いとメリット|ICT Digital Column|【公式】NTTPC

⚓その他

⚓全盛期のJeff Deanの都市伝説


つっつきボイス:「何のことかなと思ったら、USB 2.0云々はJeff Deanの都市伝説↓のコピペですね」「あ、そうでしたか😅」「1個だけ取り出すとよくわからないけど、まとめて読むと楽しいですヨ😆

参考: 全盛期のJeff Dean伝説 - Qiita

「まあどこの業界にもあるジョーク集です😆」「チャックノリス伝説みたいなものだったとは…失礼しました😅」(しばしJeff Dean伝説で盛り上がる)

参考: チャック・ノリス・ファクト - Wikipedia

⚓番外

⚓HASHWallet


つっつきボイス:「スマホとこのカードを2つ並べて使うなら、全部スマホに入ってる方が楽なのでは?😆」「全部スマホだと紛失したときに立ち直れなさそう😅」「両方ないとお金を使えないようにするというのならワカル」「スマホに入れるのってアプリの立ち上げが意外に面倒な気がしません?それならカードだけでやる方がいいかなと」「それもありますよね」

「そういえば最近VISAの非接触式カードが使えるところが増えてきてかなり楽になりました😋」「タッチするだけでクレカ決済できるヤツですね」「ほほ〜そんなものまで」「カード会社はこれまで頑なに磁気カードでしたけど、新しいKyashなんかも非接触になってますし、もう楽々❤

参考: Visaのタッチ決済(非接触決済) | Visa
参考: Kyash Card|Kyash(キャッシュ)


後編は以上です。

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200622前編)AR attributes周りの高速化進む、Active RecordでUNIONクエリを書く、Cable Ready gemほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Hacklines

Hacklines

GitHub Trending

160928_1701_Q9dJIU

Ruby: インスタンス変数初期化のメモ化`||=`はほとんどの場合不要

$
0
0

小ネタで恐縮です。RuboCopスタイルガイドからRubyのメモ化||=に関する部分を抜粋します。

⚓コンストラクタでは不要な||=を避け、単なる代入にするのが望ましい

「Rubyのインスタンス変数(@で始まる変数)は代入されるまではnilなので、||=はほとんどの場合不要」という理由です。

原文のdisjunctiveは論理学の用語で、ORの堅苦しい表現です。

# bad
def initialize
  @x ||= 1
end

# good
def initialize
  @x = 1
end

⚓条件変数初期化のショートハンド

こちらはインスタンス変数ではない条件変数に対する||=で、メモ化が望ましいとされていますが、注意書きがあります。

# bad
name = name ? name : 'Bozhidar'

# bad
name = 'Bozhidar' unless name

# good - nameがnilかfalseの場合にのみ'Bozhidar'を設定
name ||= 'Bozhidar'

警告: boolean変数の初期化には||=を使わないこと

現在の値がたまたまfalseだった場合を考えればわかるとのことです。

# bad - enabledがfalseだった場合にもtrueに設定されてしまう
enabled ||= true

# good
enabled = true if enabled.nil?

はみ出しつっつき

いつもお世話になっているBPS Webチームのkazzさんとこの話をしました。

「うん、初期化のインスタンス変数に||=は確かに不要、コピペで持ってきたコードにありがちです」

「自分はメモ化割と好きなので||=をよく使ってたこともあるんですが、メモ化は基本的に最適化なので最初からやらなくてもいいものだし、メモ化があちこちに散らばるとわかりにくくなることもあるんですよ」「メモ化は高速化が必要になってから検討する方がいいんですね」「findメソッドなんかはActiveRecordの側で既にキャッシュされるから、それをさらにメモ化する意味はありませんし」「@kamipoさんが最近のRails最適化でメモ化をやってたりしますけど、そのぐらいに考える方がメモ化をやりすぎずに済みそうですね」

「あと気をつけないといけないのは、nilが入ってくる可能性のあるオブジェクトには||=は効かないということ: nilが入っていると結局毎回読み込み実行されちゃうので最適化されない」「あ〜そうか!」「以前それを回避するためにmemoizeみたいなメソッドをこしらえたことならあります」

「ちなみに初期化ではないところのインスタンス変数で||=を使ったら、セッターっぽく見えたらしくて『これってセッターですか?』って聞かれたことあったんですけど『いえゲッターです』と返しました😆

週刊Railsウォッチ(20200629前編)RSpecをメンテしやすくする9つのコツ、application.jsのrequireをimportに置き換え、HTTP 308 Permanent Redirectとはほか

$
0
0

こんにちは、hachi8833です。ニュースウォッチ9で富嶽のニュース見ました。


つっつきボイス:「富嶽、特別価格で買えるらしいっすよ↓」「マジで?」「おひとつ包んでくださいな、みたいに?」

参考: 【やじうまPC Watch】世界一の「富岳」と同じA64FX環境をお手元に! 4,155,300円で - PC Watch

「ご自宅に2ノード単位で置けるそうです」「いちじゅうひゃくせん…400万とか書いてますけど😆」「いやいや、スパコンのモジュールが税別とはいえ400万って、むしろお安いでしょう!」「自動車一台分で買えるということか」「400万なら手の届かない値段じゃないでしょうし」

「構成はそんなにリッチじゃないでしょうけど」「2Uラックマウントで48コアCPUか」「でもこれも最近流行りのARMですし💪」「富嶽ってARMなんですか!」「今週はWWDCもあったし、ARMの話題がやたら多い」(以下延々)

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

Rails: 先週の改修(Rails公式ニュースより)

以下のコミットリストより見繕いました。ドキュメントの小さな更新が増えているようです。

follow_redirect!テストヘルパーを修正

follow_redirect!が308リダイレクトのときに同じHTTP verbでリダイレクトをフォローするよう修正。
同PRより


つっつきボイス:「HTTP 308ってありましたっけ?」「そんな上の方の番号使ったことない😆」「Mozillaのドキュメントにはparmanent redirectとありますね↓」

The HyperText Transfer Protocol (HTTP) 308 Permanent Redirect リダイレクトステータスコードは、リクエストされたリソースが Location ヘッダーで示された URL へ完全に移動したことを示します。ブラウザーはこのページにリダイレクトし、検索エンジンはリソースへのリンクを更新します (「SEO 用語」では、「リンクジュース」が新しい URL に送られたと言われます)。
308 Permanent Redirect: developer.mozilla.orgより

「お〜、308は2014年頃できたみたいですね↓」「308にすると検索エンジンがリソースへのリンクを更新しちゃうのか〜」「301のMoved ParmanentlyじゃなくてRedirectということか」「どういう文脈で使うんでしょうね?」「何だろう?検索エンジン向けとかならわからなくもないですけど🤔」

参考: webdbg.com/test/308/ — ブラウザが308をサポートしているかどうかをテストできるサイト
参考: 新たなHTTPステータスコード「308」とは? - GIGAZINE

「でも301 Moved Parmanentlyもリソースへのリンクを更新する点では同じなんですよね…」「どう違うんでしょう?」「お、308を使うとPOSTメソッドをリダイレクトできるのか!↓」「お〜なるほど理解できた!」

参考: 301 Moved Permanently - HTTP | MDN

リダイレクトが行われるとき、仕様書ではメソッド (と本文) を変更しないよう要求していますが、すべてのユーザーエージェントが従っている訳ではありません。 – まだこの種のバグが発生するソフトウェアが見つかるでしょう。従って、 301 のコードは GET または HEAD メソッドのみに使用し、このステータスでは明確にメソッドの変更が禁止されているので、 POST メソッドでは代わりに 308 Permanent Redirect を使用することが推奨されています
301 Moved Parmanently: developer.mozilla.orgより(強調は編集部)

「そしてこれか↓、301はたしかにGETに変更される可能性がある: POSTに対して301を返すとGETでリクエストし直されちゃうのでPOSTされたものを引き継げないんですけど、なるほど308なら引き継げるのね、へぇ〜」「POSTしても308なら引き継いでもらえるんですか?」「ブラウザがそういうふうに動いてくれるというか、そう動かなければならないということでしょうね」「ははぁ」

301 の場合は不正に GET メソッドに変更される可能性があるのに対し、このコードの場合はリクエストメソッドと本文が変更されません。
308 Permanent Redirect: developer.mozilla.orgより

308がなかった頃はPOSTされる可能性のあるURLを変更するとどうしようもなかったんですけど、308が使えるとPOSTされるURLもリダイレクトできるようになるのね」「これは知らなかった〜」「でも、使います?😆」「😆」「まあAPIなんかで使う可能性はあるでしょうけど」「それ思いました!APIだと欲しいヤツです」

「昔301GETで再リクエストされる問題で悩んだことがあったんですけど、当時はもうどうしようもないという結論に達して、ブラウザ側でリロードして元のページに戻すみたいな処理にしたことがありましたよ」「HTTP、深いですね…」


「でfollow_redirect!は、今まで同じHTTP verbを使ってなかったのを修正したと↓」「そうしないといけない仕様ですもんね」「307や308の場合は今のリクエストメソッドをそのまま踏襲しないといけなくて、301の場合はGETで取り直している」「なるほど〜」

# actionpack/lib/action_dispatch/testing/integration.rb#L61
      def follow_redirect!(**args)
        raise "not a redirect! #{status} #{status_message}" unless redirect?

-       method = response.status == 307 ? request.method.downcase : :get
-       public_send(method, response.location, **args)
+       method =
+         if [307, 308].include?(response.status)
+           request.method.downcase
+         else
+           :get
+         end

+       public_send(method, response.location, **args)
        status
      end

follow_redirect!はよくみるとテストヘルパーみたいなのでテストだけ修正したということかな?」「あ、ほんとだ」「Action Dispatchのtesting/にあるから、実際にはリクエストを投げずにルーティングだけ回すみたいな、よくあるコントローラのテストヘルパーなんでしょうね」「ということは、システムテストは今までもちゃんと動いていたけどfollow_redirect!ヘルパーはちゃんと動いてなかったという流れなんでしょうね」「こんな感じでテストに書けると↓」「なるほど!」

# actionpack/test/controller/integration_test.rb#L375
+ def test_308_redirect_uses_the_same_http_verb
+   with_test_route_set do
+     post "/redirect_308"
+     assert_equal 308, status
+     follow_redirect!
+     assert_equal "POST", request.method
+   end
+ end

application.jsテンプレートのrequireをESモジュールのimportに置き換えた


つっつきボイス:「これはいいね👍が割と付いてますね」「requireimportに書き換わってる」「たしかにJSらしい書き方になった🎉」

# railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt#L6
-require("@rails/ujs").start()
+import Rails from "@rails/ujs"
<%- unless options[:skip_turbolinks] -%>
-require("turbolinks").start()
+import Turbolinks from "turbolinks"
<%- end -%>
<%- unless skip_active_storage? -%>
-require("@rails/activestorage").start()
+import * as ActiveStorage from "@rails/activestorage"
<%- end -%>
<%- unless options[:skip_action_cable] -%>
-require("channels")
+import "channels"
<%- end -%>

+Rails.start()
+<%- unless options[:skip_turbolinks] -%>
+Turbolinks.start()
+<%- end -%>
+<%- unless skip_active_storage? -%>
+ActiveStorage.start()
+<%- end -%>

「修正はrails/ujs周りのコードか」「ActiveStorageやTurbolinksも書き換わってるから、そういうのはJSのimportでやろうということになったんでしょうね」

概要

今回の変更は、Webpackerのapplication.js packテンプレートファイルやドキュメントのサンプルコードにあるCommonJSのrequire()構文をESモジュールのimport構文に差し替える。

その他

今回の変更の主な目的は、新しいRailsアプリを即使えるようにインクリメンタルに改良を進めること。以下のようなメリットが得られる。

  • 連続性を今よりも広くフロントエンドコミュニティに提供できる: Webpackerを採用する魅力のひとつは、BabelインテグレーションによってESモジュール構文をサポートできることだろう。Rails開発者がWebpackやWebpackerに抱く第一印象は、Webpacker入りの新しいRailsをインストールしてapplication.jsファイルの中を見たときに決まる。昨今、Rails開発者がネットで見つけるドキュメントやコード例はほぼESモジュール構文を元にしている。
  • 混乱を軽減できる: 開発者は自分のapplication.js packにESのimportを追加するのが普通だ(ネットのコード例に沿って作業する場合が典型的)が、今のままではrequire()importが1つのファイルで混在することになる。これでは混乱の元になるし、require()importで無用な軋轢も生じる。

  • ブラウザサポートに優しい: ESモジュール構文は将来を見据えてブラウザでサポートされる前提だが、require()構文は設計が同期的で、サーバーサイドJSとしてのNode.jsで元々採用されていたCommonJSと違ってブラウザでサポートされない。Webpackがrequire()をサポートしているのは利便性のためでしかない。

  • 最適化周りのベストプラクティスが促進される: WebpackはESモジュールやを統計的に分析して”tree-shake”する(特定の条件が満たされると最終ビルドから不要なexportを除去するが、package.jsonの指名がおかしくなる副作用もある)。

新機能: RubyネイティブのHash#exceptも使えるようになった

# activesupport/lib/active_support/core_ext/hash/except.rb#L12
  def except(*keys)
    slice(*self.keys - keys)
- end
+ end unless {}.respond_to?(:except)

つっつきボイス:「Hash#exceptはまだRubyのmasterブランチに入ったところみたいです↓」「今度Rubyに入る新しい機能に対応したということですね」

参考: Feature #15822: Add Hash#except - Ruby master - Ruby Issue Tracking System

「RailsのActive Supportの機能がRubyにバックポートされることがちょくちょくありますけど、これも同じような流れなのでしょう」「Rubyネイティブの機能があるときはそれを使うようにすると」

ArelのIsDistinctFromクラスをEqualityクラスからBinaryクラスに移動


つっつきボイス:「Arelの中のクラスの置き場所が変だと思ったので移動したようです」「リファクタリングに近い感じ」

34451で追加されたIsDistinctFromNotEqualとほぼ同じ。
歴史的にはEqualityのサブクラスは特殊な機能を持つようになっていたがequality?メソッドに統合されたので、今後の新しいクラスでequalityが欲しい場合はEqualityを継承しない。少なくともIsDistinctFromはequalityノードに置くべきではない(実際のIsNotDistinctFromはほぼEqualityと同じだが、めったに使われないこのノードに特殊な機能を与えることについて興味が湧かない)。
同PRより大意

番外: スペースやハイフンを含むenumからスコープ名を生成できるようになった(取り消し)


つっつきボイス:「今までだとenumの値にハイフンを含んでいるとエラーになったけど、アンスコに置き換えるようにしたと」「たしかに.underscoreやってる↓」

# activerecord/lib/active_record/enum.rb#184
        _enum_methods_module.module_eval do
          pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
          pairs.each do |label, value|
            if enum_prefix == true
              prefix = "#{name}_"
            elsif enum_prefix
              prefix = "#{enum_prefix}_"
            end
            if enum_suffix == true
              suffix = "_#{name}"
            elsif enum_suffix
              suffix = "_#{enum_suffix}"
            end

-           value_method_name = "#{prefix}#{label}#{suffix}"
+           value_method_name = "#{prefix}#{label.to_s.parameterize.underscore}#{suffix}"
            enum_values[label] = value
            label = label.to_s
# activerecord/test/cases/enum_test.rb#L595
+ test "scopes are named like methods" do
+   klass = Class.new(ActiveRecord::Base) do
+     self.table_name = "cats"
+     enum breed: { "American Bobtail" => 0, "Balinese-Javanese" => 1 }
+   end
+
+   assert_respond_to klass, :american_bobtail
+   assert_respond_to klass, :balinese_javanese
+ end

「enumにハイフンやスペース入りの文字列を使っていいのかという点はさておき、対応するならこう変換するしかないでしょうね」「プルリク例みたいにpublic_sendする↓よりはいいかも😆」


Railsモデルでシンボルではなくstringキーを用いたのだが、その中にハイフンが入っているものがあると、欲しいスコープ名を得られないことに気づいた。

# MyModelが`enumパターンを含むとする: { "dot-fill" => 0 }`
>> puts MyModel.dot_fill.to_sql
Traceback (most recent call last):
        3: from /my_app/script/console:82:in `<main>'
        2: from (irb):1
        1: from /my_app/vendor/gems/2.6.6/ruby/2.6.0/gems/activerecord-6.0.3.2.5438067/lib/active_record/dynamic_matchers.rb:22:in `method_missing'
NoMethodError (undefined method `dot_fill' for #<Class:0x0000000125f6ede0>)
>> puts MyModel.public_send(:"dot-fill").to_sql
SELECT `my_table`.* FROM `my_table` WHERE `my_table`.`pattern` = 0
=> nil

このブランチは、生成されたスコープ名をenum値用に変換し、スペースやハイフンをアンダースコアに置き換える。これで期待どおりMyModel.dot_fillと書けるようになる。
同PRより大意


「そして以下の続きのプルリクは@kamipoさんによるものなんですけど、上の変更はbreaking changesだと指摘していますね」「こちらは大文字を含むActiveSupportみたいな文字列を今までどおり使えるようにするために上をrevertするということか」「こちらもマージされました」「こっちのユースケースの方が多いと思うのでいいと思います👍」「そもそもこの制約を意識したことがなかった😆」

# activerecord/test/cases/enum_test.rb#L595
- test "scopes are named like methods" do
+ test "capital characters for enum names" do
    klass = Class.new(ActiveRecord::Base) do
-     self.table_name = "cats"
+     enum breed: { "American Bobtail" => 0, "Balinese-Javanese" => 1 }
+     self.table_name = "computers"
+     enum extendedWarranty: [:extendedSilver, :extendedGold]
    end

-   assert_respond_to klass, :american_bobtail
-   assert_respond_to klass, :balinese_javanese
+   computer = klass.extendedSilver.build
+   assert_predicate computer, :extendedSilver?
+   assert_not_predicate computer, :extendedGold?
  end

39673ではenum名の制約を変更して大文字で始まる単語を含むメソッド名の生成を禁止しているが、これはbreaking changeの懸念がある。
とりあえずブランチを2つ用意した。既存のアプリが動かなくなってもいいという意図がなければ今はrevertするべき(このブランチはrevert用)。
逆にこのbreaking changeが意図的(つまり既存のアプリは新しい制約に移行すべき)であれば、少なくとも既存の動作を削除する前に非推奨にしておくべき。
同PRより大意


ナイスフォロー感謝です!スペースやハイフンをアンダースコアに変換したかっただけなので、capitalをなくすつもりはなかった。スペースやハイフンを変換するときにcapitalを変えないためにシンプルなgsub正規表現でやる方がいいのかもしれない。
同PRコメントより大意

Rails

ソフトウェアパターンを闇雲に適用しないこと

短い記事です。


つっつきボイス:「よく言われることですけどピックアップしてみました」「英語でもblindly applyって言うのか」「銀の弾丸は英語でもsilver bulletなのか」「それはきっと英語の方が元でしょう😆」「あ、そうでしたか😅」「『狼男は銀の弾丸でないと倒せない』という伝説が元でしたっけ🐺」

参考: 銀の弾丸 - Wikipedia


記事目次より:

  • ソフトウェアパターンは「銀の弾丸」ではない
  • ソフトウェアパターンを闇雲に適用しないこと
  • ここではこう解決する方がいいのでは?

ルーティングヘルパーとRailsアーキテクチャのtips


つっつきボイス:「書かれていることはまあ普通ですね」「Railsを学び始めたばかりだと覚えることが大量にあるから、こういうtipsが必要なのもわかります😭」


記事要点

# 同記事より
resources :users do
  # 既存の外部ルーティング
  get :avatar
  get :new_avatar
  post :create_avatar
end
  • このルーティング↑だけのうちはいいが、コントローラのコードを書き始めるとUserモデル以外のリソースに対するコントローラの責務が増えがち
    • user_save_avatarみたいなやりすぎルーティングに誘惑される)
  • コントローラは小さくしよう(コントローラが小さいほどメンテやテストがやりやすくなる)
  • Railsの7つのCRUDアクションになるべく収めよう
# ネステッドルーティングで改善後
resources :users do
  resource :avatar, only: [:show, :new, :create]
end

Railsのルーティングを極める(前編)

Rails Architects Conference 2020がオンライン開催

こちらは一足先に記事を出しました↓。

Rails Architects Conference 2020が7/1より順次オンライン開催


つっつきボイス:「6月末〜7月頭に開催されるんですが、トピックがそそる感じだったので」「オンラインカンファレンスだけど1日1トピックというのがなかなか珍しそう」「このぐらいのボリューム感の方が何かしながら流し聞けていいかも😋」「時間帯UTCなんですけど😳」「そこなんですよ、日本時間だと殆どが夜半過ぎとか早朝で」「朝2時5時3時😆」「1つだけ夜20:00ありますけど」「極東住民にはなかなかハードな時間割ですね〜🕐」

「スピーチは英語でやってくれるのかしら?」「発表者の名前はいかにもポーランド系なんですけど、集客のためにもたぶん英語でやってくれるだろうと想像してます」「よかった〜☺️」「ネイティブじゃない分英語聞き取りやすいと思います」「ポーランド語とか無理😆」「名前見てポーランド系ってわかるんですか?」「はい、昔の仕事で目が慣れました☺️」

RSpecのメンテナンス性を改善する9つのコツ(RubyFlowより)


つっつきボイス:「RSpecのコツか〜」「6.のこれ↓は誰が見ても下の方がいいですし」

# 同記事より
# BAD
it 'has correct attributes' do
  expect(user.name).to eq 'john'
  expect(user.age).to eq 20
  expect(user.email).to eq 'john@ruby.com'
  expect(user.gender).to eq 'male'
  expect(user.country).to eq 'us'
end

# GOOD
it 'has correct attributes' do
  expect(user).to have_attributes(
    name: 'john',
    age: 20,
    email: 'john@ruby.com',
    gender: 'male',
    country: 'us',
  )
end

「7.の『RSpecのトランザクション』のあたりっていろいろ面倒」

「8.の『モックでexpect使うな』」「モックをうまく使うのも難しいですよね…」

「9.の、BADでやってる個別のテストは同じ処理をやってるからGOODみたいにDRYに書こう↓というのはワカル」「こんな書き方できるんですか?」「え、普通こう書きませんか?」「やったことなかった😅」「こう書かないとcaseを増やすのが面倒だと思いますけど」「不思議にレビューでも指摘されたことなかった…」

# 同記事より
# BAD
#
describe '.extract_extension' do
  subject { described_class.extract_extension(filename) }

  context 'when the filename is empty' do
    let(:filename) { '' }
    it { is_expected.to eq '' }
  end

  context 'when the filename is video123.mp4' do
    let(:filename) { 'video123.mp4' }
    it { is_expected.to eq 'mp4' }
  end

  context 'when the filename is video.edited.mp4' do
    let(:filename) { 'video.edited.mp4' }
    it { is_expected.to eq 'mp4' }
  end

  context 'when the filename is video-edited' do
    let(:filename) { 'video-edited' }
    it { is_expected.to eq '' }
  end

  context 'when the filename is .mp4' do
    let(:filename) { '.mp4' }
    it { is_expected.to eq '' }
  end
end


# GOOD
#
describe '.extract_extension' do
  subject { described_class.extract_extension(filename) }

  test_cases = [
    '' => '',
    'video123.mp4' => 'mp4'
    'video.edited.mp4' => 'mp4'
    'video-edited' => ''
    '.mp4' => ''
  ]

  test_cases.each do |test_filename, extension|
    context "when filename = #{test_filename}" do
      let(:filename) { test_filename }
      it { is_expected.to eq extension }
    end
  end
end

「ただこのDRYな書き方にすると、テストがfailしたときにどのパターンでコケたのかがちょっと探しにくくなるところが難点なんですよ」「たしかに!」

「DRYじゃない方の書き方なら、failしたときにそこの行番号を出してくれるから探しやすいんですけど、DRYな方はeachで回しているからテストデータが似通っていたりすると落ちた場所がすぐにわからなくて😢」「でもメンテしやすいのは明らかにDRYな方なんですよね…」「そこが悩ましい」「そういえばGo言語のテストにもまったく同じ問題あります」

「テストがコケたときにitの中身も出力してくれたらもう少し探しやすくなるかな🤔」「好みが分かれそうな部分かも」

RSpec vs minitest

「ところで最近Railsプロジェクトのテストはminitestでやってますヨ」「やや、ついにminitestですか?」「RSpec、好きというほどでもないので😆」「初めて聞きました😆」

「RSpecって、キレイに書かないとという思いに囚われて結局大変な気がするんですよ」「それわかります😆」「😆」「その代わりRSpecだとモデルのSpecはとてもキレイに書けると思います: subject形式とか」「それはありますね!」「個人の感想ですけど、RSpecはそれ以外のテストにはあんまり合ってない気がします😭」

「モデルのSpecだと、テストする主体がそのままsubjectでとても読みやすい👍」「たしかに」「でもコントローラのテストとかだとね…」「コントローラ、合う場合と合わない場合はあるかも🤔」

RSpecえかきうた

その他Rails


つっつきボイス:「AciveModel::Errorsは前回も取り上げたヤツかな?(ウォッチ20200622)」「New thingsのあたりのerrors.where()でフィルタするのは前回も出ましたね」「失礼しました😅」「errorsのハッシュ改変でdeprecation warningを出すというあたりは仕様変更っぽい👀」

book.errors.where(:name) # => name属性関連の全エラー
book.errors.where(:name, :too_short) # => name属性の全"too short"エラー
book.errors.where(:name, :too_short, minimum: 2) # => name属性の全"too short"エラー、2件以上

「以前はエラーを取り出すと全部ぐちゃらっと出てきてしまったので、errors.where()でフィルタする機能は欲しかったんですよ」「6.1早く出ないかな〜😋」「新機能も速度も楽しみですよね😋」


前編は以上です。

おたより発掘

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200623後編)Bootstrap 5 alphaリリース、Lambda FunctionsとEFS、DB設計で気をつけていることほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h


週刊Railsウォッチ(20200630後編)Shopify流テスト削減、仕様化テストでレガシーコードと戦う、PostgreSQLのarray_agg()ほか

$
0
0

こんにちは、hachi8833です。ruby-jp Slack、ひと頃より落ち着いてきた感ありますが、油断すると未読たまりますね😅。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

Ruby

実行するテストを減らして「ときめく」には(Ruby Weeklyより)

つっつきボイス: 「お、Shopifyの記事」「動的解析やらいろいろ頑張ってるそうです」「Rubyでやろうとすると大変そう」


「ちょっと思ったんですけど、Shopifyが今後さらに大規模になるようなら、今後ピュアなRubyの代わりにRubyの拡張版を作って使ったりするかなと」「あ〜」「ほら、FacebookがPHPを拡張してHackという上位コンパチ言語を作ったみたいに↓」「そういえばHackってありますね!」「ググりにくそうな名前😆」

参考: Hack (プログラミング言語) - Wikipedia

「自社プロダクト向けに言語を拡張するのってゲーム業界だと割とよく行われますし、見ようによってはJetBrainsのKotlinもその一種でしょうし↓」「たしかにそれはそれでありかも!」「まあ自分たちでメンテしきれるならですけど😆」

参考: Kotlin - Wikipedia


記事見出しより:

  • テストが落ちたり落ちなかったりする原因
    • タイミング
    • データベースが不安定
    • ランダムなジェネレーター
    • テストのステートが他のステートに漏れている
  • Shopifyのモノリスティックなリポジトリ
    • テスト15万件以上
    • 年に20〜30%増加
    • Dockerコンテナを数百個パラレルにしても30〜40分かかる
  • テストの問題を解決する
  • 動的解析とは
  • 動的解析を使う理由
  • Railsで動的解析するデメリット
    • 遅い
    • 生成されたマッピングラグがHEADより遅れる
    • トレースできないファイルがある
    • メタプロで呼び出しパスがあやふやになる
    • 失敗するテストの一部をキャッチできない
  • 新しいパラレル動的解析を試す
    • 失敗するテストの「リコール率」(確実に期待通りに失敗する率)を定義
    • 速度向上
    • 計算時間の短縮
  • その他検討したアプローチ(最終的には使わず)
    • 静的解析
    • 機械学習
    • テストマシンの増強
  • テストを減らすメリット
    • 開発者に短時間でフィードバックできる
    • テストが失敗したりしなかったりする可能性を減らせる
    • CIの方がコストは低い
  • 動的分析やテストの精選への反論は、意外にも出なかった

はみだしつっつき:「ところでタイトルのspark joyって何だろうと思ってググったら、日本語の「ときめき」が由来の流行り言葉だそうです↓」「ときめきがspark joy、わかるようなそうでもないような😆」「コンマリさん、女子の世界ではえらく有名な方みたいですね」「知らない世界すぎる😆」

参考: スパーク・ジョイ! 「コンマリ」が欧米人ウケしている理由分析|THE MAINSTREAM(沢田太陽)|note

後で嫁さんに聞いたら「ときめきを感じないものはどんどん捨てる」というのがコンマリ流型付け術なんだそうです。

仕様化テストでレガシーコードと戦う(Ruby Weeklyより)


つっつきボイス:「characterization testという言葉を初めて見たんですけど説明↓が今ひとつよくわからなくて、他の記事も参考にするとどうやら『レガシーコードがどう振る舞うかを調べるためにテストを書く』ということなのかなと」「なるほどね〜」

A characterization test is a technique coined by Michael Feathers where we write tests for code where we don’t have them. It also means that, to some extent, we shouldn’t be guessing what the code should do and instead we should be in “exploratory” mode.
同記事より

# 同記事より
def mask(str)
 if str.length <= 4
   return str
 end

 # Email
 if str =~ URI::MailTo::EMAIL_REGEXP
   limit = str.length - 4
   return "#{'*' * limit}#{str[limit..-1]}"
 end

 # Phone Number or ID
 if str.is_a?(Numeric)
   limit = str.length - 4
   return "#{'*' * limit}#{str[limit..-1]}"
 end

 # String, like a name
 limit = str.length - 4
 return "#{'*' * limit}#{str[limit..-1]}"
end

「characteristic testという言葉がどのぐらい一般的なのかはわかりませんけど、記事をざっと見た感じでは、今動いているコードのブラックボックス的な部分に対してテストを書いていくというアプローチのようですね」「おぉ」

「仕様書もドキュメントもないけどメンテしないといけないときなんかに、コマンドラインで手動で動かして目検するような操作を自動化したようなイメージですね: わざとfailするテストを書いてはgot:に出た値をテストデータにコピペしてパスさせて、それを繰り返してテストパターンを増やしながら仕様を少しずつ明らかにしていく、みたいな進め方」「なるほど!」

「他にどうしても手段がないときなんかだと、ときどきこの手のテスト書くことありますヨ」「お疲れさまです…」「ヤマカンで境界値っぽい値をぶちこむのを繰り返して、最終的に以下みたいなテストを増やしていく: もちろんそれが本当に境界値なのかとか、仕様として正しいのかどうかはこれだけだとわかりませんけど、少なくともこの入力に対してこの出力が得られるという情報にはなるので、それを守ってメンテしていくと」「ふむふむ」

# 同記事より
equire_relative './../mask'

describe 'mask' do
  it "masks regular text" do
    expect(mask('simple text')).to eq('*******text')
  end

  it "masks an email address" do
    expect(mask('example@example.com')).to eq('***************.com')
  end

  it "masks numbers as strings" do
    expect(mask('635914526')).to eq('*****4526')
  end

  it "does not mask a string with 4 characters" do
    expect(mask('asdf')).to eq('asdf')
  end

  it "does not mask a string with 3 characters" do
    expect(mask('asd')).to eq('asd')
  end

  it "does not do anything with empty strings" do
    expect(mask('')).to eq('')
  end

  it "masks symbols like regular characters" do
    expect(mask('text .-@$ with symbols-')).to eq('*******************ols-')
  end
end

「そうやってレガシーコードと戦うんですね🤺」「元記事はこれでテストを固めてからリファクタリングしてますね」「こういうテストはバグが出たときにも有効ですね: エッジケースと思われる部分をテストで押さえてから修正すれば、少なくともその部分はデグレを避けられますし」「たしかに!」

「そのままだと怖くて触れないコードにこうやってアプローチするというのは割とあります☺️」「少なくともこういうテストは作業前に作らないといけませんよね」「やらずに済むのが一番ですけど😆」「仕様化テストも増やせば増やすほど当然遅くなりますし🐢」「自分もいずれこういうことしないといけなくなるのかな…😢」「そういえば今もRails 2をメンテしている知り合いいます」「マジで😆」

「なるほど、仕様化テストとも呼ばれてるのね↓」

参考: Grenning > 仕様化テスト (Characterization Test) > 仕様化テストは、チームの長期記憶としての役割も果たしてくれる - Qiita

Inkblot: 電子ペーパーをRubyで制御(Ruby Weeklyより)

# 同サイトより
require 'inkblot'
include Inkblot

Components::SimpleText.new do |st|
  st.div_height = st.div_width = 95
  st.gfonts = %w[Pacifico]
  st.font = st.gfonts.first
  st.text = 'Hello'
  st.size = 60
  st.border_size = 10
end

Components::TableList.new(
  items: %w[Apples Oranges Pears Peaches],
  fullscreen: true
)

Components::IconGrid.new do |ig|
  ig.fullscreen = true
  ig.icons = %i[alarm extension face grade]
end

Components::ScrollMenu.new do |sc|
  sc.fullscreen = true
  sc.items = (1..10).map { |x| "Option #{x}" }
end

Components::BarCode.new(
  fullscreen: true,
  code: "978054538866"
)

Components::QrCode.new do |qr|
  qr.margin_top = qr.margin_left = -5
  qr.div_height = qr.div_width = 95

  qr.message = "https://youtu.be/zt2uIhAvQZ8"
end

Components::FullScreenImage.new(
  fullscreen: true,
  path: Inkblot.vendor_path('chris_kim.bmp')
)

Components::FullScreenImage.new do |fsi|
  fsi.fullscreen = true
  fsi.url = 'https://live.staticflickr.com/'
  fsi.url << '2753/4177140189_f5fd431b26_o_d.jpg'
end

つっつきボイス:「こちらはIoT系のgemで、Waveshareは電子ペーパーデバイスのメーカーだそうです」「お、こういうの好きそうですよね?」「はい触ったことあります、電子ペーパーにArduinoが乗ったような感じのデバイスです❤️」


同サイトより

参考: 2.7inch e-Paper HAT - Waveshare Wiki

「端子にこうやって電圧を印加するといろいろ表示できたりしますよね↓」「このデバイスではそこまでプリミティブに制御したことはないかな〜」「ステッピングモーターぐらいシンプルならいいけど、こんなに複雑なのは自分ではやりたくない😆」「やらざるを得なかったことならありました😅」(以下延々)


waveshare.comより

github-ds: Active Record接続上でSQLを扱うRubyライブラリ集(Ruby Weeklyより)


つっつきボイス:「GitHubが出しているライブラリだそうです」「★500個付いてますね」

# 同リポジトリより
require "pp"

# Create new instance using ActiveRecord's default connection.
kv = GitHub::KV.new { ActiveRecord::Base.connection }

# Get a key.
pp kv.get("foo")
#<GitHub::Result:0x3fd88cd3ea9c value: nil>

# Set a key.
kv.set("foo", "bar")
# nil

# Get the key again.
pp kv.get("foo")
#<GitHub::Result:0x3fe810d06e4c value: "bar">

# Get multiple keys at once.
pp kv.mget(["foo", "bar"])
#<GitHub::Result:0x3fccccd1b57c value: ["bar", nil]>

「GitHub的に欲しいものなのかな?」「RedisインスタンスやElastiCacheなんかをわざわざ立てずにKV(キーバリュー)みたいなものをちょこっと使いたいというのは何となくワカル」「ElastiCache高いですし💵」「それにこういう形で最初に作っておけば、後でmemcachedとかに移行するときにも楽でしょうし😋」「永続化とかも考えると結局RDBでやるのが楽だったりしますよね😋」「ActiveRecordの接続があればとりあえずそういうのを作れると」

参考: Amazon ElastiCache(インメモリキャッシングシステム)| AWS

「この辺のSQL操作↓もActiveRecord::Baseconnection.executeが使いづらいから欲しかったのかも」「ストアドプロシージャっぽい機能もあるみたい」

# Select, insert, update, delete or whatever you need...
GitHub::SQL.results <<-SQL
  SELECT * FROM example_key_values
SQL

GitHub::SQL.run <<-SQL, key: "foo", value: "bar"
  INSERT INTO example_key_values (`key`, `value`)
  VALUES (:key, :value)
SQL

GitHub::SQL.value <<-SQL, key: "foo"
  SELECT value FROM example_key_values WHERE `key` = :key
SQL

# Or slowly build up a query based on conditionals...
sql = GitHub::SQL.new <<-SQL
  SELECT `value` FROM example_key_values
SQL

key = ENV["KEY"]
unless key.nil?
  sql.add <<-SQL, key: key
    WHERE `key` = :key
  SQL
end

limit = ENV["LIMIT"]
unless limit.nil?
  sql.add <<-SQL, limit: limit.to_i
    ORDER BY `key` ASC
    LIMIT :limit
  SQL
end

p sql.results

DB

PostgreSQLのhypothetical aggregates(Postgres Weeklyより)


つっつきボイス:「hypothetical aggregates言いにくい😆」「仮説的集計って言うのかな?」「へぇ〜、array_agg()っていう集約関数があるのね」

「どれどれ、rank(3.5)は、偶数の{10,2,4,6,8}をソートした配列{2,4,6,8,10}なら2と4の間に来るのでrankは2になり、奇数の{9,7,3,1,5}をソートした配列{1,3,5,7,9}なら3と5の間に来るからrankは3になると」「ふむふむ」

# 同記事より
test=# SELECT x % 2 AS grp, array_agg(x), 
              rank(3.5) WITHIN GROUP (ORDER BY x) 
       FROM   generate_series(1, 10) AS x 
       GROUP BY x % 2;
 grp |  array_agg   | rank
-----+--------------+------
   0 | {10,2,4,6,8} | 2
   1 |  {9,7,3,1,5} | 3
(2 rows)

「3.5という値はこの配列の中にはないんだけど、もし配列の中にあったとしたらrankがいくつになるだろうかというのを、配列に入れずに求められるということみたい」「あ〜やっとわかりました」「それがhypothetical aggregatesなんでしょうね」

「データを入れなくても、もし入れたら何位になるかというのがわかると」「データが確定してなくても順位が取れると」「たしかに実際のアプリケーションでそういうのが欲しいときありそう」「記事のユースケースもそういう感じですね」「データを本当に入れてしまうと他のトランザクションに影響するので、こうやってやれるのはいいですね😋」

「hypothetical aggregates、ちょっと名前カッコイイ」「怖そうな名前かな〜」「記事のこの図↓がズバリ内容を表してて秀逸👍」「わかってみるとよくわかる図ですね」「こういう機能があるということを何となくでも知っておくといつか身を助けるかも」「いつになるかはわかりませんが😆」「Rubyの機能だけでやろうとすると件数が多いときにしんどいかも」


同記事より

RDBの「配列」

「ところで、このやり方の場合array型にする必要がありそうな雰囲気ですけど、PostgreSQLなら簡単にarray型にできるのですぐ使えそう👍」「う、MySQL勢だけどぽすぐれの誘惑が〜😆」「PostgreSQLだとサブクエリの結果をarray型にしてINで取ってくるとか普通にできますけど、MySQLのarray型って機能少ないですよね」

「そんなのやれるんですかいいな〜、MySQL一筋なのにぽすぐれの話聞くたびに浮気したくなってきた😂」「まあそれをActive Recordらしく書けたりはしないんですけど、RDB単体の機能としてはそういうことができるということで☺️」

クラウド/コンテナ/インフラ/Serverless

銀座Railsスライド「RailsとDocker」


つっつきボイス:「神速さんのスライドだ」

「現状でのRails+Dockerはこういう感じというのがわかって参考になりましたし、開発環境のDockerイメージと本番環境のDockerイメージを同じにすることは諦める、というのも収穫でしたね👍」「やっぱりそうなるのか〜」「開発環境ならちょっとコンテナの中に入って作業できますけど、本番環境は余計なものを何も入れないからほとんど何もできなくなるじゃないですか😢」「本番だとvimすら入ってなかったりしますよね😢」「Alpine Linuxだとpsコマンドすら使えなかったり😭」

「私の翻訳記事↓も取り上げていただいて感激しました😂」

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

RailsとDocker

「ところで、RailsをDocker化する機能もそろそろRails本体に入っていいんじゃないかなってときどき思いますね」「それすごく思います!」「Railsがオールインワンのフレームワークということを考えると、公式のDockerコンテナ化機能があってもよさそうですし」「rails buildで一発ビルドできたらいいな〜😂」「いっそrails sしたらコンテナが立ち上がって欲しい😆」「😆」

「まあRails環境構築の必要条件にDockerが加わったらそれはそれで面倒そうですけど、rails buildみたいなコマンドでDockerイメージ作れるような機能はそのうちできてもいいと思いますよね」

その他クラウド


つっつきボイス:「こういう機能があったらいいなという話をしてたらこの発表があったんですよね」「これ見たんですけど今ひとつわかんなくて、結局何なんでしょう?」「Slackのshared channelって今までは2拠点間でしかできなかったんですけど、その制限が緩められて20拠点までできるようになったと↓」「へぇ〜」「昨日ちょうどshared channelを3拠点でできないかなって社内で話しをしてたときだったんですよ」

本日より、1 つの Slack チャンネルを最大 20 件のオーガナイゼーションが共有できるようになりました。
同記事より

参考: Slack コネクト | Slack


「ところでこのプレスリリース、何だか読みにくいですよね」「要約すれば上の抜粋の一文だけで済むのに、長い文章に埋もれてえらく把握しづらかった😢」「前は2拠点までだったけど20拠点に増えましたと冒頭で要約してくれたらずっと読みやすくなるのに」

JavaScript

ECMAScript 2020仕様がリリース(JSer.infoより)


つっつきボイス:「お、ついに正式版の仕様が出ましたね🎉」「まあ内容は以前から出回っていますけど」「ますますJavaScriptから逃げられなくなるのかな…」

「ところでこの仕様ページ、えらく重くありません?🏋🏻‍♀️」「仕様全部乗せしてるからページがめちゃ長いのか!」「この長さはヤバい」「負荷テストに使えそう」

言語/ツール/OS/CPU

三社三様

Microsoft Defender ATP for Linux

つっつきボイス:「Microsoft Defender ATP for Linuxは便利そうですよね👍」「サーバーにセキュリティソフトを入れないといけない案件もこれで対応できそうですし」「サーバーが確実に重くなりますけど😆」

「Defender ATP for Linuxの挙動が気になるな〜、監査ログ出すぐらいならいいんですけど、もし何かあったときに通信遮断されて障害になっても困るので」「何だかSELinux↓思い出しました😆」「そうそう、SELinuxをまず止めるところから始めたりしましたよね😆」

参考: Security-Enhanced Linux - Wikipedia

「Defender ATP for Linuxがどういう形式で配布されるかは知らないんですけど、もしもバイナリ配布がライセンスで許されるなら、CIでDockerコンテナをチェックしたりAmazon Linux Extrasで入れられたりするといいですよね」「たしかにDockerコンテナのセキュリティチェックって大変ですし」「きりがない😆」「サイズ650MBなので常用は無理かな〜」「for serverとかいうライセンスが必要みたいなので再配布は難しそうですね…」「面倒くさそう😆」「じゃいいや😆」

参考: 2020年6月24日 “我々のLinuxジャーニーはまだ始まったばかり”―Microsoft Defender ATP for Linuxが一般提供開始:Linux Daily Topics|gihyo.jp … 技術評論社


Googleのセキュリティスキャナー「Tsunami」

「GoogleのTsunamiはオープンソースなのね」「nmap使ってますね〜」「うっかりAWSにかけて怒られる人出そう😆」

参考: ポートスキャンツール「Nmap」を使ったセキュリティチェック | さくらのナレッジ


ARM版Mac

「ARM版Mac、x64系はRosetta 2で動くとしても、Hypervisor系のVM拡張命令は動かないってリリースにも出ているので、少なくともx64版のWindowsやVirtualBoxとかはHypervisorでは動かないけど、逆にARM版LinuxやARM版WindowsならHypervisorフレームワーク経由で動くんじゃないかって話は出てますね」「おぉ」「そういえばParallelsがARM版Windowsの仮想環境をサポートするというニュースもありました↓」

参考: ParallelsがARMアーキテクチャ搭載Macに仮想環境を提供へ、ARM on ARMのWin環境が現実的か | TechCrunch Japan

「HypervisorフレームワークがちゃんとARMに移植されて動くようになればそのあたりはやれるかなと」「ARM版Linuxは昔からありますし、そうやってARM版が普及してくると、もしかして今は誰も使っていないAWSのARMインスタンス↓が使われるようになるのでは、なんてね😆」「誰も使ってないんですか😆」「まあARMインスタンスは値段もお安くありませんし」

参考: Amazon EC2 A1 インスタンス | AWS

「今後もしかするとARMが普及することでいろいろ広がるという夢が実現するかもしれませんし、逆にARMがコケて終わるかもしれませんけど😆」「どこかで聞いたような話😆」「少なくともIntel一強よりは夢があるかなと思いますね」


以下はつっつき後の記事です。

参考: 【笠原一輝のユビキタス情報局】IntelからArmへのシームレスな移行を実現する「macOS Big Sur」 - PC Watch


ジム・ケラー

「そういえばIntelとかAMDあたりのCPUを設計したCPUアーキテクトは全部同じ人だというツイートをどこかで見かけました」「ジム・ケラーですね↓」

参考: ジム・ケラー - Wikipedia

「ちょっと前にインテル辞めたんでしたっけ?」「インテルの最近のアーキテクチャを作り終わったところで辞めたとか何とか」「DECのAlphaもやってたのか!」「いろいろ優秀すぎて想像つかない😅」「ひとりで世の中を変えるパワーのあるエンジニア、いますよね」「まさしく偉人」

以下はつっつき後に見つけたツイートです。


番外: 音

「ある意味こっちの方がうれしいかも↓」「ジャーン」「ジャーン」「2016年まで音あったんですね」「MacbookをSMCリセットか何かで完全にリセットしたときに音が出たような覚えあります」

「ところで大昔のSad Macの音って聞いたことある人います?」「あれ音するんでしたっけ?」「すっごく情けない『チャラララ〜』みたいなメロディなんですけど、運がいいとこの音が出ることがありました」「Sad Macアイコンなら嫌になるほど見ましたけど😆」

↓これでした。

参考: 画像で見る初期Macのアイコンデザイン - 6/10 - CNET Japan

その他

BasecampのHEY.comとは

参考: シリコンバレーで話題のプライバシー重視の有料電子メールサービス、「Hey」が目指していること|市川裕康 (メディアコンサルタント)


つっつきボイス:「これは?」「Basecampの作ったHay.comという招待制のメールサービスだそうです」「そういえばFacebookも昔は招待制でしたね」「Mixiも」「プライバシー重視のメールサービスか〜」「Heyというと秋葉原のゲーセンを連想しますけど😆」

参考: 秋葉原ゲーセンの老舗にしてコアなプレイヤーが集うレトロゲームの王国 「Hey」 | ゲーム文化保存研究所

「自分はもうメールってまず開かない😆」「自分も銀行のメールしか来ません😆」「過去にサブスクしたメルマガを今一生懸命解除してます😆」


「お、Basecampは本社オフィスもなくしてフルリモートにするって参考記事に書いてますね」「へぇ〜」「もともと世界各地でリモートで仕事している会社ならやれるでしょうね」「DHHの場合自宅の仕事部屋↓がめっちゃ豪華ですし✨」

番外

今どきの高校の情報科目教科書


つっつきボイス:「はてブで話題になってましたね」「よくできてる教科書との評判」「普通に大学の授業ですね」「こんなにちゃんと学べるのはいいけど、やりたくない人にはつらそうですね」「こういうのは書いてあるとおりに打ち込めば何とかなりますヨ」「ところでPythonって書きやすいのかな?」「Rでもあんまり変わらないと思いますけど😆」

参考: R言語 - Wikipedia

「今ならもっといいのがあるんでしょうけど、自分は論文とかでどういうグラフを作ろうかと思ったとき、RのWiki(Rjpwiki)↓に掲載されているグラフ実例集を随分参考にしましたね」「お〜、こんなのがあるんですね」「グラフの種類って実際のグラフも見ないと選びようがないので、ここは随分重宝しました😋」「いいこと聞いた!」

参考: グラフィックス参考実例集 - RjpWiki
参考: グラフィックス参考実例集:イメージ図 - RjpWiki


okadajp.orgより


後編は以上です。

バックナンバー(2020年度第2四半期)

週刊Railsウォッチ(20200623後編)Bootstrap 5 alphaリリース、Lambda FunctionsとEFS、DB設計で気をつけていることほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly

Publickey

publickey_banner_captured

Postgres Weekly

postgres_weekly_banner

JSer.info

jser.info_logo_captured

週刊Railsウォッチ(20200706前編)Railsでのマルチテナンシー実装戦略を比較、Railsでサブクエリを使う、URI.parserが非推奨化ほか

$
0
0

こんにちは、hachi8833です。RubyKaigiのチケット代返金処理が始まったそうです。


つっつきボイス:「RubyKaigiチケット代返金は参加者向けのお知らせということでしょうね」「チケットの返金始まったんですね!こないだDoorkeeperから届いたメールにRubyKaigiにようこそみたいなことが書いてあってもう返金無理かと思ってましたけど」「あ、紛らわしいメールが飛んで失礼しましたというのが2通目のツイートです↓」「なるほど😆」「完全に理解しました😆

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Rails: 先週の改修(Rails公式ニュースより)

以下のコミットリストのChangelogを中心に見繕いました。先週のコミット数は珍しく少なめですね。

⚓URI.parserが非推奨化

今後はRubyのURI::DEFAULT_PARSERを使うようにとのことです。よく見るとShopifyのプルリクでした。

# actionview/lib/action_view/helpers/url_helper.rb#L542
      def current_page?(options, check_parameters: false)
        unless request
          raise "You cannot use helpers that need to determine the current " \
                "page unless your view context provides a Request object " \
                "in a #request method"
        end
        return false unless request.get? || request.head?

        check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
-       url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
+       url_string = URI::DEFAULT_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)

        # We ignore any extra parameters in the request_uri if the
        # submitted URL doesn't have any either. This lets the function
        # work with things like ?order=asc
        # the behaviour can be disabled with check_parameters: true
        request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
-       request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
+       request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)

        if url_string.start_with?("/") && url_string != "/"
          url_string.chomp!("/")
          request_uri.chomp!("/")
        end
        if %r{^\w+://}.match?(url_string)
          url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
        else
          url_string == request_uri
        end
      end

つっつきボイス:「URI.parserが非推奨化?」「やべ、最近どっかで使っちゃったかも😅」「非推奨になったURI.parserはRailsのActive Supportの機能で、URI::DEFAULT_PARSERはRuby自身の機能だそうです」「なるほど〜」

これまでRegexpの重複を調べてきて、URI::Parserにはものすごい量の重複があることに気がついた。もう少し追ってみたところ、Active Supportが不要な場合であっても第2のパーサーをインスタンス化していたのが原因だった。
DEFAULT_PARSERは12年も前に追加されていたこともわかった。
URI.parserの有用性にも疑問があるが、Railsで使われている場所はほんのひと握りなので、完全に削除できるだろう。ドキュメントはないが:nodoc:も付いてないので、これがpublic APIかどうかはわからない。
@rafaelfranca
同PRより大意

「オリジナルのURI.parserは使ったことないので、使いたいニーズがどのぐらいあるのかはわかりませんけど」「上のコミットメッセージには、ほぼないだろうとありますね」「とは言えゼロではないでしょうから非推奨化して消さないとですね」「ワイ、こないだ書いたコードを見直さないと…使ってなかったよかった〜😂」「😆


「プルリクコメントにこんなの書いてありました↓」「なるほど、Ruby 1.8と1.9の頃の話だったのね」

URI.parserは、URI::ParserがまだなかったRuby 1.8とRuby 1.9との間の互換性のためにに導入されたらしい。URI.unescapeは既に5d773f8, 2f326b7, 197a995で非推奨化されている。URI.parserの非推奨化に一票。
URI.parserの振る舞いに関するドキュメントはないが、APIドキュメントには載っているのでpublic APIということになる。
同PRコメントより

⚓Ruby 1.8の頃

「まあ1.8を知るエンジニアも随分減ったかもしれませんけど」「どんな時代…?」「ほら、ハッシュの順序が維持されてなかった時代ですよ↓」「1.8やってました〜」「Ruby Enterprise Editionとかあった時代」「REEってありましたね」「2.0になってレビューで古い書き方にツッコまれまくったのを覚えてます」

参考: 要素の追加順序を保持するHashクラス (#1273692) | Ruby 1.9.0 リリース | スラド

Re: (スコア:0) by Anonymous Coward Hashが順序を保持。についてもう少し知りたい。キーの順序を保持?連想配列の実装が二分木になったとか、そういう話ですか?

Re: (スコア:0) by Anonymous Coward キーが常時ソートされた状態で保持されるという意味ではなく、each(など)で列挙すると追加した順序で出てくるという意味です。
srad.jpより

参考: Ruby Enterpriseエディションが終わる。Phusionは、Passengerに注力。 — 2012年の記事です

⚓可能な場合はLoadError#original_messageを使うようになった

こちらもShopifyのプルリクです。

LoadError#messageはRuby 2.8/3.0でDidYouMeanによって拡張されていて、$LOAD_PATHにあるものをかなりいい感じに使って訂正サジェスチョンを表示してくれる。
このおかげで、特に$LOAD_PATHの量が多い場合にメッセージへのアクセスがかなり拡張可能になる。
NameError#messageでは既に同じ問題を扱っているので、それと同じアプローチを取ることにした。
同PRより大意


つっつきボイス:「DidYouMeanをsafe_constantizeでも効かせるようにしたということですね」

# activesupport/lib/active_support/inflector/methods.rb#L329
    def safe_constantize(camel_cased_word)
      constantize(camel_cased_word)
    rescue NameError => e
      raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
        e.name.to_s == camel_cased_word.to_s)
    rescue ArgumentError => e
      raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
    rescue LoadError => e
-     raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(e.message)
+     message = e.respond_to?(:original_message) ? e.original_message : e.message
+     raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
    end

「DidYouMeanって何でしたっけ?」「ほら、『本当はこれじゃないの?』みたいなエラーメッセージですよ」「『それはタイポでは?』的なヤツ」「あ〜あれですか!何気に助かるんですよね」「『どうしてわかった?』みたいに図星だったりしますよね」「いや〜今回もどれだけ助けられたことか😂」「Rubyに後ろから見られてるような気持ちになります😆

「Rubyはこういうところがプログラマーに優しいですよね」「いちいちAPIドキュメントをひっくり返したりしなくてもわかりますし」「そういう部分がMatzが言うところの『楽しくプログラミングできる』というヤツなのかなと思いますね」「jnchitoさんが『Rubyの書き味』を引用してたのもそのあたりかも」「他の言語だとスタックトレース追ったりしないといけなくなったりしますし」「DidYouMeanでツッコまれたら取りあえず言われたとおりに変えて試すという投機的実行ができるのはいいですよね」

後で掘り起こしました↓。

⚓Marshal.load(legacy_record.dump)がMySQLで動くための後方互換性

実際にはMarshalの互換性をRailsバージョン間で維持すると明言したことは一度もないので、これまでもそれ用の型を直接削除したことがある(f1a0fa9や#29666など)が、直接削除するとキャッシュのローテーションが難しくなる。
未使用の定数を新しいバージョンで維持すれば、少なくとも1つのバージョンが続く間はキャッシュのローテーションはやりやすくなる。
同PRより大意


つっつきボイス:「なるほど、Railsのリリースバージョン間でのMarshal.loadを問題にしているということか!」「RubyのMarshalはバージョン間での互換性は保たれないものですけど、Railsでのバージョン間互換性と言われるとたしかにと思いますね」「キャッシュローテーションはたしかにMarshalでやる方が軽そう🎈

# activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L847
+       # MysqlStringエイリアスでMashal.load(File.read("legacy_record.dump"))が効くようにする
+       # TODO: Rails 6.1がリリースされたらこの定数エイリアスを削除する
+       MysqlString = Type::String # :nodoc:
+

「まあ自分はそもそもMarshalの互換性は当てにしませんけどね: 一般にバージョンが変わってもロードを維持しないといけないものならMarshalではなくJSONとかを使うべきだと思いますし」「たしかに」「MarshalはRubyのバージョンが変われば互換性が失われるものだから、そういうのを永続化的に使いたくはないですね」

⚓番外: tags_textのキャッシュ化を取り消し


つっつきボイス:「このタグはログで使うヤツみたいですね」「元のコミット↓ではメモ化||=でキャッシュしてたのを、その後のコミットで取り消したのね」

# activesupport/lib/active_support/tagged_logging.rb#L56
      def tags_text
-       tags = current_tags
-       if tags.one?
-         "[#{tags[0]}] "
-       elsif tags.any?
-         tags.collect { |tag| "[#{tag}] " }.join
+       @tags_text ||= begin
+         tags = current_tags
+         if tags.one?
+           "[#{tags[0]}] "
+         elsif tags.any?
+           tags.collect { |tag| "[#{tag}] " }.join
+         end
        end
      end

このコミットは05060ddを取り消す。
タグはファイバーごとにあるので、インスタンス変数内ではキャッシュされない。
同コミットより大意

⚓Rails

⚓提案: ハッシュ記法のショートハンド(Ruby on Rails Discussionsより)

以下のように最近のJavaScript的に書きたいという提案です。肯定否定さまざまな意見が出ています。

{topics, users, very_longly_named_objects}
# ↓
{
  topics: topics,
  users: users,
  very_longly_named_objects: very_longly_named_objects,
  even_more_very_longly_named_objects: even_more_very_longly_named_objects,
}

つっつきボイス:「このスレが割と盛り上がってるので」「あ〜、JS風のこの書き方が欲しいというのワカル!」「ローカル変数名とキー名を一致するように書くことはよくあるので、だったらこう書けるといいよねと」「特にeven_more_very_longly_named_objectsみたいに長大になるとエディタで改行して見づらくなりますし」「そうそう!」「Rubyには入れられなくても、Active Supportあたりに追加できないかな?」「ローカル変数名を取得できるならやれそうな気もしますね」「欲しい人が多いのもわかります」

後でRubyの#15236(rejected)↓を見てみました。Matzの講評を抜粋してみます。

(別のスレより)この構文についてはほとんど肯定的な気持ちになれない。理由はset構文や昔のRuby 1.8のハッシュスタイルっぽく見えるため。将来ES6の構文が普及したらこの変更を入れるチャンスはあるだろう。

JavaScriptをまったく使っていないコンサバなシニアとしては、この構文についてまだネガティブな気持ちがある。現在のRuby構文ではほぼ不可能なデストラクチャリング(代入の左側)なら最も相性がよさそう。
もちろんRubyユーザーの多くがRailsとJavaScriptを同時に使っていることは認識しているので、皆さんの意見はオープンに受け止めます。
同issueより大意


「そういえばコメントにI’m greenly jealous of JavaScriptという言い方があったんですけど、greenってたしか英語圏だと嫉妬に通じる色なんですよ」「へぇ〜」「日本語の『真っ赤な嘘』的に色の名前に含みがあるというか」

参考: ブルーは憂鬱、グリーンは嫉妬…色にまつわる英語表現(活かす!イングリッシュ Vol.12)|すぐに役立つ英会話・英語レッスン|現地情報誌ライトハウス

greenは、他にも人間を形容すると「青二才」のようなニュアンスも含むことがありますね(たぶん「初々しい」と表裏一体な感じで)。

⚓Railsでサブクエリを使う(Ruby Weeklyより)


つっつきボイス:「PostgreSQLが前提のようです」「またぽすぐれか〜😅

「このselect('avg(salary)').to_sqlみたいな書き方は自分もやったことある↓ to_sqlすれば普通にサブクエリにできるので」「ふむふむ」

# 同記事より
avg_sql = Employee.select('avg(salary)').to_sql

Employee.select(
  '*',
  "(#{avg_sql}) avg_salary",
  "salary - (#{avg_sql}) avg_difference"
)

「なるほど、FROMでサブクエリしたい場合↓」

# 同記事より
from_sql =
  PerformanceReview.select(:reviewer_id, 'avg(score) avg_score').group(
    :reviewer_id
  ).to_sql

PerformanceReview.select('avg(avg_score) reviewer_avg').from(
  "(#{from_sql}) as reviewer_avgs"
).take.reviewer_avg

「そしてHAVINGでサブクエリしたい場合↓」

# 同記事より
avg_sql = PerformanceReview.select('avg(score)').to_sql

Employee.joins(:employee_reviews).select(
  'employees.*',
  'avg(score) avg_score',
  "(#{avg_sql}) company_avg"
).group('employees.id').having("avg(score) < 0.75 * (#{avg_sql})")

「サブクエリを使う方が適切なケースは普通にありますね」「もしかすると、コンセンサスの取れる形で生SQLを書けるインターフェイスがActive Recordに公式に入ればそれで解決するのかなという気がしてきた」「そうかも」「言い換えるとArelで頑張るには限界があるということで」「あ〜」「上みたいな書き方をやっていくと今度はWITH句も使いたくなるだろうし😆」「今ならMySQLでもPostgreSQLでもWITH使えますよね」


記事見出しより:

  • RailsのActive Recordを使うということは
  • Railsにおけるサブクエリとは
  • 私たちのデータの概要
  • WHEREにサブクエリを書く
    • WHERE NOT EXISTS
  • SELECTにサブクエリを書く
  • FROMにサブクエリを書く
  • HAVINGにサブクエリを書く
  • まとめ

⚓Railsでのマルチテナンシー実装戦略を比較


つっつきボイス:「Railsで複数のテナントの扱いをどう実装するかという戦略の話なのかな」「中身読まないうちに推測すると、rowレベルは複数顧客のデータを同じテーブルに入れるし、dbレベルはデータベースを顧客ごとに分けるというヤツで、スキーマレベルは顧客ごとに別のテーブルを作るんでしょう、きっと」「たぶんそれっぽいこと書いてる気がします」「マルチテナンシーで思いつくのが取りあえずその3つなので😆」「スキーマレベルはcreate schemaとcreate tableって書いてるのでそうだと思います」

  • row(行)レベル
  • スキーマレベル
  • dbレベル

「記事にこんな感じで表が載っています↓」


同記事より

「実際マルチテナンシーをどう実装するかって悩ましいんですよ: たとえばrowレベルにはrowレベルのつらさがありますし」「見えちゃいけないものが見えてしまうとか?」「それは実装がダメすぎ😆」「rowレベルで問題になりやすいのは、パフォーマンスが落ちる問題と、テーブルがバカでかくなったときにどうするかというスケーリングの問題: 1個のテーブルが極限までデカくなってしまうとまともにメンテできなくなる可能性もあるので」「なるほど」

「テナントをdbレベルで分けてあれば、あるデータベースで問題が起きても他のテナントが死なずに済むというメリットが得られます: たとえばテナントのほとんどは小規模だけど、一部のテナントはものすごく激しく使うような案件なら、dbレベルだと障害範囲を限定できるのがいいんですよ」「ふむふむ」「その代わりインフラをメンテナンスするコストが跳ね上がるのがしんどいですけど😭

「スキーマレベルはまず使わないのが普通なので、これを検討することはほぼないかな」「自分の経験でもrowレベルかdbレベルのどっちかですね」「スキーマレベルでやっていてテナントが数千件とかになったら、テーブルが数千件できるということですよね…」「スキーマレベルはないわ〜」「/dtしてテーブルがどひゃ〜っと表示されたら死にたくなりそう」「イヤ〜😭」「表には『Extract a single tenant’s data = Easy』とか書いてあるけど、果たしてそうかな〜😅?」

「あとRails固有の問題なんですけど、rowレベルでやるとマイグレーションが激重になりがちなのが深刻なんですよ: 1個のテーブルに全テナントのデータが入るのでrails db migrateの遅さが半端なくなる」「あ〜、そうですよね」「dbレベルなら最悪でもテナントごとにマイグレーションを実行できるんですけどね」「rowレベルだと、stagingでは問題なかったのに本番でマイグレーションがなかなか終わらないということが起きがちなので、staging環境のデータベースには十分な量のダミーデータを入れておきたいですね」「たしかに!」「でないとコワすぎるので」

「エンタープライズなアプリのマルチテナンシー周りには気をつけたい」「やべマルチテナントやったことあるわ〜rowレベルだったわ〜😆」「😆」「まあ最近のMySQLやPostgreSQLはデータベースをロックしないでマイグレーションする手順も確立されているので、気をつければ大丈夫ですよ」「つまり気をつけないと死ぬってことですよね🤣」「🤣

「ただ、どの方法を選ぼうとユーザー数やデータ量が増えれば気にしなければいけないのは一緒なので、この戦略を選びさえすれば楽になるというのはないと思ってます」「そうですよね」

⚓提案: schema.rb生成中にテーブル名やカラム名などをソートする機能(Ruby on Rails Discussionsより)


つっつきボイス:「なるほど、schema.rbの項目ソートか」「ただ既存のカラム名を無断でソートするのはやめて欲しい: データベース内部の物理配置に影響するので、勝手にソートされると意味が変わっちゃう」「あ、それもそうか!」

後で見ると、提案した方はfix-db-schema-conflicts gemの作者で、RubocopによるオートコレクトとぶつからないためにこのgemのソートロジックをRailsのスキーマ生成に入れませんか(カラムの並び順に依存するアプリ用に並び順を維持するオプションも付けて)という提案でした。structure.sqlは変更されないそうです。

「テーブル名のソートはいいと思います😋」「テーブル名なら全然構わない」「ところで今自分のRailsアプリ見るとテーブル名はアルファベット順になってますね」「あ、そうなんですか?」「外部キーのソートはRDBMSに依存しそうな気がする🤔」「結局はCREATE TABLE文の中に書かれているものの順序が、データベース内部のデータ構造に影響するかどうかがポイントでしょうね」


前編は以上です。

おたより発掘

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

週刊Railsウォッチ(20200707後編)Rubyで無名structリテラル提案、書籍『AWS認定ソリューションアーキテクト』、21世紀のC言語ほか

$
0
0

こんにちは、hachi8833です。七夕なのに悪天候…

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Ruby

⚓提案: 無名structリテラル

# 同issueより
${a: 1, b: 2}
# 上は以下とほぼ同じ
Struct.new(:a, :b).new(1, 2)
# 同issueより
s1 = ${a: 1, b: 2, c: 3}
s2 = ${a: 1, b: 2, c: 3}
assert s1 == s2

s3 = ${a: 1, c: 3, b: 2}
s4 = ${d: 4}

assert_equal false, s1 == s3
assert_equal false, s1 == s4

つっつきボイス:「Rubyに無名structリテラルがあってもいいですよね」「むしろ今までなかったのが不思議かも」「C言語ならこういうのをよくstructマクロで実現したりしますよね: Rubyはマクロがないからこういうふうに構文で実現する必要がありますけど」「なるほど」

「進行中なのでこれからでしょうね」「ついに$マークを使うのかな?」「あとStructOpenStructのあり方にどう影響するかも気になる」

「あとは今後structリテラル#{}が今のRubyのハッシュリテラル{}のようにどんどん使われるようになるかどうかでしょうね: リテラルが用意されるとみんな一気に使うようになる傾向があると思うんですよ」

「たしかに、Rubyでハッシュがつい乱用されがちなのは、Rubyのハッシュリテラルが書きやすすぎるからなのかもと思うときがあります」「それそれ、そんな感じでstructリテラル#{}が導入されたら今よりもStructが使われるかもしれませんね」

「そうなると今度はハッシュとstructリテラルをうまく使い分けられなくてわけわからないコードになる可能性もあるかも」「ふむふむ」「RubyでStructを使う人はたいていの場合あえて意識的に使っていますし」「特に何も考えないとついハッシュを使っちゃったりしますね😅

追記(2020/07/07)

記事公開後に以下のツイートにやっと気づきました。

⚓Rubyのエンコーディングエラー(Ruby Weeklyより)


つっつきボイス:「文字エンコーディングはたまにしくじることありますね」「この辺は日本人の方が知見たまってるかも🇯🇵

「エンコーディングのCompatibilityErrorは昔ちょくちょく踏んだけど最近あまり見なくなったかな〜」

# 同記事より: CompatibilityError
string_in_utf8 = "Löve"
string_in_ascii = "Löve".force_encoding('US-ASCII')
string_in_utf8.start_with?(string_in_ascii)
...
# Encoding::CompatibilityError (incompatible character encodings: UTF-8 and US-ASCII)

# == で比較するとraiseされずにfalseが返る
string_in_utf8 = "Löve"
string_in_ascii = "Löve".force_encoding('US-ASCII')
string_in_utf8 == string_in_ascii
=> false

「まあ今でもUTF-8とcp932を行き来するときなんかはエンコーディングエラーに気をつけないといけませんけど」「ですね」

参考: Microsoftコードページ932 - Wikipedia


同記事見出しより:

  • エンコーディングをざっくり知る
  • エンコーディングエラーとは
  • Encoding::ConverterNotFoundErrorがいつ起きるかと修正方法
  • Encoding::CompatibilityErrorがいつ起きるかと修正方法
  • Encoding::UndefinedConversionErrorがいつ起きるかと修正方法
  • Encoding::InvalidByteSequenceErrorがいつ起きるかと修正方法
  • エンコーディング問題のもう少し複雑な解決方法
  • その他の問題

⚓Ruby実装ごとの互換性レポート(Ruby Weeklyより)


つっつきボイス:「こんな表が掲載されてます↓」


同記事より

「CRubyの100パーセントおめでとうございます〜🎉」「当然です😆

「TruffleRubyのスコアが高いですね」「JRubyより高いのが意外だけど、考えてみたらコマンドラインの部分はJRubyが低くても仕方なさそう」


同リポジトリより


同サイトより

「Opalまである😆」「オパールって何でしたっけ?」「RubyからJSへのトランスパイラですね」

「Artichokeは?」「う、自分で記事書いておいて思い出せない…😅」「ははぁRustで書かれたRubyですか↓」

Artichoke RubyをUbuntu上でビルドしてみた

「そういえばMiniRubyっていうHaskell実装もありますけど↓、さすがにMRIコンパチではなさそうでしたね」「MiniRubyはRubyライクですって」

「ライクって便利な言葉😋」「まあ本気でライクを超えようとしたらCRubyの巨大なparse.yあたりを再現しないといけなくなるので、新しくRubyと同じものを作ろうとすると大変でしょうけど」

参考: parse.y の歩き方 - ワシの Ruby は 4 式まであるぞ -

「CRubyのコードにgccに依存する部分があったりすると別実装で対応が難しいかな?」「たしかに」「メモリ構造まで一致させないと動かないような部分もあるかもしれないので、互換性を100%にするのはかなり難しいか、もしかすると無理な可能性もあるかも🤔

参考: GNUコンパイラコレクション - Wikipedia

「でもこの記事を見た感じでは思ったより互換性高い🎉」「スゴいですね🎉

⚓AWS SDK for Ruby V2のメンテナンスモード移行&サポート終了のお知らせ


つっつきボイス:「SDK for Ruby V2も11月に終るのか〜」「SDKのV2まだ使ってる人いるのかな?」「AWS懐かし〜(今GCPの人なので)、以前V2で作りました」

「今回のSDKのV2->V3移行は、API互換さえ保たれていれば問題なく動くでしょうね: サーバー側でdeprecateされたらダメですけど」「ふむふむ」

⚓クラウドSDKのサードパーティgem依存はつらい

「AWS SDK for Rubyってあまり使ったことないんですけど、GCPにもRubyで似たようなことをやれるヤツがあって、たしかそっちは結構破壊的にインターフェイスが変わったりすることがありました😭」「マジで?」

参考: API と Ruby ライブラリ  |  Google Cloud

「しかもGCPは内部でfaraday gemに依存しているものがあったりして結構困りました」「サードパーティgemが入ってくるんですか!😳」「そこは切り離して欲しい〜😭」「GCPでちょっとRubyを使いたいだけだったのに内部で古いfaradayがロックされてて、その古いfaradayで動くように他のバージョンを下げないといけなくなったりしてつらかった…」

「なのでこの種のAWS SDKとかGCPのとかは、なるべく外部gemに依存しないように作って欲しいです!」「ほんとそうですよね…」「仮にパフォーマンスのためにサードパーティgemが必要だとしても、せめてサードパーティgemなしでも構築する道を用意して欲しいところです🙏

⚓その他Ruby


⚓クラウド/コンテナ/インフラ/Serverless

⚓書籍『AWS認定ソリューションアーキテクト-プロフェッショナル』


つっつきボイス:「この本早速電子書籍で買いましたよ」「きっと買うだろうなと思いました😋

参考: 【書評】ついにでた!日本初のAWS認定試験プロフェッショナルレベル対応の書籍は、充実した模擬問題と解説を使って学習できます! | Developers.IO

「そういえば自分の取ったAWSソリューションアーキテクト、そろそろ更新の時期だったかな…?」「おぉ、お持ちなんですね」「この資格が日本に上陸した一番最初に取って、たしか2回更新したから5〜6年前かな〜」「いいな〜」「期限は一応2年間ですが、去年3年間に延長されたんだったかな↓」「最近GCPなのでAWSを1年半触ってませ〜ん😅」「今の自分の資格がどうなってるか確認してみよっと(しばらくダッシュボードを操作)」

参考: AWS 認定ソリューションアーキテクト- プロフェッショナル

⚓プロフェッショナル試験とアソシエイト試験の違い

「ちなみにAWSのプロフェッショナル認定は結構難しいです」「どんなのが出るんですか?」「一応サンプル問題のPDF↓は公開されているんですけど、古い問題がずっと更新されないままなんですよ」「ありゃ😆

参考: PDF AWS-Certified-Solutions-Architect-Professional_Sample-Questions.pdf

「プロフェッショナル認定試験の難しいところは、文章を読み取る力がかなり要求されるところだと思います: 通常のアソシエイトだとそんなに問題文は長くないんですけど、プロフェッショナル試験は問題文も回答選択肢も長いので」「たしかにこれは長いですね…」「長い文章を読んで適切な選択肢を選ばないと合格できないのがプロフェッショナル」

「自分が取得したアソシエイトレベルの試験は『こういう状態のとき、この部分はどうなるか?』みたいな短めの4択問題だったりしますけど、プロフェッショナルは『こういう前提でこうしようとしているけど、その理由はなぜなのか?』とか『開発者に渡すべきポリシーは次のどれか?』みたいな感じでもっと細かい」「なるほど!」

⚓書籍を買ったらすぐ受験すべき

「この本買っちゃおうかな〜?」「興味があるならもちろんどうぞ: ただこの種の書籍はあっという間に陳腐化するので、買ったらすぐに受けるべきです」「あ〜そうでしたか!😳」「AWSの試験問題はどんどん更新されていきますし、AWSには自分も知らないような新しいサービスが続々入ってきたりサービスの内容が変わったりするので、正直昔の本は受験の役に立ちません」「そうなんですね…」

「たとえば最近だとS3のリザーブドインスタンス(RI)周りが大きく変わりましたよね: スタンダードRIの他にコンバーティブルRIというものも登場しましたし、しかも最近はスタンダードRIもインスタンスタイプを変更できるようになったんですよ」「おぉ〜!」「やべ〜AWS知らないおじさんになってしまった😅」「AWSのサービス内容は結構移り変わりが激しいので」

参考: リザーブドインスタンス(RI)- Amazon EC2 | AWS

「なのでこの種の本は出たら即買いして、そして期間を置かずに受験しないとすぐ陳腐化します」「な〜るほど!」「バカ売れする本でもないので改定される可能性はほとんどないでしょうし」「ありそう😆」「日本語で勉強したいなら即買い即受験をおすすめします」

「それにAWSの認定試験は実務を理解してないと合格が難しいですし、扱う実務の範囲も広いので知らないサービスがよく出てきてその分難しいです」「う〜む」

⚓ドメインレジストラ7社比較レビュー

つっつきボイス:「この前お名前.comからGoogleのドメインへの移行作業やりましたけどお名前.com使いづらかった😢」「お名前.comは合併や買収を繰り返しているので、サービスによってDNSの管理主体も機能も画面も違う場合がありますね」「ありゃ😅」(以下延々)

「ちなみに自分はどのドメインレジストラの場合もたいていRoute 53に向けちゃいます」「ふむふむ」「レジストラとDNSサービスは別物なので、まあこういう記事を参考に自分の好きなところを使えばいいと思います☺

参考: Amazon Route 53(スケーラブルなドメインネームシステム (DNS))| AWS

「あと最近は防弾ドメインの評判がよくないせいかWHOIS代行をやるところが減ってますけど、お名前.comは一応今もやっていますね」「へ〜」

参考: 海賊版サイト問題の解決を阻む「防弾ホスティング」 その歴史から現在までを読み解く (3/4) - ITmedia NEWS

⚓その他インフラ

⚓言語/ツール/OS/CPU

⚓PHP 8は今年後期に公開予定

union型とattributesが気になりました。


つっつきボイス:「PHPって7が超新しいみたいな印象なのに8が出るのか〜」「PHPはマイナーバージョンでも仕様が大きく変わりますよね」「もう別物ですよね」

「へ〜、PHP 8にJITコンパイラが入るのね」「JITの前はどうやってたんでしょう?」「PHPは5系までしか知りませんが高速化は昔からやってますね: PHP 5あたりからZend Engineのようなものが入ってますし」「なるほど」

参考: 【PHP】PHP と Zend Engine のバージョン - Qiita

「昔はPHPをApacheモジュールで動かすことが多かったけど、最近だとRailsっぽくPHPサーバーで動かすのが多いのかなという印象ありますね」「自分も最近のはわからないけど後ろにApacheモジュールを置いてZendにNginxを置くとかやってました」

参考: モジュール一覧 - Apache HTTP サーバ バージョン 2.4

「PHPサーバーってFPMとかですよね↓」「そういえばDocker HubにもPHPのFPMイメージがあった気がする」「FPM知らなかった😅」「まあRubyで言うWebrickのようなものと思っておけばいいでしょう」

参考: PHP: FastCGI Process Manager (FPM) - Manual
参考: WEBrick - Wikipedia

「自分はApacheモジュールでPHPを使ってた時期が長かったので、FPMで挙動が変わると怖そうで手出ししてませんが」「わかります」「特にサーバー環境変数はApacheを間に挟むかどうかで変わりそうですし」「自分もまだ勇気出ない😆」「まあFPMは既にすごく使われてるので、新しいプロジェクトならFPM版のPHPでいいんじゃないかと思います: 想像ですけど構造的にもFPM版の方がApacheモジュールよりもアプリケーションリソースを上手に使ってくれそうな雰囲気ですし」「FPMのページ見てるけどめっちゃ細かく設定できるみたい👀

参考: php - Docker Hub

⚓Dependency Injection

つっつきボイス:「『生成知識をファクトリーで隠蔽しよう』のあたりから何となくわかる気もする」「こういうのってDI(依存性の注入)って言うんだ知らなかった〜(やってたけど)😅」「いわゆるコンポジット系のパターンで、インターフェイス経由で叩こうみたいなヤツですね」

参考: 依存性の注入 - Wikipedia

「DIという概念自体は昔からあってJava方面で使われてたりしたんですけど、DIを当たり前に使おうみたいに全面的にフィーチャーしてたのはたしかC#勢だったかな〜」「へぇ〜」「あくまで印象ですけど、.NET系が割とDIを多用したことでDIが世の中に広まったのかなと」

参考: ASP.NET Core での依存関係の挿入 | Microsoft Docs

「昔はDIにするとガンガン差し替えられるぞみたいな感じで流行ったところがありますけど、最近はどちらかというとテストしやすくするためにDIを使うことが多いと思うので、昔のように同じインターフェイスを差し替えまくることってそんなにないんじゃないかなという気はしますね」「そうかも」「まあファクトリーまで作り始めると散らばりやすくなるでしょうね」

⚓21世紀のC言語

つっつきボイス:「21世紀にC言語?!」「まあCRubyがC言語で書かれてますし」「そういうことか」「C++ではないんですね?」「C++はもう全然別の言語といってもいいぐらいでしょう」「それもそうですね😆

参考: C++ - Wikipedia

「この勉強会の参加者数の上限が32767人というところにニヤリとしてしまいます😆」「32ビットsigned intの最大値🤣」「あ〜そういうことか🤣」「unsingnedではないと🤣

参考: signed と unsigned の違い | C言語 | プログラミング│C│シンメトリック公式BLOG

⚓C言語よもやま

「C言語は読めて損はないと思います: がっつり読むまでいかなくても、ヘッダーとか構造体のあたりを読む必要にかられることはたまにありますし」「C言語、昔挫折したんですよね〜: 何かいい本ないかしら?」「自分はLinuxカーネル周りを読んだりデバイスドライバをちょっといじるためにCを覚えましたね: OSのプロセスやスレッド周りを理解するには、Cをがっつり知らなくてもヘッダーや定義を読んで追うぐらいができれば上等だと思います」「ふむふむ」「あとはCの神マクロとか🤣」「神マクロですか🤣

「この間はてブで見かけたこの辺の記事↓なんかが参考になりますね: CのマクロはLinuxカーネルを追うのに読まざるを得なかったことがあったので、この記事はとてもわかりみがある」「おぉ」

参考: Linuxカーネルで学ぶC言語のマクロ - 覚書

「記事にあるような意味のよくわからないdo whileとか出てくるんですよ↓」「わ、わからん😅」「ジェネリック的なマクロとか、ビルド設定に応じて何もしないようにするマクロなんかも本当によく出てきますヨ」

/* 同記事より */
#define swap(a, b) \
        do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

「これ!こういう構造体のコード↓を見たときにLinuxではこうやるのかと驚きましたし」「こ、これは?」「親構造体へのポインタをマクロで取りに行くというヤツで、要はメモリアドレス的にさかのぼって取りに行くだけですけど」「ひえ…😅

/* 同記事より */
/**                                                                                                                                                                                                                                           
 * container_of - cast a member of a structure out to the containing structure                                                                                                                                                                
 * @ptr:        the pointer to the member.                                                                                                                                                                                                    
 * @type:       the type of the container struct this is embedded in.                                                                                                                                                                         
 * @member:     the name of the member within the struct.                                                                                                                                                                                     
 *                                                                                                                                                                                                                                            
 */
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

「こういうのとかメモリのアラインメントの話とかでCがちょっとわかった気持ちになれましたね」「Javaだと絶対出てこない感じ😆」「C++は知りませんけど、今はさすがにC++でもこんな書き方しないかな〜?」「しない方がよさそう😆」(以下延々)

⚓その他言語


つっつきボイス:「そういえば最近雑誌買ってないな〜」「買わないんですか?」「読みたいのはたいてい特集なので、特集が別冊になったときに買うことはあります」「今本屋に物理的に行けなくなってますし」「店頭で買うとたまに同じ号を2冊買っちゃうことがあって😭」「まあそれはそれでいいじゃないですか☺

「昔はUNIX Magazineを舐めるように読んでましたし、ぷらっとホームの広告なんかもも楽しく読んでましたし」「最近そういうのしなくなったかも」

参考: UNIX magazine 最終号 | スラド
参考: ぷらっとホーム - Wikipedia

「雑誌の特集は強い人が書いているのでやっぱり質が高いですよね👍」「雑誌だとコピペできないのが残念ですけど」「最近はたいてい記事にURL書いてますよ〜😋」「書籍もURL載せてますし」

「自分は老眼なので電子書籍じゃないと買う気がしないんですけど、Kindleで買ったときに残念なのがソースコードの行の隙間がすごく大きくて場所を取っていることとコピペしづらいこと😭」「あ〜あれはやりづらいですね」「組版のエンジニアが頑張ってくれないとどうしようもない」「ソースコードの背景色がなくて本文と区別が面倒なことも多くて😢

「それなら画像をキャプチャしてOCRでソースを取り出せばよかったりして」「そこまでやりますか😆」「その方が謎の制御文字やら全角文字やらが入らなさそうですし」

⚓その他

⚓京の形見分け


つっつきボイス:「スパコン『京』の形見分けというか」「京に寄付できるのはいいですよね」「5万円以上寄付すると京グッズがもらえるけど先着順のみか〜」「寄付自体は今も継続してますけど、グッズはもうおしまいですね」「桐の化粧箱欲しい〜」

「もらいそこなった人がせめてもとマジックで京と書いてみたのを見つけました↓」「こ、これは😆」「気持ちわかる〜」「Pentium Pro?」「Core 2 Duoって書いてるのが見えた」

参考: Intel Core 2 - Wikipedia

「ついでですが、例の富嶽のアーキテクチャが公開されているのを初めて知りました↓」「そうそう、富嶽は公開されてますヨ: ブロック図なんかもしっかり書かれているので興味ある人はどうぞ」「えらいな〜」

「よくぞここまで公開しましたね」「まあ国のプロジェクトですし」「このドキュメントだけでも相当金がかかってるはずですし、しかも日本語ですよ🇯🇵

「個人的にはA64FXという名前がちょっとAthron 64 FXみたいだな〜って😆」「それちょっと思いました😆」「初めて買ったのがAthron 64 FXでした」

参考: Athlon 64 FX - Wikipedia

「しかしこういうのを見ると、税金を投入する価値があるって思いますね」「まさに国力に直結しますよね」「もちろん技術者としては英語が読めるべきというのはありますしそれももっともなんですけど、英語の壁のせいでこういう技術のパイが広がらなかったらもったいないですし」「日本語だと読みやすさが違いますし」「ファースト1マイルのところで英語の壁でフィルタされてしまうと、『最初は日本語で勉強するけど後から頑張って英語でも読む』みたいな流れも止まってしまいますし」「たしかに」

⚓番外

⚓論文を読む理由


つっつきボイス:「いつもと毛色が違いますけど、自分が論文をちゃんと読む機会がなくて、この記事みたいに『この論文は自分に読まれるために書かれたのでは?』という気持ちにまだなったことがなくて😅」「論文は読めるようになるまでが相当つらいですけど😆」「やっぱり修練が必要なんですよね…」

⚓論文を読めるために必要なこと

「論文を読むことの難しさは『最初のうちは自分が正しく読めているかどうかがなかなかわからない』という点にあると思うんですよ: だからこの記事みたいに他の人と一緒に輪読したりしてそこを確認しながらでないと読むこと自体がなかなかできない」「おぉ」

「まず論文には、受理されるためによく見せようと書いている部分もあったりするので、そういう部分を疑いながら選り分けて読み進められるようになる必要があります」「ふむふむ」

「それから論文には紙面の限界があるので、分野にもよりますが、論文に記載されていない膨大な背景知識も要求されるんですよ: なので論文をまったく読んだことのない人がたまたま最新の論文を目にしたとしてもまず読みきれないと思います」「あ〜なるほど!」

「もちろんまともな論文であれば関連研究としてリファレンスを記載しているので、それも全部追って読めば一応読めるんですけど、1つの論文を読むために相当数のリファレンスも読まないといけなくなります」「ですよね」

「その分野の論文を読み慣れている人なら、その論文の他に後どれだけのリファレンスを読んで頑張らないといけないかが大体想像つくんですけど、読み慣れていない人だとちょうど記事にもあるようにたいてい『浅いうわっつらしか学べない』で終わっちゃう可能性大でしょうね」「う〜む😅


「今のCOVID-19関連の論文も、それだけ読んでわかるものでもなさそうですね」「まあ医療系の論文はコンピュータ系とかなり文化が違うようですけど」「あぁ、そうかも😳


「論文のスタイルは分野によって相当違うことがあります: たとえばコンピュータサイエンス(CS)系だと指導教官がlast authorになるという慣習があるんですけど、分野によっては著者名を単にアルファベット順に記載するところもあるそうです」「へぇ〜」

「論文を知らない人向けに補足すると、論文の多くは共同研究なので著者が複数であることが多くて、1番目がfirst author(筆頭著者)と呼ばれるんですけど、CS方面ではその論文に一番コミットした人がfirst authorになるという慣習があります」「へぇ〜」「そして重要なのは、first authorにならないとその人の業績とみなされない文化があること」「そうなんですね…」

「共同研究はいろいろセンシティブなところがあって、たとえばその論文に50%相当で貢献していたとしても、論文のfirst authorになるかどうかで業界での認識が大きく変わります」「なるほど」「共同研究だと論文を複数出して、論文ごとにfirst authorを交代するなどの配慮をすることもよくありますし」

「つまり論文を読むときは分野の文化的背景についても知っておく必要があるわけです🎓: たとえば論文を読み慣れてくるとlast authorを見るだけで『これはあそこの研究室で出している論文かなるほど』みたいな情報が読み取れるようになる」「な〜るほど!」

「CSだとlast authorは指導教官とか研究チームをリードする立場の人がなることが多いので、last authorの経歴を追えばどんな研究をしているかがだいたい見えてきますし、『この研究所はこういう研究に強いんだな』という情報もそこから芋づる式に見えてきます」「そうやって読むんですね」「大学名よりはlast authorの方がそういう情報にたどり着きやすかったりしますね」

「そういったわけで、論文の読み方やそうした文化的背景をがっつり教えてもらう機会がないと、独力で論文を読めるようになるのはかなり難しいと思います」「大学や大学院ってそのための場所ですよね…」「論文を読めるようになりたい人は、たとえば社会人向けの大学院のようなところで学ぶといいんじゃないかと思います」


後編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200706前編)Railsでのマルチテナンシー実装戦略を比較、Railsでサブクエリを使う、URI.parserが非推奨化ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly

週刊Railsウォッチ(20200713前編)rspec-openapiでスキーマ自動生成、Rails Architect Conf動画、where()ハッシュキーに比較演算子条件を書ける機能ほか

$
0
0

こんにちは、hachi8833です。これが今度のAWS Summit Tokyoの目玉イベントなんですね。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

つっつきボイス:「もう今年も半分終わりか〜」「半分超えちゃいましたね」「AWS Summit Tokyo、今年はリモートでやるってどこかで見たナ🎉

「その『電笑戦 ~ AI は人を笑わせられるのか』とかいう電脳大喜利みたいな企画に募集がいっぱい来たそうです」「『ボケて』のAI版みたいな😆」「また大変そうな企画を😆」「ちゃんと生成モデルを作ってやるんですね」

「素人目にはこれで笑い取れるものを生成できるんだろうかって思いますけど」「たしかこの種の試みって他でもやっていて、一発で大ネタをキメるのは難しいけど、いくつか候補を出してその中から選ぶみたいなのだと割といい感じにできるらしいし、学習結果をフィードバックしたりして強化したりもできるので、何度も回しているとだんだん上手くなってくるという感じらしいですね」「人間様が集まって勝手にフィルターとして機能してくれると最高でしょうね」「笑いの場合、どういうコンテキストなのかにもよるでしょうね」「時代もありそうですし」

「深層学習は、人間でもなぜそれを思い付いたのか説明できないようなことを扱うのに適してますけど、その代わりAIもなぜそれが正しいのかという理由の説明はできないという」「そこですよね」「結果の説明責任がないのが深層学習ですし」「『理由はわかりませんが皆さんこうしてらっしゃいます』的な」「深層学習はその辺が誤解されやすいところ」「傍から見ると野生の勘とか女の勘みたいな挙動ですよね😆

「それに普通に考えたら難しそうなことにチャレンジするところに価値があるでしょうし」「やってみたら意外にいい結果が出るかもしれませんよね」

⚓今年のAWS Summit Tokyoは

「今年のAWS Summit Tokyoは9月にリモート開催か〜」「まあ人多すぎてしんどいイベントなので、むしろリモートでいいと思います」「同意です!」「講演聞くだけなら自宅の方が快適ですよね」「人が多いと面白そうなセッションの部屋があっという間に埋まっちゃうのもつらくて、最近行ってませんでしたし」「入れないのつらいですよね…」「どの開催地でも結局部屋足りなくなるときしかありませんでしたね」

「まあ行ったら行ったでAWS認定持っている人だけが入れる休憩ゾーンみたいなのを使えますけど😋」「そんな空港のVIPルームみたいなのがあるんですか、いいな〜」「あれでAWS認定取った甲斐はあったかなと」

「リモート開催が今後平常運転になるかもですね」「その分、会場で仕事探したり商談したい人には残念でしょうけど」「それもそうか」「興行として成功するにはそのあたりが今後の課題かも」「AWS Summitはテック系イベントですけど、ブースのあたりを見ていると来客の半分ぐらいがビジネス系寄りな印象がちょっとありますね」「へぇ〜」

⚓Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogを中心に見繕いました。ドキュメントの修正が増えてるので、そろそろ6.1が近い?

⚓ActionDispatch::SSLリダイレクトのデフォルトHTTPステータスを308に

# #L
-   def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
+   def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
      @app = app

      @redirect = redirect
      @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
      @secure_cookies = secure_cookies

      @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
+     @ssl_default_redirect_status = ssl_default_redirect_status
    end
...

      def redirection_status(request)
        if request.get? || request.head?
          301 # Issue a permanent redirect via a GET request.
+       elsif @ssl_default_redirect_status
+         @ssl_default_redirect_status
        else
          307 # Issue a fresh request redirect to preserve the HTTP method.
        end
      end

つっつきボイス:「これはこの間話題になったHTTP 308か(ウォッチ20200629)」「まだステータス名覚えられない😅」「308 parmanent redirect、っと…」

⚓新機能: whereでハッシュキーへの比較演算子条件記入をサポート


つっつきボイス:「@kamipoさんがしれっと入れてました」「おぉ、これは😍

# 同PRより
posts = Post.order(:id)

posts.where("id >": 9).pluck(:id)  # => [10, 11]
posts.where("id >=": 9).pluck(:id) # => [9, 10, 11]
posts.where("id <": 3).pluck(:id)  # => [1, 2]
posts.where("id <=": 3).pluck(:id) # => [1, 2, 3]

型キャストとテーブル名/カラム名解決という点においては、("create_at >= ?", time)よりもwhere("create_at >=": time)という書き方の方がよい。
同PRより大意

# 同PRより
class Post < ActiveRecord::Base
  attribute :created_at, :datetime, precision: 3
end

time = Time.now.utc # => 2020-06-24 10:11:12.123456 UTC

posts = Post.order(:id)
posts.create!(created_at: time) # => #<Post id: 1, created_at: "2020-06-24 10:11:12.123000">

# SELECT `posts`.* FROM `posts` WHERE (created_at >= '2020-06-24 10:11:12.123456')
posts.where("created_at >= ?", time) # => []

# SELECT `posts`.* FROM `posts` WHERE `posts`.`created_at` >= '2020-06-24 10:11:12.123000'
posts.where("created_at >=": time) # => [#<Post id: 1, created_at: "2020-06-24 10:11:12.123000">]

「これは"create_at >= ?"のようにハッシュのキーに条件を入れるというやり方を選んだのか!」「へぇ〜、そんな方法が!」「ハッシュの、しかもキーに!?」「RuboCopに怒られないかしら?😆

「DSLをいたずらに拡張するよりはこの方がうまいですね〜: これなら互換性も保たれるし、条件を文字列で直書きするのと違ってSQLインジェクションにも対策できるし」「このプルリク、ハートマークが47個も付いてますね❤」「引数で渡した文字列がそのままSQLに渡されるよりはいいよねとみんなも思ってるんじゃないかしら」

「あと型キャストについてですが、?プレースホルダで渡す従来の方式だと、渡したTimeWithZoneオブジェクトに入ってる値がそのままSQLのプレースホルダに渡されようとするので、Active Recordが本来ミリ秒で値を丸める処理が通らない: なので、Active Recordがクエリを投げる前に通常のActive Record内でRuby→SQLの間の型変換を通せるようにArelで解釈ができる仕組みにしたかったのかなと思いました」「おぉ」

# activerecord/lib/active_record/relation/predicate_builder.rb#L112
          elsif table.aggregated_with?(key)
            mapping = table.reflect_on_aggregation(key).mapping
            values = value.nil? ? [nil] : Array.wrap(value)
            if mapping.length == 1 || values.empty?
              column_name, aggr_attr = mapping.first
              values = values.map do |object|
                object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
              end
              build(table.arel_attribute(column_name), values)
            else
              queries = values.map do |object|
                mapping.map do |field_attr, aggregate_attr|
                  build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
                end
              end

              grouping_queries(queries)
            end
+         elsif key.end_with?(">", ">=", "<", "<=") && /\A(?<key>.+?)\s*(?<operator>>|>=|<|<=)\z/ =~ key
+           build(table.arel_attribute(key), value, OPERATORS[-operator])
          else
            build(table.arel_attribute(key), value)
          end

「条件の中身もend_with?(">", ">=", "<", "<=")でちゃんと解析してますね↑: 何でも書けるわけじゃなくてここでチェックされる演算子しか使えないということで」「なるほど、やんちゃできないようになってると」「ぱっと見何でも書けそうに見えるけど対策済み😋

# activerecord/lib/active_record/relation/predicate_builder.rb#L138
+         OPERATORS = { ">" => :gt, ">=" => :gteq, "<" => :lt, "<=" => :lteq }.freeze

「あぁこれ見て納得した↑: もともとArelの中ではここにある演算子を使って比較演算の式をビルドするんですよ」「あ〜なるほど」

「ナイス落とし所: 引数で渡した文字列をそのままSQLに渡さずに済むという点でとても現実的な解だと思います👍」「この書き方を初めて目にしたらちょっとドキドキするかも」「そうかも😆」「あってもおかしくないけど、もしかすると見慣れない書き方に拒否反応が出るかもしれなくてやらなかったのかな?」「ハッシュのキーというとスペースを含まないスネークケースの文字という意識が先に立ちそうですし」

⚓Journeyの文字列を式展開に変えてアロケーションを削減


つっつきボイス:「こちらは小ネタですが」「+による文字列結合がよろしくないので式展開に変えたというヤツですね」

# actionpack/lib/action_dispatch/journey/path/pattern.rb#L110
          def visit_STAR(node)
-           re = @matchers[node.left.to_sym] || ".+"
-           "(#{re})"
+           re = @matchers[node.left.to_sym]
+           re ? "(#{re})" : "(.+)"
          end

Rubyでの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

⚓ガイド: Action Cableの記述を追加


つっつきボイス:「こちらも小ネタですが、Action Cableの機能説明が1個足されてました」「いいですね〜」

# guides/source/action_cable_overview.md#L757
### クライアントサイドログ出力
クライアントサイドログ出力はデフォルトで無効になっています。これは`ActionCable.logger.enabled`をtrueにすることで有効にできます。
  ```ruby
  import * as ActionCable from '@rails/actioncable'
  ActionCable.logger.enabled = true
  ```

⚓Rails

⚓大規模マイグレーションを高速化するためにコールバックを全部スキップした(Hacklinesより)

# 同記事より
# GOOD
class BackfillEmployeesWithFriendlyId < ActiveRecord::Migration[5.0]

  # 空のクラスのおかげで大規模マイグレーションを遅くするコールバックを簡単に全スキップできる
  class FriendlyIdEmployee < ActiveRecord::Base
    self.table_name = 'employees'
    extend FriendlyId
    friendly_id :slug_candidate, use: [:slugged, :finders]

    def slug_candidate
      if first_name || last_name
        "#{first_name} #{last_name}"[0, 20]
      else
        "employee"
      end + " #{SecureRandom.hex[0, 8]}"
    end
  end

  def up
    print "Updating friendly_id slug for employees"
    FriendlyIdEmployee.where(slug: nil).each do |row|
      row.save; print('.')
    end
    puts ''
  end
end

つっつきボイス:「タイトルでわかっちゃうぐらい短い記事ですが」「マイグレーション高速化のためにコールバックをスキップするという選択肢はありでしょうね」「コールバックチェインは何やっても遅くなるし」「スキップしないのが理想だけどそうせざるを得ないときはありますし」「ここではfriendly_idを入れたことで大規模マイグレーションすることになったと」「friendly_idはきっかけということですね」

何ということでしょう…Employee.all.each(&:save)がproduction環境で地獄のように遅い。
同記事より

⚓Rails Architects Conference 2020 Onlineの動画がアップ

Rails Architects Conference 2020が7/1より順次オンライン開催


つっつきボイス:「以前記事にしたArkencyさんのRails Architects Conference、結局網膜裂孔で見そびれました…😢」「そうそう、動画上がってたので後で見ようと思ってた」「うっしゃ、後で見ようっと」

「ざっとスライド見た感じでは、話すこと前提で何もかもは書かない方針かな」「英語圏とかキーノートスピーチによくあそう」「プレゼンをちゃんと聞いて欲しいなら、全部盛りしないでこうやって話す用のスライドにした方がいいでしょうね」「たしかに」


以下は同サイトに上がっている動画です(日本語タイトルは仮)。最後の2つは現時点でスライドなしの動画のみです。

⚓キーノート: 今こそ変革の時 — 次のRailsアプリを正しく始めるには

⚓Railsでのマルチテナント

⚓つらくないRailsアップグレード方法とは

⚓Railsのビューを高速化するシンプルな方法

⚓一見よさげなRubyコードにも「低凝集度」「強結合」が潜む(動画のみ)

⚓顧客が知っているのは「何が欲しい」ではなく「今欲しい」(動画のみ)

⚓pastrubies.com: 過去のRuby/Rails情報を配信(RubyFlowより)


同サイトより


つっつきボイス:「詳しい説明が見当たらないんですけど、サイト名のとおり過去のRuby/Railsに関する記事を配信するサイトのようです」「RubyConf 2005 Agenda Releaseというタイトルとかちょっと衝撃的」「当時はペチパーやってたな〜」「どういう基準で選んでるんだろう?」「開いてみたらarchive.orgだったとは」

⚓Rails Bytes: Railsアプリテンプレートを置けるリポジトリ(Hacklinesより)


同サイトより


つっつきボイス:「Everyday Railsの記事で、このRails Bytesがいいよと紹介されていました」「なるほど、Railsアプリのテンプレートを公開できるサイトね↓」「自分で作ったものもアップロードできるようです」


同サイトより

「適当にrails newで作っておいてrails app:template LOCATION="https://railsbytes.com/script/x7msKX"みたいに実行すると一発起動できるそうです」「他の人の作ったテンプレートを見られるのはよさそうですね👍


後でやってみました。

$ bundle exec rails app:template LOCATION='https://railsbytes.com/script/x7msKX'
/Users/hachi8833/deve/rails/hello_world/vendor/bundle/ruby/2.7.0/gems/thor-1.0.1/lib/thor/actions.rb:222: warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
hello world from https://railsbytes.com 👋

⚓rspec-openapi: request specからOpenAPIスキーマを生成

# 同リポジトリより
openapi: 3.0.3
info:
  title: rspec-openapi
paths:
  "/tables":
    get:
      summary: 'tables #index'
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: returns a list of tables
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                    # ...


つっつきボイス:「この間の銀座Railsの#23でk0kubunさんがちらっとお話していたgemです」「あああ〜これ欲しかったヤツ!😂」「これはたしかに欲しいですね〜」「request specから自動生成か〜、もう作っちゃったけど使っちゃおうかな〜?」

「こういうの探してたんですよ〜」「あとは開発手順にうまくはまるかどうかでしょうね: Railsエンジニアが完全にAPI仕様の決定権を持っているならこれでやるのがよさそうだけど、フロントエンド側のエンジニアがAPI仕様を決めたいという場合だとしんどくなるかも」「あ、たしかに」「フロントエンドエンジニアもAPI仕様策定に参加するなら、これまでどおりSwagger(OpenAPI)でやる方がどちらも中身を読めるでしょうし」

参考: API Documentation & Design Tools for Teams | Swagger

「ともあれスキーマ生成は人間がやらずに済めばそれに越したことはないので、これでうまくいくなら使いたいですね」「このgem、まだ作って1か月経ってないのか」「今だとGraphQLの方が新しく使われそうな気もしますけど」「結構よさそう👍


前編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200707後編)Rubyで無名structリテラル提案、書籍『AWS認定ソリューションアーキテクト』、21世紀のC言語ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

週刊Railsウォッチ(20200714後編)ruby-warning gemでワーニングを手軽に抑制、rubocop -aの振る舞いが変わる、書籍『MySQL徹底入門 第4版』ほか

$
0
0

こんにちは、hachi8833です。昨日のGitHubダウン皆さまお疲れさまでした。

⚓Ruby

⚓ruby-warning: ワーニングにフックをかけるgem

以下のツイートでこのgemを知りました。


つっつきボイス:「@jeremyevansさんが作った、まさにwarningにフックをかけるgemだそうで」「へ〜、そういうのができるのか!」「中身はわからないけどどんなふうにフックかけてるのかな?」

# 同リポジトリより
# Ignore all uninitialized instance variable warnings
Warning.ignore(/instance variable @w+ not initialized/)

# Ignore all uninitialized instance variable warnings in current file
Warning.ignore(/instance variable @w+ not initialized/, __FILE__)

# Ignore all uninitialized instance variable warnings in current file
Warning.ignore(:missing_ivar, __FILE__)

# Ignore all Fixnum and Bignum warnings in current file
Warning.ignore([:fixnum, :bignum], __FILE__)

# Write warning to LOGGER at level warning
Warning.process do |warning|
  LOGGER.warning(warning)
end

# Write warnings in the current file to LOGGER at level error
Warning.process(__FILE__) do |warning|
  LOGGER.error(warning)
end

# Write warnings in the current file to $stderr, but include backtrace
Warning.process(__FILE__) do |warning|
  :backtrace
end

# Raise warnings in the current file as RuntimeErrors, with the warning
# string as the exception message
Warning.process(__FILE__) do |warning|
  :raise
end

# Raise keyword argument separation warnings in the current file as
# RuntimeErrors, and write ambiguous slash warnings to $stderr, including
# the backtrace
Warning.process(__FILE__, keyword_separation: :raise,
                ambiguous_slash: :backtrace)

# Deduplicate warnings
Warning.dedup

# Ignore all warnings in Gem dependencies
Gem.path.each do |path|
  Warning.ignore(//, path)
end

「これを使えばキーワード引数みたいな特定のdeprecation warningを抑制できたりして?」「2つ目のy-yagiさん記事を見ると、まさにそれができるようですね」「これは嬉しい❤」「@st0012さんにこのgemのことを知らせたら『2.7で超助かりそう!』って言ってました」

「y-yagiさん記事によると、実はRuby 2.4からそれ用のインターフェイスが用意されていたのか↓」「あらホントだ」

Ruby 2.4からwarningを出力する為のWarning module が提供されるようになりました。Ruby 2.4からはwarningを出力する際は、この Warning moduleのwarnメソッド経由で呼ばれるようになっています。つまり、Warning moduleのwarnメソッドを再定義することで、warningの出力方法をカスタマイズできるようになっています。
y-yagi.tumblr.comより

「Ruby 2.7でキーワード引数のdeprecation warningを止める機能が欲しいというリクエストはあちこちでありましたけど、このgemを見てると文句を言う前に自分で実装しようよ皆さん、というメッセージを感じますね」「まあそれは😆」「自分らもwarningを操作できるインターフェイスがあるなんて知りませんでしたけど、できるとわかっていればたしかに誰でもこういうコードは書けますね↓」

# y-yagi.tumblr.comより
module Warning
  def warn(str)
    return if str.match?("gems")

    super
  end
end

$VERBOSE = true

「言われてみればですけど、warningにフックかけたいというのはいかにもみんなが欲しがりそうな機能ですし、もっと早く知られていてもよかったのかもって思いますね」「このruby-warningみたいに使いやすいインターフェイスを備えたgemを作るには経験値も必要でしょうけど」

「そういう仕組みがあるに違いないということに気づけなかったのは修行不足だった…」「これはもう知らなければ気づけなさそうですけど」「でも言語がこういう拡張ポイントを備えるというのは割と一般的だと思いますし、ましてやRubyはそういう拡張がやりやすい言語なので、そういうものがあるに違いないというところに鼻が利くべきだったなと反省」「Rubyならあるはず!とあのとき思えれば…」「しかもRuby 2.4から仕組みがあったなんて」「deprecation warningに不満を言ってただけで手を動かしてなかったのがバレたような気持ち😅

⚓rubocop -aの振る舞いが変わる


つっつきボイス:「2つとも基本的に同じ内容だと思います」「なるほど、rubocop -aしたときのデフォルトが、safeとマークされたものだけを修正するようになったと」「従来の--safe-auto-correctは非推奨になって、大文字のrubocop -Aにするとすべて自動修正するのか」

「たしかに新しい動作の方がみんなが期待する動作に近いと思いますね」「オートコレクトでコードが動かなくなるのは誰も期待しませんし」「safeでないオートコレクトはコワいです…」

⚓Enumerable#each_entryとは

# docs.ruby-lang.orgより
class Foo
  include Enumerable
  def each
    yield 1
    yield 1,2
  end
end

Foo.new.each_entry{|o| print o, " -- "}
# => 1 -- [1, 2] --

つっつきボイス:「ruby-jp Slackでちょっと話題になってたのですが、これ知らなかったので」「『一要素として複数の値が渡された場合はブロックには配列として渡されます』ってどういうことだろう?🤔」「これだけだとよくわからない…」

「StackOverflowにこういうのがあった↓」「『一要素として複数の値が渡された場合はブロックには配列として渡されます』というそのものの動作か」「複数でない場合は普通のeachと同じなんですね」

Foo.new.each_entry{|o| p o}
# =>
1
[1, 2]
nil

Foo.new.each{|o| p o}
# =>
1
1
nil

Foo.new.each{|*o| p o}
# =>
[1]
[1, 2]
[]

違いは次のとおり。each_entryは1回の繰り返しで渡される要素数に応じて振る舞いを変え、すべての要素を単独のブロック変数に渡す点。yieldの引数がゼロ個の場合はnilをパラメータに取り、yieldの引数が1個の場合はそれをパラメータに取り、それより多い場合は配列としてパラメータを受け取る。
一方eachの場合、各繰り返しでは渡された最初のオブジェクトだけを取る。
StackOverflowより大意


後で当時のコミットを掘り起こしてみると、yieldの引数が複数の場合にarrayにするという意図だったように思えました。

参考: * enum.c (enum_each_entry): new method #each_entry to pack values · ruby/ruby@970e90d

⚓>bundler v2.2.0.rc1リリース

そういえばbundlerはもうRubygemsの一部になっていたのでした。


つっつきボイス:「お、もうすぐ2.2.0が」「どの辺が変わるんでしょう?」「新機能いろいろ増えてるみたい」「Windowsサポートとか大変そうなのが書いてある」「bundlerをアップデートするときはRuby自身のアップデートも含めて慎重にやりたいですね」


ハイライト:

  • WIndowsサポート
  • Gemfileやgems.rbをマルチプラットフォームで完全サポート

主な機能:

  • bundle infoにgemのメタデータが含まれる
  • bundle list --without-groupbundle list --only-groupで複数グループ(スペース区切り)指定をサポート
  • bundle gem--rubocopフラグをサポート
  • bundle gem--test-unitフラグをサポート
  • bundle installによるgemのコンカレントインストールで利用可能なプロセッサ数を自動で使うようになった(Windowsは除く)
  • CI向けの--ciフラグとgem.ciコンフィグをサポート

主な改善:

  • bundler outdatedreleaseタスクなどの表示を改善
  • bundle gemでデフォルトの.rubocop.ymlファイルとRuboCopコンシャスなgemスケルトンができる

⚓その他Ruby


つっつきボイス:「Matzが三三七拍子をお題にプログラミング講座をやった動画があって、それを見て三三七拍子の音を出してみたということだそうです」「三三七拍子のリズムに乗ってノリノリで教えるのかと思っちゃいました😆」「Matzの講座、TEDの正式な動画だ!」

⚓DB

⚓書籍『MySQL徹底入門 第4版』


つっつきボイス:「ぽすぐればかりじゃいかんと思って」「MySQL徹底入門が更新されたのか!」「まじっすか、やった〜🎉」「これは割と有名な本ですけど、今度の版では8.0に対応してくれてますね」「買っちゃおう〜」

「今度のMySQL徹底入門は改訂版だと思うので全部を書き直してはいないと思いますが、やはりいい本なので、持ってなければ買って損はないと思います👍」「新しいからレビューはまだついてないか〜」

⚓MySQLは5.6のアップグレードが大変

「今MySQL 8.0使ってるところってどのぐらいあるんでしょうね」「もうだいぶ使われてると思いますよ: 5.7から8.0への移行はそんなに大変じゃないですし」「いいこと聞いた、今度やってみます〜」「5.6から5.7への移行に比べれば全然楽勝ですし👍」「おぉ、そこが谷の深いところなんですね」

「MySQL 5.6はマイナーバージョンでも挙動が変わるという結構つらいバージョンだったんですよ」「そうそうっ」「5.6.xを5.6.x+1に上げるだけでも手こずるとかは今でもたぶんよくあると思います」

「コンフィグオプションにBarracudaなんちゃらを書かないとutf8mb4にインデックスが張れないとかあったと思いますし」「ひえ〜」

参考: MySQL(InnoDB) で charset を utf8mb4 にする注意点の現在 - DEV Community 👩‍💻👨‍💻

「というふうにMySQLの5.6はいろいろつらいので、5.6という話が入ってきたら速攻で5.6.いくつですか?って聞かないといけない😆」「😆」「5.7になるとだいたい大丈夫になってきますし、8系もだいたいその延長と考えていいと思います」


追いかけボイス: 「このmysql-params.tmtms.netというサイト↓、MySQLのバージョン間でどんなパラメータが増えたり減ったりしたかのdiffが見れるのがスゴい」

⚓CSS/HTML/フロントエンド/テスト/デザイン

⚓Googleが保存する自分のアクティビティデータを自動削除できるようになった


つっつきボイス:「Googleが保存しているアクティビティや履歴やYouTube視聴履歴を3か月とか18か月後に自動削除するオプションがChromeに入ったそうで、自分もとりあえず3か月で削除するようにしてみました」「最近Chromeに通知が出るようになってましたね」

「そういえばある時期からChromeは自分の履歴データを全部CSVでダウンロードできるようになってます」「あ、そうでしたか!」「例のGDPRの流れで一昨年ぐらいからその機能が入ったんですよ」「へぇ〜」

参考: Google Japan Blog: EU 一般データ保護規則 (GDPR)に向けた Google の取り組みについて

「Chrome設定のマイアクティビティだったかな、これはGDPRとは別の機能っぽいですけど」「初めて見ました!」「使ったことなかった」「普段は使いませんよね😆

参考: Google Chromeの閲覧履歴をCSV形式で保存する方法|もりやまよしあき@WEB集客コンサルタント|note

「履歴が必要になるとすれば、何か事件に巻き込まれたとか、会社のGoogleアカウントで私的なブラウズやってないかをチェックするときとかでしょうかね」「うろ覚えですが、SlackもたしかワークスペースオーナーならDMをダウンロードできる機能があったと思います: デリケートな機能なので簡単にはできないようになってるはずですが」「へぇ〜」「あくまで何か事件があったときの法的対応向け機能でしょうね」

参考: プライベートチャンネルとダイレクトメッセージの保存ポリシーを設定する | Slack

⚓言語/ツール/OS/CPU

⚓Unicode便利サイト


つっつきボイス:「1個目はruby-jp Slackで見かけたもので、残りの2つは自分が普段愛用しているUnicode便利サイトです」


「話は逸れますけど実はUnicodeにも麻雀牌の絵文字がひととおり揃ってるんですよ」「へぇ〜麻雀のがあるんだ!」「さっき電子書籍の独自フォント絡みでそのあたりを眺めてたんですけど、どうやら中国式の牌みたいで、白牌に縁取りがありますし、jokerとかいう日本式麻雀では使わない牌もありますし」「ホントだ〜」「縁取りつきの白牌、ジャッキー・チェンの映画で見ました🇨🇳」「逆に赤ドラはありませんけど😆」(以下延々)

「そのときに気づいたんですけど、Slackに麻雀の絵文字を貼り付けると、他の文字はそのまま文字として貼り付けられるのに、なぜか🀄だけはコロンで挟まれる例のSlack絵文字(:mahjong:)に変換されちゃうんですよ」「あらホントだ」「スマイリー😊マークなんかでも同じような現象がありますし、ここのサイトからいくつか麻雀絵文字をSlackに貼ってみるとわかりますよ」「逆に:mahjong:と入力すると🀄になりますね」「麻雀牌を🀄で代表させちゃったのかな」「チュンってどれですか〜?😭(麻雀わかんないの)」「レッドドラゴンって書いてあるやつです」(以下牌活字特殊弁当活字についてなど延々)

後でやってみました↓。


「ついでなんですけど、救急車や救急医療のシンボルは世界的に赤十字🏥からこの「star of life」マーク↓に急速に移行中なんだそうです(Unicodeには未登録)」「そういえばこの青いマーク、テレビでWHOの偉い人の後ろに映ってま したね」「日本でもこれを付けた救急車が走り始めてるそうですがまだ見たことなくって」「スター・オブ・ライフってジョジョのスタンド名みたい」「真ん中の杖がアスクレピオスの杖とかいうんだそうです」

参考: スター・オブ・ライフ – Wikipedia

「赤十字のマークって本来は軍事用のはずだからそれで移行を目指してるのかも?」「それにたしか赤十字のマークって利用に法律上の制限があって、勝手に使ってはいけなかったと思う↓」「あ、ホントだ」「ジュネーブ条約なのか」「そういえば日本はジュネーブ条約に加入してますね」「戦時法にも抵触しそうですし」「赤十字マークはUnicodeに入ってもおかしくなさそうなので、まだ入ってないのは何か理由があって遅れてるのかな?」

参考: 赤十字マークの意味と約束事|赤十字について|日本赤十字社
参考: デザインに赤い十字(✚)を使うと法律違反って知ってましたか? | 初代編集長ブログ―安田英久 | Web担当者Forum

⚓テープの時代


つっつきボイス:「テープデバイスまだまだ使われてるんですね」「LTOいろいろ懐かしい」「容量が400テラバイトってデカい!」「このクラスになると速度も爆速のはずですよ」「へぇ〜!」「DDSぐらいしか触ったことないかな」「DATなら業務で使ったことあります」「テープ触ったことない〜」「私もありませんよ😆」「みんなやってるのかと思ってた😆

参考: Linear Tape-Open - Wikipedia
参考: デジタル・データ・ストレージ(DDS) - Wikipedia
参考: DAT - Wikipedia

「自分は大学時代にエンジニアたるもの一度はテープデバイスを触ってみなければという使命感にかられていろいろ遊びましたヨ」「やっぱり触ってるんですね」「UNIXのtarコマンドがtape archiveの略だとか、mtコマンドでテープデバイスを制御するとかいろいろやったな〜」

参考: tar 】コマンド――アーカイブファイルを作成する/展開する:Linux基本コマンドTips(40) - @IT
参考: mt - コマンド (プログラム) の説明 - Linux コマンド集 一覧表

「テープデバイスは、特にLTOなんかだと世代交代がすごく早いので、古いデバイスがあってもメディアがなかなか手に入らなかったりしますね」(以下延々)

⚓その他ツール


つっつきボイス:「このスライドかなり頑張ってますね」「作ったのは大学生みたい」「クロックの話とかマイコンつくるところまでやってる!」「ソフトウェアとかウォッチドッグタイマーの話してるから、もうプログラミングの世界に突入してる」(以下延々)


「そういえばこのフルカラーシリアルLEDテープ↓という部品が面白いんですよ: 個別のLEDに制御回路が付いているので信号を次々に伝搬させることができて結線が超シンプルになりますし」「あ〜バケツリレー的に信号が伝わるんですね、すげ〜」「しかもLEDユニットを切り離しても使えますし」「いいな〜」「7セグLED1個でもあんなに結線が必要なことを思えばシリアル接続は結線がとても楽ですよね」「ちょっとお高いですけど」「アンペア数が結構高いから電気食いそう😆」(以下延々)

参考: フルカラーシリアルLEDテープ(1m) - スイッチサイエンス
参考: 7セグメントLED | マルツオンライン

「電子回路わがんない😅」「私も小学校のときわかりたかったんですけど当時誰に教わったらいいのかわかんなくて😢」「電子回路はとりあえずブツが手に届くところにあって触れる環境があるのが大事でしょうね」「部活とか」「その頃田舎に転校したのが致命的でした😇

⚓その他

⚓matplotlib


つっつきボイス:「この間Rのグラフのお話しされてたのでこういうの好きかなと思って(ウォッチ20200630)」「そうそう、Rjpwikiにはまさにこんな感じのグラフサンプルがあったんですよ!」「きっとこういうのじゃないかなと思いました😋」「こういうチャートサンプルがないとホントつらいんですよ、こういう感じのグラフを出したいのにどう書けばいいのかがわからなくて」「大事ですね〜」「チートシートまでいかなくてもWikiがあればありがたい🙏

⚓番外

⚓人類のデフォルト設定?


つっつきボイス:「『ラットに抗不安薬を投与した場合閉じ込められた仲間の不安を感じないために仲間を助ける確率が低下する』ってスゴい」

「心理学方面だとこういう研究ってよく行われてて、たとえばヒーロー型思考をする人がいて率先して助けに行くと他の人もそうするようになるとか」「他の人が釣られる感じですね」「チームビルディング系でこういう話を割と見かける印象があって、こういう性格の人を配置すると安定しやすくなるとかいろいろありますし」

参考: 【目的別】チームビルディングゲーム5つのパターンとオススメゲーム

「ただラットの実験結果と人間への適用をどこまで結び付けられるかでしょうね〜」「ラットの結果をいきなり根拠にしていいのかとか」「ラットと人間だと環境も相当違いますし」「人間よりもラットそのものへの興味が涌いてきますけど」「人間に適用できるのは当分先になりそうですね…」「人体で試すわけにもいかないのでしょうがないでしょう☺


後編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200713前編)rspec-openapiでスキーマ自動生成、Rails Architect Conf動画、where()ハッシュキーに比較演算子条件を書ける機能ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby: Symbol#to_sはRuby 2.7 previewでfrozen Stringを返したが今は違う(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Ruby: Symbol#to_sはRuby 2.7 previewでfrozen Stringを返したが今は違う(翻訳)

壊れたインターフェイスが修復されたことで壊れていたことを知る

私がRubyを愛してやまないポイントのひとつは、言語設計のさまざまな側面についてさまざまな方向から注目を集めるやり方です。Ruby言語の変更はJRuby界隈からやってくることがよくあります(Charles Nutter氏からのFeature #16150など)。TruffleRubyを現在率いているeregonことBenoit Daloze氏も主要なコメ主のひとりです。言うまでもありませんが、今もRuby言語の主要な言語設計者であるMatzを含むCRuby界隈の人々もそうです。

このバグにはいくつか興味深い影響がありますので、それについて少しばかり語ってみたいと思います。あるインターフェイスが当初十分練り上げられていなかったからといって、後の修正が困難になるとは限りません。別にto_sを槍玉に挙げようというのではありません。to_sはほとんどの場合それなりによいインターフェイスです。しかしRubyでは、当初ささやかだったものはことごとく、言語が成熟するにつれて多くのユーザーに使われるようになります。どんなインターフェイスでも、利用法が変わったり利用者が増加するに連れて、多少なりとも問題を抱えるようになるものです。本記事で申し上げるのは、そうした多くのよい例の中のほんの一例に過ぎません。

「で、どこがどう変わったの?」

ご存知のように、Rubyのあるオブジェクトでto_sを呼ぶと、そのオブジェクト自身をstringに「変換したもの」が返ることが期待されます。たとえば数値の7to_sを呼べば文字列の"7"が返りますし、:bobのようなシンボルでto_sを呼べば"bob"というstringが返ります。文字列の場合は、何も変更せずに自分自身を直接返します。

Rubyには他にもto_ato_hashto_fto_iといった「型変換」メソッドがひととおり揃っています。さらにややこしいのが、ほとんどの型に型変換演算子が1つではなく2つあることです。stringに変換するメソッドにはto_sto_strがありますし、arrayならto_ato_aryといった具合です。これらの演算子や、その他の型変換方法、それらがどう使われているかについて詳しく知りたい方には、Avdi Grimm氏の良書『Confident Ruby,』を強くおすすめしておきます。この書籍は普通に購入することも、「はがき」を送って’引き換え’にすることもできます。とにかく今は私を信じてください。この手の「型変換演算子」は山ほどあり、to_sはその中のひとつに過ぎません。

Ruby 2.7-preview2はRubyのランダムなプレリリース版のひとつですが、このときからSymbol_to_sは変更できないfrozen stringを返すようになりました。これによっていくつかのコードが動かなくなりますが、それがまさにこの変更で私が踏み抜いた部分なのです。私はRubyで書かれた割と前のスピードテストを定期的に動かしていますが、このテストには私が踏んだ小さな潜在的問題がたくさんあります。

これが問題である理由

この問題はいつ起きるのでしょう?誰かがto_sを呼んだだけでほとんどの結果が台無しになります。以下は私がつまづいたコードですが、これは古いActive Supportを元にしています。

    def method_missing(name, *args)
      name_string = name.to_s
      if name_string.chomp!("=")
        self[name_string] = args.first
      else
        bangs = name_string.chomp!("!")

        if bangs
          self[name_string].presence || raise(KeyError.new(":# is blank"))
        else
          self[name_string]
        end
      end
    end

「これは完璧な方法なのに、それが新しい変更で壊れたの?」と思いますか?そぉぉぉぉなんです!実に、実にいい質問です!(Q1)

少なくとも当時の私が即答できそうになかった「いい質問」は他にもあります。

  • Q2: stringは普通string自身を返すんだとしたら、受け取ったstringを改変すると元のstringも変わるの?
  • Q3: 毎回新しいstringをアロケーションするのは最適化で問題になるの?(Schneems氏はこの問題を#34197で回避せざるを得なくなりました)

これらはいずれも難問です。Q1を明示的に修正すればおそらくQ2がぶっ壊れますし、逆もしかりです。しかもQ3はぞっとします。果たしてこの振る舞いを部分的に停止してよいものでしょうか?Rubyでは可能ですが、気にするべきなのはそこではありません。

本記事冒頭で、to_sというインターフェイスは「練り上げが完璧ではなかった」と書きました。言いたかったのはそこです。to_sは「うまくやれる場合もある」限定的なインターフェイスであり、それが使われるコンテキストについて単に十分考察されていなかったのです。これはどんなインターフェイスでも同様で、「これまで想像もしなかった新しい利用法、新しいコンテキスト、新しい応用が出現する」か、「元の設計が間違っている」か、そのどちらかになるのです。

間違っている」なんて書くのは強すぎでしょうか?そうとも限りません。Charles Nutter氏は#16150のコメントで、現在の設計は私たちの利用法において単純に「安全でない」と指摘しています。結果を改変したらどうなるかが保証されておらず、改変が合法かどうかを決められる保証もありません。実際、人々は結果を改変しているのです。仮にみんなが結果を改変していなければ、安全と最適化のために結果をちょいとfreezeしたとしても誰ひとり気が付かないでしょう(詳しくは後述)。

そしていつの日か、変換メソッドは(to_sに限らず)一般に結果の改変が安全でないことにも気づくでしょう。to_sだけを犯人扱いするのは疑問です。

「三人寄れば文殊の知恵」、そして現実的な答え

Ruby 2.7について言えば、答えは既に出ています。Symbol#to_sがfrozen stringを返すと一部のコードが壊れます。特に「どこが壊れるか」については#16150のコメントで6つほどリストアップされていて、古いものもあればはっきりしないものもあるようです。しかしそれこそpreview版で明らかにしようとしていることですよね?この変更が問題になることが2.7の最終リリースまでに判明すれば、ロールバックは簡単です。これまでもそうしたことはありましたし、今後もあるでしょう。

(これについては実際このとおりになりました。2.7.0リリース版にはこの変更は含まれておらず、この機能については現在再検討中です。この変更は今後再び入るかもしれませんし、形を変えて入るかもしれません。実際にRubyコアチームは、実現可能な後方互換性の維持に努めています。)

それまでは、to_sで呼び出した結果を改変しようとしているそこのあなた、それは改変しないことをおすすめします。言語のその部分が後で壊れるかもしれない(壊れないかもしれない)というだけではなく、今後も動作する保証がないことがわかっているからです。一般に、変換メソッドで複製したオブジェクトの結果が改変可能であると信じてはいけません。結果がfrozenかもしれませんし、悪くすると元のオブジェクトを改変してしまう可能性が無きにしもあらずです。さらに言えば、この改変には保証がありませんし、今できているからといって今後もやれるという保証もないのです。

かくして進歩によって新たな問題が発生し、私たち一同はインターフェイスの設計についてささやかな教訓を得ることになったのです。

関連記事

Rails: Active Recordメソッドのパフォーマンス改善とN+1問題の克服(翻訳)

週刊Railsウォッチ(20200721後編)『パーフェクトRuby on Rails』増補改訂版発売間近、scan_left gemでレイジーなinjectほか

$
0
0

こんにちは、hachi8833です。今週木金は祝日のため、来週7/27、7/28の週刊Railsウォッチは通常記事となります🙇

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓臨時ニュース: 『パーフェクトRuby on Rails【増補改訂版】』が7/25より発売🎉

つっつきの後で知りました。紙の書籍と電子版同時発売だそうです。

Kindle/EPUB版もあるのがありがたいです🙏。詳しくは以下をどうぞ。

⚓Ruby

⚓Polyphony: Rubyでfine grainedコンカレンシー(Ruby Weeklyより)


同サイトより

# 同サイトより
require 'polyphony'

# Kernel#spin returns a Fiber instance
counter = spin do
  count = 1
  loop do
    sleep 1
    puts "count: #{count}"
    count += 1
  end
end

puts "Press return to stop this program"
gets

名前が好きです。


つっつきボイス:「RubyのFiberをいい感じに使えるらしい」「fine-grainedって日本語だと高粒度?この方面ではあまり日本語では言わないかな〜」

「Railsアプリのビジネスロジックでコンカレンシーをふんだんに使うことはあまりないと思いますけど、それこそRackサーバーを自分で書いて複雑なことをさせたいときとか、RubyでSocketをリッスンするようなアプリを書くときとか、後はサイトにもあるようにデータベースコネクタのコネクションプール↓なんかだとコンカレンシーを制御したくなるでしょうね」「そういえばPolyphonyにはHTTPSもできるフル機能のWebサーバーも含まれているそうです」「Webサーバーはまさにコンカレンシーが求められるところですね」

# 同サイトより
DB_CONNECTIONS = Polyphony::ResourcePool.new(limit: 5) do
  PG.connect(DB_OPTS)
end

def query_records(sql)
  DB_CONNECTIONS.acquire do |db|
    db.query(sql).to_a
  end
end

「通常のコンカレンシーだとスレッドの管理になりますけど、PolyphonyはFiberまで使うところがfine-grainedということなんでしょう」「Fiberレベルのコンカレンシーgemということですね」

⚓Huginn: IFTTTやZapier的なWeb連携自動化(Ruby Weeklyより)


同リポジトリより

参考: Webサービスの連携を自動化する Huginn の紹介 - Qiita


つっつきボイス:「ずっと前のウォッチでごく簡単に触れたことがありますが、IFTTTやZapier的なことをやれるRailsアプリだそうです」「オープンソースなのね」

参考: IFTTT: Every thing works better together
参考: Zapier | The easiest way to automate your work

「こういうWeb連携アプリって自分で運用すると大変なので誰かにやって欲しい😆」「そうかも」「インスタンス管理とかOSアップデートとかの面倒見るのがほんとつらいし、止まったときにいろんなところでお困りが発生しますし」「それを考えると、高くてもZapierを使いたくなるかな〜」

「まあ今だとKubernetesが流行ってますし、このHuginnをコンテナでサクッとそこにデプロイできるのであればそんなに大変じゃないかもしれませんけど」「コンテナならやりやすくなりそうですね」

⚓scan_left: injectをレイジー&インクリメンタルに(Ruby Weeklyより)

# 同リポジトリより
require "scan_left"

# 比較のために#injectも記載

ScanLeft.new([]).scan_left(0) { |s, x| s + x } == [0]
[].inject(0) { |s, x| s + x }                  == 0

ScanLeft.new([1]).scan_left(0) { |s, x| s + x } == [0, 1]
[1].inject(0) { |s, x| s + x }                  == 1

ScanLeft.new([1, 2, 3]).scan_left(0) { |s, x| s + x } == [0, 1, 3, 6]
[1, 2, 3].inject(0) { |s, x| s + x }                  == 6

# オプション: `ScanLeft`クラスの利用を明示的に回避したい場合は
# 以下のようにEnumerableでrefinementを使う手もある
#
# このrefinementは`#scan_left`メソッドをEnumerableに直接追加して
# 構文をより明瞭にできる

using EnumerableWithScanleft

[].scan_left(0) { |s, x| s + x }        => [0]
[1].scan_left(0) { |s, x| s + x }       => [0, 1]
[1, 2, 3].scan_left(0) { |s, x| s + x } => [0, 1, 3, 6]

つっつきボイス:「injectのオルタナですか」「そういえばここにもinjectの好きな人が約1名」「ワイのことだ〜😋」「記事にあるように、Enumerableでlazy的なinjectをやれそうですね😋

⚓遅延評価

「遅延評価は、使う側がどこかで処理を止めたいときには速くなりますね」「遅かれ早かれやらなければならない処理はスキップしようがないので、遅延評価にすれば速くなるわけではないでしょうけど」「やらなくてよかった計算をスキップできれば効果が大きい」

「@kamipoさんがRailsの改修でよくやっていますけど、Active Recordベースのカラムって参照されないものの方が多いなんてこともよくあるじゃないですか」「そうそうっ」「カラムはいっぱいあるけど実はidしか取ってなかったとか」「そういうオブジェクトを毎回作ると無駄が大きい」

「ライブラリコードなら使うかどうかわからないようなものを遅延評価で書いておけば効果は大きいと思いますけど、サービスのコードだと、ある程度下のレイヤならともかく、遅延評価を取り入れたところでどうせ全部実行されることの方が多そうですし😆」「おっしゃるとおり😆

「逆にそこら辺を遅延評価で書いてしまうとプロファイラで見ても評価が後ろにずれてしまってどこが遅いのかわかりにくくなりますし」「遅延評価でデバッグしづらくなるの、あるある」「まあ遅延評価にすればいいってもんじゃないよということで」


なお、作者はこの機能リクエストをRuby本体にも投げているそうです↓(審議中)。

⚓無名structリテラル続報(Ruby Weeklyより)


つっつきボイス:「前回も扱った無名structリテラルですが(ウォッチ20200707)、今日久しぶりにつっつきに参加いただいたkazzさんに見せたかったので」「こういうふうに${}でstructを定義できるヤツです↓」「ははぁ〜、なるほど〜😋

# 同記事より
roxie = ${name: "Roxie", breed: "whippet-cross"}

「無名structリテラル、あってもいいですよね」「私も欲しい〜」「むしろこれがなかったから今までRubyのstructがあまり使われなかったんじゃないかって思ったり」

「Rubyのstructってnewする割にはインスタンス1個のまま使われるイメージあるかな」「structはイミュータブルハッシュ的に使われることが多いですよね、自分もそうやって使ってますけど」「たしかにイミュータブルハッシュは欲しい!」

「でも考えてみれば、本来イミュータブルハッシュってハッシュでやるべきじゃないんですよね」「そうそうっ」「無名structを使うとイミュータブルであることを明示できるのがいいよね、と思うわけですよ」「あぁ〜わかりますそれ😂

「無名structリテラルで作ったものはまさにValue Objectとして使えるわけですけど、Value Objectを手軽に定義できるのはとても嬉しい」「structで書くのタイプ量多くてダルいですし😆」「それでついついハッシュに流れてしまうという😆」「無名structリテラル、入ったら使おうかな〜」「もういくつ寝ると来るんでしょうね」

Rails tips: Value Objectパターンでリファクタリング(翻訳)


同記事より:

  • 無名structリテラルが有用な理由:
    • structならキーのタイポを防げる
    • 無名structはイミュータブルであるという意図がはっきり伝わる
    • structならドット記法でシンプルにアクセスできる
    • OpenStructは遅い
  • 同プロポーザルにも「今はハッシュの方が速い」とあるが、ハッシュの利用頻度が高い分最適化が進んでいるだけではないか。

参考: class OpenStruct (Ruby 2.7.0 リファレンスマニュアル)

⚓その他Ruby


つっつきボイス:「ScientistはGitHubの公式gemなのね」「科学系か教育用かなと思ったけど、carefully refactoring critical pathとあるし、パフォーマンスをチェックしながらリファクタリングするgemっぽいですね」

# 同リポジトリより
require "scientist"

class MyWidget
  def allows?(user)
    experiment = Scientist::Default.new "widget-permissions"
    experiment.use { model.check_user?(user).valid? } # old way
    experiment.try { user.can?(:read, model) } # new way

    experiment.run
  end
end

⚓DB

⚓ENUMは銀の弾丸ではない


つっつきボイス:「ぽすぐれのenum型の話: データベースレベルで所定の値だけが入れられるようバリデーションする方法として以下が紹介されてますね」

  1. ENUM型
  2. シンプルなCHECK制約
  3. CHECK制約とFUNCTIONの組み合わせ
  4. 外部キー

「1.のENUMはスキーマの一部で、項目が増えたり減ったりするとスキーマを変更することになる: 増えるときはともかく減らすときは単純にALTER TABLEするわけにもいかなくて面倒になりがちかな」「デメリットとしてはユーザー側で項目を増やしたり減らしたりできない: すぐに変わらないアプリケーションロジックと密に絡むENUMならまあ大丈夫かと思いますが、Redmineの選択項目みたいに運用で項目が増減する可能性があるなら果たしてENUMがいいのかどうかは気にするといいでしょう」

-- 同記事より
CREATE TYPE gender AS ENUM ('male', 'female');

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender gender NOT NULL
);

「なおRailsのenumだと文字列やシンボルを数値に置き換えますが、ぽすぐれのENUMはENUM型なのでSQLレベルではちゃんと要素がmaleとかfemaleみたいに現れますね」「なるほど、RailsのenumとPostgreSQLのENUMは違うんですね」「Railsのenumを使うとSQLクエリで数値になるのが読みづらくて、あまり好きでないかな〜」

「他にPostgreSQLのENUMのデメリットとしては、ENUMの値はそのままでは文字列を結合したりサブストリングを取り出したりできない: ぽすぐれだと一応キャストはできるようですが、意識しておく必要はありますね」

「ENUMに向いているデータとして記事では性別が挙げられてますが、バッチのステータスなんかもまず増減しないので向いているでしょうね」


「2.のCHECK制約で書く方法」

-- 同記事より
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender TEXT CHECK (gender IN ('male', 'female'))
);

「2.のデメリットとして項目が増えすぎると破綻することがあると、なるほど↓」「ぽすぐれにはENUMがあるからこの書き方はせずに済みますけど、ENUMが入る前のMySQLでこの書き方を使った覚えがありますね」

-- 同記事より

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    country TEXT CHECK (country IN (
        'ad',
        'ae',
        'af',
        'ag',
        'ai',
        'al',
        'am',
        'an',
        'ao',
        ...
    ))
);

参考: ENUM型 | MySQLの使い方


「3.はCHECK制約の参照先をFUNCTIONにするというもので、単なるCHECK制約だと同じ項目を別のところでも使うとDRYでなくなってしまいますけど、FUNCTIONにすることで回避できます」

-- 同記事より

CREATE OR REPLACE FUNCTION valid_gender(TEXT) RETURNS BOOLEAN AS $$
BEGIN
    RETURN ($1 IN ('male', 'female'));
END
$$ LANGUAGE plpgsql;


CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    gender TEXT CHECK (valid_gender(gender))
);

「4.は正規化レベルを1つ上げて外部キーとして扱うというもの: これが一番RDBらしい解決方法かなと思います」「デメリットとして追加テーブルがめちゃくちゃ増える可能性があるというのもごもっとも」

-- 同記事より
CREATE TABLE countries (
    code TEXT PRIMARY KEY
);

INSERT INTO countries (code) VALUES ('ad'), ('ae'), ('af'), ('ag'), ('ai'), ('al'), ('am'), ('an'), ('ao'), ...

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    country TEXT NOT NULL,

    FOREIGN KEY (country) REFERENCES countries(code)
);

「記事の末尾にまとまっているこの表↓は実装方法を複数知っておくうえでなかなかいいですね」「Railsの場合、Railsのenumという列がもうひとつ加わるかな」「タイトルの銀の弾丸云々も、どちらかというとENUM以外の方法もあるよという点に重きを置いている感じですね」「実践的でいい記事だと思います👍

⚓JavaScript

⚓Elmとは

Haskellライクな言語だそうです。

// https://guide.elm-lang.orgより
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Browser.sandbox { init = 0, update = update, view = view }

type Msg = Increment | Decrement

update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

参考: Railsで愉快な言語Elmを使う - Qiita


つっつきボイス:「エラーメッセージが読みやすいとかTSとReduxより楽という噂を聞いたのでElmをエントリしてみました」「まあ使いたい人が使うヤツでしょう」「一応Webpackerで入れられるそうですが、Railsでの利用例の記事は今のところあまり見かけない感じでした」「最近だとフロントエンドとバックエンドでリポジトリを分けて、RailsはAPIとかGraphSQLを担当するみたいなのが増えてる感じはあるので、ElmみたいなのをRailsと同じリポジトリに入れることはそんなになさそうな気もしますけどね」

⚓CSS/HTML/フロントエンド/テスト/デザイン

⚓QUIC


つっつきボイス:「この記事はてブで見ましたけど、よくここまでやったなという感じ」「最近はDNSもQUIC実装が進んでたりしますけど、この記事みたいにBGP over QUICやるのはアツい」「まあBGPを触る機会がそもそもありませんけど😆

参考: BGP(Border Gateway Protocol)とは

「結局、TCPは便利だけどあらゆるニーズに応えられるとは限らないということでしょうね: フローコントロールを自分でやりたいならTCPはいらないでしょうし」

⚓その他フロントエンド


つっつきボイス:「前にも話題にした気がしますけど(ウォッチ20200204)ついに始まったのね」「受験料1万円か〜」「試験を受ける金銭的なメリットはぱっと思いつきませんけど、セキュリティを勉強するエンジニアが理解度を確かめるのには有用だと思います👍」「徳丸先生のことだから問題もそれなりに難しそうだし、範囲も相当広いんじゃないかしら」

⚓番外

⚓ゲーム音楽サイトはRails+React製


同サイトより


つっつきボイス:「ゲーム音楽って知らない世界なんですが、はてブ民が泣いて喜んでたので」「泣かないけど全俺が喜んだ: ありがとうございます、ありがとうございます😂

「そうそう、このサイトのソースコードがGitHubリポジトリに上がってますよ↓: しかもRailsベースのGraphQLとReactベースのフロントエンド」「お〜マジですか!」「作者のブログもありますし↓」

「しかしいつの間に?」「いや単に自分の好きな曲がなかったので、どうやってデータをクローリングしてるのかなと思って昨日ソースコードを見てた🤣」「そうでしたか🤣」「シューティングの曲が少ないな〜と思いながらざっと見た感じでは、どうやらクローラじゃなくて自分でデータ入れてるみたい」「それもスゴい💪


後編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200720前編)10月開催「Kaigi on Rails」CFP募集中、enumにデフォルト値設定機能、RailsでBitemporal Data Modelほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly


Ruby PStoreの実用的な使いみち(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Ruby PStoreの実用的な使いみち(翻訳)

この数週間、arkencyブログでいくつもの改善を進めてきました。そのひとつがブログ記事のソースコード公開です。私たちの結論は、記事をオープンにすることでフィードバックループを短縮でき、ブログの読者が以下のように内容を改善できるようになるというものでした。

Nanoc + Github

ここ数年、私たちのブログはnanocという静的サイトジェネレーターで回していました。markdownファイルをひとまとめに突っ込んでレイアウトを付けると、反対側からHTMLが出てくるというものです。このマジックを「コンピレーション」と呼ぶことにしましょう。nanocの素晴らしい機能のひとつにdata sourcesというものがあります。これを使うと、ローカルファイルシステム以外のコンテンツでもレンダリングできるようになります。適切なアダプタを用いることで、記事やページといったさまざままデータ項目をサードパーティAPIからフェッチできますし、SQLデータベースやGitHubからもフェッチできるのです!

私たちのブログのバックエンドとしてGitHubを選んだ理由は、何も考えずに使えるからです。開発者はGitHubに慣れ親しんでいますし、markdownをプレビューできるなかなか素敵なWebエディタも統合されています。内容についての議論はプルリクで始められます。忘れないうちに大事なことを書いておくと、実装の多くの部分をoctokitという、APIとやりとりできるgemに任せることができました。

初期のデータアダプタでは以下のような感じで記事をフェッチしていました。

class Source < Nanoc::DataSource
  identifier :github

  def items
    client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
    client
      .contents(ENV['GITHUB_REPO'])
      .select { |item| item.end_with?(".md") }
      .map    { |item| client.contents(ENV['GITHUB_REPO'], path: item[:path]) }
      .map    { |item| new_item(item[:content], item, Nanoc::Identifier.new(item[:path])) }
  end
end

このコードでは以下を行なっています。

  • リポジトリ内のファイルのリストを取得
  • 拡張子でフィルタし、markdownファイルのみを残す
  • 各markdownファイルの中身を取得
  • nanocのitemオブジェクトに変換

問題点を調べるにはこれで十分です。このコードを「現実に」使い始めると、たちまち問題が発生します。どこが問題になるかおわかりですか?

元データの改善

markdownファイルが100個もあるリポジトリからコンテンツを取り出そうとすると、100+1件ものHTTPリクエストが必要になります。

  • サイトでレイアウト変更によるコンテンツ再コンパイルが発生すると時間がかかって嫌になる
  • 1時間あたりのAPIリクエスト数に上限がある(トークンを使えばもう少し多くリクエストできますが、それでも上限があります)

これらのリクエストをパラレルにしたところで、リクエストのクォータに達するのが早まるだけです。必要なリクエスト数制限のために、何か一工夫しなければならないところです。

ありがたいことに、octokit gemはHTTPのやりとりにfaradayライブラリを用いています。しかもfaraday-http-cacheミドルウェアの活用方法についてもある程度ドキュメントに記載されています。

class Source < Nanoc::DataSource
  identifier :github

  def up
    stack = Faraday::RackBuilder.new do |builder|
      builder.use Faraday::HttpCache,
                  serializer: Marshal,
                  shared_cache: false
      builder.use Faraday::Request::Retry,
                  exceptions: [Octokit::ServerError]
      builder.use Octokit::Middleware::FollowRedirects
      builder.use Octokit::Response::RaiseError
      builder.use Octokit::Response::FeedParser
      builder.adapter Faraday.default_adapter
    end
    Octokit.middleware = stack
  end

  def items
    repository_items.map do |item|
      identifier     = Nanoc::Identifier.new("/#{item[:name]}")
      metadata, data = decode(item[:content])

      new_item(data, metadata, identifier, checksum_data: item[:sha])
    end
  end

  private

  def repository_items
    pool  = Concurrent::FixedThreadPool.new(10)
    items = Concurrent::Array.new
    client
      .contents(repository, path: path)
      .select { |item| item[:type] == "file" }
      .each   { |item| pool.post { items << client.contents(repository, path: item[:path]) } }
    pool.shutdown
    pool.wait_for_termination
    items
  rescue Octokit::NotFound => exc
    []
  end

  def client
    Octokit::Client.new(access_token: access_token)
  end

  def repository
    # ...
  end

  def path
    # ...
  end

  def access_token
    # ...
  end

  def decode(content)
    # ...
  end
end

以下が追加されていることにご注目ください。

  • upメソッドは、nanocでデータソースを回すときに使われ、キャッシュミドルウェアを導入します
  • concurrent-ruby gemのConcurrent::FixedThreadPoolは、マルチスレッドでのコンカレントなリクエストを実現します

このキャッシュさえ動いてくれれば…faradayにはインメモリキャッシュが備わっているのですが、nanocでの作業フローでは無力です。私たちは何としても、キャッシュをコンパイルプロセスの実行全体に効かせたいのです。ちょうどドキュメントにはキャッシュのバックエンドをRailsのキャッシュ機構に切り替える方法が記載されているのですが、これもnanocの場合は助けになりませんでした。皆さんも、大量のHTMLをコンパイルするだけのためにRedisやMemcachインスタンスを起動したくはありませんよね。

またしても腕まくりが必要になってきました。APIで期待されるものがわかれば、ファイルベースのキャッシュバックエンドを構築できるでしょう。そして実は、コードの基本部分を再実装する手間から私たちを解放してくれるgemが標準ライブラリにあるのですが、なぜかその名をほとんど知られていません。再び巨人の肩に乗せてもらうのはこれで十分です。

PStoreを使う

PStoreはファイルベースの永続化メカニズムで、Hashを利用します。ここに保存できるRubyオブジェクトはMarshalによってシリアライズされてからディスクに吐き出されます。PStoreはトランザクショナルな振る舞いもサポートし、しかもスレッドセーフです。今回の目的にはぴったりですね!

class Cache
  def initialize(cache_dir)
    @store = PStore.new(File.join(cache_dir, "nanoc-github.store"), true)
  end

  def write(name, value, options = nil)
    store.transaction { store[name] = value }
  end

  def read(name, options = nil)
    store.transaction(true) { store[name] }
  end

  def delete(name, options = nil)
    store.transaction { store.delete(name) }
  end

  private
  attr_reader :store
end

結局このキャッシュストアはpstoreの単なるラッパーに過ぎないことがわかりました。これは便利!内部のtransactionブロックのあたりでMutexを用いたことで、スレッド安全性も達成できました。

class Source < Nanoc::DataSource
  identifier :github

  def up
    stack = Faraday::RackBuilder.new do |builder|
      builder.use Faraday::HttpCache,
                  serializer: Marshal,
                  shared_cache: false,
                  store: Cache.new(tmp_dir)
      # ...
    end
    Octokit.middleware = stack
  end

  # ...
end

faradayに永続的なキャッシュストアをプラグインしたことで、キャッシュされたレスポンスのメリットを享受できるようになりました。これにより、以後のGitHub APIへのリクエストはスキップされ、ローカルファイルでリクエストを扱うようになります。キャッシュが古くなるまでは…

キャッシュの正当性はさまざまなHTTPヘッダーで制御できます。GitHub APIで重要なのはCache-Control: private, max-age=60, s-maxage=60です。これとDateヘッダーを組み合わせることで、このコンテンツは最後にレスポンスを受信してから60秒間は正当であるということをざっくり示せます。コンテンツの更新頻度が高ければ、たぶんこれで十分でしょう。ブログ記事の場合、個人的にはもう少し長い方が好みですが。

いよいよnanoc-githubの最後のピースをはめる時が来ました。キャッシュ時間を延長できるようにするfaradayのミドルウェアを用います。これはかなり原始的なコード片で、max-ageの値を好きな値に置き換えます。ここで必要な値として3600秒という時間をセットします。一般的なアイデアは次のとおりです。まずAPIからのHTTPレスポンスがキャッシュにヒットする前にレスポンスを改変します。次にキャッシュミドルウェアは、元のではなく改変されたmax-ageを元にキャッシュの正当性を検査します。シンプルかつ十分ですね。以下のコードをミドルウェアスタックに追加しますが、このときスタック内で正しい順序になるよう注意しましょう😅

class ModifyMaxAge < Faraday::Middleware
  def initialize(app, time:)
    @app  = app
    @time = Integer(time)
  end

  def call(request_env)
    @app.call(request_env).on_complete do |response_env|
      response_env[:response_headers][:cache_control] = "public, max-age=#{@time}, s-maxage=#{@time}"
    end
  end
end

以上でおしまいです!本記事が皆さまのお役に立ち、1つ2つでも学びがあればと願っています。私のTwitterアカウント(@pawelpacana)までご感想をお寄せいただくか、私たちのプロジェクトに★をお付けください。

pawelpacana/nanoc-github - GitHub

Happy hacking!

おたより発掘

関連記事

ソフトウェアパターンを闇雲に適用しないこと(翻訳)

いろいろな言語の連想配列系リテラル記法を比較してみた

$
0
0

参考: いろいろな言語での Map, Dictionary 的なものの名前 - Qiita

上の記事を見ていて、連想配列系の構文でどんなリテラル記法が使われているのかが気になったので、リテラル記法に絞って順不同(思い付いた順とも言う)で調べてみました。メジャーな言語のほか、新し目の言語もチェックしてみました。

あくまで連想配列系構文の基本的なリテラル記法を知りたかったので、細かな機能やヘルパー関数などについては省略しています。サンプルコードが洗練されてないのはご容赦🙇

参考: 連想配列 - Wikipedia

map/dict/hash専用のリテラルがある言語とない言語でざっくり分けました。また、メソッドなどを用いるアクセスは最小限にとどめています。検証には主に以下のサイトを使いました。

間違いがありましたら@hachi8833までお知らせください。

専用の生成リテラル記法のある言語
RubyCrystalElixirPythonPerlJavaScriptTypeScriptDartGoV言語SwiftPHPGroovyLua
専用の生成リテラル記法のない言語
JavaKotlinC#RustScalaJuliaOcamlC言語/C++

⚓専用のリテラル記法のある言語

⚓Ruby

参考: class Hash (Ruby 2.7.0 リファレンスマニュアル)

  • 名称: hash
  • キーはシンボル:でも表記できる(現在は主流)
    • 文字列キーとシンボルキーは文字が同じでも等価にならない点に注意
  • 追加したキーバリューの順序がeachなどで保たれる点が特徴
# リテラルによる生成
hash1 = { 'apple' => 'りんご', 'peach' => 'もも' }

# リテラルによる生成(キーがシンボルの場合)
hash2 = { apple: 'りんご', peach: 'もも' }
  • 実行可能なサンプルコード
hash1 = { 'apple' => 'りんご', 'peach' => 'もも' }
hash2 = { apple: 'りんご', peach: 'もも' } 

# 参照
hash1['apple']
a = hash2[:apple]
puts a # 出力用

# 更新
hash2[:apple] = "林檎"
puts hash2 # 出力用

# 追加
hash2[:pear] = "なし"
puts hash2 # 出力用

# 削除
hash2.delete(:pear)
puts hash2 # 出力用

⚓Crystal

参考: Hash · GitBook

CrystalはRubyコンシャスな文法なので、Rubyととてもよく似ていますが、Rubyにない書き方もできます。

  • 名称: hash
  • シンボルをキーにする場合でも=>は省略できない
# リテラルによる生成
hash = { "apple" => "りんご", "peach" => "もも" }
hash2 = { :apple => "りんご", :peach => "もも" }

以下のようにすることでキーや値の型を明示的に指定できるようです。

# キーはStringかSymbol、値はStringかInt32
hash = Hash(String | Symbol, String | Int32){"foo" => "bar", :baz => 1}

以下のようにリテラルを使わずに生成もできます。

hash = Hash(typeof("foo", "bar"), typeof(1, "baz")).new
hash["foo"] = 1
hash["bar"] = "baz"

# 上は以下と同等
Hash{"foo" => 1, "bar" => "baz"}
  • 実行可能なサンプルコード
hash = { :apple => "りんご", :peach => "もも" }

# 参照
a = hash[:apple]
puts a # 出力用

# 更新
hash[:apple] = "林檎"
puts hash # 出力用

# 追加
hash[:pear] = "なし"
puts hash # 出力用

# 削除
hash.delete(:pear)
puts hash # 出力用

⚓Elixir

参考: Keyword lists and maps - Elixir
参考: 急いで覚えるElixir: Enumerable編 - Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

RubyコンシャスといえばElixirもそうみたいですね。map、keyword listとありますが、どちらもdictとして振る舞えます。ここではmapのみを取り上げます。

  • 名称: map(dict)
  • %{}で生成
  • キーに任意の値を使える
  • 破壊的操作はできず、[] =で更新することもできない
  • シンボルをキーにする場合でも=>は省略できない
map = %{ :apple => "りんご", :peach => "もも" }
  • 実行可能なサンプルコード
map = %{ :apple => "りんご", :peach => "もも" }

# 参照
m = map[:apple]
IO.puts m # 出力用

# 参照2
m = Map.get(map, :apple)
IO.puts m # 出力用

# 更新
u = Map.put(map, :apple, "林檎")
IO.inspect u # 出力用

a = Map.put(map, :pear, "なし")
IO.inspect a # 出力用

# 削除
d = Map.delete(map, :pear)
IO.inspect d # 出力用

⚓Python

参考: 組み込み型 — Python 3.8.4rc1 ドキュメント

  • 名称: dict(マッピング型)
# リテラルによる生成
d = {'apple': 'りんご', 'peach': 'もも'}
  • 実行可能なサンプルコード
d = {'apple': 'りんご', 'peach': 'もも'}

# 参照
a = d['apple']
print(a) # 出力用

# 更新
d['apple'] = "林檎"
print(d)

# 更新2(このappleは引用符で囲めない)
d.update(apple="林檎")
print(d) # 出力用

# 追加
d['pear'] = "なし"
print(d) # 出力用

# 削除
del d['pear']
print(d) # 出力用

⚓Perl

  • 名称: hash
  • 生成時のハッシュ変数に%を付けるのが特徴
  • 生成リテラルに()を使う
  • ハッシュ変数へのアクセス時は%ではなく$
# リテラルによる生成
my %h = ('apple' => 'りんご', 'peach' => 'もも');
  • 実行可能なサンプルコード
use strict;
use warnings;
$, = "\n";      # 出力用

my %h = ('apple' => 'りんご', 'peach' => 'もも');

# 参照
$a = $h{'apple'};
print $a, "\n"; # 出力用

# 更新
$h{'apple'} = "林檎";
print %h, "\n"; # 出力用

# 追加
$h{'pear'} = "なし";
print %h, "\n"; # 出力用

# 削除
delete $h{'pear'};
print %h, "\n"; # 出力用

⚓JavaScript

参考: Map - JavaScript | MDN
参考: キー付きコレクション - JavaScript | MDN
参考: Object - JavaScript | MDN
参考: Object.fromEntries()でオブジェクトをmapする - 藤 遥のブログ

昔ながらのObjectと、機能が多く新しいMapがあります。ここではObjectを扱いました。Objectを連想配列と呼んでいいのかどうか判断がつかなかったのですが、知りたいのはリテラルなのでとりあえずここに置きました。

  • 名称: Object(オブジェクトリテラル)
  • キーは「プロパティ」と呼ばれる
  • obj.pear = "なし";のように、存在しないプロパティ(キーに相当)をいきなり書いて追加できるのが特徴
// リテラルによる生成
const obj = {apple: 'りんご', peach: 'もも'};
  • 実行可能なサンプルコード
const obj = {apple: 'りんご', peach: 'もも'};
console.log(obj) // 出力用

// 参照(以下の2つは同じ)
obj['apple'];
obj.apple;

// 更新(以下の2つは同じ)
obj['apple'] ="林檎";
obj.apple = "林檎"
console.log(obj) // 出力用

// 追加(以下の2つは同じ)
obj['pear'] = "なし";
obj.pear = "なし";
console.log(obj) // 出力用

// 削除
delete obj.pear;
console.log(obj) // 出力用

なおMapには生成用のリテラル構文がなく、new Map([ [キー, 値]... ])で生成します。Object.fromEntries()を使うとObjectに変換できます。

const map = new Map([ ['apple', 'りんご'], ['peach', 'もも']]);
const obj = Object.fromEntries(map);

⚓TypeScript

参考: TypeScriptの型: 辞書型を定義する (Dictionary)|まくろぐ

リテラル記法はJavaScriptのを使っていますが、独自の辞書型も定義できるので、ここに置いていいかどうか迷っています(追記: この{ [expr]: value }という記法はECMAScript 2015以降でも使えると教わりました)。

  • 名称: 辞書オブジェクト(ユーザー定義)
  • interfaceを用いて、キーや値の型を指定した辞書型をユーザーが定義できる
  • (JavaScriptのオブジェクトリテラル記法に乗っかった形だが、オブジェクト型とは異なるらしい)
interface UserDictionary {
  [id: string]: string;
}

const dic: UserDictionary = {'apple': "りんご", 'peach': "もも"};
  • 実行可能なサンプルコード
interface UserDictionary {
  [id: string]: string;
}

const dic: UserDictionary = {'apple': "りんご", 'peach': "もも"};
console.log(dic); // 出力用

// 参照1
console.log(dic['apple']);

// 参照2
console.log(dic.apple);

// 更新
dic['apple'] = "林檎";
console.log(dic);  // 出力用

// 追加
dic['pear'] = "なし";
console.log(dic);  // 出力用

// 削除
delete dic['pear'];
console.log(dic);  // 出力用

⚓Dart

参考: コレクション – Dart逆引きリファレンス | Developers.IO
参考: 【Dart】Mapの順番(HashMap, LinkedHashMap, SplayTreeMap) - のんびり精進
参考: Dart 2 Language Guide

Map、LinkedHashMap、HashMap、SplayTreeMapというものがあるそうです。ここではMapのみを取り上げます。

  • 名称: Map
  • JavaScriptのようなドット表記ではアクセスできない
// リテラルによる生成
var a = {'apple': 'りんご', 'peach': 'もも'};

// この場合Map<String, String>と推論される
  • 実行可能なサンプルコード
void main() {
  var a = {'apple': 'りんご', 'peach': 'もも'};

  // 参照
  print(a['apple']);

  // 更新(以下の2つは同じ)
  a['apple'] ="林檎";
  print(a); // 出力用

  // 追加(以下の2つは同じ)
  a['pear'] = "なし";
  print(a); // 出力用

  a.remove('pear');
  print(a); // 出力用
}

⚓Go

参考: Goプログラミング言語仕様 - golang.jp
参考: 逆引きGolang (マップ)

  • 名称: map
// map[]とリテラルによる生成
m := map[string]int{ "apple": "りんご", "peach": "もも" }

// 参考: 型定義のみの場合
var m map[string]int

// 参考: makeにはリテラルを直接書けない
m = make(map[string]int, 20)
  • 実行可能なサンプルコード
package main

import (
    "fmt"
)

func main() {
    m := map[string]string{"apple": "りんご", "peach": "もも"}

    // 参照
    a := m["apple"]
    fmt.Println(a) // 出力用

    // 更新
    m["apple"] = "林檎"
    fmt.Println(m) // 出力用

    // 追加
    m["pear"] = "なし"
    fmt.Println(m) // 出力用

    // 削除
    delete(m, "pear")
    fmt.Println(m) // 出力用
}

⚓V言語

参考: v/docs.md at master · vlang/v

  • 名称: map
    • おそらく現時点は文字列のみをキーにできる
// リテラルによる生成
mut m := {
    'apple': "りんご"
    'peach': "もも"
}

// 参考: 定義(空のmap)
mut m := map[string]int
  • 実行可能なサンプルコード
mut m := {
    'apple': "りんご"
    'peach': "もも"
}

// 参照
a := m['apple']
println(a) //出力用

// 更新
m['apple'] = "林檎"
println(m) // 出力用

// 追加
m['pear'] = "なし"
println(m) // 出力用

// 削除
m.delete('pear')
println(m) // 出力用

⚓Swift

参考: 配列と辞書(連想配列) - Swift開発者Wiki
参考: Swiftの配列とハッシュ(連想配列) - Qiita

配列と連想配列がほぼ同じスタイル。

  • 名称: 辞書(連想配列)
  • nilを代入することで削除する点が特徴。
// リテラルによる生成
var d = [ "apple":"りんご", "peach":"もも" ]
  • 実行可能なサンプルコード
var d = [ "apple":"りんご", "peach":"もも" ]

// 参照
var a = d["apple"]
print(a)

// 更新
d["apple"] = "林檎"
print(d) // 出力用

// 追加
d["pear"] = "なし"
print(d) // 出力用

// 削除
d["pear"] = nil
print(d) // 出力用

⚓PHP

参考: PHP: 配列 - Manual

PHPでは配列と連想配列の区別がないそうです。

  • 名称: 配列(配列の要素に名前を付ける機能)
  • 生成用リテラルに[]を使っている
    • PHP 5.4までは[]によるリテラル記法はなかった
# リテラルによる生成
$a = ['apple' => 'りんご', 'peach' => 'もも'];

# 参考: 昔のarray()による生成
$a = array('apple' => 'りんご', 'peach' => 'もも');
  • 実行可能なサンプルコード
<?php
  $a = ['apple' => 'りんご', 'peach' => 'もも'];

  // 参照
  $b = $a['apple'];
  var_dump($b); // 出力用

  // 更新
  $a['apple'] = "林檎";
  var_dump($a); // 出力用

  // 追加
  $a['pear'] = "なし";
  var_dump($a); // 出力用

  // 削除
  unset($a['pear']);
  var_dump($a); // 出力用
?>

⚓Groovy

参考: 8. マップ - Apache Groovyチュートリアル

  • 名称: map
Map map = [apple:"りんご", peach:"もも"]
  • 実行可能なサンプルコード
class Code {
  static void main(String[] args) {
    Map m = [apple:"りんご", peach:"もも"]
    println(m); // 出力用

    // 参照
    println(m.apple); // 出力用

    // 更新
    m.apple = "林檎";
    println(m); // 出力用

    // 追加
    m["pear"] = "なし";
    println(m); // 出力用

    // 削除
    m.remove("pear");
    println(m); // 出力用
  }
}

⚓Lua

参考: Lua テーブル(Table)

  • 名称: テーブル(配列と同じ扱い)
  • JavaScriptのようにフィールド名をドット.でも指定できる
  • nilを代入することで削除するのが特徴
table = { apple="りんご", peach="もも" }
  • 実行可能なサンプルコード
table = { apple="りんご", peach="もも" }

-- 参照1
table = { apple="りんご", peach="もも" }

-- 参照1
t = table["apple"]
print(t) -- 出力用

-- 参照2
t = table.apple
print(t) -- 出力用

-- 更新
table["apple"] = "林檎"
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

-- 追加
table["pear"] = "なし"
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

-- 削除
table["pear"] = nil
for key, val in pairs( table ) do -- 出力用
       print ( key, val )
end

⚓専用のリテラル記法のない言語

⚓Java

参考: 【HashMap】Javaで連想配列を扱う!サンプルつき

専用のリテラル構文はありません(HashMapなどのライブラリを用いる)。ここではHashMapのみをチェック。

  • 名称: HashMap
import java.util.HashMap;
public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map;
        {
                map = new HashMap<String, String>();
                // 追加
                map.put("apple", "りんご");
                map.put("peach", "もも");

                // 参照
                System.out.println(map.get("apple"));

                // 更新
                map.put("apple", "林檎");
                System.out.println(map.get("apple")); // 出力用

                // 追加
                map.put("pear", "なし");
                System.out.println(map.get("pear")); // 出力用

                // 削除
                map.remove("pear", "なし");
                System.out.println(map.get("pear")); // 出力用
        }
    }
}

⚓Kotlin

参考: KotlinとMap - Qiita

気になってKotlinも調べたら、[]というindexing operator構文が追加されていましたが、mapの生成リテラルはないようです。ここではhashMapOf()のみ調べています。

  • 名称: map(mutableとreadOnly)
  • []でのアクセスが推奨
    • get()put()set()なども使えるが非推奨
fun main() {
    // hashMapOfによる生成
    val id = "apple"
    val name = "りんご"
    val hashmap = hashMapOf(id to name)
    println(hashmap) // 出力用

    // 追加
    hashmap["peach"] = "もも"
    println(hashmap) // 出力用

    // 更新
    hashmap["apple"] = "林檎"
    println(hashmap) // 出力用

    // 追加
    hashmap["pear"] = "なし"
    println(hashmap) // 出力用

    // 削除
    hashmap.remove("pear")
    println(hashmap) // 出力用
}

⚓C sharp

参考: ハッシュテーブル(連想配列)を使うには?[C#/VB、.NET 全バージョン]:.NET TIPS - @IT

  • 名称: ハッシュテーブル
using System;
using System.Collections;

public class test {
  static void Main() {
    Hashtable ht = new Hashtable();

    // 追加
    ht["apple"] = "りんご";
    ht.Add("peach","もも"); // Add()でも追加できる
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }

    // 更新
    ht["apple"] = "林檎";
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }

    // 追加
    ht["pear"] = "なし";
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }    

    // 削除
    ht.Remove("pear");
    foreach (DictionaryEntry de in ht) { // 出力用
      Console.WriteLine("{0} : {1}", de.Key, de.Value);
    }
  }
}

追記(2020/08/03): 以下の情報ありがとうございます🙇

C#のHashtableは.net framework2.0 (15年前)から非推奨では。System.Collections.Generic.Dictionaryはリテラルというか専用の初期化構文がある - rkchy のブックマーク / はてなブックマーク

参考: Dictionary<TKey,TValue> クラス (System.Collections.Generic) | Microsoft Docs

⚓Rust

参考: ハッシュマップ - The Rust Programming Language

専用の構文はなく、HashMapライブラリを用います。

  • 名称: HashMap
  • ハッシュのアルゴリズムを変更できる
  • 値の更新にinsert()を使うのが特徴
use std::collections::HashMap;

fn main() {
    // HashMap::new()による生成
    let mut map: HashMap<String, String> = HashMap::new();

    // 追加
    map.insert(String::from("apple"), String::from("りんご"));
    map.insert(String::from("peach"), String::from("もも"));
    println!("{:?}", map);  // 出力用

    // 参照
    let apple = String::from("apple");
    let name = map.get(&apple);
    println!("{:?}", name); // 出力用

    // 更新
    map.insert(String::from("apple"), String::from("林檎"));
    println!("{:?}", map); // 出力用

    // 追加(entry()とor_insert()を使用する場合)
    map.entry(String::from("pear")).or_insert(String::from("なし"));
    println!("{:?}", map); // 出力用

    // 削除
    map.remove("pear");
    println!("{:?}", map);  // 出力用
}

追記(2020/08/03): もっといい書き方があるそうです。ありがとうございます!

⚓Scala

参考: Scala Mapメモ(Hishidama’s Scala Map Memo)

Scalaをどちらに分類するか迷いましたが、連想配列のためだけのリテラルというわけではなさそうだったのでここに置きました。

  • 名称: Map(連想配列)
  • ->はタプルを作るメソッド
  • +で更新または追加、-で削除する点が特徴
// リテラルによる生成
var a = Map[String, String]( "apple"->"りんご", "peach"->"もも" )
  • 実行可能なサンプルコード
object Main {
  def main(args: Array[String]): Unit = {
    var a = Map[String, String]( "apple"->"りんご", "peach"->"もも" )

    // 参照
    var d = a("apple")
    println(a); // 出力用

    // 更新
    a = a + ("apple"->"林檎");
    println(a) // 出力用

    // 追加
    a = a + ("pear"->"なし");
    println(a); // 出力用

    // 削除
    a = a - ("pear")
    println(a); // 出力用
  }
}

⚓Julia

参考: 実例で学ぶJuliaプログラミング言語入門
参考: Learn Julia in Y Minutes
参考: Collections and Data Structures · The Julia Language
参考: Julia入門 辞書(ハッシュテーブル)、Set型について - 0x00 nullbyte blog

  • 名称: Dict(Associative Collection)
d = Dict("apple"=>"りんご", "peach"=>"もも")

# 型も指定できる
d = Dict{String, String}("apple"=>"りんご", "peach"=>"もも")

# 以前は以下のリテラルが使えたが、0.4以降でdeprecateされた
d = { "apple"=>"りんご", "peach"=>"もも" }
d = [ "apple"=>"りんご", "peach"=>"もも" ]
  • 実行可能なサンプルコード
d = Dict("apple"=>"りんご", "peach"=>"もも")

# 参照
println(d["apple"])

# 更新
d["apple"] = "林檎"
println(d)

# 追加
d["pear"] = "なし"
println(d)

# 削除
delete!(d, "pear")
println(d)

⚓OCaml

参考: Hashtbl

  • 名称: ハッシュテーブル
let hash = Hashtbl.create 5;;
Hashtbl.add hash "apple"  "りんご";;
Hashtbl.add hash "peach"  "もも";;
  • 実行可能なサンプルコード
let hash = Hashtbl.create 5;;
Hashtbl.add hash "apple"  "りんご";;
Hashtbl.add hash "peach"  "もも";;

(*参照*)
print_endline ((Hashtbl.find hash "apple"));; (*出力用*)
print_endline ((Hashtbl.find hash "peach"));; (*出力用*)

(*更新*)
Hashtbl.replace hash "apple"  "林檎";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

(*追加*)
Hashtbl.add hash "pear"  "なし";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

(*削除*)
Hashtbl.remove hash "pear";;
Hashtbl.iter (fun x y -> Printf.printf "%s -> %s\n" x y) hash;; (*出力用*)

⚓C言語/C++

参考: GLib のハッシュテーブルを使う | Linux 修験道
参考: C 言語のハッシュテーブル! | Linux 修験道
参考: C/C++ で使える Hashtable - いけむランド
参考: C++ ハッシュ連想配列クラス unordered_map 入門

専用の構文はなく、定番のライブラリもひとつというわけではなさそうです。長くなりそうなのでサンプルコードは作りませんでした。ネットではハッシュテーブルを手作りする記事が多く見られました。

  • 名称: 「ハッシュテーブル」と呼ばれることが多い
  • 手作り、またはライブラリ次第

と思ったら、C++11以降ではstd::unordered_mapが使えると教わりました。後で追加してみます。

参考: C++ ハッシュ連想配列クラス unordered_map 入門


追記(2020/08/03): C++でやってみた方がいらっしゃいました。ありがとうございます!

調べてみて

専用のリテラル構文を持つ言語では、{}を生成用リテラルとし、[]でアクセスするものが多く、記法としてはこれが一応多数派なのかなと思えます。Perlでは()、PHPとSwiftでは[]を生成用リテラルに用いていることを知りましたが、Swiftのような新しい言語でも[]で生成しているのを見ると、それはそれで筋が通っていそうな気がしました。

キーバリューをつなぐ記号も、=>->:=などさまざまでした。

ばらつきが最も大きいのは、キーバリューの削除方法のようです。メソッドで削除する言語、削除用キーワードを使う言語、nil代入で削除する言語があり、語もさまざまです。

使う側としては、やはりRubyやPythonのようなシンプルな生成用リテラルがある方が断然楽です。RustのHashMapでキーバリューをいくつも書くのはかなり大変そう…

一方専用のリテラル構文を持たない言語は、おそらくパフォーマンス上最適なハッシュ関数を用途に応じて選びたいという要求から、あえてリテラルを固定しない傾向があるのかもしれないと思えました(あくまで推測です)。このタイプではScalaが比較的書きやすそうなリテラルだと思いました(個人の感想です)。

実際、以下のようにハッシュ関数にはいろいろなものがあり、特性もさまざまです。

参考: ハッシュ関数 - Wikipedia

Juliaが当初採用していた生成用リテラルを後にdeprecateしたというあたり、考えさせられました。

個人的には、TypeScriptのユーザー定義の辞書型が既存のJavaScriptとぶつからないよう割とうまく切り抜けていると思いました。

ざっくりまとめ

生成リテラル

{}
RubyCrystalPythonJavaScriptTypeScriptDartV言語Lua
%{}
Elixir
()
Perl
[]
SwiftPHPGroovy
map[型]型{キーバリュー}
Go
Map[型, 型](キーバリュー)
Scala

キーとバリューをつなげる記号

=>
RubyCrystalElixirPerlPHPJulia
:
PythonJavaScriptTypeScriptDartGoV言語SwiftGroovy
=
Lua
->
Scala

アクセス記号

[]
RubyCrystalElixirPythonJavaScriptTypeScriptDartGoV言語SwiftPHPGroovyLuaKotlinC#Julia
{}
Perl

おたより発掘

週刊Railsウォッチ(20200803前編)書籍『パーフェクトRuby on Rails』増補改訂版、マルチDBで抽象クラスをscaffold生成、GitLabがPumaに乗り換えほか

$
0
0

一週間ぶりのご無沙汰です、hachi8833です。医師がまとめた以下のPDFを知人の医者が推薦しておりました。


つっつきボイス:「今年も半分以上過ぎましたね」「やめて〜聞きたくない😆

「上のスライドざっと見ましたけどわかりやすくていいですよね」「でも読んで欲しい人ほど読んでくれなかったりするという😆」「永遠の課題ですね…」

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Rails: 先週の改修(Rails公式ニュースより)

以下のコミットリストのChangelogを中心に見繕いました。

⚓新機能: マルチデータベースで抽象クラスをscaffold自動生成

  • マルチデータベースで抽象クラスを自動生成する

マルチプルデータベースのアプリでscaffoldによる生成を行う場合、現在のRailsでは--database=引数を渡しても抽象クラスを生成しない。この抽象クラスには設定を書き込むための接続情報が含まれ、そのデータベース用に生成されるあらゆるモデルが自動的にこの抽象クラスを継承するようになる。

使い方

  • 以下はanimalsコネクションから抽象クラスを1つ生成する
rails generate scaffold Pet name:string --database=animals
class AnimalsRecord < ApplicationRecord
  self.abstract_class = true
  connects_to database: { writing: :animals }
end
  • このAnimalsRecordを継承するPetモデルを生成する
class Pet < AnimalsRecord
end
  • 既に抽象クラスが作成済みで、Railsデフォルトと異なるパターンに従う場合、--database=を引数に取る親クラスを渡せるようになる
rails generate scaffold Pet name:string --database=animals --parent=SecondaryBase
  • これにより、AnimalsRecrdではなくSecondaryBase`という親から継承できるようになる
class Pet < SecondaryBase
end

Changelogより大意


つっつきボイス:「マルチデータベース、今使いまくってます」「おぉ、勇気ある!」「メインのデータベースをリードオンリーでやってるんですけど、クラスを上書きしないといけないとか、ハマりましたよ」「ApplicationRecordに相当するものを複数作って、接続先に応じて継承元を変えるというのは、自分もswitch_pointでそういう実装してました」

「データベースが切り替わったときにActive Storageのattachやblogも切り替えるようにしたかったんですけど」「あ、それやると面倒になりがちです」「はい、とても面倒くさくなりました😅」「内部でjoinするコードが自動生成されたりするとややこしくなりそうですし」「やりながら技術的負債になるよなこれって思いました…後で触りたくない…😇


「で、このプルリクは、まさに今話したような、switch_point gemなんかでよく行われる、抽象クラスの作成をジェネレーターでサポートしたということですね: self.abstract_class = trueで抽象クラスを有効にしたものを作るところまでやってくれると↓」

class AnimalsRecord < ApplicationRecord
  self.abstract_class = true
  connects_to database: { writing: :animals }
end

「何と!そんなことしてくれるようになったとは…それ使いたかった〜😢」「そこは自分で抽象クラスを手書きすればいいじゃないですか😆」「まあそうなんですけど、ちょっと悔しい」「見事入れ違いになっちゃいましたね」「まあここで生成しているものはswitch_pointを使ってる人なら普通にやってることですし、みんなだいたいこう実装するんだからジェネレートをサポートしたということで」

「ここではApplicationRecordを継承する形で抽象クラスを生成してますね: ちなみに自分はApplicationRecordと並列する形で接続先に応じた抽象クラスを書いてますけど、まあそこは好みでしょうね」「なるほど、AnimalRecordを継承すると抽象クラスで指定したデータベースに切り替わるんですね↓: writingを指定する度胸はまだありませんけど😅」「writingにするかどうかは用途次第かな」

class Pet < AnimalsRecord
end

⚓Active Storage: 添付ファイルがパージされたときに親モデルをtouchできるようになった

現在のdeletepurgepurge_laterメソッドで用いられているが、そのせいで添付ファイルがパージされたときに親モデルを更新するコールバックをトリガできない。この振る舞いが原因で、#39858で報告されているようなキャッシュ戦略上の問題がいくつか発生している。
変更点:

  • attachment#purgerecord&.touchを追加
  • attachment#purge_laterrecord&.touchを追加
  • ついでにattachment.rbの無駄な空行を削除
  • 変更前だとfailし、変更後ならパスするテストを追加

その他情報:
deletedestroyに変更することはしなかった(after_destroy_commit :purge_dependent_blob_laterのトリガを回避するため)。
同PRより大意


つっつきボイス:「touchされてないとカウンタキャッシュなんかが正しく更新されなくなってしまうので、これは必要な修正でしょうね」


「最近すっかりActive Storageおじさんになっちゃいました」「この間の記事↓ありがとうございます🙏」「いえいえ〜Active Storageネタまだまだあるんでまた書きますよ」「お〜😂

ActiveStorageでアップロードしたファイルとプレビュー画像に認証をかける

「この記事でやってるような認証は、他のストレージエンジンでもだいたいこうせざるを得ないですよね」「S3なんかだと期限付きURLがあったりとか、ストレージエンジンによって挙動が違う部分もありますし」

⚓Redisキャッシュストアでの複数の問題を修正

つっつきボイス:「Redisがらみで似たような修正が3つほどあったのでまとめて貼りました」


Redisキャッシュストアでfetch_multiに渡したオプションがwrite_multiには正しく渡されるがread_multiに正しく渡されなかった。このためnamespace:オプションを渡すと常に失われてしまっていた。このプルリクで修正された問題を再現するスニペットは以下のとおり(テストにも追加済み)。
同PRより大意

store = ActiveSupport::Cache::RedisCacheStore.new
store.fetch_multi("ab", "xy", namespace: "mynamespace") { |key| puts "missed cache for #{key}"; key }
store.fetch_multi("ab", "xy", namespace: "mynamespace") { |key| puts "missed cache for #{key}"; key }

「なるほど、read_multi_mgetにオプションを渡しそびれてたのね↓」

# activesupport/lib/active_support/cache/redis_cache_store.rb#L352
        def read_multi_entries(names, **options)
          if mget_capable?
-           read_multi_mget(*names)
+           read_multi_mget(*names, **options)
          else
            super
          end
        end

fetch_multiread_multiがそれぞれあるというのが何とも」「Redisの内部では最終的にMGETあたりになるんでしょうけど↓」


  • MemCacheStoreDalli gem↓の圧縮を無効にした
  • Dalliで余分な圧縮がかかる問題によって圧縮が2回行われたり、指定の圧縮スレッショルド以下にもかかわらず圧縮がかかったりする問題を修正した
    同PRより大意

  • Redisやmemcachedのキャッシュストアでraw:trueを指定して読み出すと値が圧縮される問題を修正
  • CPUに負荷をかける不要な操作を防いだことでrawキャッシュの読み出しも高速化したはず
    同PRより大意

「次の2つは、Dalliで無駄な圧縮が発生しないようにしたそうです」「Dalli最近使ってなかったな〜」「Redisが使える状況ならあえてmemcachedベースのストアを使う理由もありませんけどね」「ちなみにDalliはmemcachedストアのクライアント」「Redisにrawモードがあるって知らなかった」「修正前はrawでないときは圧縮かけてたのか」

petergoldstein/dalli - GitHub

「そもそも最近memcached使ってませんし」「Redisがあるから😆」「たしかにmemcachedは構築が楽なんですけど、後からRedis的なこともやりたくなるんだったら最初からRedisにしておく方がいいでしょうし」「memcachedのメリットは超絶シンプルなことぐらいですし」「memcachedはクラッシュすると飛んじゃいますし」「そうなんですよ…」

「でもRedisはRedisで、永続化データベースのつもりで使われまくって後で地獄を見るというのもよくありますし👹」「ちょっと前に流行った、NoSQLをMySQLみたいに使うのと似てますよね」「それそれ」「Redisを更新しないといけなくなると、アップデートの間はサービスが数時間停止したり」「まあmemcachedはプロセスを再起動しただけで消えちゃいますけど」


「たしかmemcachedってRailsよりもさらに昔からあった気がしますね」「めちゃレガシー!」「Wikipediaの英語版↓見ると2003年からですって」「GitHubより昔」「ボクの職歴より長い😆」「大学の課題で組み込み機器の内蔵フラッシュストレージに書き込めないという条件を回避するためにmemcachedに保存してたことがあったぐらいだから相当昔ですね〜」「そんなことやってたんですか😆」「そのぐらいmemcachedはシンプルですしコードベースも小さかったと思います」

参考: Memcached - Wikipedia

⚓Action MailboxでX-Original-ToにSendgrid envelopeのrecipientを設定するようになった

SendgridではMailgunと同様、オリジナルのペイロードにあるenvelope JSONにあるパラメータとしてのみBCC受信者を渡すらしい。
このプルリクはSendgridペイロードからのrecipientをprependするコードをX-Original-Toヘッダー以下のraw_emailに追加する。
#38738に多大なヒントを得た。
関連: #38446
同PRより大意


つっつきボイス:「あら、これからSendgrid実装しようと思ってたのに」

「Action Mailboxを使う予定なんですか?」「よく考えたらAction Mailboxでやらなくてもいいかな…」「他にいろんなやり方もあるので今ならそんなにこだわらなくてもいいでしょうし、Webhookでも普通にやれますし」「Action Mailbox自身もSendgridと連携するときにはWebhook使いますし」

参考: Webhookとは? - Qiita

「Sendgridなら使っている人多いし、いいんじゃないでしょうか」「障害多いですけど🤣」「ありゃ😆」「この頃月1に近いペースで落ちるってbabaさんもぼやいてました」「まあメールサービスに障害は付き物なので」「メールの到着が遅れるだけならまだいいんですけど、APIが死ぬとメール自体が到着しなくなるのが厄介」「メールをキューに入れてから死んで欲しい😆

⚓コレクション関連付けがきっかり1度だけオートセーブされるよう修正

再現手順

この振る舞いはRails 6.0.3で新しく発生した。
has_many through:関連付けが用いられ、かつjoinテーブルの2つの外部キーにuniqueness制約がある場合、作成直後のレコード1件を更新するとsaveが2回行われてしまい、uniqueness制約違反が発生する。

class Team < ApplicationRecord
  has_many :memberships
  has_many :players, through: :memberships
end

class Player < ApplicationRecord
  has_many :memberships
  has_many :teams, through: :memberships
end

class Membership < ApplicationRecord
  # membershipsのplayer_idとteam_idにuniquenessインデックスがあるとする
  belongs_to :player
  belongs_to :team 
end

player = Player.create!(teams: team])
player.update!(updated_at: Time.current)

期待される動作

Rails 6.0.2.2では、新しいPlayerMembershipが1つずつsaveされ、かつPlayerからTeamへもリンクされる。以後の更新も成功する。

実際の動作

2回目のMembership#update!で書き込まれたタイミングでActiveRecord::RecordNotUnique例外が発生する。
この問題はどうやら#39124の変更に関連しているらしい。この問題は回避可能だが、これはRailsの以前の振る舞いとは異なる予想外の変更だ。
同PRより大意


つっつきボイス:「この間(ウォッチ20200720)で取り上げた#39173があの後修正されたそうです」「裏で自動的にsaveされるのってちょい怖いですよね」

「テストコードで久しぶりにHABTM見た↓」「まだ動くんだな〜😆

# activerecord/test/models/post.rb#L289
class PostWithAfterCreateCallback < ActiveRecord::Base
  self.inheritance_column = :disabled
  self.table_name = "posts"
+ has_many :comments, foreign_key: :post_id
  has_and_belongs_to_many :categories, foreign_key: :post_id

  after_create do |post|
    update_attribute(:author_id, comments.first.id)
  end
end

参考: 2.8 has_many :throughhas_and_belongs_to_manyのどちらを選ぶか — Active Record の関連付け - Railsガイド
参考: HABTMリレーションシップは悪であるという論争 | A-Listers

⚓Rails

⚓書籍『パーフェクトRuby on Rails【増補改訂版】』


つっつきボイス:「前回のウォッチでは号外止まりだったので」「皆さん続々購入かけてるみたい」「まだ1/3ぐらいしか読んでません😅

「@joker1007さんがDockerの章をたくさん書いてくれたらしいですし↓」「お〜🎉

「こういう最新情報が書籍という形にまとまって手に入るというのはいいことだと思います!」「ホントありがたい🙏」「Railsに参入する初心者がたくさんいるからこそこういう本が出せて売れるわけですし」「Railsの強い人もこんなふうに読んでるそうです↓」「目次見ただけでもわかりみしかない」


「今日ちょうど昼の勉強会でも少し話したんですけど、Linuxカーネルの最新情報がまとまった日本語の本って(自分の観測範囲では)ここ十数年見かけていないんですよ」「そんなに🤣」「なのでLinux方面でそういう情報を追おうとするとホント大変」「自分が学生の頃に読んだ『詳解Linuxカーネル』のLinuxカーネルは2.6で、3.0が出るか出ないかの頃、あとは『Linuxデバイスドライバ』を読んでなるほどと思ったりしたんですけど、今同じ方法で勉強すると書いてあるとおりには動かない部分も出てくるでしょうし」「たしかに😢

参考: Linuxカーネル - Wikipedia

「こういう渋い本ってやっぱり売れないのかな」「『詳解Linuxカーネル』は少なくとも日本語の情報としてあそこまで網羅している本が今のところ見当たらなくて」

「Railsでも、どこにどういう機能があるかという脳内マップが一度出来上がれば、あとは差分を追いかければ済むけど、脳内マップがまだできてない人は、パーフェクトRailsやRailsガイドの他にも、まとまった本を何冊も読んで脳内マップを育てて機能を追いかけられるようにしておくのがよいと思います💪」「1冊で終わらせないと」「そういうときに古い本しかないと悲しいですよね」

⚓lambdaじゃなくてService ObjectやQuery Objectを使うメリットは?(Ruby on Rails Discussionsより)


つっつきボイス:「lambdaでどんだけでかいコードを書きたいのかと」「レスでも、lambdaだと大きすぎるときにService Objectとかを使うとありますね」「lambdaだとエラー時に吐くものがProcになるからデバッグ大変になるし」「処理をまとめるだけならlambdaでもできますけど、クラスや関数を使わないで全部lambdaで書いてもいいかというとそんなことはありませんし」

参考: class Proc (Ruby 2.7.0 リファレンスマニュアル)

「上の他にもdiscuss.rubyonrails.orgでいろいろ盛り上がってて、たとえば以下はapp/javascriptというディレクトリ名はちょっと違うのではという話題でした↓」

app/frontendとかapp/packsにしようぜって言ってる人も😆」「気持ちはわかるけど」「自分で好きな名前にカスタマイズすればいいと思いますけど」「この中ではapp/assets/webpackとかapp/assets/sprocketsがマシかな〜: それならwebpackerやsprocketsみたいなものを通る前のデータがここにあるというアピールになりそうですし、こういうディレクトリに置いたものはそのままの形では公開されないわけですし」

⚓GitLabがUnicornからPumaに乗り換えた話(Ruby Weeklyより)


つっつきボイス:「GitLabのWebサーバーがPumaになったのね」「いつの間に」「世間の多くもいつの間にかPumaに乗り換えてた感ありますね」「Pumaがこんなに普及したのって何ででしょう?」「たぶんUnicornの頃はスレッドセーフでないコードが残っていたからなかなか乗り換えられなかったんでしょうけど、今はスレッドセーフでないコードを書く人がほとんどいなくなったからPumaに乗り換えても大丈夫という流れになったのかもですね」「なるほど」「乗り換えた理由はやっぱりメモリ使用量か」

Pumaにした理由
メモリ肥大化の問題を多少なりとも解決できるのではないかと信じて初期調査を開始した。Unicornのシングルスレッドプロセスから乗り換えれば、実行されているプロセス数や各プロセスのメモリオーバーヘッドを削減できる。Rubyのプロセスはかなりのメモリを消費するが、スレッドのメモリ消費量は、アプリケーションメモリの大半を占めるワーカーよりずっと小さくなる。I/Oが発生するとスレッドは一時停止するが、別のスレッドは引き続きアプリケーションのリクエストを処理できる。そういうわけで、マルチスレッドはメモリやCPUをベストな形で活用し、メモリ消費をおよそ40%も削減できる。
同記事より抜粋・大意

「そういえばGitHubはUnicorn使ってた気がしますね」「今も500エラーページに怒ったユニコーン出ますよね」「GitHubともなるとUnicornもチューニングしまくってるでしょうし、おいそれとは乗り換えられないでしょうね」

後で調べましたが、GitHubの現在のWebサーバーが何なのかは裏が取れませんでした。

参考: [GitHub] プルリクするとユニコーンが怒り出すのだが ? - Atuweb 開発 Log

⚓GoodJob: PostgreSQLベースのマルチスレッドActive Jobバックエンド(Ruby Weeklyより)

bensheldon/good_job - GitHub


つっつきボイス:「ぐっじょぶっていう名前が😆」「ぽすぐれでDelayed::Jobみたいなことをやるそうで、ポーリングの設定もありました」

「実はPostgreSQLってこういう裏で定期的に何かするみたいなジョブバックエンドに使える機能もあるんですよ」「へ〜ぽすぐれにそんな機能があるなんて」「ぽすぐれには何でもあります」「MySQL勢だけどまたつっつき恒例のぽすぐれ乗り換え誘惑が〜😆」「使わない機能もいっぱいありますけどね」「選択肢があるのは大事: まあPostgreSQLのそういう機能にロックインされるとメンテが大変になったりもしますけど」

「このツールを使うと、データベースがぽすぐれならジョブキューもぽすぐれでやれるってことなんですね」「ですです、Redisが要らなくなる😆

⚓HashWithIndifferentAccess


つっつきボイス:「ついさっき公開された記事です」「ThorでHashWithIndifferentAccessexceptメソッドの挙動が微妙に変わったのか: 踏んだことはないけど」「区別しないはずなのに区別されてたんですね」

# 同記事より
# Rails 5.2以前
h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2)
h.except(:foo) #=> {"bar"=>2}
# Rails 6.0
h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2)
h.except(:foo) #=> {"foo"=>1, "bar"=>2}

参考: RailsのAPI ActiveSupport::HashWithIndifferentAccess

以下のプルリクが現在オープンされています。


追記(2020/08/04): koicさんから補足いただきました🙇

⚓その他Rails


つっつきボイス:「これも大事」「テストのメールドメインにはexample.comを使いましょう皆さん」「それ用でない他のドメインを使ったりすると攻撃されるきっかけになることもありますし、たとえ自分で持っているドメインでも何かのはずみで失効する可能性がありますし」「記事にもありますけど、テスト用に使っていいドメイン名はRFCとかで定義されてたと思います」

参考: 例示/実験用として利用できるドメイン名 - @IT
参考: JPドメイン名の活用について | よくある質問 | JPRS

「開発環境に限定するなら他のを使ってもいいのかなという気もしますけど」「でもそうする理由がありませんよね」「どうしてもexample.com以外でやりたいなら自分のメアドでやって欲しい🤣」「ドメイン階層をたどらないといけないテストは@localhostだとできないので、どっちみち@example.comとかにしないといけなくなりますし」

「ところでhoge.comって本当に実在するって知らなかった〜😆」「ありますよ〜😆

参考: hogeとは (ホゲとは) [単語記事] - ニコニコ大百科


以下はつっつき後に見つけたツイートです。


前編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200721後編)『パーフェクトRuby on Rails』増補改訂版発売間近、scan_left gemでレイジーなinjectほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

週刊Railsウォッチ(20200804後編)「RubyKaigi Takeout 2020」9月オンライン開催、メールバリデータtruemail、Gitのmasterが変更可能にほか

$
0
0

こんにちは、hachi8833です。「Kaigi on Rails」のプロポーザルがいっぱい集まったそうです🎉。

さらにこんなイベントもあったことに後から気づきました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

Ruby

臨時ニュース: 「RubyKaigi Takeout 2020」が9月4〜5日にオンライン開催

ついさっき知りました。詳細はこれからのようですが、わずか1か月先なんですね。

最近のRuboCop Railsアップデート

少し前に本体にpendingモードが導入されたことをやっと知りました↓。

参考: RuboCop 0.79.0 リリース解説 - koicの日記


つっつきボイス:「RuboCop 0.80ぐらいから新しいcopをいきなり有効にしなくなっていたそうです」「動いているプロジェクトにとってはその方がありがたいですよね」

「それにしてもRuboCopのルール多いな〜」「これもルールにするの?みたいなのまで入ることもありますし」「素の設定で開発している人っているんだろうかと思ったり」「自分も新し目の設定はyamlで黙らせちゃうこと多いです😆」

Rubyのメソッドオーバーロード


つっつきボイス:「オーバーロードで遊んでみたという記事ですが、やってみたらやっぱりだいぶ遅かったみたいです」

「そしたらWeaverさんがもっと速くしてみたそうです↓」「できるのはわかったけど、使うかな〜?」「C++かと思った」「Twitterのスレには他にもいくつかコードやgemがあったんですけど、だいたい『業務では使うな』って書いてありました🤣」

# Warming up --------------------------------------
#   method overloading   126.264k i/100ms
#               method   255.798k i/100ms
# Calculating -------------------------------------
#   method overloading      1.711M (± 8.2%) i/s -      8.586M in   5.057014s
#               method      5.697M (± 3.3%) i/s -     28.649M in   5.035081s

# Comparison:
#               method:  5696723.0 i/s
#   method overloading:  1711428.7 i/s - 3.33x  slower

「C++のようにメソッドの引数も含めてシグネチャになる言語だとオーバーロードでやるんでしょうけど、Rubyならオーバーロードしなくてもやれますし」「やってみたかったのでやった系の記事なんでしょうね」

参考: メソッドのシグネチャ(signature)とメソッドの構文(syntax)の違い - いっしきまさひこBLOG

truemail: フレームワークを問わないメールバリデータ(Ruby Weeklyより)

rubygarage/truemail - GitHub


同リポジトリより

Truemail.configuration

=> #<Truemail::Configuration:0x000055590cb17b40
 @connection_timeout=1,
 @email_pattern=/regex_pattern/,
 @smtp_error_body_pattern=/regex_pattern/,
 @response_timeout=1,
 @connection_attempts=3,
 @validation_type_by_domain={},
 @whitelisted_domains=[],
 @whitelist_validation=true,
 @blacklisted_domains=[],
 @verifier_domain="somedomain.com",
 @verifier_email="verifier@example.com",
 @not_rfc_mx_lookup_flow=true,
 @smtp_safe_check=true,
 @logger=#<Truemail::Logger:0x0000557f837450b0
   @event=:all, @file="/home/app/log/truemail.log", @stdout=true>>

つっつきボイス:「メールのバリデーターだそうです」「なるほど、書式チェックだけじゃなくて許可/不許可リストとかとの照合もしてくれると」「SPFやDKIMあたりも見てくれるのかな?」

後で調べると、truemailにはSPF/DKIMは入っていませんでした。

参考: 受信時の送信ドメイン認証(SPF/DKIM) | メール機能 | 共用サーバー標準 | 共用サーバー | サービス | レンタルサーバーのWADAX

「こうやって許可/不許可リストのドメインも定義できるのね↓」「いい感じにカスタマイズしてメールバリデーターにできそう」「うれしい人はきっとうれしい😂」「MXバリデーションができるのもよさげ」「メールアドレスをタイポしたときにもある程度検出できるかも」「メアドを厳密にチェックしたいときなんかにいいでしょうね👍」

# 同リポジトリより
require 'truemail'

Truemail.configure do |config|
  config.verifier_email = 'verifier@example.com'
  config.whitelisted_domains = ['white-domain.com', 'somedomain.com']
  config.blacklisted_domains = ['black-domain.com', 'somedomain.com']
  config.validation_type_for = { 'somedomain.com' => :mx }
end

「よくわからん正規表現書かなくてよくなりそう」「メアドチェックの正規表現を自分で書くのはもう無理😇」「地獄」「正規表現が長くなると重くなりますし」

その他Ruby

つっつきボイス:「RubyGemsのブログは、rubygems.orgのAPIキーがHoneycom.ioのイベントログに出てしまっていたのを直したという記事でした: RSSリーダーで個人ダッシュボードのRSSをフィードしている人だけ影響していた可能性があるそうです」「何と!」「2つ目は年末恒例のFukuoka Ruby Awardのエントリ募集のお知らせです」


Aaron Pattersonさんの動画セッション(Ruby Weeklyより)

「@tenderloveさんの動画はチラ見しただけですが、インスタンス変数使いすぎると遅くなるぞ、どれだけ遅くなるか調べたという感じの内容でした」「@tenderloveさん個人のYouTubeチャンネルかと思ったらBoulder Rubyのチャンネルなのね↓」

参考: Boulder Ruby Group (Boulder, CO) | Meetup


「知名度の高いMatzがこういう話↓をフォローしてくれるところがありがたいですよね」「プログラミングを必修授業にしたらプログラミングがキライになる人がきっと大量生産されるとマジ思いますし」

必修、つまり義務化すると、適性のない子でもいやいや勉強をしなくてはならない。これを強制すると、結果的にプログラミング嫌いの子供を大量に生むことになって、逆効果になるんじゃないかと思ったからです。
同記事より

「自分も体育でやらされてサッカー嫌いになりましたし」「先生が嫌いになるとその科目まで嫌いになるというパターン」「で社会に出てからその科目を勉強してみたら思った以上に面白かったり」「そうやって遠回りしがちですよね…」「私も技術家庭の時間がいやでいやでしょうがありませんでしたし」「え?技術とか大好きそうですけど?」「先生が嫌いだったので😅」「授業と縁がなくなってくると面白くなるという」「数学大嫌いだったのに今は本とか買っちゃいますし」「数学って授業じゃないとあんなに面白いのに😆」

思い返すと自分は嫌な先生に出会ったことがなかったのが幸運だったかもしれません。単に忘れっぽいだけかもしれませんが。

DB

アプリケーションDBAが教えるSQLの技(StatusCode Weeklyより)


つっつきボイス:「これは主にPostgreSQLの話みたい」「WAL(Write Ahead Log)とかはぽすぐれの仕様にありますね」

参考: 29.2. ログ先行書き込み(WAL)

「へ〜、UNLOGGEDテーブルってのがあるのね」

-- 同記事より
db=# CREATE UNLOGGED TABLE duplicate_users AS
db-#     SELECT
db-#         lower(email) AS normalized_email,
db-#         min(id) AS convert_to_user,
db-#         array_remove(ARRAY_AGG(id), min(id)) as convert_from_users
db-#     FROM
db-#         users
db-#     GROUP BY
db-#         normalized_email
db-#     HAVING
db-#         count(*) > 1;
CREATE TABLE

db=# SELECT * FROM duplicate_users;
 normalized_email  | convert_to_user | convert_from_users
-------------------+-----------------+--------------------
 me@hakibenita.com |               2 | {3}

参考: PostgreSQL 10の Unlogged Table と Declarative Partitioning - Qiita

「WITHとRETURNINGを使う話」「正規化されてないデータをこうやってCTE(Common Table Expressions)とかで直したり無理やり取り出したりできる感じですね」

-- 同記事より
WITH duplicate_users AS (
    SELECT
        min(id) AS convert_to_user,
        array_remove(ARRAY_AGG(id), min(id)) as convert_from_users
    FROM
        users
    GROUP BY
        lower(email)
    HAVING
        count(*) > 1
),

update_orders_of_duplicate_users AS (
    UPDATE
        orders o
    SET
        user_id = du.convert_to_user
    FROM
        duplicate_users du
    WHERE
        o.user_id = ANY(du.convert_from_users)
    RETURNING o.id
),

delete_duplicate_user AS (
    DELETE FROM
        users
    WHERE
        id IN (
            SELECT unnest(convert_from_users)
            FROM duplicate_users
        )
        RETURNING id
)

SELECT
    (SELECT count(*) FROM update_orders_of_duplicate_users) AS orders_updated,
    (SELECT count(*) FROM delete_duplicate_user) AS users_deleted
;

参考: 7.8. WITH問い合わせ(共通テーブル式)

「low selectivityってあるのはいわゆるカーディナリティのことでしょうか?」「ここではカーディナリティのようですね」

-- 同記事より
db=# INSERT INTO users (username, activated)
db-# SELECT
db-#     md5(random()::text) AS username,
db-#     random() < 0.9 AS activated
db-# FROM
db-#     generate_series(1, 1000000);
INSERT 0 1000000

db=# SELECT activated, count(*) FROM users GROUP BY activated;
 activated | count
-----------+--------
 f         | 102567
 t         | 897433

db=# VACUUM ANALYZE users;
VACUUM

参考: MySQL(InnoDB)でカーディナリティの低いカラムにINDEXを張る - Qiita

「partial indexはぽすぐれ独自の機能みたい」「へ〜こんなことができるんだ↓: これならカーディナリティの高い部分にだけインデックスを効かせたりできますね」「ここまで細かくDBを世話するかどうかですけど、でかいデータだと効果大きそう」

-- 同記事より
db=# CREATE INDEX users_unactivated_partial_ix ON users(id)
db-# WHERE not activated;
CREATE INDEX

「CLUSTERコマンドとか知らないものもあるな」

参考: CLUSTER

「BRIN(Block Range INdex)はインデックスの種類だったかな: 普段使うというより、困ったときに調べて使う機能」「ぽすぐれ、懐が深いですね」

参考: 67 BRINインデックス

「最後の『時間のかかるプロセスの開始はx時ジャストを避けよ』はぽすぐれに限らず言えるヤツですね: x時ちょうどにバッチを開始すると他のバッチも同じ瞬間に起動して負荷が上がる可能性が高いから気をつけないと」「あ〜それですか」「最近のRailsに入ってきたjitterもそういうのを避けるのに使えますし(ウォッチ20191216)」「RailsのActive Jobみたいにジョブをキックする機能にはよくこういうオプションがあります」

「気持ち的にはx時ジャストに動かしたいですけど」「まあジャストにする積極的な意味がないなら記事にもあるようにsystemdのRandomizedDelaySecとかでかわすのがいいでしょうね」「ぽすぐれの機能かと思ったらsystemdでしたか」「cronもanacronあたりを使えばやれたと思います」

参考: systemd - Wikipedia
参考: anacron - Wikipedia

「Railsだとjitterって言うのか〜」「まあ実際は数分もずれたりしませんし、誤差程度にしかずれないから問題にはならないでしょう」「開始タイミングをちょっとずらすことが身を助けることもあるんですね」「ごく短い瞬間にタスクが集中するとそういう問題が起きやすいので」

「全体にDBAのノウハウ記事としてよさそう👍」


目次より:

  • 更新が必要なものだけをUPDATEすること
  • バルク読み込み中は制約やインデックスをオフにすること
  • 中間データはUNLOGGEDテーブルに置くこと
  • プロセス完了の実装にWITHとRETURNINGを使う
  • カーディナリティの低いカラムへのインデックス作成は避けること
  • 部分インデックスを活用しよう
  • 常にソート済みのデータを読み込むこと
  • カラムのインデックスの相関をBRINインデックスで高める
  • インデックスを「見えなくする」技
  • 時間のかかるプロセスの開始は「x時ジャスト」を避けること
  • まとめ

その他DB

クラウド/コンテナ/インフラ/Serverless

AWS Fraud Detectorがリリース(StatusCode Weeklyより)


つっつきボイス:「元記事は英語でしたがもう日本語版出てました」「fraudは不正」「オンライン活動を簡単に特定できるマネージドサービスですか」

「ある程度以上の規模のサイトになるとこういう不正検知を自分たちで作りますけど、不正検知のアルゴリズムを自分で書かなくてもその部分をAWSに外出しできるようになったという感じ」「モデルのトレーニングの話とかも出ているので、おそらく教師データを自分たちで突っ込むといい感じに動いてくれそう」

「こういうインテリジェントな機能がAWSにあるなら使うかも」「外部の有償サービスを導入するよりはやりやすそうですね」

JavaScript

フロントエンドクライアント(GraphQL)でJWTを扱う究極ガイド


つっつきボイス:「徳丸先生がイチオシしてた記事」「そのアーキテクチャを選んだ理由がちゃんと書かれているのがよいと」「JWTトークン、ゆくゆくは必要になってくるのかな」「ざっと覗いてみた感じだととても親切に書かれてるっぽい😋」

「アーキテクチャ設計では、まず前提条件があって、それに基づいてアーキテクチャを選ぶというのは当然かつ重要ですね」「何となくこれにしてみた、ではないと」「世の中でこれがベストプラクティスとされているからこれにした、が理由のひとつにあるぐらいならまあいいかなとは思いますが、この設計は他と比べてここが有利とか、仕様上の条件や性能指標はこの設計で十分満たせる、というところまで持っていくのが重要」

「JWTのようなAPI認証系は後になっていじりたくない部分の筆頭ですよね😆」


見出しより:

  • JWTとは
    • セキュリティ上の懸念点
    • JWTの構成
    • セッショントークンではなくJWTを使う理由
  • ログイン
    • トークンを永続化するかどうか
    • クライアント側でのトークンの利用
  • JWTでGraphQLクライアントをセットアップする
    • トークン期限を扱う
    • エラーハンドリング
  • ログアウト
    • トークンの無効化
    • トークンの「不許可リスト」
  • サイレントリフレッシュ
    • トークンをリフレッシュするしくみ
    • リフレッシュしたトークンの保存場所をどうするか
    • トークンのリフレッシュを伴うログインフロー
    • トークンの期限が切れたときのリフレッシュ
  • セッションの永続化
    • セキュリティ上の懸念点
    • エラーハンドリング
  • 強制ログアウト
  • SSR(Server Side Rendering)

言語/ツール/OS/CPU

パタヘネとヘネパタ

パタヘネって今も更新され続けているんですね。この間セールで買ったKindleの第5版(上下巻)ではMIPSでしたが、次の版はRISC-Vとやらを使うようです。

参考: RISC-V - Wikipedia


つっつきボイス:「この間Amazonでパタヘネ第5版の上下巻Kindle版がセールだったので買っちゃったんですが、少なくとも自分にとっては感激する本でした」「これって何の本ですか?」「文字どおり『コンピュータの構成と設計』😆」「MIPSっていうプロセッサをお題に機械語やアセンブラの動作を中心にがっつり解説してます」

参考: MIPSアーキテクチャ - Wikipedia

「まだ流し読んだ程度ですけど、何しろ自分が大昔に初めて覚えた言語が8080アセンブラだったので、当時と比べてすごい進歩だなと😂」

参考: Intel 8080 - Wikipedia

「その記事によると翻訳の質がイマイチみたいですけど?」「いえ、用語を日本語の熟語で表す傾向がややあるかなとは思いましたけど、誤植も少なかったし自分が読んだ限りでは全般にとてもしっかり訳してあっていいと思います👍」「じゃ自分もKindle版ポチっちゃおうかな…ありゃ上巻だけ買っちゃった?」「😆」「今ならキャンセルして買い直せると思いますよ」

「このパタヘネは米国西海岸などの大学でのコンピュータサイエンス(CS)の学部向け教科書として書かれているんですよ: もう明らかに授業で使うための本」「あ〜なるほど!」「目次にもこの本を授業でどう使うかが詳しく書かれていて、たとえばこの章とこの章は必須、この章とこの章は選択でいいよとか」「たしかに書かれてました」「日本だったら単位落とす学生が続出しそうですけど😆」

「加算器もハーフアダーとフルアダーが登場したり、マルチプレクサもあったり」「半加算器と全加算器ですねわかります」「表紙にもあるようにハードウェア寄りの話も結構登場しますし」「さらっと読める部分は本当にさらっと読めますけど、込み入っている部分は相当集中しないと読み進められない本」

「もうひとつのヘネパタは1万9000円とかするのか😳」「そっちは買ってません」「ヘネパタはまだ読んでないな〜」「パタヘネとヘネパタ、つい取り違えそうになっちゃうんですが、自分が買ったのはパタヘネ(コンピュータの構成と設計)の方でした😅」「ヘネパタはコンピュータ・アーキテクチャですね」

「やはりパタヘネはいい本: OSといえばタネンバウム本↓と言われるのと同じように、コンピュータアーキテクチャ方面ならパタヘネやヘネパタが定番として挙げられる教科書ですね」「買ったパタヘネちらっと開いてみたら、せ…せやなとしか言いようがない感じ😅」「上から順に読もうとすると挫折しやすいので、目次で当たりをつけてから読むといいと思います」

参考: アンドリュー・タネンバウム - Wikipedia

Gitでデフォルトのmasterブランチを別の名前にできるようになった


つっつきボイス:「Gitの新しいバージョンでmasterというブランチ名を設定で変えられるようになったそうです↓」「前はmasterという名前がハードコードされてたとは😳」「新しいプロジェクトからぼちぼち使うことになるかな」

$ git config --global init.defaultBranch main

「これをやるにはGitをアップデートしないといけなくなりますけど、古いGitを使ってる人って意外にいるんですよね」「あ、そうかも」

「そういえばブランチ名変更を強制じゃなくて選択可能にしたところにGitの良心を感じたってbabaさんが胸をなでおろしてました」「そりゃGitがデフォルトブランチ名を強制変更したらあちこちで火を吹いてぶっ壊れますよ😆」

その他OS・ツール

つっつきボイス:「このio_uringというのはLinuxのかなり新しいI/Oで、非同期に強いそうです」「Linuxは新しいインターフェイスをちょくちょく増やしてますね」「いくつかの言語がこのio_uringを取り入れようとしてるみたいです」

「今日ちょうど昼の社内勉強会でLinux入門をやったときにも少し話したんですけど、OSのシステムコールがサポートしていないと実現できない機能というものがあって、まさにこのio_uringがそういうものですね」「うんうん」「OSのシステムコールが拡張されると今まで書けなかったコードが書けるようになったり」「最近はマルチコアが当たり前になってますし、Dockerへの集約も進んでいるので、こういう非同期周りの重要性は以前にも増して上がってくるでしょうね」


「ruby-jp Slackで見かけたツイートです」「Rubyが動かないレベルってマジで…?」「CRubyはまだ厳しいでしょうね: Rosettaもありますけど完成度はそこまで高くなさそうですし」

参考: Rosetta - Wikipedia

その他

Mac OS 8もElectronで

felixrieseberg/macintosh.js - GitHub

felixrieseberg/windows95 - GitHub


つっつきボイス:「Mac OS 8のライセンスってもう切れてるんだっけ?」「私もわからなくって」「その下のWIndows 95の方が自分的に萌えるので後で調べてみようっと」「95エミュレータのREADMEに『Doomは動くのか: はい』ってあるあたりがワカッテラッシャル」

参考: Mac OS 8 - Wikipedia

「OS 8はさすがに触ったことないかな〜」「OS 8って97年ですか!」「完全モノクロだったMac OSの5ぐらいからです👴」「漢字Talkからだったかも」「それ何ですか?」「当時の日本ではMac OSが漢字Talkっていう名前だったんですよ」「漢字を表示するのにえらい苦労してましたね」「やべえTigerからなので何もわからないです〜」「もう知らなくていい知識😆」

参考: 漢字Talk - Wikipedia

「漢字TalkってOSの名前なんですか!」「たしかにOSの名前っぽくないですよね、アプリっぽいというか」「それで言うと超漢字もOSっぽくないですし」「かな漢字変換ソフトかと思っちゃいます」(以下SimCityやDoomの話など延々)

参考: 超漢字 - Wikipedia

Qiitaのエンジニア白書2020


つっつきボイス:「名前とか入力するとダウンロードできました」「この種のアンケートで定番の、今やってる言語とかこれからやりたい言語とかもありますね」「母集団を見ると回答者のうちエンジニアが2,334名でエンジニア未経験614名って未経験多くない?」「年収のところを見たらGo言語は収入が高いとか何とか」「そうかな〜?」「それってCOBOLやってる人の収入が高いみたいにエンジニアの絶対数が少ないからだったりして😆」(以下延々)

番外

5次元


つっつきボイス:「これはすごく難しそうだけど面白そうですよね」「発想は面白そうだけどゲーム性がどうなのかはやってみないとな〜」「UIの使いやすさとかプレイヤーが理解できるのかとか、そこですよね」「実はここをこうすると必ず勝てるとか😆」「チート😆」「SFとかアニメの小道具だったらかなりいい感じですよね」

「そういえば米国の「ビッグバン★セオリー」というドラマ↓にも「4D Chess」が登場していたんですね」「このドラマも随分前からやってるけどシーズン11もあるのか」「絵に描いたようなこじらせ系ギークが登場するヤツ」

参考: 4D Chess / 4次元チェス
参考: ビッグバン★セオリー/ギークなボクらの恋愛法則 - Wikipedia


後編は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200803前編)書籍『パーフェクトRuby on Rails』増補改訂版、マルチDBで抽象クラスをscaffold生成、GitLabがPumaに乗り換えほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby 公式ニュース

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

週刊Railsウォッチ(20200811山の日短縮版)RSpec Queueでパラレルテスト、カロリーメイトとRubyのコラボ、Rubyのcoercionほか

$
0
0

こんにちは、hachi8833です。昨日は山の日ということで短縮版でお送りします。

回答しそびれましたが、Ruby 2.7のirbがとてもよくなったので自分も最近pryを使わなくなってました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

⚓Rails: 先週の改修(Rails公式ニュースより)

今回はchangelogの更新はありませんでした。

⚓validates_inclusion_ofメソッドにeach_object:オプションを追加

特定の属性に対応する値が配列の場合、バリデーションでは指定のメンバーにその配列が含まれているかどうかをチェックする。:each_objectを使うと配列内の各オブジェクトが指定のメンバの中になければならないということを指定できる。
これは、たとえばデータベースカラムでarray: trueオプションを使っていて、指定の有効な値リストが配列のメンバーに含まれているかどうかを指定したい場合に有用。
変更前: features属性が["Bluetooth"]を含む配列の場合、以下のバリデーションは失敗する(["IR", "Bluetooth", "Wireless"].include?(["Bluetooth"])がfalseになるので)。

validates_inclusion_of :features, in: %w(IR Bluetooth Wireless)

変更後: 以下はパスする(featuresの各オブジェクトが指定のメンバーに含まれているかどうかが実際にチェックされるので)。

validates_inclusion_of :features, in: %w(IR Bluetooth Wireless), each_object: true

Railsに初めて投げたプルリクにつき、フィードバックは大歓迎です。
同PRより大意


つっつきボイス:「だいぶ昔のプルリクがマージされていました」「2015年って書いてますけど互換性とか大丈夫だったのかしら?」「さすがにgit rebaseとかやってるでしょうけど」

validates_inclusion_ofっていう書き方がそもそも古いですし: Rails 3ぐらいじゃなかったっけ?」「あ、もっと簡潔な書き方があるんですね」「もう随分前からvalidates :age, inclusion:みたいに書くのが普通です」「道理でvalidates_inclusion_ofって見覚えないと思った😆

# api.rubyonrails.orgより
validates :terms, acceptance: true
validates :password, confirmation: true
validates :username, exclusion: { in: %w(admin superuser) }
validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
validates :age, inclusion: { in: 0..9 }
validates :first_name, length: { maximum: 30 }
validates :age, numericality: true
validates :username, presence: true

「このプルリクは、今までだと:featuresがstringならうまくいくんですけど、array of stringだと期待どおりに動かなかったのをeach_object: trueを指定することで動くようにしたということでしょうね」

# 変更前
validates_inclusion_of :features, in: %w(IR Bluetooth Wireless)

# 変更後
validates_inclusion_of :features, in: %w(IR Bluetooth Wireless), each_object: true

「それにしても5年越しのマージとは」「こんなに塩漬けにされてたのって何でだろ?」「改善内容はわかるけど、たぶんこれで困る人が少なかったからじゃないですかね」「必要なものなら色んな人が推すでしょうし」「これなら自分でバリデーター作れるでしょうし」「コメント見ると年に1度ぐらいレスやstaleタグが付けられてますね」「七夕みたい💫」「そして4日前にマージされたと」「初プルリクがついにマージ、お疲れさまです…」「これでRailsのプルリクも1個減りましたね」

⚓マルチプルDBのdb:migrate:redoで名前を指定できるようになった

» bin/rails db:migrate:redo:secondary
== 20200728162820 CreateAnimals: reverting ====================================
-- drop_table(:animals)
   -> 0.0025s
== 20200728162820 CreateAnimals: reverted (0.0047s) ===========================

== 20200728162820 CreateAnimals: migrating ====================================
-- create_table(:animals)
   -> 0.0028s
== 20200728162820 CreateAnimals: migrated (0.0029s) ===========================

つっつきボイス:「ああマルチプルDBのdb:migrate:redoね」「これは?」「マルチプルDBのrakeタスクは指定先データベースの識別子を渡せるようになっているんですけど、redoで対応していなかったのを対応したんでしょうね」「redoの対応が漏れてたのか」

db:migrate:redoを開発で使う機会ってめったにないと思いません?」「自分もないな〜」「もちろん機能としては前からありますし、CIで使いたいときもありそうですけどね」

⚓StiClass.allで暗黙のcreate_withを回避するようにした

現在は暗黙のcreate_withを使っても使わなくても、ensure_proper_typeによってsti_nameが設定されている。

      def initialize_internals_callback
        super
        ensure_proper_type
      end

      # Sets the attribute used for single table inheritance to this class name if this is not the
      # ActiveRecord::Base descendant.
      # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
      # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
      # No such attribute would be set for objects of the Message class in that example.
      def ensure_proper_type
        klass = self.class
        if klass.finder_needs_type_condition?
          _write_attribute(klass.inheritance_column, klass.sti_name)
        end
      end

create_withtype_conditionを抑制したりオーバーライドするのが目的。type_conditionscope_for_createですべてのSTIサブクラス名の配列になるので、scope_for_createでは望ましい振る舞いにならず、find_sti_classが失敗する。
この想定外の振る舞いは、ArelのInノードが誤ってEqualityノードのサブクラスになっていたことによる。私見ではほぼバグと思われるが、この振る舞いに依存している人もいる(#39288も参照)。
残念ながらこの暗黙のcreate_withには、orがSTIサブクラスのリレーションで失敗するという不本意な副作用があった(#39956)。
今回の変更は、type_conditionを抑制したりオーバーライドする方法が変わる。#39956orの問題を修正するため、暗黙のcreate_withは、scope_for_createtype_conditionで除外される。
同PRより大意


つっつきボイス:「@kamipoさんによるArel周りの修正です」「Arel難しくて黙り込んでしまう自分😅」「説明にも書いてある#39956の問題↓を解決するためのプルリクなのでこっちを見る方がわかりやすそう」

「昔Arelのコードを叩いてゴニョゴニョするコードを見たことがあってですね」「あ〜それはつらいヤツ」「コードレビューに出すのがとてもつらくて、それ以来Arelの直いじりは止めて欲しいと思ってます」「その書き方は止めましょうと自信を持って言いたい😆」「少なくともメンテしやすいコードにはなりませんし」

参考: Rails 6.0で不要なArel.sqlを減らす - koicの日記

⚓selenium-webdriver gemが必須でなくなった

# actionpack/lib/action_dispatch/system_testing/driver.rb#L6
      def initialize(name, **options, &capabilities)
        @name = name
        @browser = Browser.new(options[:using])
        @screen_size = options[:screen_size]
        @options = options[:options] || {}
        @capabilities = capabilities

-       @browser.preload unless name == :rack_test
+       if name == :selenium
+         require "selenium/webdriver"
+         @browser.preload
+       end
      end

つっつきボイス:「これだけ5月にマージされたプルリクなんですけど、以下の翻訳記事の原著者が投げたプルリクとのことだったので拾ってみました↓」

2020年のRailsでブラウザテストを「正しく」行う方法(翻訳)

「Selenium使わないときでもドライバが入ってきてたので必須でないようにしたと」「poltergeistとかちょっと懐かしい名前が見えた」「他のWebブラウザドライバを使うならSelenium要らないので、もっともな修正」

teampoltergeist/poltergeist - GitHub

「Seleniumに限らないけど、使わないものが入ってくると違和感ありますね」「Seleniumって依存関係もサイズも大きそうな印象ありますし」「Webブラウザのドライバだけならそこまででかくなさそうですけど、どうなんだろう?」

参考: Seleniumプロジェクトとツール :: Seleniumドキュメント

Seleniumのロゴがいつの間にかフラットなデザインに変わってますね↓(参考: 以前のロゴ)。


selenium.devより

⚓Rails

⚓Railsのマイグレーションはスレッドセーフであるべきか(Ruby on Rails Discussionsより)


つっつきボイス:「Railsディスカッションから拾いました」「マイグレーションは稼働中のアプリでも動作させる前提になっていることを考えるとスレッドセーフであって欲しいですけど」「そりゃその方がいいですよね😆

「そうか、マルチプルDBだと複数のマイグレーションが同時に走る可能性もないわけじゃないのか」「schema migrations tableもDBごとにありそうですし」「この辺あんまり考えたことなかったな〜」

参考: Active Record マイグレーション - Railsガイド

「DB単位で同時にマイグレーションが走ることはないけど、DBをまたいだアプリケーション全体レベルで考えると2つのDBのマイグレーションが同時に実行されることはありそう」「そのときにスレッドセーフに書くべきかどうかということですか、やっと理解」「自分は踏んだことないんですけど、2つのデータベースが密に連携してたりするとデッドロックしたりするのかな?」「いわゆる競合状態のお手本にありそうなシチュエーションっぽい」

「この辺は推測になりますけど、Railsレベルでは複数DBを跨いだマイグレーションの同時実行制御はしてなさそうな気がします: DBをまたいで保証しようとしたら相当大変そうですし」「一般的にDBをまたいで相互にロックするようなマイグレーションはあんまりしないと思いますけど、やったらどうなるのかな?」

「マルチじゃない場合、schema migrations tableをトランザクションでロックするぐらいはしてるかも: そしたらrakeタスクが複数動いてもスレッドが同時にクリティカルセクションに入ることはなくなるのでrakeタスクを実行しても大丈夫でしょうけど」「それがマルチDBになると、複数のDBをまたぐ処理を書いたらスレッドアンセーフになる可能性はありそう」「あ、ちょっと見えてきたかも」「1つのデータベース単位ではマイグレーションがスレッドセーフでも、データベースが複数になったらクリティカルセクションを同時に通るときに何か起きそうな感じですね」


追いかけボイス: Railsレベルではマイグレーションロックがあるみたいですね。

参考: Railsのmigrationにロックが掛かっているのか調べた(Mysqlの場合) - Qiita

⚓bundle execすべきかどうか、それが問題だ(Ruby Weeklyより)


つっつきボイス: 「記事は見出し冒頭にあるようにBundler入門という感じですね」「割と基本的な話みたいでした」「サブタイトルの方が目立ってる😆

⚓Railsのbinstub

「ところで今のRails wayではbundle execよりbin/railsみたいなbinstubから実行するのが正式じゃなかったっけ?」「はい、最近はRailsガイドでもそうなってるはずです」「まあ自分は今もbundle execでやっちゃいますけど😆」「実は私も😆

参考: Rails のコマンドラインツール - Railsガイド

bin/railsって気にしたことなかったんですけど、bundle exec railsと同じということなんでしょうか?」「同等でよかったと思います」「へ〜」

「今のRails wayではbin/の下にあるbinstubもGitにコミットすることになってますし、.gitignoreでもbinstubは無視されないようになってますし」「え〜、自分まったく気づかずにずっとbundle execでやってた😭」「bundle execでも間違いではないと思います」「使っても大丈夫ですよ〜」

「あくまでRails wayとしてはbinstubが正式ということなので、Railsの教科書やリファレンスを書くならbin/railsとかで書くべきでしょうね」「なるほど、そういう本でbundle execを使いまくってたら違うと」「まあ細かい話なのでそんなに気にしすぎることはありませんけど、一応ということで」

「ところでいつ頃からRailsでbinstubが正式になったんだっけ?」「Rails 4あたり?」「そんな昔からだったんですね…」「binstubの話は当時ことさら話題になってなかったような覚えあります」

後で調べると、どうやらRails 4の頃みたいです↓。

参考: Rails 4.0 と bundler install --binstubs について - おもしろwebサービス開発日記


【翻訳+解説】binstubをしっかり理解する: RubyGems、rbenv、bundlerの挙動

⚓RSpec Queue: RSpecをパラレルに分散実行(Ruby Weeklyより)

skroutz/rspecq - GitHub


つっつきボイス:「Rspecをキューイングしてパラレルに実行できるヤツか」「RSpec Queueのgem名がrspecqってなっているのが個人的に好き❤」「短くてよくわかりますし」「こういうネーミングセンスを身に付けたい」

「こうやってワーカーを分けてビルドidを付けるといい感じにキューイングしてくれるみたい↓」「おぉ〜」

# 同リポジトリより
$ rspecq --build=123 --worker=foo1 spec/

「RSpecってそのままだとパラレルにならないから、こういう形でパラレルにすることになるのかな?」「完全おまかせでパラレルにするよりも多少制御はしやすいでしょうね」「すごく大きいテストだとうれしいかも」「前処理が極端に重いテストなんかも制御しやすいかも」「テストのワーカーをいっぱい立ち上げたときなんかだと、完全にラウンドロビンにするんじゃなくて多少制御したいこともありそうですし」

参考: ラウンドロビン - Wikipedia

⚓Rubyと「Firefox Send」で巨大ファイルをセキュアに転送する

Sendは無料の暗号化ファイル転送サービスで、任意のブラウザから安全かつシンプルにファイルを共有できます。エンドツーエンド暗号化を用いて、ファイルを共有した瞬間からファイルを開くまでの間のデータをセキュアに保ちます。
同記事より


つっつきボイス:「Firefox Sendって何でしょう?」「ブラウザ内のサービスなのか外部サービスなのかと思ったら、どうやらMozillaがやってる外部サービスみたい」「ブラウザ同士の認証不要なファイル転送をサポートするインフラというかサービスっぽい」「コマンドライン版もあるのね↓」

timvisee/ffsend - GitHub

「any browserってあるからどのブラウザでもやれそう」「記事ではOpen3でRubyから操作してますね↓」「デカいファイルを渡すときに四苦八苦することがときどきあるので、こういう手段があると知ってたら使うかも」「あとはそれを思い出せるかどうか😆

参考: module Open3 (Ruby 2.7.0 リファレンスマニュアル)


動画では期限を設定できて、最大2.5GBまで扱えるとありますね。なお、後で見たら「改装中」となっていました↓🚧


send.firefox.comより

⚓その他Rails


つっつきボイス:「ついさっき流れてきたツイートです」「Everyday Rails買うとこんなおまけがついてくるのね」「Rails独自のMinitest拡張ってあんまり気にしたことなかったかも」

参考: Everyday Rails… Aaron Sumner 著 et al. [Leanpub PDF/iPad/Kindle]

⚓RailsとMinitest

「自分はRailsアプリでMinitest使おうと思ったことないな〜」「お、自分は新しいRailsプロジェクトはしれっとMinitestで書き始めましたよ😋」「私も今度はこっそりMinitestにしようと思ってます」「え〜、マジですか?😳

参考: library minitest/unit (Ruby 2.0.0)

「RSpecってセットアップも毎回地味に面倒じゃないですか: 今やってるのは自分がrails newから始めてるので速攻でMinitestでセットアップしましたし」「やべ〜今までRSpecちまちま入れてたけど、今度は自分もMinitestでやってみようかな…😅」「作っているのはAPIサーバーなのでそんな高度なことがしたいわけでもありませんし、それならMinitestのassertでいいよねって思いますし」

RSpecえかきうた


つっつき後のツイートです。

⚓Ruby

⚓Ruby 3の型付け進捗


つっつきボイス:「Rubyの型付け進捗気になってた」「soutaroさんとSorbetブログで2つ記事が出てました」「RBSという型付け用DSL」「何の略だろう…?」

ruby/rbs - GitHub

RBSとはRubyプログラムの構造を記述する言語で、これを用いてクラスやモジュールの定義(クラス内で定義されたメソッド、インスタンス変数とそれらの型、継承関係やmix-in関係)を書き下せます。定数やグローバル変数も定義できます。
同リポジトリより大意

# 同リポジトリより
module ChatApp
  VERSION: String

  class User
    attr_reader login: String
    attr_reader email: String

    def initialize: (login: String, email: String) -> void
  end

  class Bot
    attr_reader name: String
    attr_reader email: String
    attr_reader owner: User

    def initialize: (name: String, owner: User) -> void
  end

  class Message
    attr_reader id: String
    attr_reader string: String
    attr_reader from: User | Bot                     # `|` means union types: `#from` can be `User` or `Bot`
    attr_reader reply_to: Message?                   # `?` means optional type: `#reply_to` can be `nil`

    def initialize: (from: User | Bot, string: String) -> void

    def reply: (from: User | Bot, string: String) -> Message
  end

  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User]
    attr_reader bots: Array[Bot]

    def initialize: (name: String) -> void

    def each_member: () { (User | Bot) -> void } -> void  # `{` and `}` means block.
                   | () -> Enumerable[User | Bot, void]   # Method can be overloaded.
  end
end

「RBSは型定義を別ファイルにするのね」「Rubyにもいよいよ型が入ってくるのか〜」「別ファイルにするのは懐かしのC言語開発で.hファイルとかを思い出すのでそんなに違和感はないかな」「すべてに型を付けなくても必要なところにだけ型を書ければいいんじゃないかなって思いますし」「自分もそう思います」

「このソルベットって?」「ソルベ(Sorbet)はShopifyが以前から手掛けているRubyの型付けですね」「アイコンが可愛い❤

速報: Ruby向け型チェッカー「Sorbet」をStripeがオープンソース化


1本目記事流し読み:

  • Ruby 3のRBSという型シグネチャ用言語を発表
  • 背景:
    • 動的型付けvs静的型付けは古くから問題にされている: 静的型付けは大規模プロジェクト向きだが柔軟性が低下し、動的型付けは短期開発に向いているがコードベースの規模や人数の拡大が難しい
    • 4年前にMatzがRuby 3で静的型チェックを導入すると宣言以来、さまざまな言語の型チェックを調べたうえでRubyコミュニティは型チェッカー構築基盤の開発を決定した
  • RBSは以下のような感じ
# 同記事より
# sig/merchant.rbs

class Merchant
  attr_reader token: String
  attr_reader name: String
  attr_reader employees: Array[Employee]

  def initialize: (token: String, name: String) -> void

  def each_employee: () { (Employee) -> void } -> void
                   | () -> Enumerator[Employee, void]
end
  • RBSの主な機能とRubyの重要な特性
    • ダックタイピング
    • 不統一性(non-uniformity)
  • RubyプログラムでRBSの型を使ってできること
    • バグを見つけやすくなる
    • nil安全性
    • IDE統合の向上
    • ダックタイプをより安全に
  • SorbetとRBS
    • RubyコミュニティはSorbetチームとも連携し、RBSがSorbetやSorbet独自のRBI型シグネチャ形式とぶつからないようにしている
    • SorbetやSteep(Ruby製でRBSを使用)のような静的型チェッカーもRBSの型定義を使える
    • 相互利用のためRBS gemにはRBI->RBS変換機能も付属(逆変換は開発中)
  • まとめ

soutaro/steep - GitHub
pocke/rbs_rails - GitHub

⚓カロリーメイトとQuine

既にあちこちでバズってますが。


つっつきボイス:「かなり盛り上がってますね」「mameさんの書いたQuine、マーケティング用のサイトにこういうのをしれっと仕込むオーバーキル感がすごい」「読める気がしない…」

# otsuka.co.jpより
caloriemateliquid .Quine $ cat CML_quine.rb
    n=2;eval$s=%q{Z=?\s;eval"$><<S=Z*4"+(%w{+"n=#{-~n%3};eval$s=%q{#$s}#YE";$>.isat ty&&
   (r="\e[43;3#{C="#{n*5%9+1}m"}#{T}\e[4"+C+S[1568,79]+E="\e[0m";r[81,21]="\e[37m# {(["Ca
  f\u00e9_au_lait","Yogurt","Fruit_mix"][n].chars*Z).tr(?_,"").center(21)}\e[3"+C ;a=%~POS
  A[`ER]`PASX1cTc22V6NNP.QOYGMXXIG7KK:bCCaVN8WZ[]UQMMS`cBFFJJHHY`QTUIUURRPTOcRV_a LLUT`WXL
 W]a_c_bV`XXYa_9}+[T='  B  A  L  A  N  C  E  D   F  O  O  D  ']*0+%w{bZZYb_][9cc ????`9^acG
 G,,N9DU`DKcUKU3K4!4!4!QXTSSS""9`9`#U`KcK--S;;/GOT<QE$U=>F==Q0@%U/P/B=S0Q`PM&XVV V15CMRHMSH
RKO>==QMQVR    'b`&DK>BS<XE$T>T33DDDUM<V@@E(((TCT0A<0A"')5CXPcQa54X@@Y#KcK--S;; /GOT<Q`$)T)T
:a4A%%#X   VS6a   '   b`&DK>BS<T7**]  ^^b6+++]~;   P=Str    uct.new(:x,:d,:p,:v );M=(-5**7..
b=0).m   ap{[]};A=s  =[];t=Time.now;q=?y.succ;(    s=S     .scan(/.+/ );M[0]<<P [25i-b%3*5i-
9,0,0  ,2+1  i] ;6  0.t   ime  s  {  |i    |j=  i  %2  0  ;i<  40 ?    [    M[j -1],m=M[j],M
[j+1  ]].  ea  ch  {|  n|  m.   ea  ch  {|  p| n. ea ch  {|  q|  d=p .x  -q  .x ;w=d.abs-4;w
<0&   &(  i<2 0?  p.  d+=  w  *w:  p.     p+=  w    *(   d  *(3 -p. d-     q.d) +(p.v-q.v)*4
)/p   .d  )}  }   }   :M  .  shif  t   .ea c  h{   |p|  y,  x=  (   p.  x+= p.v +=p.p/10).re
ct;   p.p   =  [4  3-   b/9 .0-y,1  ].    m  in- [x,p  .d=0   ,  x   -9    2].s ort[1]*2i;p.
v/=[   1,p.v.a   bs/2].max;M[20-j+[0  ,(x+  4).div(5   ),19].sort[1]]<<p;35.tim es{|w|v=x.to
_i-3+w         %7;c=s[w=y.div(2)-2+        w/7];(x-v)**2+(y-w*2)**2<16&&0<=w&&c &&(k=(w*2-21
 )**2/99)<=v&&c[v]&&k+79!=v&&c[v]=q}}};(24-b/18..21).map{|k|s[k]=Z*(k=(k*2-21)** 2/99)+q*79
 +Z+q*2*(6-k)};s*="\e[B\r";"  Your favorite flavor  ";b+=1;A<<"\e[A\r"*21+s.gsub (/\172+/){
  "\e[43m"+$&.tr(q,Z)+E})while+s.count(q)<1950;A.map{|q|sleep([t-Time.now+3,2e-2] .max);$>
  <<s=q};$><<s.gsub(?m,";33m").gsub(Z){S.slice!(/./)};b=?]*33.upto(91){|i|a=~/../ ;a=$'.gs
   ub(i.chr,$&)}*2;Z<<8;(b+a.gsub(?^,"^]"*41)+b).bytes{|c|c-=86;c<8?sleep(3e-2):$> <<(c<(
    'CalorieMate-Liquid-Quine';9)?r.slice!(/\e.*?m|./):c>9?"\e[%X"%c:Z)});puts})*"" }#YE

ちょっと整形しようとして諦めました😅


直接関係ありませんが、mameさんご本人が別のコードで音楽付き動画をアップしているのをruby-jp Slackで知りました。なるほどの選曲です。

参考: 水上の音楽 - Wikipedia

⚓Rubyのcoercion系メソッドには注意(Ruby Weeklyより)


つっつきボイス:「nilのcoercion(強制型変換)に注意しようという記事みたいです」「Rubyのcoercionってそんなにあったかなと思ったらto_iみたいなヤツね」

# 同記事より
nil.to_h => {}
nil.to_a => []
nil.to_f => 0.0
nil.to_r => (0/1)
nil.to_c => (0+0i)

参考: 型変換 - Wikipedia

「文字列のto_iはたしかに予想外のことになりやすい↓」「まあ使い方がよくないという説もありますけど」

# 同記事より
"312".to_i
# 312

"312 oh hai".to_i
# 312

「大文字で始まるInteger()みたいなcoercionメソッド↓、そういえばこういう機能あったな〜」

# 同記事より
Integer(nil)
# TypeError (can't convert nil into Integer)

Float(nil)
# TypeError (can't convert nil into Float))

「昔は使った覚えあるけど基本的にはcoercionってそんなに使わないかも」「Integer(nil)ってダメなんだ😳」「ホントだ、今やってみたらエラーになった」「こういうのを型チェックで救えるといいですよね」「書いた時点で見逃すとなかなか見つからなくてproductionでnilエラーになったりするヤツ」

後で気づきましたが、記事を書いた人はvirtusやdry-typeを作った方でした。

solnic/virtus - GitHub
dry-rb/dry-types - GitHub

以下の翻訳記事も趣旨が似ていますね。

Rubyの明示的/暗黙的な型変換についてのメモ(翻訳)

⚓Rubyのrescueでやんちゃしてやった(Ruby Weeklyより)


つっつきボイス:「パーサーの目を盗んでrescueにクラス定義を書いたということみたいです」「壊しに行く気満々のコード😆

# 同記事より
begin
  raise "omg"
rescue =>
  (class Box
   class << self
     attr_accessor :contents
   end
end; Box).contents
end

puts Box.contents

# => "omg"

「こういう凶悪なコード、よく考えつくな〜」「やったらできちゃうんですね」「Ruby公式とかに投げたら盛り上がりそう」

⚓その他

⚓コントのような


つっつきボイス:「200mばかし離れたところに物理サーバーを無停止で移設せよというミッションを頑張ってクリアしたそうです」「本当にそういうミッションがあったんですか?」「5分ぐらいサーバー止めたらダメ?って聞いたら一瞬たりともダメと言われてますね😆

「移設元サーバーのインターフェイス次第ですけど、意外にやりようはありそう: 電源は今の時代ならUPSつないで発電機回せば割とどうにでもなるんですけど、それよりネットワークが切れないようにする方が大変そうかな🤔」「そんな感じですね」 「移設元と移設先で上流の同じスイッチにつなげられるならやりやすいんですけど」「DNSアップデートとか書いてるから別ネットワークなのかも」「ネットワークが変わると途端に大変になるんですよ…完全なゼロダウンタイムは難しいでしょうし」「お、記事見ると移設先から延々ネットワークケーブルを引いて、先にネットワークを移行してからカートに乗せて移動してるのか、それならやれるかも」

「それにしても一瞬たりとも止められないサーバーって…」「それならどこかクラウドのデータセンターに置けばいいのにって思っちゃいますけど」「作業料の他にコンサル料ももらったって書いてあってよかったよかった」「じゃいいか😆

「こういうのを考えること自体は結構楽しいですよね😋」「やった人お疲れさまです!」「こういう移行方法の問題点は、途中で何かあったときのリカバリープランがないことですけど」「雨が降ったらどうするとか」「ネコがケーブルかじったりとか」「子どもがぶつかってサーバー落っことしたりとか」「周りを用心棒で固めないと」「途中に道路があったらさらに大変そう…」

「こういうの最近あんまりやってないけど、たまにやると楽しいんですよ」「責任抜きでやれるなら😆」「ISUCONの物理サーバー版みたいな企画あったら面白いかも」

参考: ISUCON公式Blog

後で日本語記事も出ましたね↓。

参考: 物理サーバーを稼働させたまま引っ越しさせた意外な方法がネットで話題に - GIGAZINE


今回は以上です。

バックナンバー(2020年度第3四半期)

週刊Railsウォッチ(20200804後編)「RubyKaigi Takeout 2020」9月オンライン開催、メールバリデータtruemail、Gitのmasterが変更可能にほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

Viewing all 1079 articles
Browse latest View live