Quantcast
Channel: hachi8833の記事一覧|TechRacho by BPS株式会社
Viewing all articles
Browse latest Browse all 1759

週刊Railsウォッチ(20180723)Railsdm Day 3 Extremeを後追い、PSDにはZeplin.io、好みの分かれるJSX、負荷テストツール比較ほか

$
0
0

こんにちは、hachi8833です。2週間ぶりのご無沙汰でした。暑さでもうろうとしています☀

各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ

今回のつっつきも、BPSの福岡拠点であるウイングドアの皆さまと画面共有しながら行いました。いつもありがとうございます。

⚓【お知らせ】週刊Railsウォッチ「公開つっつき会 第1回」を8/2(木)に開催

週刊Railsウォッチを日頃ご愛顧いただきありがとうございます。
ウォッチを出す前に毎週木曜に社内で「つっつき会」を催していますが、このたび8/2(木)に「公開つっつき会 第1回」を開催するはこびとなりました。以下のTECH PLAYページからエントリーいただけますので、つっつき会を覗いてみたい方はお気軽にご参加ください。普段から質問や発言も自由な気楽な集まりとして行っていますので、普段なかなかできない質問やツッコミなどもお気軽にどうぞ。

⚓特集: Rails Developer Meetup 2018 Day 3 Extremeその後

このボリュームを一日でやるとは…😭今週のウォッチはその分後半のエントリを意識的に減らしました。


つっつきボイス: 「文字どおりエクストリームなボリュームでしたね…キャンセル待ちもならず😢」「スケジュールの密度ちょっと高いかなー😅」「今年3月のRailsdmのときは自分たちも発表しました↓が、タバコ吸ったら休憩時間終わっちゃう感じでした🚬」「ともあれ、Railsdmは技術プレゼンの質もいいし、それでいて技術系に限定されないバラエティ豊かな点も貴重😋」「😃」「あと現場のつらみの話をいろいろ聞けるのもいいですねー: 単なる新しい技術の話とかはググれば済むけど、試してはまった話は実際にやってみた人じゃないとできないので」「毎回出席は体力的に大変ですが」「後追いで興味のあるプレゼンのスライドやYouTube動画で確認するだけでも役に立つし」「ストリーム中継の時点でYouTubeだからそのまま動画として残ってるのはありがたいです」

「TechRachoの舞台裏」をRails Developers Meetup 2018で発表してきました

⚓kamipoさん動画のさわり


つっつきボイス: 「とりあえずkamipoさんのActiveRecord話を見たかったので後追いでごくあっさりとチェックしてみました: 冒頭『何にも準備しなくていいと聞いてたんで』と言ってたぐらいで、その場でコミット眺めながら進めてました」

Q: 仮にMySQLだけをサポートするARを作れるとしたらどんな最適化ができそう?
A: RDBMSを絞れば確かに最適化はしやすい: mysql2 gemは既にいろいろやりすぎてて線引きがしにくいけどpg gemはやりすぎてないのでこれに絞れるなら最適化の余地は大きそう。
14:38

「PostgreSQLのRails向け拡張って結構いろいろあるし便利な機能が本当にいっぱいあるんだけど、Railsにそういうのを導入するとPostgreSQLでしか動かなくなっちゃうのでRails標準でやるのは難しいでしょうねー🤔」「やはり」「個人的にはポスグレで行って欲しいけどねっ😎

Q: #30000の次にキリ番ゲットしたら直したいARは?
A: もし取れたら、ロールバックしたときにdirty valueもロールバックしたい(今は4種類のステートだけロールバックされる)。
19:05

「dirty valueというものがあったとは…」「dirty value、自分はなるべく使わないようにしてるんだけどな…どこでどう変わるか予測しきれないことあるし」「😲」「1つ手前に戻すぐらいならいいんだけど、コミットとかし始めるとどこまで確実にロールバックできるのか正直不安だし😔」「それもそうですね…」「自分は直接dirty valueを触るよりは別の変数に取っておきたい派🤓

Q: MySQLのdefault character setをutf8からutf8mb4に変えるというのはどうでしょう?
A: MySQLのinnodbにはkey prefix lengthの制限があるので厳しいです: この問題をユーザーに押し付けていいんだったら変えられる。
22:25

「key prefix lengthの件はそのとおりで、MySQLだとinnodbのkey prefix lengthをケアしないとインデックス付けた瞬間にコケるとかあるんですよ」「ありゃー」「MySQLをバージョン8にすればいいんじゃね?っと思ったけど、database.ymlでデフォルトでutf8って書かれたらダメだろうし…実際はどうだったかな?」(RubyMineで探し始める)「あったあった: あー、やっぱりymlテンプレートのデフォルトエンコーディングはutf8か↓」「残念😢」「誰もが新しいバージョンのMySQLを使ってくれていれば問題なくデフォルトをutf8mb4に変えられるんですけど、MySQL 5.6の初期バージョンあたりを使う人たちはkamipoさんの言うようにkey prefix lengthの問題を踏むことになっちゃうので、5.7より前のサポートを打ち切るとかしないとねー🕶

# https://github.com/rails/rails/blob/bd7b61aefb4382f3cf2113c883f12bc5a3e99ae5/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml#L14
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
...

MySQLのencodingをutf8からutfmb4に変更して寿司ビール問題に対応する

Q: 非推奨にしたいActiveRecord APIは?
A: 6.0までにということだったらscoping(クラスメソッド)を殺したい(#32380の例を参照)。他にも片手ぐらいある。
24:12


Q: ActiveRecordにぜひ入れたいまったく新しい機能は?
A: 若い人に任せたい気持ちだけど、1つあるとすれば#12937。typecastしないpluckが欲しい。(年に数件は同じようなissueが上がる)。
34:56

「kamipoさんがリモート参加だったらしく音声がちょっと聞き取りにくくて、最初crackって言ってるのかなと思ったらpluckだったみたいです」「(issueを見ながら)あー、たとえばsumしたものをpluckすると勝手にtypecastされちゃうってやつか」「なのでtypecastしないバージョンのpluckが欲しいということなんですね」「確かにSQLのSELECTを手動でカスタマイズしているときなんかは生のSQL結果に近いデータが欲しいことが多いし、そういうときに余計なtypecastやられると邪魔なんですよね」

Q: 「これを殺したら速くなる」機能は?
A: (中略)たぶんdirty tracking(before_type_cast)を殺せばインスタンス生成がものすごく減ると思う。(中略)つまりAttributes APIをなくせばいい(🤣)。ちなみにDiscourseのSam Saffronが作ってるいろんなgemは、使ってないARの機能を殺すことで速くしている。
40:17

「dirty trackingは余分なこといろいろやってそうではある」「上は相当端折ってますが、dirtyに対応するために相当いろんなインスタンスが生成されてるみたいです」「でしょうねー: でないとあんなにたくさんのdirty attributesに対応できるはずがないし」「Attributes APIをなくす云々はギャグで言ってる感」「そこにありますからね😉

「Sam SaffronさんはTechRachoでも翻訳記事いくつか出してます↓」「使わないARの機能を殺すというのはそれはそれでありかもね😎」「後でRailsをアップグレードするのが大変そう」「アップグレードよりは、他のgemがその機能を使ってるかもしれないからそのあたりをケアしないといけないのが面倒かも」

Rails: productionでCPU usageが100%になる問題をデバッグする(翻訳)

⚓Form Objectの話

「全部追う時間はないので他にとりあえず気になったものとしては諸橋さんのForm Objectの話: 以前のウォッチでForm Objectの必然性がひとつ減ったかもという話が出てたので」「RailsのForm Objectは、それがうまくはまるときに使えばいいんじゃないかな: 特に最近はフォームが複雑なときはVue.jsなどのSPA的なライブラリで作ることが増えたので、その意味でもForm Objectの活躍の機会は減りつつあるかも」「😲」「フロントエンジニアが組み立てたJSONオブジェクトがPOSTされるから、Form Objectを作る必要はあまりないし、Form Objectを使う大きな理由のひとつはフォームヘルパーを使いたいからだけど、フォームヘルパーを使わなくてもフォームを生成できるなら正直どちらでやっても大差ないかなと」「確かにー」「最近Form Objectを使う人が減っているのはそういう理由もあるんじゃないかなー」「😃」「Form Objectの立ち位置はやや中途半端になりつつある感があるけど、個人的には全部サーバーサイドでやれるという点でForm Objectは好き❤

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

2週間分になってしまいました(´・ω・`)。

⚓SQLite3アダプタの「ReadOnly」オプションに対応

アダプタ側で:readonlyオプションが追加されたことに対応したそうです。

# activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L15
 module ActiveRecord
   module ConnectionHandling # :nodoc:
     def sqlite3_connection(config)
       # Require database.
       unless config[:database]
         raise ArgumentError, "No database file specified. Missing argument: database"
       end

       # Allow database path relative to Rails.root, but only if the database
       # path is not the special path that tells sqlite to build a database only
       # in memory.
       if ":memory:" != config[:database]
         config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
         dirname = File.dirname(config[:database])
         Dir.mkdir(dirname) unless File.directory?(dirname)
       end

+      db_opts = config.symbolize_keys.merge(results_as_hash: true)
+
       db = SQLite3::Database.new(
         config[:database].to_s,
-        results_as_hash: true
+        db_opts
       )
...

⚓#translateメソッドが_htmlサフィックスに対応

translatet)メソッドでは_htmlサフィックスが無視されていたために、html_safeを使うべきでない場所でもhtml_safeが乱用されがちだった。このPRでは訳文の配列のキーに_htmlサフィックスがある場合にhtml_safeをサポートする。
同PRより大意

# actionview/lib/action_view/helpers/translation_helper.rb#L60
       def translate(key, options = {})
         options = options.dup
         has_default = options.has_key?(:default)
         remaining_defaults = Array(options.delete(:default)).compact
...

        if html_safe_translation_key?(key)
           html_safe_options = options.dup
           options.except(*I18n::RESERVED_KEYS).each do |name, value|
             unless name == :count && value.is_a?(Numeric)
               html_safe_options[name] = ERB::Util.html_escape(value.to_s)
             end
           end
           translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
-
-          translation.respond_to?(:html_safe) ? translation.html_safe : translation
+          if translation.respond_to?(:map)
+            translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
+          else
+            translation.respond_to?(:html_safe) ? translation.html_safe : translation
+          end
         else
           I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
         end

つっつきボイス: 「arrayに対してtranslateするとhtml_safeされてなかった…これはバグですね🐞しかも場合によってはですが割とアブナイ」「😨」「yaml由来の内部データをtranslateで変換するなら基本的に大丈夫だけど、yamlじゃなくてデータベースに入れて運用してたりする場合は要注意🕶

RailsビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある

⚓rails notesコマンドの変更と一部非推奨化


つっつきボイス: 「rails notesコマンドってそもそも初めて知りました」「ワイも😲: あー、これはFIXMEみたいなコメントも拾えるやつか: JetBrainsのIDEを長年使っている自分としてはIDEで簡単にチェックできるから直接使うことはまずないかなー😎」 「確かにー」

Rails開発のイケてるIDE RubyMineを使う(1)紹介編

「まあコマンドでこういうコメントをチェックできるということは、たとえばCIサーバーなんかでFIXMEがあったらマージを許さないように設定するみたいな使い方はあるかもですね」「😃

⚓新規にアップロードされたファイルを代入時ではなくsave時に保存するようになった

24ファイルも変更されています。

従来、アップロード済みファイルがhas_one_attachedhas_many_attachedで追加されたライターメソッドによってレコードに代入されると、即座にストレージで永続化されていた。たとえば以下ではparams[:avatar]内のアップロード済みファイルが保存対象になっていた。

@user.avatar = params[:avatar]

レコードに追加されたblobのバリデーションをサポートしたいが、無効なファイルの保存を防がないとまともなバリデーションができない。ファイルがストレージに投げられた後で無効だとわかってもほとんど意味がない。AWSの請求額を抑えるために事前にファイルサイズのバリデーションを行いたくても、バリデーション前にファイルが保存されてしまえばコストを削減できない。

このPRでは、レコードに代入されたattachableのActive Storegeへの保存を、即座にではなくレコードのsave後に行うようになる。そのためにActive Storageの添付ファイルのトラッキングを導入し、上述のライターメソッドが使われるときにレコード内の添付ファイルの変更保留をトラッキングする。この変更はレコードがsaveされるとアプリのデータベースで永続化され、関連するattachableはsaveトランザクションがコミットされたときに初めてストレージにアップロードされる。
ついでに添付ファイルAPIのテストを追加/再編成しておいた。
同PRより大意


つっつきボイス: 「テンポラリの添付ファイルをどのタイミングで永続化するかはいつも悩ましい問題ですね」「AWSの課金にも影響するでしょうし」「そっちよりはむしろゴミファイルが残ることなんかの方が問題でしょうね: 添付ファイルがテンポラリ領域にある限りは消し忘れても後で消えてくれるんですが、保存のタイミングが早すぎるとその後でエラーが起きたときにひとりでに消えてくれないし」「そっかー😲

⚓Railsのログの標準出力へのリダイレクトの挙動を上書きできるようにした

// productionでも標準出力をオンにする
rails server -e production -l

// developmentで標準出力をオフにする
rails server -e development -l false
# railties/lib/rails/commands/server/server_command.rb#L165
         def server_options
           {
             user_supplied_options: user_supplied_options,
             server:                using,
-            log_stdout:            @log_stdout,
+            log_stdout:            log_to_stdout?,
             Port:                  port,
             Host:                  host,
             DoNotReverseLookup:    true,
...
           options[:early_hints]
         end

+        def log_to_stdout?
+          options.fetch(:log_to_stdout) do
+            options[:daemon].blank? && environment == "development"
+          end
+        end

つっつきボイス: 「rails server -lが使えるようになるのね、なるほどなるほど」

⚓和暦にも使えるlabels_for_year_optionsオプションをdate_selectに追加

kamipoさんのPRです。

# 同PRより
date_select('user_birthday', '', start_year: 1998, end_year: 2000, labels_for_year_options: ->year { "Heisei #{ year - 1988 }" })
    <select id="user_birthday__1i" name="user_birthday[(1i)]">
    <option value="1998">Heisei 10</option>
    <option value="1999">Heisei 11</option>
    <option value="2000">Heisei 12</option>
    </select>
    /* 略 */

つっつきボイス: 「これは個人的におおっと思っちゃいました」「ははー、年号のステップを刻めるようにしたってことか: 年号は和暦でも何でもお好きなものをどうぞってことで🕶」「😆」「lambdaが使えるってのは結構よさそう」

Unicodeにおける日本の元号の開始日・終了日の定義について

「和暦というか元号対応って細かいところがいろいろ面倒なんですよねー: やったことあります?」「もうライブラリに任せてますー🤓」「😆」「さりげに面倒なのが『平成1年』だけ『平成元年』って表示しないといけない場合: 『昭和64年』→『平成元年』の方はたいていのライブラリで対応できるんですけど、『平成1年』=『平成元年』の方を正規表現で無理やり何とかしようとしてもたまにカバーできなかったりとか: ちなみに上のコード例もそこは未対応ですね」「つらそう…」「我ながら何やってるんだろうと思いながら😅」「次の年号でもその手の対応が必要なところは多そうですね😅

「あそうそう、その名もwarekiっていうgemありますよ」「欲しい人絶対いますよねー」「全元号対応といいつつそこまでではなかった気もするけど…あー今見ると対応元号めちゃ増えてる!」「ホントだー: マニア御用達の皇紀まであるし🤓」「😆」「😆」「しかもいつの間にか1年=元年も対応してるし: やるなー」「うるうの表示まであるし」「元号対応を自前で実装するぐらいならwarekiを使うべき」

  • リポジトリ: sugi/wareki — ruby 向け和暦ライブラリ。和暦と標準Dateの双方向変換をサポート。全元号対応。
# 同リポジトリより
require 'wareki'

Date.today.strftime("%JF")              # => "平成二十七年八月二十二日"
Date.civil(1311, 7, 20).strftime("%JF") # => "応長元年閏六月四日"

Date.today.to_wareki_date # => Wareki::Date インスタンス
Wareki::Date.new("明治", 8, 2, 1).to_date # => 標準 Date インスタンス 1875-02-01

Wareki::Date.parse("正嘉元年 うるう3月 12日") # => Wareki::Date インスタンス
Date.parse("正嘉元年 うるう3月 12日)" # => 標準 Date インスタンス 1257-04-27

参考: 神武天皇即位紀元 - Wikipedia

⚓HTTP Cache-Controlサポートを追加

# actionpack/lib/action_dispatch/http/cache.rb#L205
-            extras  = control[:extras]
+            extras = control[:extras]
             max_age = control[:max_age]
+            stale_while_revalidate = control[:stale_while_revalidate]
+            stale_if_error = control[:stale_if_error]

             options = []
             options << "max-age=#{max_age.to_i}" if max_age
             options << (control[:public] ? PUBLIC : PRIVATE)
             options << MUST_REVALIDATE if control[:must_revalidate]
+            options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
+            options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
             options.concat(extras) if extras

つっつきボイス: 「Cache-Controlヘッダサポートがさらに追加されたのか: stale-while-revalidateとかstale-if-errorとか初めて見たし🤔

参考: RFC 5861 - HTTP Cache-Control Extensions for Stale Content

⚓ActiveRecord::Relation#pluckのメモリ割り当てを削減

# activerecord/lib/active_record/result.rb#L99
     def cast_values(type_overrides = {}) # :nodoc:
-      types = columns.map { |name| column_type(name, type_overrides) }
-      result = rows.map do |values|
-        types.zip(values).map { |type, value| type.deserialize(value) }
-      end
+      if columns.one?
+        # Separated to avoid allocating an array per row
+
+        type = column_type(columns.first, type_overrides)

-      columns.one? ? result.map!(&:first) : result
+        rows.map do |(value)|
+          type.deserialize(value)
+        end
+      else
+        types = columns.map { |name| column_type(name, type_overrides) }
+
+        rows.map do |values|
+          Array.new(values.size) { |i| types[i].deserialize(values[i]) }
+        end
+      end
     end

つっつきボイス: 「#pluckはよく使われるし、この最適化はありですね🧐

⚓サービスURLのデフォルト期限をカスタマイズ可能にした

# activestorage/app/controllers/active_storage/blobs_controller.rb#L10
   def show
-    expires_in ActiveStorage::Blob.service.url_expires_in
+    expires_in ActiveStorage.service_urls_expire_in
     redirect_to @blob.service_url(disposition: params[:disposition])
   end

つっつきボイス: 「これはActiveStorageのファイルの置き場所のURLですね: それをサービスURLと呼んでるってことか」「ローカルならfile://とかだったり」

⚓AC::Parametersのtransform_valuesの結果を修正

# actionpack/lib/action_controller/metal/strong_parameters.rb#L642
-    def transform_values(&block)
-      if block
-        new_instance_with_inherited_permitted_status(
-          @parameters.transform_values(&block)
-        )
-      else
-        @parameters.transform_values
-      end
+    def transform_values
+      return to_enum(:transform_values) unless block_given?
+      new_instance_with_inherited_permitted_status(
+        @parameters.transform_values { |v| yield convert_value_to_parameters(v) }
+      )
     end

     # Performs values transformation and returns the altered
     # <tt>ActionController::Parameters</tt> instance.
-    def transform_values!(&block)
-      @parameters.transform_values!(&block)
+    def transform_values!
+      return to_enum(:transform_values!) unless block_given?
+      @parameters.transform_values! { |v| yield convert_value_to_parameters(v) }
       self
     end

つっつきボイス: 「AC=ActionController」「テストにassert_kind_ofがある↓ってことは、これまでの挙動がバグだったってことか」「おー😲

# actionpack/test/controller/parameters/accessors_test.rb#L193
+  test "transform_values converts hashes to parameters" do
+    @params.transform_values do |value|
+      assert_kind_of ActionController::Parameters, value
+      value
+    end
+  end
+
+  test "transform_values without block yieds an enumerator" do
+    assert_kind_of Enumerator, @params.transform_values
+  end
+
+  test "transform_values! converts hashes to parameters" do
+    @params.transform_values! do |value|
+      assert_kind_of ActionController::Parameters, value
+    end
+  end
+
+  test "transform_values! without block yields an enumerator" do
+    assert_kind_of Enumerator, @params.transform_values!
+  end

⚓touchオプションの振る舞いをPersistence#touchに合わせた

これもkamipoさんです。

increment!#27660)とupdate_counters#26995)に追加されたtouchオプションの挙動がPersistence#touchと同じでない。
touchオプションを属性名に渡しても、Persistence#touchのようにupdate_atupdate_on属性が更新されない。
Persistence#touchincrement!touchオプションに変更されたために、counter_cacheで属性名が渡されるtouchオプションでupdate_atupdate_on属性が更新されないという問題が#31405で再発した。
この不整合は意図したものではないと思われるため、touchオプションでupdate_atupdate_on属性が更新されるようにして不整合を解消する。
同PRより大意

ちょうどRailsdmの動画でもkamipoさんがこのあたりに言及していました。


つっつきボイス: 「touchはUnixのtouchコマンド↓のアナロジーでしょうか?」「Railsのはupdate_atなんかを強制的に適用やつなので、由来はたぶんそうですね: カウンタキャッシュのアップデートなんかにも使うし」

参考: touch (UNIX) - Wikipedia

⚓Rails

⚓「データドリブンエンジニアリング」とは


同記事より


つっつきボイス: 「Code Climateの久々の記事だったので」「定量的なデータを元に改善を着実に進めていこうっていう趣旨みたい」「Data-Driven Engineeringと対になる概念がNarrative-Driven(ストーリーに基づく)ってことみたいで、それよりもメリットが大きいぞってことですね」「上の認知バイアスのチャート、面白そうだけどめっちゃ細かくて読みづらい💦

参考: 認知バイアス - Wikipedia

認知バイアス(にんちバイアス、英: cognitive bias)とは、認知心理学や社会心理学での様々な観察者効果の一種であり、非常に基本的な統計学的な誤り、社会的帰属の誤り、記憶の誤り(虚偽記憶)など人間が犯しやすい問題である。
Wikipediaより

⚓Active Recordの読み書きを洗練させる

# 同記事より
class Product < ApplicationRecord
  # executed daily
  def self.recompute!
    where("
      announce_on    = :today OR
      preorder_on    = :today OR
      publication_on = :today
    ", today: Date.current).find_each do |p|
      p.recompute_dependent_columns
      p.save!
    end
  end

  def self.visible
    where(is_visible: true)
  end

  def self.buyable
    where(is_buyable: true)
  end

  def visible?
    is_visible?
  end

  def buyable?
    is_buyable?
  end

  def publication_on=(val)
    super.tap{ recompute_dependent_columns }
  end

  def preorder_on=(val)
    super.tap{ recompute_dependent_columns }
  end

  def announce_on=(val)
    super.tap{ recompute_dependent_columns }
  end

  def recompute_dependent_columns
    self.is_buyable = [
      preorder_on, 
      publication_on
    ].reject(&:nil).min <= Date.current
    self.is_visible = [
      announce_on, 
      preorder_on, 
      publication_on
    ].reject(&:nil).min <= Date.current
  end  
end

つっつきボイス: 「普通の書き方、書くのは少し面倒だけど読みやすい書き方、さらにシンプルな書き方↑という順序で説明しています」「ふむむー、気持ちはわかるけど最終的なコードを見た感じはちょっとオーバーキルだよなー: と思ったら記事にも同じこと書いてるし↓w」「🤣」「🤣

Depending on your preferences this might seem even easier than the previous solution. Or, it might look ugly or like an over-kill.
同記事より

⚓ActiveRecordとArelでクエリの流れを追う(Ruby Weeklyより)


同ブログより


つっつきボイス: 「いいこと書いてありそうなタイトルだったので」「ふむむー、ARとArelの関係をもっと掘り下げるのかなと思ったら#to_sqlすればだいたいわかるようなチュートリアル的内容ですね😎」「その分初級中級向けにはいいのかも?🤔

⚓Apache SolrとElasticsearchを比較する


同サイトより

2018年5月のおすすめ情報↓

  • Solrはこんなときに
    • Javaプログラマーが多い
    • ZooKeeperを既に使ってる
    • Javaを既に使ってる
    • 特定のニュアンスの関連度が必要な検索アプリを作る
    • eコマース/求人情報/製品の検索エンジンを作る
    • 検索機能を機能やエクスペリエンスの中心に据えたい
  • Elasticseachはこんなときに
    • Ruby/PHP/Pythonフルスタックプログラマが多い
    • JSONがないと生きていけない
    • Kibana/ELKでログを管理している
    • 分析機能が中心のアプリ
  • 悩み中の人に
    • 私がこれまで手がけた高度な検索アプリはどれも検索ワークフローをカスタマイズ/チューニングしないといけなかったし、現時点でもElasticsearchはそのためにがっつりハックが必要。迷ったらSolrをどぞ。

つっつきボイス: 「solr-vs-elasticsearch.comってドメインをわざわざ取ってたので」「最近こういう感じのドメイン名を使ったサイトちょくちょく見かけますね」「どういうときにどっちを選ぶかという目安を上に雑に訳してみました↑」「だいたいこのとおりで、Solrは検索エンジンを自分たちで作るという要素が強くて、Elasticsearchはデータ処理系につなぐときに結構便利」「サービスの中核としてカスタム検索エンジンが欲しいときはSolr、Elasticsearchはカスタマイズが大変って書いてますね」「カスタマイズしたいならSolrでないとつらい: でも現場レベルではデフォルトで使う方が多いですけどね😉

⚓Cucumberをおすすめしない理由(Ruby Weeklyより)

このCode with Jasonというサイトはよさそうでした。


cucumber.ioより


つっつきボイス: 「Cucumberへのヘイトがたまってそうな記事でした」「Cucumberはねー、英語ネイティブにとってはまだいいんですが日本語でテスト書こうとするともろに地獄💀」「やっぱりー」「日本語で書くならturnip↓の方がまだまともに書けた気がする: 一応更新もされてるみたいだし」「turnip、以前使ったことあったそういえば」「ま、今ならシステムテストでいいんじゃね?って思うけど😉

[RSpec][Turnip] 一般的に使えるTurnipステップ集

Rails 5.1以降のシステムテストをRSpecで実行する(翻訳)

⚓コントローラアクションのパフォーマンス改善記事とORMパフォーマンス測定論文(RubyFlowより)


つっつきボイス: 「上の論文を記事の方で絶賛しつつ引用していたので」「ICSE(International Conference on Software Engineering) 2018の論文か: ICSEの位置付けが知りたいなー👀


icse-conferences.orgより

「(しばらくググって)ざざっと調べた感じICSEはランキングも割と高いしかなりちゃんとしたカンファレンスみたい」「おー、そうやって信頼度を調べるんですね😲」「あんまりマイナーなカンファレンスの論文をありがたがって読んでもしょうがないんで😆


icse2018.orgより

「アブストラクトをざっと見たところ、ORMのいくつかの実装についてパフォーマンスを計測・調査した論文ですねこれは」「😃」「しかもこれかなり面白そう: 会議論文として査読も通ってるはずだし、時間のある人はちゃんと読むととても参考になるんじゃないかな」「おー」


同論文より

「で記事の方はコントローラのアクションのパフォーマンスを落とすアンチパターンとそれを特定する方法についてみたいです」「Scoutで測定してますね」「そういえば以前ウォッチでも取り上げたことありました」


同記事より


github.com/scoutappより

⚓⭐switch_point: master/slaveデータベース接続で負荷分散⭐


つっつきボイス: 「お、switch_point」「今日のBPS社内勉強会で言及されてたので」「↓図を見れば一目瞭然ですが、たとえばslaveデータベースはリードオンリーで、masterデータベースは書き込み可能でというふうにできる: しかもuse_switch_pointモデルごとに接続先を指定できる↓のが地味に便利」「使いみちは?」「レプリカに対して使うものなので、基本は負荷分散のためですね: メンテもされているはずだし結構優秀なgemです💪」「😃


同リポジトリより

# 同リポジトリより
class Article < ActiveRecord::Base
  use_switch_point :blog
end

class Category < ActiveRecord::Base
  use_switch_point :blog
end

class Comment < ActiveRecord::Base
  use_switch_point :comment
end

「以前はActiveRecordのデータベース接続はグローバルにしか持てなかったのでswitch_pointみたいなものを使わないとこういうことがまともにできなかったんですが、確かRails 5.1のあたりでデータベース接続を複数持てるようになったので以前よりはやりやすくなってるはず」

久々の⭐を進呈いたします。おめでとうございます。

⚓その他Rails

⚓Ruby trunkより

⚓Rubyのパターンマッチング構文の提案

# 同issueより
case expr
in pat [if|unless cond]
  ...
in pat [if|unless cond]
  ...
else
  ...
end

pat: var                                                   # Variable pattern. It matches any value, and binds the variable name to that value.
   | literal                                               # Value pattern. The pattern matches an object such that pattern === object.
   | Constant                                              # Ditto.
   | var_                                                  # Ditto. It is equivalent to pin operator in Elixir.
   | (pat, ..., *var, pat, ..., id:, id: pat, ..., **var)  # Deconstructing pattern. See below for more details.
   | pat(pat, ...)                                         # Ditto. Syntactic sugar of (pat, pat, ...).
   | pat, ...                                              # Ditto. You can omit the parenthesis (top-level only). 
   | pat | pat | ...                                       # Alternative pattern. The pattern matches if any of pats match.
   | pat => var                                            # As pattern. Bind the variable to the value if pat match.

# one-liner version
$(pat, ...) = expr                                         # Deconstructing pattern.

つっつきボイス: 「zverokさんもパターンマッチングを提案してましたが、これは別の方でした」「あー、Rubyコードに対するパターンマッチ式を記述する構文を作ろうみたいな話ね」「zverokさんのときもMatzが『やるならなるべくいい構文にすべき』ってissueに書いてたと思います」「構文が変わるとなるとあのめちゃ長いparser.yとの格闘がつらそう😢

zverokさんの記事は近々翻訳を公開します。

⚓Ruby

⚓Staytus: アプリのステータスを表示するサービス(Ruby Weeklyより)


同サイトより


つっつきボイス: 「Staytusは『ステータス』と読ませたいみたい?」「あーなるほど、AWSのService Health Dashboard)↓みたいなのを表示できるサービスってことね」「ですです: 自分で作り込むよりは楽そう」「値段次第ですね💰


status.aws.amazon.comより

⚓Bash/Zshのタブ補完をRubyで書く(Ruby Weeklyより)

# 同記事より
  def matches(command_line)
    prepare_argv!(command_line)
    parse_and_consume_options!

    project_name_prefix = extract_project_name_prefix
    project_names = find_project_names
    filtered_names = filter_names(project_names, project_name_prefix)

    puts filtered_names
  rescue GetoptLong::InvalidOption
    # getoptlong prints the error automatically
    exit(1)
  rescue => error
    STDERR.puts error.message
    exit(1)
  end

つっつきボイス: 「Gobyのコントリビュータsaveriomiroddiさんの記事だったので拾ってみました」「これは何をしようとしてるのかな…?ははあ、Bashとかのタブ補完をRubyで書いているのかなるほど: 自分でタブ補完書いてみたい人に😋

⚓Wkhtmltopdfは有害(RubyFlowより)


同サイトより


つっつきボイス: 「Wkhtmltopdfって私は知りませんでしたが、昔からあるヤツですか?」「ですです: これ今でも使ってる人いるんだろうか🤔」「ビルドが面倒とかJavaScriptがないとページ番号扱えないとかドキュメントが貧弱とかつらみが書かれてますね」「Wkhtmltopdfはマジ古くからあるからまあいろいろあるでしょうね: 一応速いんじゃなかったかな?」「よく見るとビルドにQtが必要って書いてある😲」「マジで!?」「QtのGUI表示は本当にきれいなんですがめちゃでかくてビルド大変💦


qt.ioより

⚓Rearmedシリーズ(RubyFlowより)

# westonganger/rearmed-rbより
hash.join{|k,v| "#{k}: #{v}\n"}

hash = {foo: 'foo', bar: 'bar', other: 'other'}
hash.only(:foo, :bar) # => {foo: 'foo'}
# or without monkey patch: Rearmed.hash_only(hash, :foo, :bar)

hash.only!(:foo, :bar)

hash.to_struct
# or without monkey patch: Rearmed.hash_to_struct(hash)

# Only monkey patched if using Ruby 2.2.x or below as this method was added to Ruby core in 2.3.0
items = [{foo: ['foo','bar']}, {test: 'thing'}]
items.dig(0, :foo, 1) # => 'bar'
# or without monkey patch: Rearmed.dig(items, 0, :foo, 1)

# Only monkey patched if using Ruby 2.3.x or below as this method was added to Ruby core in 2.4.0
hash.compact
# or without monkey patch: Rearmed.hash_compact(hash)
hash.compact!

つっつきボイス: 「これはどうやらActiveSupport的なものを自分で作ってみた感じ」「オレオレモンキーパッチというか🐒」「RubyとRailsとJSとそれぞれ作ってますね」「業務で使うのはちょっと考えるなー🤔: こういうパッチは割と簡単に書けますけどね😉

[Rails5] Active Support::Inflectorの便利な活用形メソッド群

⚓フィボナッチヒープをRubyで実装(RubyFlowより)

require 'fibonacci_heap'

heap = FibonacciHeap::Heap.new
foo = FibonacciHeap::Node.new(1, 'foo')
bar = FibonacciHeap::Node.new(0, 'bar')
baz = FibonacciHeap::Node.new(2, 'baz')
heap.insert(foo)
heap.insert(bar)
heap.insert(baz)
heap.pop
#=> #<FibonacciHeap::Node key=0 value="bar">
heap.decrease_key(baz, 0)
heap.pop
#=> #<FibonacciHeap::Node key=0 value="baz">

つっつきボイス: 「フィボナッチヒープというデータ構造をRubyで実装したそうで、ダイクストラアルゴリズムと相性がいいそうです」

参考: フィボナッチヒープ - Wikipedia
参考: ダイクストラ法 - Wikipedia


ダイクストラ法 - Wikipediaより

⚓Ruby 2.6のString#splitはブロックを渡せる

# 同記事より
fruits = []

input_str = "apple, mango, potato, banana, cabbage, watermelon, grapes"

input_str.split(", ") { |value| fruits << value if is_fruit?(value) }
=> "apple, mango, potato, banana, cabbage, watermelon, grapes"

fruits
=> ["apple", "mango", "banana", "watermelon", "grapes"]

つっつきボイス: 「久々のBigBinaryさん記事でした」「おー、split後の結果処理をブロックで渡せるのはナイスなショートハンドかも😋」「splitの後でeachするよりラク👍

⚓mrubyの「Hakoniwa」とは


同サイトより

# 同サイトより
Haconiwa::Base.define do |config|
  config.name = "new-haconiwa001" # to be hostname

  config.cgroup["cpu.shares"] = 2048
  config.cgroup["memory.limit_in_bytes"] = "256M"
  config.cgroup["pid.max"] = 1024

  config.add_mount_point "/var/another/root/etc", to: "/var/your_rootfs/etc", readonly: true
  config.add_mount_point "/var/another/root/home", to: "/var/your_rootfs/home"
  config.mount_independent_procfs
  config.chroot_to "/var/your_rootfs"

  config.namespace.unshare "ipc"
  config.namespace.unshare "uts"
  config.namespace.unshare "mount"
  config.namespace.unshare "pid"

  config.capabilities.allow :all
  config.capabilities.drop "cap_sys_admin"
end

つっつきボイス: 「Hakoniwaという名前がいかにもコンテナな感じ」「あーこれどこかで見たナ」「コンテナ設定をmrubyのDSLで書けるみたい」

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

⚓OpenAPI(旧Swagger)


swagger.ioより


つっつきボイス: 「Swaggerも今日のBPS社内勉強会で話題が出たので」「SwaggerというかOpenAPIはそろそろ知っておかないとヤバイですね: 使ったことはなくても名前ぐらいは押さえておきたい😎

参考: Swaggerの概要をまとめてみた。 - Qiita
参考: Rails + swagger-blocks で OpenAPI 形式の API ドキュメントを作成する - Qiita

swagger: {自動} : 威張って歩く、自慢する

⚓readコマンドでパスワードを履歴に残さないようにする


つっつきボイス: 「readコマンドを知りませんでした😅」「readコマンドをバッチにすると知らずにそこで止まっちゃうことあるので個人的にはあまりこの使い方は好きではないです: バッチにするなら対話的なものは含めない方がいいと思うし🧐

参考: readコマンド(標準入力から読み込んで変数に格納する) : JP1/Advanced Shell

⚓Amazon Translateに日本語など追加

// 同記事より
import boto3
translate = boto3.client("translate")
lang_flag_pairs = [("ja", "🇯🇵"), ("ru", "🇷🇺"),
                   ("it", "🇮🇹"), ("zh-TW", "🇹🇼"),
                   ("tr", "🇹🇷"), ("cs", "🇨🇿")]
for lang, flag in lang_flag_pairs:
    print(flag)
    print(translate.translate_text(
        Text="Hello, World!",
        SourceLanguageCode="en",
        TargetLanguageCode=lang
    )['TranslatedText'])

つっつきボイス: 「ほほーAmazon Translateなんてのがあるとは」「今ならこういうAPIがあっても不思議でない」「ちなみにこの手の機械翻訳をチェックする時は割と長めの文章を食わせて下の方を見るようにしてます: 長文の出だしは整ってても下に行くと崩壊しているとかよくあるので😆」「今なら翻訳エンジンも複数選べるし、候補のひとつとして押さえておくのはいいと思う」

⚓SQL

⚓pgmongo: MongoDBをPostgreSQLに置き換える(Postgres Weeklyより)


同リポジトリより

まだproduction readyではないとあります(´・ω・`)。


つっつきボイス: 「MongDBをポスグレに置き換えるってもしかしてJSONBに入れてるだけなんじゃ?と思ったらどうもそれっぽい↓」「🤣」「🤣」「どんな人が使うんだろか?」「うっかりMongoDBにしちゃったけどPostgreSQLに引き返したい人なんじゃ?やっぱり🕶」「🤣」「諦めて作り直す方が早いきっと」「諦めが肝心」

# 同リポジトリより
db.createCollection('users')  ->  CREATE TABLE IF NOT EXISTS "users" (data jsonb)
db.users.find({ lastLogin: { $lte: '2016' } })  ->  SELECT data FROM "users" WHERE data->>'lastLogin'<='2016'
db.users.update({}, { $set: { active: true } })  ->  UPDATE "users" SET data = jsonb_set(data,'{active}','true'::jsonb)
db.users.find({}, { firstName: 1 } )  ->  SELECT jsonb_build_object('firstName', data->'firstName', '_id', data->'_id') as data FROM "users"
db.blogs.insert({ title: 'first post', comments: [] })  ->  INSERT INTO "blogs" VALUES ('{"_id":"5b45b641eb4bd93896d57888","title":"first post","comments":[]}'::jsonb)
db.blogs.remove({ 'state.trash': true })  ->  DELETE FROM "blogs" WHERE data->'state'->'trash'='true'::jsonb


mongodb.comより

⚓JavaScript

⚓JSXとは何か(JavaScript Weeklyより)

<!-- 同記事より -->
<div><span>Hello</span> <span>World</span></div>;
React.createElement("div", {
  children: [
    React.createElement("span", null, "Hello"),
    " ",
    React.createElement("span", null, "World")
  ]
});
// Note: babel uses the third argument for children:
React.createElement(
  "div", // type
  null, // props
  // children are the rest:
  React.createElement("span", null, "Hello"),
  " ",
  React.createElement("span", null, "World")
);

つっつきボイス: 「今更ですがJSXって何だったかなと思って」「JSXはReactと一緒に使うヤツで、前からありますよ: ただねー、JSXの記法↑は個人的にどうにもキモくて😅…この書き方どう思います?」「いやーそうでも…使ってみるとなかなかいいヤツですよ❤」「結構好みが分かれてるみたい」「長らくPHPやってからRubyに来たときに『カンマがない!』って思ったのとちょっと似てるかなーって」「そうですかー、自分は個人的にスクリプト全体が{ }か何かで囲まれてないとシームレスすぎてどうにも不安になるんですよ: Reactで一番耐え難かったのがこのあたり🤮」「どっちもわかるわー🤔」「ズボンの下にパンツ穿いてない気持ちになりそう」「jQueryなんかでも書こうと思えばこうやって書けそうな気がしないでもない」「とはいえJSXって既にかなり普及してるし、今からBackbone.jsやるよりは全然いいですけどね😋


jsx.github.ioより

参考: JSX - Wikipedia

追記

JSXがいくつもあったとは…失礼いたしました。https://jsx.github.io/がDeNAによるJSXというAltJS言語なんですね。

参考: 「JSX」って名前のものが色々あって混乱する - Qiita

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

⚓負荷テストツール8種比較(RubyFlowより)

以下のツールのメリットとデメリットが紹介されています。

  • JMeter
  • Locust
  • Loader.io
  • BlazeMeter
  • Radview WebLOAD(有料)
  • Micro Focus LoadRunner(有料)
  • IBM Rational Performance Tester(有料)
  • SmartBear LoadUI Pro(有料)

つっつきボイス: 「こういうツールの名前をとりあえず知っておくと後で探せますね😋
「とりあえずJMeterは筆頭で歴史もとても長い: ちょうど最近BPSに入社したインフラエンジニアの人も今使ってますね」「おー😲」「そしてJMeterはめちゃめちゃ重いw」「重いんですか?負荷テストツールなのに?」「負荷テストやってるとたいていクライアントが先に音を上げて死ぬという😆: かなり強いサーバーをJMeterの負荷で落とそうとするとJMeterをクラスタ化したりしないといけなかったり」「はりゃー相打ちというか😮」「ただJMeterはプロキシモードで使えるというメリットがあって、JMeterのプロキシサーバー経由でブラウザ画面をぽちぽち操作するとその操作でシナリオを作れるんですよ」「それはありがたい😊」「負荷テストのちゃんとしたシナリオ、それもステートフルなシナリオが作りやすいのがJMeterのいいところ💪: まあ今ならRailsのシステムテストがあるからそっちでもやれますけどね🕶」「😃」「それがなかった頃はJMeterは重宝しました」

「後はあんまり知らないなぁ…BlazeMeterはJMeterと連携できるのか…おや?よく使われるAB(Apache Benchmark)とかskipfishとかがこのリストにないなあ」「AB?また探しにくそうな」「ABはひたすらPOSTリクエストをもの凄いレートで投げられるんで、一瞬でサーバーを落とすことすら可能🧐」「すげー!」「skipfishは?」「skipfishはGoogleのツールで、セキュリティチェックが主だから負荷テストとは少し違うといえば違うかな: WordPressサイトなんかを目がけてかければいろいろ結果を出してくれる」「へー!」

参考: ab - Apache HTTP server benchmarking tool - Apache HTTP Server Version 2.2
参考: セキュリティチェックツールskipfish入門 | apps-gcp.com

⚓Zeplin: PhotoshopなしでPSD確認/コメント付け


同サイトより


つっつきボイス: 「この間の新入社員歓迎会の席で話題になってたZeplinです: PhotoshopのPSDファイルをPhotoshopなしで開けるしコメントも付けられるしで、いつの間にかBPS社内で結構使われてるので」「ZeplinはWindows版もありますしね: この種のツールはSketchとかAdobe XDとかInVisionとか他にも山ほどあるんですが、最終的に大事なのはデザイナーが使っているツールときちんと連携できるかどうかがポイント」「そういえば今日も別のよく似たツールの話してたのに、そのツールの名前が思い出せない…😢」「何しろほんといっぱいあるから」「ともあれZeplinみたいなツールは、今やないとデザイナーと共同作業やってられないぐらいには必要ですね」

↓割とよく間違えられますがこれは「ヒンデンブルグ号」です。

参考: ヒンデンブルク号爆発事故 - Wikipedia
参考: ツェッペリン - Wikipedia


追いかけボイス: 「ちなみに類似ツールを探すときはツール名 alternativeでググると探しやすいです: こんなサイトもあったりしますし↓」

参考: Zeplin Alternatives and Similar Software - AlternativeTo.net

⚓言語よろずの間

⚓RubyistもElixirを学ぶべき理由(Ruby Weeklyより)


つっつきボイス: 「台湾だとRubyとElixirがよく合同でカンファレンスやってますよね」「ウォッチでも何度か取り上げました↓」


2018.rubyconf.twより

⚓Artifact 2.0: Rustで書かれた設計ドキュメント化ツール

designという語を見ると「(ソフトウェア)設計」なのか「デザイン」なのか毎度身構えます。


つっつきボイス: 「こういうのはまず間違いなく『設計』でしょう」「ですよね😳」「↑プロモーション動画が凝ってる❤

⚓Go言語のガベージコレクタを徹底解説(公式ブログ)


つっつきボイス: 「Go言語のロゴが変わったからGopherくんいなくなったのかなー?可哀想に…と思ったらちゃんといた😋↓」「えかったえかった」「元はスライドみたいですがめちゃめちゃ長いです」


同ブログより

⚓その他

⚓VS IntelliCodeのAIサジェスチョンが強化


つっつきボイス: 「何でも今回のアップデートではコーディング規約まで推測するらしくて: 『あなたのところはきっとこんなルールでやってるのよね』って感じで推測するのかなと」
「ちょうど今目についたんだけど、コーディング規約といえばEditorConfigに機能が増えるべきだよなと常々思ってる」「EditorConfig?: もしかしてそれってエディタを超えて使えるんですか?」「そうですよ、たぶんどのエディタでもこのファイルがあれば読み込んでくれる: 知りませんでした?」「知らなかったー😅

# 同サイトより
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8

# 4 space indentation
[*.py]
indent_style = space
indent_size = 4

# Tab indentation (no size specified)
[Makefile]
indent_style = tab

# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2

# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

「標準のEditorConfigってシンプルなコンベンションしかなくて機能がまだまだ足りないんですよ: Rubyで言うなら通常の2スペースのインデントまではできても、行が長くなりすぎて折り返したときのインデント(コンティニュエーションインデント)を自分は4スペースにしてブロックと区別したいのにそれが設定できない💦: JetBrainsのIDEになら当然のようにある設定なのに…」「ありゃー😢」「EditorConfigは言語とも独立しているし汎用性重視だからだと思うんですけど、やっぱり物足りない」

参考: どんなエディタでもEditorConfigを使ってコードの統一性を高める - Qiita

参考: コーディングをAIが支援してくれる「Visual Studio IntelliCode」がアップデート。既存コードからコーディング規約を推測し、適切な設定ファイルを生成 - Publickey

⚓新型Macbook Proはアツい?

参考: 新MacBook Pro+Core i9でマシンがアチアチになるとの報告 | ギズモード・ジャパン


つっつきボイス: 「さて新型Macbook Proに70万かけるべきかどうか💰」「高!」「それだけかけてもデスクトップマシンよりは速くならないだろうし🐢

⚓番外

⚓自転車向け新型シャフトドライブ


つっつきボイス: 「これは動画を見る方が早いです: 伝達効率99%がアツい」「へー」「大根をおろせそうなギアも🥕

参考: 斬新なギア構造が決め手! 動力の伝達効率が99%のシャフト・ドライブ自転車 | ギズモード・ジャパン

⚓AIによる自律的殺人兵器非開発の誓約書


つっつきボイス: 「ターミネーターは作りません、みたいな?」

参考: 「AIによる自律的殺人兵器非開発の誓約書」発表。Google Deepmind設立者やイーロン・マスクら署名 - Engadget 日本版


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180702)Ruby 2.2メンテ正式終了、Ransackがつらくなるとき、書籍『Domain-Driven Rails』、GitHubの高可用MySQLほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

Serverless Status

serverless_status_banner

JavaScript Weekly

javascriptweekly_logo_captured


Viewing all articles
Browse latest Browse all 1759

Trending Articles