Quantcast
Channel: TechRacho
Viewing all 2925 articles
Browse latest View live

Rails 6にDNSリバインディング攻撃防止機能が追加された(翻訳)

$
0
0

概要

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

参考: DNS Rebinding ~今日の用語特別版~ | 徳丸浩の日記

Rails 6にDNSリバインディング攻撃防止機能が追加された(翻訳)

Rails 6にはDNSリバインディング攻撃から保護する機能が#33145で追加されました。この機能はdevelopment環境ではデフォルトで有効になっており、他の環境でもオプションで有効にできます。

DNSリバインディング攻撃とは

DNSリバインディング攻撃は、攻撃者が悪意のあるクライアントサイドスクリプトを仕込んだWebページを用いて標的ネットワークに侵入し、そのネットワーク内のブラウザをプロキシとしてネットワーク内の他のデバイスを攻撃することを指す。

Railsアプリで生じる影響

ローカル実行されているRailsアプリケーションに対して、攻撃者がDNSリバインディングを用いてリモートコード実行(RCE)する可能性があります。攻撃者が的確にアプローチすると、ローカルENV(環境変数)情報や、ローカルRailsアプリの全情報にアクセスできてしまうことがあります。

Rails 6の場合

試しにdevelopment環境のRails 6アプリに対して何らかのカスタムドメイン(ngrockやlvh.me、あるいはhostsファイルを編集するだけでも可能)を用いてアクセスしてみると、以下のようなエラーが表示される場合があります。

Blocked host: yourcustomdomain.com

しかしこのときlocalhostを指定すると何の問題もなくアプリにアクセスできます。このエラーの背後では、Rails 6がActionDispatch::HostAuthorizationミドルウェアの助けを借りてDNSリバインディング攻撃からの攻撃を懸命に防ごうとしています。development環境では、以下の設定がデフォルトで含まれています。

Rails.application.config.hosts = [
  IPAddr.new("0.0.0.0/0"), # All IPv4 addresses.
  IPAddr.new("::/0"),      # All IPv6 addresses.
  "localhost"              # The localhost reserved domain.
]

カスタムドメインからのアクセスを許可するには、Rails.application.config.hostsにそのドメインを追加します。たとえばdevelopment.rbに以下のように記述します。

# 'yourcustomdomain.com'からのアクセスを許可する設定
Rails.application.config.hosts << 'yourcustomdomain.com'

サブドメインからのリクエストを許可するには以下のようにします。

# 'www.yourcustomdomain.com'や'blog.yourcustomdomain.com'などの
# サブドメインを許可する設定
Rails.application.config.hosts << '.yourcustomdomain.com'

この保護を完全にバイパスして任意のドメインからのリクエストを許可する必要がある場合は、以下のようにします。

# 任意ドメインからのリクエストを許可する設定
Rails.application.config.hosts = nil

なお、development環境以外のconfig.hostsはデフォルトで空になる点にご注意ください。つまりドメインはチェックされず、保護は無効になります。

保護を有効にするのであれば、該当する環境ファイルでRails.application.config.hostsに指定したいドメイン名を値として設定します。

関連記事

Rails 6のcookieに「purpose」メタデータが追加(翻訳)


Docker + ngrokで10分で作るLINEbot

$
0
0

BPSの福岡拠点として一緒にお仕事をさせていただいています、株式会社ウイングドアの田上です。
本日はタイトルの通り、Docker + ngrokを使ってLINEのオウム返しbotを作ってみたのでご紹介いたします🦜

準備するもの

  • Docker for Macのインストール
  • ngrokのインストール
  • LINEアカウント

Docker for Macのインストール

Docker Hub

公式サイトに従ってDocker for Macをインストールしてください。

ngrokのインストール

ngrokを使うとローカル環境に立ち上げたWebアプリをお手軽にインターネットに外部公開することができます。

ngrok – secure introspectable tunnels to localhost

こちらも、公式サイトに従ってngrokをダウンロードしてください。

LINEbot用アカウントの作成

LINE Developers

  • LINE Developersにアクセスし、ログインをクリックします。

  • [LINEアカウントでログイン]をクリックし、お持ちのLINEアカウントでログインをします。

  • プロバイダー>[作成] にて、プロバイダー情報を開設します。

  • 作成したプロバイダーが一覧に表示されるので、選択します。

  • チャネル設定>[新規チャネル作成] を選択します。

  • Messaging APIを選択します。

  • アナウンスに従って入力を行ってチャネルを開設します。

  • 開設したチャネルがリストに追加されるので選択します。

  • Messaging API設定>チャネルアクセストークンにて、[発行]ボタンを押すとチャネルアクセストークン文字列が生成されるため、控えておきます。

LINEbotを動かすDockerコンテナの作成

次に、LINEbotを動かすためのプログラムを準備していきます。

今回はプロジェクト構成を以下の通りにしました。

LINEBot_test
├── docker-compose.yml
├── nginx
│   └── nginx.conf
└── www
   └── html
       └── index.php

docker-compose.yml

version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./www/html:/var/www/html
    depends_on:
      - php
  php:
    image: php:7.3-fpm
    volumes:
      - ./www/html:/var/www/html

nginx/nginx.conf

server {
   listen 80;
   server_name _;
   root  /var/www/html;
   index index.php index.html;
   access_log /var/log/nginx/access.log;
   error_log  /var/log/nginx/error.log;
   location / {
       try_files $uri $uri/ /index.php$is_args$args;
   }
   location ~ \.php$ {
       fastcgi_pass php:9000;
       fastcgi_index index.php;
       fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
       include       fastcgi_params;
   }
}

www/html/index.php

LINEbotのプログラム部分となります。
Messaging APIのリファレンスを参考にリクエストメッセージを組み立てていきます。

参考:Messaging APIリファレンス | LINE Developers

手順3 Dockerコンテナの立ち上げ

  • プロジェクトルートで以下のコマンドを実行し、Dockerコンテナを起動します。
docker-compose up -d

手順4 ngrokで立ち上げたDockerコンテナへのポートフォワーディング

  • ngrokを使い、立ち上げたDockerコンテナへポートフォワーディングを行います。
ngrok http -host-header="0.0.0.0:8080" 8080

  • コマンドを実行すると上記の状態となりますので、ForwardingのhttpsのURLを控えておきます。

Webhook URLの登録

  • LINE Developersにて、Webhook URLにngrokでポートフォワーディングしているhttpsのURLを登録します。

httpsのURLでなければWebhook URLへの通知が届かないので注意してください。

実行

  • Messaging API設定のQRコードより友達登録を行い、メッセージを送信すると、送信した内容がオウム返しされるはずです❗

まとめ

実際にやってみましたが、Dockerとngrokがインストールされている前提で、
合計8分前後でオウム返しbotを作成することができました☺

今回はオウム返しをするだけですが、Messaging APIは他にも様々な機能がありますので、是非いろいろ試してみてください❗


株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

docker-compose.ymlの中で値を使い回す方法

$
0
0

小ネタで恐縮です。docker-composeは便利なのですが、docker-compose.ymlの中でローカル変数的に値を手軽に使い回す方法を知りたくなりました。

version: "3.4"

services:
  app: &app
    build:
      context: .
      dockerfile: ./.dockerdev/Dockerfile
      args:
        RUBY_VERSION: "2.7.0-slim-buster"
        PG_MAJOR: "12"
        NODE_MAJOR: "11"
        YARN_VERSION: "1.19.1"
        BUNDLER_VERSION: "2.0.2"
    image: enno_docker:1.0.0
    tmpfs:
      - /tmp
# (略)

  postgres:
    image: postgres:12
    volumes:
      - .psqlrc:/root/.psqlrc:ro
      - postgres:/var/lib/postgresql/data
      - ./log:/root/log:cached
    environment:
      - PSQL_HISTFILE=/root/log/.psql_history
    ports:
      - 5432
    env_file: .env

上のargs:でPostgreSQLのバージョンをPG_MAJOR: "12"と指定しますが、これはDocker側の環境変数にexportされてDockerfile側で参照する前提です(この場合はpsqlのビルドに使われます)。

このバージョン番号を、その下にあるPostgreSQLのDocker Hubイメージのバージョン指定image: postgres:12にも使おうと思ってimage: postgres:{$PG_MAJOR}などと書いてもエラーになります。

WARNING: The PG_MAJOR variable is not set. Defaulting to a blank string.
Creating network "enno_docker_default" with the default driver
ERROR: no such image: postgres:{}: invalid reference format

解決方法

またしてもStack Overflowですが、yamlの機能である程度実現できることがわかりました。「アンカー/エイリアス」機能を用います。

「アンカー/エイリアス」機能自体はdocker-compose.ymlでも普通に使われていますが、ここでのポイントは以下のように「x-で始まるキー」を書くことで、これでDocker Composeで無視されるようになります。そこに&アンカー名を書くと、値を*アンカー名でエイリアスとして参照できます。以下ではx-varにしていますが、わかりやすい名前でよいと思います。

version: "3.4"

x-var: &APP_IMAGE_TAG
  "my_app:1.0.0"
x-var: &RUBY_VERSION
  "2.7.0-slim-buster"
x-var: &PG_MAJOR
  12
x-var: &POSTGRES
  "postgres:12"
x-var: &NODE_MAJOR
  12
x-var: &YARN_VERSION
  1.21.1
x-var: &BUNDLER_VERSION
  2.1.2

services:
  app: &app
    build:
      context: .
      dockerfile: ./.dockerdev/Dockerfile
      args:
        RUBY_VERSION: *RUBY_VERSION
        PG_MAJOR: *PG_MAJOR
        NODE_MAJOR: *NODE_MAJOR
        YARN_VERSION: *YARN_VERSION
        BUNDLER_VERSION: *BUNDLER_VERSION
    image: *APP_IMAGE_TAG
    tmpfs:
      - /tmp
# (略)

  postgres:
    image: *POSTGRES
    volumes:
      - .psqlrc:/root/.psqlrc:ro
      - postgres:/var/lib/postgresql/data
      - ./log:/root/log:cached
    environment:
      - PSQL_HISTFILE=/root/log/.psql_history
    ports:
      - 5432
    env_file: .env

参考: アンカーとエイリアス — プログラマーのための YAML 入門 (初級編)

YAML では、データに「&name」で印をつけ、「*name」で参照することができます (C 言語におけるアドレスやポインタと同じ表記です)。前者をアンカー (Anchor)、後者をエイリアス (Alias) といいます。

なお上の例でもわかるように、シェルの環境変数みたいに値を結合したり一部を置き換えたりすることはできませんでした。本当はimage: postgres:{$PG_MAJOR}のように書きたかったのですがダメでした😢

わかってしまえばどうということはありませんでしたが、それでも変動する可能性のある値をdocker-compose.ymlの冒頭にまとめられたので、バージョン更新のときに見落としにくくなったと思います(つか前は見落としました)。docker-compose自体の機能ではないので、そのつもりで使うことにします。

関連記事

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

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

週刊Railsウォッチ(20200210前編)Railsのベンチマークジェネレータ、長いバックグラウンドジョブと戦う、Timestamp切り詰めの謎、Open APIツールほか

$
0
0

こんにちは、hachi8833です。そういえば明日は祝日ですね。コロナウイルス流行の様子が早速ビジュアライズされたようです。

「まあこれで何かわかったとしても自分らに打てる手は限られてますし😷」「情報の動き激しくて…😅」(以下延々)

参考: CNN.co.jp : 新型ウイルス、潜伏期間中の感染例は「誤り」 独当局

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

今回は第19回公開つっつき会を元にお送りします。お集まりいただいた皆さまありがとうございます!

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

今週はコミットログより見繕いました。今週は細かめの修正が多い印象です。

⚓default_scopedをpublic APIに

# activerecord/lib/active_record/scoping/named.rb#L25
        def all
          scope = current_scope
          if scope
            if scope._deprecated_scope_source
              ActiveSupport::Deprecation.warn(<<~MSG.squish)
                Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
                in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
-               To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`,
-               or `#{name}.default_scoped` if a model has default scopes.
+               To instead access the full set of models, as Rails 6.1 will, use `#{name}.default_scoped`.
              MSG
            end

            if self == scope.klass
              scope.clone
            else
              relation.merge!(scope)
            end
          else
            default_scoped
          end
        end
...
-       def default_scoped(scope = relation) # :nodoc:
+       # Returns a scope for the model with default scopes.
+       def default_scoped(scope = relation)
          build_default_scope(scope) || scope
        end

つっつきボイス:「上は実はついさっきkamipoさんがリツイートしたコミットです🐦」「お、unscopedしてしまった後でもデフォルトスコープを取れるのがdefault_scopedっていうことみたい: 自分はあんまり使わなそうだけどpublicメソッドが欲しい気持ちはワカル☺

API: unscope — ActiveRecord::QueryMethods

default_scopedは、スコーピング上デフォルトスコープになっているスコープを強制的に返す唯一の方法であり、マイグレーションでのスコープのリークを回避するのに必要。
同PRより大意

default_scopeを使わないのが一番なんでしょうか?🤔」「そういうわけにもいかないことがありますし、小さいシステムならdefault_scopeがあっても嫌がられないと思いますが、育ってくるとちょっとね…😅

Railsのdefault_scopeは使うな、絶対(翻訳)

⚓delegateのeachを取り除いた

# actionpack/lib/action_dispatch/testing/integration.rb#L80
    class Session
      DEFAULT_HOST = "www.example.com"
      include Minitest::Assertions
      include TestProcess, RequestHelpers, Assertions

-     %w( status status_message headers body redirect? ).each do |method|
-       delegate method, to: :response, allow_nil: true
-     end
-
-     %w( path ).each do |method|
-       delegate method, to: :request, allow_nil: true
-     end
+     delegate :status, :status_message, :headers, :body, :redirect?, to: :response, allow_nil: true
+     delegate :path, to: :request, allow_nil: true

つっつきボイス:「見出しに”delegate allows multiple method names are passed”とあるのは…?」「delegateの内容は変わってないので、eachのループをなくしたということなんでしょうね☺: タイトルは単にこの部分の挙動をメモった感じかな」「走り書きでしたか😅」「Railsの起動がちょっと速くなりそう☺」「以前%w()で書かれてた理由はわかりませんけど😆

⚓キーワード引数対応: define_methodをstringのevalに変更

# actionpack/lib/action_dispatch/testing/integration.rb#L356
      %w(get post patch put head delete cookies assigns follow_redirect!).each do |method|
-       define_method(method) do |*args, **options|
-         # reset the html_document variable, except for cookies/assigns calls
-         unless method == "cookies" || method == "assigns"
-           @html_document = nil
-         end
+       # reset the html_document variable, except for cookies/assigns calls
+       unless method == "cookies" || method == "assigns"
+         reset_html_document = "@html_document = nil"
+       end

-         result = if options.any?
-           integration_session.__send__(method, *args, **options)
-         else
-           integration_session.__send__(method, *args)
+       definition = RUBY_VERSION >= "2.7" ? "..." : "*args"
+
+       module_eval <<~RUBY, __FILE__, __LINE__ + 1
+         def #{method}(#{definition})
+           #{reset_html_document}
+           result = integration_session.#{method}(#{definition})
+           copy_session_variables!
+           result
          end
-         copy_session_variables!
-         result
-       end
+       RUBY
      end

「これもさっきと同じintegration.rbファイルの変更ですね」「今までdefine_methodで定義していたのをmodule_evalに変えたのね😋

「『キーワード引数をif options.any?で扱うのはイケてない』みたいなことが書いてあるのでキーワード引数関連かな🤔: definition = RUBY_VERSION >= "2.7" ? "..." : "*args"とかありますし、stringのevalにすればdefine_method経由ではなくなってstringで書けるようになるので、definitionで2.7以降の場合とそうでない場合に...*argsを使い分けられるようになったということか😋」「なるほど!」「速度が目的ではなさそうなので、たぶんstringじゃないと2.7対応がやりにくいんでしょうね☺

「コミットで引用されてるb7e591aa43de73を見るとamatsudaさんが**options引数周りで試行錯誤してますね」「あぁ、Ruby 2.7のキーワード引数変更問題に引っかからないようにするためのやり方を試行錯誤してる感じ: if options.any?の結果で分岐するか、上みたいにstringでRubyコードそのものにパッチを当てる形で修正するか」「Railsのフレームワークでは複数バージョンのRubyに対応しないといけないので大変😅

⚓スキーマキャッシュ読み込み時のDB configフォールバック

# activerecord/lib/active_record/railtie.rb#L127
    initializer "active_record.check_schema_cache_dump" do
      if config.active_record.delete(:use_schema_cache_dump)
        config.after_initialize do |app|
          ActiveSupport.on_load(:active_record) do
            db_config = ActiveRecord::Base.configurations.configs_for(
              env_name: Rails.env,
              spec_name: "primary",
            )
+           next if db_config.nil?
+
            filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
              db_config.spec_name,
              schema_cache_path: db_config.schema_cache_path,
            )
            if File.file?(filename)
              current_version = ActiveRecord::Migrator.current_version
              next if current_version.nil?
              cache = YAML.load(File.read(filename))
              if cache.version == current_version
                connection_pool.schema_cache = cache.dup
              else
                warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
              end
            end
          end
        end
      end

つっつきボイス:「修正は1行追加だけ」「前回のウォッチでもマルチDBがreplicaじゃなくてprimaryから取ってきちゃう問題の修正があったのを少し思い出しますね🤔ウォッチ20200203)」「マルチDBも大変そう…」

概要
スキーマキャッシュはデフォルトでprimaryデータベースconfigを読み出す。しかしspec nameがprimaryのデータベースconfigがアプリにない場合、ファイル名探索に失敗する。
ここでは念のためフォールバックを追加した。
その他
スキーマキャッシュのファイル名はハードコードされていたが、Railsではデフォルトパスをオーバーライドできるようになっている。#38348ではオーバーライドを考慮するようにしたが、spec nameがprimaryのデータベースconfigがないアプリの対応が漏れていた。
GitHubにはprimaryという名前のデータベースがないこともわかったので、このActive Recordイニシャライザに依存していないとしても失敗する。
同PRより大意

⚓GitHub ActionsのRailsビルドを数秒短縮

# .github/workflows/rubocop.yml#L21
-       gem install bundler:2.1.2
+       gem install bundler:2.1.2 --no-document

つっつきボイス:「ドキュメント生成やめたのね😆」「言われてみればなるほど😆」「ドキュメント生成は結構重いし🏋🏻‍♀️」「速くなるのわかります😋

⚓細かな修正系


つっつきボイス:「微修正をまとめてみました」「Railsガイドのパスの間違いとか☺

# activerecord/test/cases/migration_test.rb#L211
      if current_adapter?(:Mysql2Adapter)
        if ActiveRecord::Base.connection.mariadb?
          assert_match(/Can't DROP COLUMN `last_name`; check that it exists/, error.message)
        else
          assert_match(/check that column\/key exists/, error.message)
        end
-     elsif
+     elsif current_adapter?(:PostgreSQLAdapter)
        assert_match(/column \"last_name\" of relation \"people\" does not exist/, error.message)
      end

elsifの条件が抜けてたとは↑😆」「その下のassert_matchが条件扱いされるからエラーなしで通っちゃってた😆」「RuboCopに引っかからなかったのか👮🏼‍♀️

# railties/lib/rails/generators/rails/benchmark/templates/benchmark.rb.tt#L3
-require_relative "../config/environment"
+require_relative "../../config/environment"

「こんなエラー↑が今頃見つかるとは😆」「つかrails benchmarkってコマンドありましたっけ?」「知らない😆」「そんな機能が入ってたことの方がびっくり😳

⚓Railsのベンチマークジェネレーター

概要: パフォーマンス最適化を比較するベンチマークを生成する。
デフォルト: method = ips
:rails generate benchmark opt_compare path_a path_b
上で以下が生成される:
benchmarks/opt_compare.rb
--methodで他のベンチマーク手法を指定できる。有効な手法はips、bm、bmbm
Changelogより

「軽くググると昨年12月13日にベンチマークジェネレーターがmasterに入ってるし↑😆」「Rails 6.1あたりで使えるようになるのかな?」「そんなに複雑なことはやってないと思うけど、ベンチマークって自分でやろうとすると毎回ググるので、ベンチマークのテンプレートをRailsで生成できるならいいかな〜😋」 ​

⚓番外: オプション引数をつぶして回る

# actionpack/lib/abstract_controller/translation.rb#L26
-   def localize(*args)
-     I18n.localize(*args)
+   def localize(object, **options)
+     I18n.localize(object, **options)
    end
# actionpack/lib/abstract_controller/translation.rb#L13
-   def translate(key, options = {})
+   def translate(key, **options)
      if key.to_s.first == "."
-       options = options.dup
        path = controller_path.tr("/", ".")
        defaults = [:"#{path}#{key}"]
        defaults << options[:default] if options[:default]
        options[:default] = defaults.flatten
        key = "#{path}.#{action_name}#{key}"
      end
      I18n.translate(key, **options)
    end
# activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L709
-     def foreign_key_exists?(*args)
-       @base.foreign_key_exists?(name, *args)
+     def foreign_key_exists?(*args, **options)
+       @base.foreign_key_exists?(name, *args, **options)
      end

つっつきボイス:「ああ*argsoptions = {}を置き換えるヤツ☺」「options = {}みたいなレガシーな書き方がまだまだ残ってた😆」「だいぶ前にbabaさんがキライだって言ってた書き方ですね↓」

Railsフレームワークで多用される「options = {} 」引数は軽々しく真似しない方がいいという話

options = {}は、キーワード引数が登場する前のRuby 1.9とも互換性のある書き方だったと思います」「そういえばキーワード引数が登場したのはRuby 2.0でしたね」

「ところでお集まりの皆さんの中でRuby 1.8あたりを経験された方は?: さすがにいませんね😆」「Rails 4からなので😆」「自分の時代は今は亡きRuby Enterprise Edition↓使ってたので1.8経験しました: サイトは生き残ってるけどやはり1.8.7で止まってるか〜😆」「そういえばここってPassengerの会社がやってたんですね😳」「そうそうPhusion

参考: Welcome — Ruby Enterprise Edition


rubyenterpriseedition.comより

「ちなみにRuby Enterprise Editionは一応今でもrbenvにエントリぐらいはあります↓」「おぉっ」「でもさすがにOpenSSLのバージョンが今と違ってるでしょうからビルドは無理じゃないかな〜🤣: 試しに裏でビルドしてみよう」(しばらく経って)「やはりダメか😆

ree-1.8.7-2011.03
ree-1.8.7-2011.12
ree-1.8.7-2012.01
ree-1.8.7-2012.02

参考: OpenSSLが古すぎてbundle updateできない

「今の時代にやんごとない事情でRuby 1.8を動かさないといけなくなったらDockerコンテナでやるとかになるでしょうね🐳」「Docker Hub見ると1.8コンテナ作ってる人がかろうじているけど、どこまで信用できるかわかりませんし😆


つっつきボイス:「上はさっき流れてきたツイートです」「さらっと『全部のコミット読んでる』」「最強すぎ💪」「最後は眼ぢから👁

⚓Rails

今回は大半がRuby Weeklyのエントリになりました。軽く敗北感。

⚓RailsのTimestampが切り詰められた(Ruby Weeklyより)

# 同記事より
{"created_at"=>2020-01-02 13:36:22.459149334 +0000, "updated_at"=>2020-01-02 13:36:22.459149334 +0000}
{"created_at"=>2020-01-02 13:36:22.459149000 +0000, "updated_at"=>2020-01-02 13:36:22.459149000 +0000}

つっつきボイス:「あ〜マイクロ秒以下が落ちちゃうヤツ↑あるある🤣」「🤣」「reloadすると変わる↓とある」

# 同記事より
before perform: 1577985089.0547702
after perform:  1577985089.0547702
after reload:   1577985089.05477

「Active Recordはsaveメソッドを呼ぶとINSERTなりUPDATEなりが行われるんですけど、DBに実際に保存した値を読み込み直すことまではしないので、saveした直後に値を参照すると、DBに保存された値そのものではなくRuby側で設定したときの値を返します」「後でreloadしてみると値が変わってたとは😇」「😳」「めったに踏みませんけど、たまに出くわしますね☺


目次より:

  • 問題: macOS上のdev環境ではテストが通るのにCIでほぼ失敗する(何とかログは取れた)
  • 調査
    • 桁落ちを発見
    • プリントデバッグでは再現せず、calendar.attributes['created_at']でattributeハッシュを直接フェッチすると再現した
  • 修正
    • DateTimeオブジェクトを生成して秒以下を丸め、このオブジェクトでタイムスタンプを明示的に設定した
  • 原因
    • Active RecordのタイムスタンプがTime.nowで設定されているがTimeの精度はOSに依存する
  • 疑問(説明求む)
    • calendar.attributes['created_at']だとリロードされたのにcalendar.created_atだとリロードされないのはなぜ?
    • Linuxと比較してみるとmacOSでの精度は6桁どまりという驚きの結果(7桁目以降は1と2と8と9しか現れない↓)
# 同記事より
10000.times.map { Time.now}.map{|t| t.to_f.to_s.match(/\.(\d+)/)[1] }.select{|s| s.size == 7}.group_by{|e| e[-1]}.map{|k, v| [k, v.size]}.to_h

# MacOS => {"9"=>536, "1"=>555, "2"=>778, "8"=>807}
# Linux => {"5"=>981, "1"=>311, "3"=>1039, "9"=>309, "8"=>989, "6"=>1031, "2"=>979, "7"=>966, "4"=>978}

⚓sequenced: ARモデル向けのシーケンシャルID生成(Ruby Weeklyより)

# 同リポジトリより
class Question < ActiveRecord::Base
  has_many :answers
end

class Answer < ActiveRecord::Base
  belongs_to :question
  acts_as_sequenced scope: :question_id
end

つっつきボイス:「このgemの嬉しみってどのあたりでしょう?」「冒頭に書いてあるそのまんまですが、プライマリキーではない、スコープドのシーケンシャルIDを生成できるということですね☺

「使いみちとしては、たとえばですがUserモデルにhas_many :picturesがあったとすると、どのユーザーについてもユーザー独自の1個目の画像をURLで表現できるようにしたい、なんて場合があればでしょうね」

「Railsで普通にネステッドスコープを作ると、ネステッドの先もサロゲートキーになりますよね: 言い換えればそこは名前空間が同じになるので、ユーザーAにとっての画像1もユーザーBにとっての画像1も同じものを指してしまう」「ふむふむ」「このsequenced gemを使えば、/ユーザーA/1で取れる画像が必ずそのユーザーAにとっての1個目の画像になり、/ユーザーB/1だとそれとは異なるユーザーBにとっての1個目の画像になる、といった具合です」

「このgemを使うかどうかは別として、こういうのをやりたい場合があるのはわかりますね☺」「自分で書くのもありな機能でしょうか?」「スコープドのシーケンシャルID生成ではシーケンス番号がかぶらないように保証するのが面倒くさくなりがちなので、このgemがトランザクション管理までやってくれるのであればワンチャンあるかも🤔

「READMEの下の方にdata integrityとかあるしやってくれるかな?…とよく見ると『これはPostgreSQLでしかコンカレント安全でない』って書いてあるし😆」「😆」「というわけでぽすぐれ以外ではシーケンシャルがIDかぶる可能性あります: 以下のPARTITION BYあたりとかPostgreSQL向けっぽいですし↓」

# 同リポジトリより
# app/db/migrations/20151120190645_add_sequental_id_to_badgers.rb
class AddSequentalIdToBadgers < ActiveRecord::Migration
  add_column :badgers, :sequential_id, :integer

  execute <<~SQL
    UPDATE badgers
    SET sequential_id = old_badgers.next_sequential_id
    FROM (
      SELECT id, ROW_NUMBER()
      OVER(
        PARTITION BY burrow_id
        ORDER BY id
      ) AS next_sequential_id
      FROM badgers
    ) old_badgers
    WHERE badgers.id = old_badgers.id
  SQL

  change_column :badgers, :sequential_id, :integer, null: false
  add_index :badgers, [:sequential_id, :burrow_id], unique: true
end

「こういうことをしたいときが数年に1回ぐらいはありそう😆」「😆」「そのときにこのgemを思い出せるかどうかですけどっ😆

⚓Open API仕様記述ツール


同サイトより


つっつきボイス:「今日のWebチーム内発表がOpen APIの話題だったので先週(ウォッチ20200203)に引き続いて」「そうそう、Open API(Swagger)で仕様を書くことがあるんですけど、上のサイトはOpen APIのツール集ですね: Open APIは夢のツールとまではいきませんが😆

「チーム内発表ではstoplightというツール↓が登場してましたね」「stoplightはちゃんとWYSIWYGエディタになってて、とにかくyamlを手書きしなくていいのが嬉しい❤


stoplight.ioより

「Swaggerエディタ↓って結局yamlを書かないといけないのがつらくありません?😆」「ある程度しょうがないかと思うけど生成されるyamlのファイル長すぎじゃね?って思ったりもするけど、Open APIがなかったらExcelで仕様書かないといけなくなりますし😆


editor.swagger.ioより

参考: Open API仕様記述ツールを比較してみた - Qiita

「Qiitaにある中でAPI Blueprintは書いたことありますね: ちょっとMarkdown風ですが自由に書けすぎる感😆


apiblueprint.orgより

⚓Chrome 80のSameSite属性


つっつきボイス:「SameSiteはもう入ってきたんでしたっけ?」「Chrome 80は昨日見ている目の前でインストールされました」「SameSiteの動作変更は来週からみたい」「デフォルトのSameSiteは本来こうあるべきかなと思いますけど😆」「SameSite=None; Secureを付けると不具合を発生するブラウザって…」

「これで影響を受ける決済ってどのぐらいあるんでしょう?」「まあカード会社のサイトに行って戻るようなECサイトではそもそも普通POSTではやりませんし」「おぉ」

「今どきクロスサイトでPOSTして、しかもまた戻ってくるような実装ってあんまり思いつかない🤔: トラッキング系のシステムとかならもしかするとあるかもしれませんが、jnchitoさんの記事もテスト方法が中心で実装されている例については見当たらないようなので、後で参考記事↓と合わせて読んだ方がよさそう」

参考: Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita

⚓長期間動き続けるジョブと戦う(Ruby Weeklyより)

割と長い記事です。

  • バックグラウンドジョブでやるべきとき:
    • リクエストがタイム・アウトする
    • メモリスパイクの発生
    • UXに影響する
  • ツール
    • Active Jobにするかどうか
    • Delayed JobかSidekiqか
  • バックグラウンドジョブの基礎
    • グローバルID
    • ジョブの事前条件をチェック(破壊的なアクションを叩かないよう注意)
    • 巨大なジョブを分割
  • ユーザーを上手に待たせる
    • 完了をメールで通知
    • ポーリングしてプログレスバーを表示
    • 合わせ技プラスアルファ
    • 上級技の紹介(取り扱い注意)
  • まとめ

つっつきボイス:「wrangleはざっくり『戦う』ぐらいの感じで😆: Ruby Weeklyのタイトルではtameになってましたが」「長期間のバックグラウンドタスクはいろいろ大変😅

「メモリスパイク、どのツールにするか、Delayed JobかSidekiqか、ジョブはグローバルIDで管理せよ、ジョブは冪等に書け、でかいジョブは分割せよ…と、なかなかいい感じにまとまった記事👍」「Resqueは選択肢に入ってないらしい😆」「やっぱ古いか😆

「ユーザーを待たせる間どうするかの話も押さえてありますね: すぐ終わるダウンロードならいいけど20分ぐらいかかるようなジョブだとユーザーが待ちきれなくてブラウザ閉じちゃったりするので😆、どうハンドリングするかとか」「😆」「対策としてメールで通知するか、ポーリングしてプログレスバー出すかとか実践的なことが書かれてますね😋

「前に勉強会で話していただいたバッチ処理の話↓にも通じるところありそうですね」「たしかに近いかも☺」(ここで当該社内向けスライドを一同で閲覧)

Webのバッチ処理とオンライン処理のポイントとシステムの応答性能を学ぶ#1(社内勉強会)

「ただこの記事が掲載されているboringrails.comは今年春に発売予定のRails本↓に収録するためにサンプル記事以外は非表示にしているようです😅ウォッチ20190930)」「ありゃ😆」「もしかすると後で非表示になるかも」


boringrails.comより

⚓その他Rails

つっつきボイス:「前にもウォッチ↓で取り上げた内容ですが一応リマインダーとして」「そうそう、Active SupportのdowncaseupcaseswapcaseとかがRuby本体でできるようになってましたね☺

週刊Railsウォッチ(20181022)Railsの名前空間地獄とrequire_dependency、PostgreSQL 11がリリース、clean-rails.orgほか


つっつきボイス:「短い記事です」「2.7のdeprecation warningを止める方法が3つ書かれてるけど結局RUBYOPTでやるのは一緒で、それをどう渡すかの違いだけ😆

参考: 環境変数 (Ruby 2.7.0 リファレンスマニュアル)


つっつきボイス:「楽しい😋」「コミッター内でこれだけスタイルの好みが違うという😋」「hashブラケット前後のスペースを入れるかどうかは結構分かれますね〜☺」「自分は入れる派」「linterにお任せ😆


前編は以上です。後編は祝日をはさんで2/12(水)となります。

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

週刊Railsウォッチ(20200204後編)Ruby3.0の他のbreaking change、Rubyのシリアライザ、GitHubのcode ownersほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

週刊Railsウォッチ(20200212後編)Rubyistが解説するUnicodeとUTF-8、Sorbetが速い理由、CSSの歴史、2019年の脆弱性まとめほか

$
0
0

こんにちは、hachi8833です。昨日の大江戸Ruby会議をすっかり見落としてました😇

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓Ruby

⚓Typhoeus: RubyのコンカレントHTTPリクエストgem(Ruby Weeklyより)

参考: Rubyの並列HttpリクエストGem Typhoeusを使ってみる - Qiita


つっつきボイス:「Typhoeusというnet/http的なgemがあるのを初めて知りました」「Typhoeusをさっきググったら病気の名前が出たような覚えが(typhus: チフスがサジェスチョンされてたのでした😅)」「ギリシャ神話由来っぽい↓」「こっちでしたか!」

参考: ギリシャ神話|怪物:テューポーン、ギリシャ神話最大最強の怪物!

「typhoeusはたしか前からあって、コンカレントに動作するのでベンチマークとか作るときにいいらしい☺: 記事にはGIL(Giant Interpreter Lock)の話とかもありますし、データベース接続プールもコンカレンシーで話題になりますね」

参考: RubyのGIL(Global Interpreter Lock)について | blog.kotamiyake.me


目次より:

  • GILとI/Oブロッキング
  • 事例: Slack APIリクエスト
  • ネイティブRubyスレッド
  • concurrent-ruby gemでPromiseする
  • マルチスレッドとRails SQLデータベースプール
  • TyphoeusのHydra API
  • まとめ

そういえばHydra(ヒドラ、英語圏ではハイドラっぽい発音)もギリシャ神話ネタですね。Typhoeusのスペルを何度も間違えました😅

# 同記事より
def get_all_conversations
  endpoint = "https://api.slack.com/conversations.list?token=#{access_token}&exclude_archived=true"
  hydra = Typhoeus::Hydra.hydra

  groups_params = "type=public_channel"
  groups_request = Typhoeus::Request.new("#{endpoint}&#{groups_params}")

  channels_params = "type=private_channel"
  channels_request = Typhoeus::Request.new("#{endpoint}&#{channels_params}")

  hydra.queue groups_request
  hydra.queue channels_request

  hydra.run

  groups_json = groups_request.response.body
  channels_json = channels_request.response.body

  # 以下パース用コード
  ...
end

⚓Rubyの新しい機械学習gem 16種(Ruby Weeklyより)


つっつきボイス:「Rubyのgemをいっぱい作っているankaneさんの記事ですが、最近Rubyで機械学習周りをいろいろやっているみたいです」「Rubyで直接書くんじゃなくて外部のライブラリを呼ぶタイプのgemを紹介しつつ、パフォーマンス的にはPythonとそう遜色ないみたいな話なのかなと眺めながら想像してみました😆」「知らない名前のgemも半分ぐらいありますね😳


目次より:

  • テーマ: PythonでもC++のようなコンパイル言語で動かしてそれをラップしていることがほとんどなので、Rubyでも同じアプローチが取れる。
  • ライブラリ利用パターン
  • ライブラリ紹介(パフォーマンスで選びました)
    • 勾配ブースティング(Gradient Boosting)
    • 深層学習
    • おすすめライブラリ
    • 最適化
    • 文書分類(Text Classification)
    • データ交換
    • その他
  • これだけは言っておきたい: rumale gemはScikit-learnに迫るスゴさ!💪
  • 結び

⚓Rubyistが解説する文字エンコーディング・Unicode・UTF-8(Ruby Weeklyより)


つっつきボイス:「主に歴史を通じて文字エンコーディングを学ぼうという記事のようです」「やはり最初はモールス信号から😆

参考: モールス符号 - Wikipedia

「モールス符号はほぼ思い出せないけど、一応アマチュア無線免許持ちなので超々なつかしい〜😂」「英語でよく使うeが一番短いとかありましたね」「符号化の歴史から始めるあたりがシブい😋」「SOSのエンコードとデコードもなつかしい↓」

# 同記事より
SOS -> Encode('SOS') -> ...---... -> Decode('...---...') -> SOS
-----------------------              --------------------------
       Sender                                 Receiver

「記事は大学の授業レベルぐらいの感じかな: 小学生だと難しいだろうけど、中学生高校生ぐらいならこういう話を面白がる子いるかも」


目次より:

  • モールス符号
  • 人力エンコードから自動エンコードへ
  • ASCIIの出現(1963年)
  • ASCIIの問題点
  • Unicodeの登場(1988年)
  • UTF-8の登場(1993年)
    • 1バイトのUTF-8
    • 2バイトのUTF-8
    • 3バイトのUTF-8
    • 4バイトのUTF-8
    • UnicodeよりUTF-8の方が空間がでかい
  • Rubyで使えるいろんなエンコード
  • Rubyスクリプトのエンコーディングを調べる
  • Rubyでエンコーディングするときのコツ
  • 絵文字で遊ぼう
  • まとめ(今回はRuby 2.6.5を使用)

Rubyの内部文字コードはUTF-8ではない…だと…?!

後で気づきましたが、英文字を入力するとモールス符号と音に変換してくれるサイト↓が同記事で紹介されていました。割と簡単に作れそう😋

⚓Ruby trunkより

# 同issueより: 2.7と現在のmasterの挙動
% echo "class C" > c.rb
% ruby -e 'begin p require "./c" ; rescue SyntaxError; end; p require "./c"; C'

つっつきボイス:「ライブラリがSyntaxError吐いた後にもう一回requireしてもライブラリが読み込まれないというRuby 2.7のバグだそうです」「このバグを踏んだのか自動化で見つけたのか、それにしてもよく見つけたな〜😳

⚓Sorbetが速い理由(Ruby Weeklyより)


つっつきボイス:「Rubyの型チェッカーSorbetの中の人の記事です」「C++で書かれてるから速い、と☺」「コンパイラ作る人たちが頑張ってる領域っぽい😆」「この辺のお好きな方はどうぞ😋

sorbet.orgより

目次より:

  • Sorbetは主にパフォーマンス上の理由でC++で書くことを選んだ(それだけで速くなるとは言わない)
  • キャッシュ局所性を高める設計
  • GlobalState*Ref
  • Googleの高速なAbseilをデータ構造に採用
  • 文字列操作を回避
    • エラーをlazyに組み立てる
    • メトリクスのインフラストラクチャ
  • シンプルな型推論
    • ローカルのみの推論
    • 前方のみの推論
  • パフォーマンスの継続的取り組み
  • invariantチェックの向上
  • プロファイルドリブンのパフォーマンス向上(PGOでやりたかった)
  • 高速なシリアライゼーションフォーマット
  • その他ツールチェインの最適化
  • まとめ

⚓その他Ruby

つっつきボイス:「かなり長い記事ですが、まずモナドがわかってません😆」「見た感じスタックマシンを作ろうとしてるっぽい🤔」「おぉ?」「Rubyで処理系を書きながらリファクタリングを進めてモナドを作っていく感じですね☺」「なるほどそういう趣旨でしたか😳」「and_then使ってるあたりとかちょっとモナドっぽい↓」

# 同記事より
def words_in(blogs)
  Many.new(blogs).and_then do |blog|
    Many.new(blog.categories)
  end.and_then do |category|
    Many.new(category.posts)
  end.and_then do |post|
    Many.new(post.comments)
  end.and_then do |comment|
    Many.new(comment.split(/\s+/))
  end.values
end

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


つっつきボイス:「正規表現じゃなくても書けるところを正規表現で書いちゃってるみたいなパターン集か」「この書き方↓とかたしかにちょっとぉという気持ちになりますし😆

# 同記事より: 完全一致を調べる(悪例)
string =~ /^fixed pattern$/

「これ↓とかはコードレビューで見かけるといいなって思いますね👍: case文の比較がトリプルイコール===であることをうまく利用していて、何というかRubyらしい書き方という気がします」

# 同記事より: 修正例
case string
when "fixed pattern"
  ...
when /pattern/
  ...
when "another fixed pattern", /another pattern/
  ...
end

「空文字のsplitstring.split(//).each{...})は何がしたいのかと聞きたくなるヤツ😆」「意外によく見かけるって書かれてますね😳

「そうそう、Rubyにはeach_なんちゃら的なメソッドがいろいろあって、eachより短く書けるし速度も速いものが多いので🚀、うまく使い分けたいところですね☺

# 同記事より(修正例)
string.each_char{...}
string.each_line{...}

reverse_each_*とかも含めるといっぱいありますね。

Ruby: `each`よりも`map`などのコレクションを積極的に使おう(社内勉強会)

String#scanはうまく使うときれいに書けることがよくありますね😋

# 同記事より
strings.map{|s| s.scan(/\d+/)} # => [["2019", "2", "1"], ["2019", "2", "1"], ["2019", "2", "1"]]

gsubを重ねまくるパターンあるある↓😆」「最初にgsub覚えちゃうとそれで全部やろうとしちゃったりしますね(しました😅)」「Rubyを書き慣れてないとやっちゃいそう」

# 同記事より(悪例)
string # => "foo\\bar\n¥baz'qux"
string.gsub("\\", "\").gsub("¥", "¥").gsub("'", "’").gsub("\n", " ") # => "foo\bar ¥baz’qux"

「式展開#{}は内部で必ずto_sされるのでやらなくていいヤツ↓」

# 同記事より(悪例)
"foo #{object.to_s} bar"

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`

「Rubyがセカンドランゲージやサードランゲージだったりするとこういう書き方するかもしれませんね: 正規表現のコード例あたりはプログラミング自体に慣れてない人が書いちゃいそうな感じかな☺

⚓DB

⚓OracleからPostgreSQLに移行するときのコツ(DB Weeklyより)


つっつきボイス:「出たOracleからの移行😆」「コワイよ〜😆」「大変だよねという気持ちしかない…」

「OrafceってOracleの関数をPostgreSQLのに変換してくれるツールか😳」「やっぱり必要ですよね」「そもそもOracleがいろいろ特殊だし、あんなに使われてるのにSQL標準をぶっちぎってたりするし😇」「最初にOracle触ってたらオラクルマンになれたんだろうか😆


目次より:

  • Orafceのインストール方法
  • 制約をオンオフする
  • NOT NULL制約を無効にする
  • GRANTコマンドの違いについて
  • PostgreSQLでデータベースオブジェクトをdropできるか
  • NOT NULLのチェック
  • ROWID、CTID、IDENTITYカラム

⚓その他DB

つっつきボイス:「Recutilsって昔からありそうな雰囲気ですね」「GNUですし何となく見たことあるし、この書式↓はRed Hat系のインストールスクリプトを思わせますね(あれはBashスクリプトだから違ってそうですけど☺)」

# 同記事より
%rec: Service
%key: Id
%auto: Id
%type: Provider rec Provider
%type: Mileage int
%sort: Mileage
%mandatory: Id Date Mileage Description Provider

%rec: Provider
%key: Id
%auto: Id
%mandatory: Id Name

つっつきボイス:「エスキューエルは素人ってそこまで言わなくても😆」「質問をよく見たら英語圏からでした😆」「😆英語圏ならシークゥエルって普通に言いそう」「カンファレンスの英語はだいたいシークォーとか発音してますね」

「関係ありませんけど、日本人が国際カンファレンスでIPv6をついアイピーブイろくって言っちゃうみたいな😆」「はてブでもWindows 10をウィンドウズじゅうって言っちゃうとかありましたね😆」「じゅうは言わなさそうだけどブイろくは言っちゃいそう😆」「なぜかv4はブイよんとは言わずにブイフォーですけど😆」「言語学的に言いやすい方に流れるとか😆」「日本だとV6は小室系を指してしまいがちですし😆

彼氏がWindows10のことウィンドウズじゅうって言う

ウィンドウズ充なんですよ

2020/02/04 22:39

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

⚓サーバーレス監視ツールトップ10(Serverless Statusより)


つっつきボイス:「サーバーレス監視ツールがずらっと並んでますね」「日本だと、はてなとかが使ってるアレが有名ですね」「えっとマカレルでしたっけ?」「そうそう、高いけど😆

参考: Mackerel(マカレル): 新世代のサーバー管理・監視サービス

⚓その他インフラ

つっつきボイス:「BPS社内Slackに流したヤツです」「そうそう、WSL 2で試した方います?」「まだかな〜☺」「自分は既存の資産がありすぎるのと、これを使うためにはWindowsをプレビュー版にアップグレードしないといけないというのがあって(でないとこのチェックボックス↓をオンにできない)、さすがにメインマシンにプレビュー版を入れたくはないし🤣


つっつきボイス:「SQLite.orgのブログです」「たしかにSQLiteはサーバーレス😆」「ファイルベースだし、ノリ的にはBerkeley DBみたいなものだし😆

参考: Berkeley DB - Wikipedia


つっつきボイス:「これの嬉しみってどの辺でしょう?」「まぁクエリを投げまくるようなシステムだとBigQuery破産するからRedShiftに移りたい気持ちはわからなくもないですけど、柔軟性という点ではBigQueryの方が断然上だと自分は思いますけどね☺」「なるほど!」

「BigQueryはスキャンするデータ量に対して課金されるので、巨大データでユーザーリクエストのたびにクエリを1件投げるようなサービスを組んでしまうと、ユーザーにも課金するとかしないと速攻で破綻しますけど、AWS RedShiftはインスタンス課金なのでコストが見えやすい: なので移行したい気持ちはわかります☺」「ふむふむ」「ただしRedShiftはデータが入り切るかという問題があります: 一方BigQueryは上限なんか考えないでデータ突っ込みまくるという超絶富豪的運用が可能ですし💰」「データは多いけどクエリ量は少ないみたいな用途がBigQueryに向いてる感じなんですね😋



つっつきボイス:「BPS社内Slackに貼っていただいたヤツです」「公式がAWS芸人をフィーチャーするという広告戦略がいろいろ謎😆」(以下延々)

⚓JavaScript

⚓ES2020の新機能


つっつきボイス:「お、決まりましたか🎉」「名前も年号でES2020になった」「こんなにアグレッシブに仕様を改定しているのに世の中で最も広く使われている言語のひとつというのがJavaScriptの稀有なところかも☺」「しかも後方互換性を壊さないように」「ブラウザが自動更新されるのが当たり前の世の中になってきたというのが一番大きいかもですね☺

⚓その他JS

つっつきボイス:「そりゃみんなJSライブラリは更新したくないでしょう🤣」「壊れるから🤣」「Rubyは特にRailsが活発なおかげで関連gemにissueやプルリク投げたりというのが行われてますけど、JSは組み合わせが無限にありすぎて下手にいじると動かなくなりそうですし😆」「記事はCloudFlareのブログでした」「なるほど、彼らはCDNをホスティングしてるから、更新されてないJSライブラリがいっぱいあるぞみたいな情報が取れるでしょうね😋



つっつきボイス:「そのnというツールは名前短すぎなんですが、Nodeのバージョンアップがちょっとだけ楽だったのでメモりました」

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

⚓CSSいまむかし(Frontend Focusより)


つっつきボイス:「こちらもCSSの歴史を辿る長い記事でした」「若者におすすめ😆

「かつてはXHTMLこそ正義とか言われてた時代ありましたし⚔」「SGMLから始めて欲しい😆」「人間らしい温かみを感じるtableレイアウト久しぶりに見た😆」「XHTMLパーサーは当時速いとか言われてたけど、その後HTMLパーサーがどんどん強くなっちゃいましたし☺

参考: Extensible HyperText Markup Language - Wikipedia
参考: Standard Generalized Markup Language - Wikipedia

「↓こんなのもなつかしすぎる😆」「ありましたねこういうの😆

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

目次より:

  • 超黎明期
  • 事例: 今でも動くSpace Jamサイト(1996年の映画)
  • サムネイルグリッド
  • 黎明期
  • 暗黒時代: ブラウザバグとの戦い
  • ブラウザ戦争終結とその後の停滞
  • Quirks(後方互換)モード
  • XHTMLの興亡
  • CSSレイアウト誕生
  • サムネイルグリッド2
  • DHTML
  • Web 2.0
  • 野望
  • 次なる暗黒期: CSSハック
  • 未来はゆっくりとやってくる
  • ブラウザプレフィックス地獄
  • Flexbox
  • IEの苦悶に満ちた緩やかな終焉
  • そして現在
    • さまざまなレイアウト
    • 美観上のテクニック
    • 構文など
    • 昨今のサムネイルグリッド
    • 一方私は…
  • CSSのまだ見ぬ未来

⚓その他フロントエンド

つっつきボイス:「Browsersync、どこで見かけたか思い出せませんが😅」「名前からして、違うブラウザでまったく同じ動きをさせられる感じですけど☺」「そんな感じですね」「PCとモバイルでレスポンシブデザインを同時にチェックしたいときとかにいいかも😋


browsersync.ioより


「忍者やトランスジェンダー系絵文字が追加されてました」「disguised face(変装)って私の顔みたい…」「smiling face with tearって嬉し泣き?」「joy(😂)と違うんでしょうか😆」「心臓の絵がリアルすぎ😆」「バレンタインでハートマークの代わりに使ったりして😆」「昆虫系もリアル…」「bubble teaの絵ってタピオカでしょう😆


unicode.orgより

リアル絵文字は自粛しました😆


バブル・ティー◆大粒の黒いタピオカが入ったミルクティー。台湾発祥。

⚓言語・ツール

⚓2019年の脆弱性まとめ


つっつきボイス:「言語別だとPHP系が突出して多かったのにビックリしました😳↓」「季節変動とかカテゴリ別とかいろいろありますね☺


同記事より

「昔は言語とかミドルウェアとかパッケージソフトウェアの脆弱性がよくCERTに上がってましたけど、最近だとRailsもCERTにあがったりとフレームワークの脆弱性が増えてるので、インジェクションとかも多くはフレームワークあたりじゃないかな🤔」「WordPressのプラグインとかも多いですし😅」「こんな脆弱性踏む人がいるの?って思うようなのも出ますし😇

参考: JPRS用語辞典|CERT(サート)


目次より:

  • 2019年度脆弱性統計
  • OWASPカテゴリ別
  • 最多はインジェクション
  • サードパーティコンポーネントの脆弱性が増加
  • DOSとCSRF
  • IoT脆弱性はなぜか減少気味
  • API脆弱性はゆっくり増加
  • CMSはWordPressが最多
  • サーバーテクノロジではPHP系が最多
  • データベースはMySQLが最多
  • Twitterの動きから
  • 2020年度の予測
    • インジェクションやクロスサイトスクリプティングは今後もトップだろう
    • サードパーティによる脆弱性も増えるだろう
    • OWASP Top 10 for APIの登場で開発者への周知が進むだろう
    • IoTベンダにもセキュリティ情報の認知が広まっている
  • あなたのアプリやデータを守るには

⚓その他言語

つっつきボイス:「mimallocはマイクロソフトが作ったmallocオルタナティブだそうです」「『excellent performance』ですって」「マイマロック?」「ミーマロックと読むみたい」「OS作っているところならこういうのはやってるでしょうね☺


同リポジトリより


つっつきボイス:「↑これ超わかる〜😭」「lnコマンドってたまにしか使わないからさらに間違えがち😢」「どこにもリンクしてないゴミシンボリックリンク作っちゃうとかあるある😆」「一応ソースとデスティネーションの順になってるんですけどね」

参考: 【 ln 】コマンド――ファイルのハードリンクとシンボリックリンクを作る:Linux基本コマンドTips(16) - @IT


⚓その他

⚓合成音声


つっつきボイス:「イケナイ使い方をつい想像しちゃって😆」「前からあるっぽいですけど、ポイントはリアルタイム変換できるようになるかどうかですね: そしたらVTuberとかが一斉に使い始める📺

⚓番外

⚓冷凍法


つっつきボイス:「冷凍することでRAMからデータを抜き取る?」「温度が低いほど電荷のリークが遅くなるって初めて知りました」「へぇ〜これは知らなかった😆

参考: コールドブート攻撃とは | セキュリティ用語解説 | 日立ソリューションズの情報セキュリティブログ


後編は以上です。

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

週刊Railsウォッチ(20200210前編)Railsのベンチマークジェネレータ、長いバックグラウンドジョブと戦う、Timestamp切り詰めの謎、Open APIツールほか

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

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

Ruby Weekly

Publickey

publickey_banner_captured

DB Weekly

db_weekly_banner

Serverless Status

serverless_status_banner

Frontend Focus

frontendfocus_banner_captured

JSer.info

jser.info_logo_captured

React Status

react_status_banner

XcodeでリアルタイムにプレビューできるカスタムViewを作る

$
0
0

BPSの福岡拠点として一緒にお仕事をさせていただいています、株式会社ウイングドアのウメバヤシです。

XcodeのInterface Builder(以下、IB)でレイアウトを作成している時に、ちょっとボタンの角を丸くしたい時などに便利なカスタムViewの作り方を紹介します。

xibファイルなどでレイアウトとコードを作成して、それをStoryboardで利用する方法もありますが、今回はさくっとコードだけで実装する方法をとってみました。

IBDesignableとIBInspectable

カスタムViewを作る前にIBDesignableとIBInspectableについて説明しておきます。
この2つの機能はXcode6から使用できるようになっています。

BDesignableとは
クラス定義の前に「@IBDesignable」とつけることで、Viewプロパティの変更をIB上でリアルタイム反映させられる機能。
IBInspectableとは
“Viewのプロパティ値をIB上のAttributes Inspectorに表示させ、GUIで設定できるようにする機能。

UIViewの拡張

まず、IB上で通常は変更できないプロパティを設定できるようにするため、下記のコードでUIViewを拡張します。

プロパティ宣言の前に「@IBInspectable」とつけることで、IBからプロパティ値の変更が可能になります。

extension UIView {

    @IBInspectable
    var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
        }
    }

    @IBInspectable
    var borderWidth: CGFloat {
        get {
            return layer.borderWidth
        }
        set {
            layer.borderWidth = newValue
        }
    }

    @IBInspectable
    var borderColor: UIColor? {
        get {
            if let color = layer.borderColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.borderColor = color.cgColor
            } else {
                layer.borderColor = nil
            }
        }
    }

    @IBInspectable
    var shadowRadius: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue
        }
    }

    @IBInspectable
    var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable
    var shadowOffset: CGSize {
        get {
            return layer.shadowOffset
        }
        set {
            layer.shadowOffset = newValue
        }
    }

    @IBInspectable
    var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }
}

画像のようにAttributes Inspectorに項目が増えて設定できるようになります。
これだけではまだStoryboardには変更が反映されないので、次にIBDesignableを使って、リアルタイムで反映されるようにします。

カスタムViewを作る

今回は一例として、UIViewを継承したカスタムViewを作ってみます。
下記コードで@IBDesignable属性をつけた「DesignableView」クラスを作成します。

@IBDesignable
class DesignableView: UIView {
    // コードから生成したときに通る初期化処理
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupAttributes()
    }

    // InterfaceBulderで配置した場合に通る初期化処理
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setupAttributes()
    }

    private func setupAttributes() {
        // 角の半径の初期値
        self.cornerRadius = 6.0
    }
}

IBでCustom Classを設定する

StoryboardでUIViewを配置し、Custom Classに先ほど作成したDesignableViewを設定します。

すると、角半径の初期値の6.0がデザイナーに反映され、Viewの見た目が変わったと思います。
※画像では見えやすいようにViewの背景だけ色を変えています。

IBInspectableで設定したプロパティについてもAttributes Inspectorで設定できるようになっています。

試しに上の画像のような設定にしてみると、こんな感じになります↓↓↓

実はこれ、User Defined Runtime Attributesという項目に設定を追加しているだけで、IBInspectableで拡張しなくても設定できるのですが、ひとつひとつ追加するのが面倒なので、拡張しておけば簡単に設定できます。

こんな感じでさくっとちょっとリッチなレイアウトが作成できるようになりました😊

ちなみに、UIViewを拡張したので、UIViewを継承した他の標準パーツもプロパティが増えていると思います。

まとめ

IBDesignableとIBInspectableを使って簡単にカスタムできるViewを作ってみました。

プロダクトの中でViewのレイアウトを統一させたいような時も、カスタムクラスで初期値を設定しておけば、後々の変更にも強い実装になるのでおすすめです。
ただXcodeのIBでは、プロパティを変更した際に自動でレンダリングが走るので、PCスペックによってはこの処理が若干重くなってしまいます。。
気になる方はメニューから「Editor」-「Automatically Refresh Views」のチェックを外し、自動レンダリング機能をオフしておくといいかもしれません。



株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

Ruby 2.7: Module#autoload?にinheritオプション引数が追加(翻訳)

$
0
0

概要

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

訳注: 記事公開時点では日本語APIドキュメント(Module#autoload?に反映されていません。

Ruby 2.7: Module#autoload?にinheritオプション引数が追加(翻訳)

Module#autoload

このメソッドは、指定のクラスやモジュールのファイルパスを登録し、初めて実際にアクセスされたときにそのモジュールをlazyに読み込みます。

例:

# person.rb
module Person
  autoload(:Profile, './profile.rb')

  puts "Profile is not loaded"
  Profile
  puts "Profile has been loaded"
end
# profile.rb
module Profile
  puts "Profile is loading"
end

それではperson.rbを実行してみましょう。

> ruby person.rb
Profile is not loaded
Profile is loading
Profile has been loaded

Module#autoload?

このメソッドは、モジュールが読み込まれていない場合は登録済みのファイルパスを返し、モジュールが読み込み済みの場合はnilを返します。

# person.rb
module Person
  autoload(:Profile, './profile.rb')

  p autoload?(:Profile)
  Profile
  p autoload?(:Profile)
end
# profile.rb
module Profile
  puts "Profile is loading"
end
> ruby person.rb
"./profile.rb"
Profile is loading!
nil

以上の例から、autoload?の最初の呼び出しでは登録済みのファイルパスが返ることがわかります。Profileを呼び出すとProfileモジュールが読み込まれます。2度目のautoload?呼び出しではモジュールが読み込み済みなのでnilが返ります。

今度は継承した場合の例を見てみましょう。

# person.rb
class User
  autoload(:Profile, './profile.rb')
end
# manager.rb
require_relative './person.rb'
class Manager < User
  p autoload?(:Profile)
end
# profile.rb
module Profile
end
> ruby manager.rb
"./profile.rb"

Managerクラスでautoload?を呼び出すと、親クラスで定義されているProfileのファイルパスが返ります。しかし、autoloadが子クラスで定義されているかどうかを確認する方法がありません。

@fb85a42で、Ruby 2.7のautoload?Module#const_defined?と同様のinheritというオプション引数が追加されました。inheritの値はtrue(デフォルト)またはfalseです。

この新メソッドのシグネチャはautoload?(CONSTANT_NAME, inherit = true)です。

# manager.rb
require_relative './person.rb'
class Manager < User
  p autoload?(:Profile)
  p autoload?(:Profile, false)
end
> ruby manager.rb
"./profile.rb"
nil

1つ目のautoload?呼び出しでは、親クラスにautoloadが定義されているのでファイルパスが返ります。2つ目の呼び出しではinheritオプションがfalseなので先祖クラスの定数がチェックされなくなり、nilが返ります。

関連記事

Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)

Rails 6のcookieに「purpose」メタデータが追加(翻訳)

週刊Railsウォッチ(20200217前編)Railsのオプション引数退治、HSTSのデフォルトmax-ageが1年から2年に変更、semantic_logger gemほか

$
0
0

こんにちは、hachi8833です。皆さま息災でいらっしゃいますか。


つっつきボイス:「たしかに相当影響でかそう😰」「いろんなものが届かなくなったりしそう🏷」「人が大勢集まるイベントも影響受けそうですし🥺

RubyKaigiまでに落ち着いてくれるといいのですが。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

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

今回は公式の更新情報の他にコミットログからも見繕いました。#38383は先週引き当てたので省略🎯

⚓HSTSのmax-ageをデフォルトで2年に設定

参考: Strict-Transport-Security - HTTP | MDN


つっつきボイス:「前のHSTS max-ageのデフォルト値はいくつだったんだろ?🤔、なるほど1年か↓」

# actionpack/lib/action_dispatch/middleware/ssl.rb#L49
  class SSL
    # :stopdoc:

-   # Default to 1 year, the minimum for browser preload lists.
-   HSTS_EXPIRES_IN = 31536000
+   # Default to 2 years as recommended on hstspreload.org.
+   HSTS_EXPIRES_IN = 63072000

「hstspreload.org↓の推奨値に従ったのね☺」「Mozillaでも2年が推奨値になってるんですね😳


hstspreload.orgより

「HSTSの設定を間違えると場合によってはドメインが使い物にならなくなります🤣: たとえば暗号化されないHTTPも使う必要があるドメインをうっかりHSTS有効で公開してその情報がひととおり行き渡ってしまうと、そのドメインへのHTTPアクセスがブラウザレベルではじかれてしまうのでサービスホスティングに使えなくなるという😇」「ひぇ😅」「まあ今どきHTTP+HTTPSで公開するか?というのはありますけど☺

「コンテンツをHTTPで公開しないけどリダイレクトはしたいということはあるんですが、HSTSを有効にするとリダイレクトできなくなって割と詰みます😇

参考: ネイキッドドメイン+HTTPSで運用するRailsアプリを5.1にアップグレードしたら、サブドメインも強制的にHTTPSになってしまった話 - Qiita
参考: リダイレクトも忘れずに!常時SSL化をする為の13の重要点 | さくらのSSL

HSTSの弱点は1回目のアクセスがhttpになってしまうことですが、予めブラウザに「このサイトはhttpsでアクセスしてください」と登録しておくことができます。これが「HSTS Preload」です。こちらのサイトにURLを登録しておくことで、初回のアクセスからhttpsで接続させることができます。
HSTSを利用する上で注意が必要なのは、何かしらの事情でサイトを「http」に戻した場合です。例えば、HSTSで1年間のキャッシュが指定されている場合、次回以降のアクセスが(1年以内であれば)httpでアクセスされることはありません。このため、サイトがhttpに戻ってしまうと、いつも訪れていた閲覧者がアクセスできない状況に陥ってしまう可能性があります。また、HSTSのキャッシュ時間設定値は最低1年(31536000秒)となるため、これも注意が必要です。
ssl.sakura.ad.jpより

⚓PostgreSQL 11〜のパーティションドインデックスをサポート

PostgreSQL 11以降のupsert_allでpartitioned indexサポートを追加
Changelogより

# activerecord/test/schema/postgresql_specific_schema.rb#L112
+ if supports_partitioned_indexes?
+   create_table(:measurements, id: false, force: true, options: "PARTITION BY LIST (city_id)") do |t|
+     t.string :city_id, null: false
+     t.date :logdate, null: false
+     t.integer :peaktemp
+     t.integer :unitsales
+     t.index [:logdate, :city_id], unique: true
+   end
+   create_table(:measurements_toronto, id: false, force: true,
+                                       options: "PARTITION OF measurements FOR VALUES IN (1)")
+   create_table(:measurements_concepcion, id: false, force: true,
+                                          options: "PARTITION OF measurements FOR VALUES IN (2)")
+ end

つっつきボイス:「PostgreSQLはもともとPARTITION BYという構文がありますけど、パーティションごとのインデックスも作れるのね」「upsert_allで効く、と」

参考: 5.10. テーブルのパーティショニング

サブパーティショニングと呼ばれる方法を使って、パーティションそれ自体をパーティションテーブルとして定義することができます。 パーティションには、他のパーティションとは別に独自のインデックス、制約、デフォルト値を定義できます。 インデックスは各パーティションで別々に作成されなければなりません。
postgresql.jpより

「ぽすぐれのバージョンもチェックしてる↓: バージョン番号の桁数ってこんなふうなんだ😆」「6桁貫きですか😳

# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L160
+     def supports_partitioned_indexes?
+       database_version >= 110_000
+     end

「こんなふうに書くのか↓: パーティショニング使ったことないのでテストで書き方を知るという😆」「😆」「ものすごく長大になる証跡ログなんかではパーティショニングしておく方がよかったりしますね🧐

# activerecord/test/cases/adapters/postgresql/schema_test.rb#L382
      @connection.execute "CREATE INDEX \"#{PARTITIONED_TABLE}_Index\" ON #{SCHEMA_NAME}.#{PARTITIONED_TABLE} (logdate, city_id)"
      assert_nothing_raised { @connection.remove_index PARTITIONED_TABLE, name: "#{SCHEMA_NAME}.#{PARTITIONED_TABLE}_Index" }

「パーティションドテーブルはBigQueryにもあった気がする」

参考: パーティション分割テーブルの概要  |  BigQuery  |  Google Cloud

⚓concerningprepend: trueを指定できるようになった

# activesupport/lib/active_support/core_ext/module/concerning.rb#L108
  module Concerning
    # Define a new concern and mix it in.
+   def concerning(topic, &block)
+     include concern(topic, &block)
+   def concerning(topic, prepend: false, &block)
+     method = prepend ? :prepend : :include
+     __send__(method, concern(topic, &block))
+   end
# activesupport/lib/active_support/concern.rb#L109
+   class MultiplePrependBlocks < StandardError #:nodoc:
+     def initialize
+       super "Cannot define multiple 'prepended' blocks for a Concern"
+     end
+   end

まれなケースとして、あるモジュールを先祖の階層で(単なるincludeではなく)prependする必要が生じることがある。そういう場合で、かつインラインconcernが望ましい場合、concerningでそのconcernをprependすべきと指示できれば有用なことがある。
concerningprepend: trueを指定することでこれが可能になった(デフォルトはfalse)。
@8543974より


つっつきボイス:「concerningでprependできるとうれしいことって何でしょう?」「そもそもレアケースではないかと😆」「kazzさんに話してみたら使いみちで考え込んでました」「prepend: falseが今までの挙動だったのはわかるけど、何が問題だったとかどう使いたいとかが具体的に書いてないし🤣」「このコミットたちをまとめるプルリクってないの?」「それがどうも見当たらなくて、コミットだけみたいです😅

「concernってそもそもあんまりやりたくないし😆: 使うと何となくDRYになったような気がするけど、育ってくると読みにくさが半端なくなる😢」「例の定番記事↓でも言ってるヤツですね」「gemの形にでもなれば違うかもしれませんけど」

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)


後で気づきましたが、以下のコミットにドキュメントが少し追加されていました。

ActiveSupport::Concernprependをサポート。
extend ActiveSupport::Concernしたモジュールをprependできるようになる。

module Imposter
  extend ActiveSupport::Concern

  # `included`と同じ(`prepend`されたときしか実行されない点を除く)
  prepended do
  end
end

class Person
  prepend Imposter
end

concerningも更新されてconcerning :Imposter, prepend: true doできるようになった。
Changelogより

その後の#38462から辿って、どうやら以下の#37174が始まりだったようです。この#37174はなぜかマージされず、実際には上の個別のコミットに分けられたものがマージされています🤔。書いてあることは上の個別コミットと大差ありませんでした。

⚓PostgreSQLのOIDを符号なしintegerに修正

# activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb#L4
  module ConnectionAdapters
    module PostgreSQL
      module OID # :nodoc:
-       class Oid < Type::Integer # :nodoc:
+       class Oid < Type::UnsignedInteger # :nodoc:
          def type
            :oid
          end
        end
      end
    end
  end

つっつきボイス:「issue #38425↓によると、OIDがでかい場合に問題が生じることがあったと」「OIDはぽすぐれのオブジェクトIDでしょうね」「OIDがでかすぎるとActive Recordがpg_typeを正しく認識できなくなってカラムをStringと認識しちゃうのか😳」「ホントだ、oid::integerにcastすると死んでる💀: つまりintegerの最上位ビットに到達すると発現する😇」「相当でかいシステムでないと踏まなさそう」

-- Original load_additional_types code
SELECT * FROM foooid WHERE oid::integer IN (2779325372);
 pk | oid | name
----+-----+------
(0 rows)

「なのでunsigned integerで処理することにしたと」「テストも最上位ビットが立つ値に変更されてる↓」

# activerecord/test/cases/adapters/postgresql/datatype_test.rb#L57
  def test_update_oid
-   new_value = 567890
+   new_value = 2147483648

電卓で確認してみました↓。

⚓content_pathを明示的にstringに変換

# activesupport/lib/active_support/configuration_file.rb#L12
    def initialize(content_path)
-     @content_path = content_path
+     @content_path = content_path.to_s
      @content = read content_path
    end

つっつきボイス:「content_pathPathnameが渡されていた場合に対応したそうです」「ああRubyのPathnameオブジェクトを渡すと動かなかったのか: そりゃ渡したいよね😆

require 'pathname'

Pathname.new("foo/bar")       # => #<Pathname:foo/bar>
Pathname.new("foo/bar").to_s  # => "foo/bar"

⚓オプション引数退治は続く


つっつきボイス:「kamipoさんの今週の退治シリーズをまとめてみました」「ああ、@6708f3aとかたしかにこれがあるべき姿↓: 引数を*で受けてextract_options!で展開とかしたくない😆

# activesupport/lib/active_support/message_encryptor.rb#L137
-   def initialize(secret, *signature_key_or_options)
-     options = signature_key_or_options.extract_options!
-     sign_secret = signature_key_or_options.first
+   def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil)
      @secret = secret
      @sign_secret = sign_secret
-     @cipher = options[:cipher] || self.class.default_cipher
-     @digest = options[:digest] || "SHA1" unless aead_mode?
+     @cipher = cipher || self.class.default_cipher
+     @digest = digest || "SHA1" unless aead_mode?
      @verifier = resolve_verifier
-     @serializer = options[:serializer] || Marshal
+     @serializer = serializer || Marshal
    end

「以前からRailsのコードはextract_options!でオプションを展開してたんですけど、extract_options!使われると、Railsのソース読んでても(特にAction Pack周りとか)そこに何が入ってくるのかがマジでわからないんですよ😭」「😭」「こういうふうに修正してもらえるとええわ〜って思います👏㊗🎁」「引数の後ろに**がないところが特にうれしい😋: これをforwardされるとわけわからなくなりますし😆」「😆

# api.rubyonrails.orgより
def options(*args)
  args.extract_options!
end

options(1, 2)        # => {}
options(1, 2, a: :b) # => {:a=>:b}

|(*secrets)|のかっこ、なぜ必要なのかぱっと見わからないけど、ないと展開の順序あたりがうまく動かないんでしょうね😢」「ここでかっこを適切に付ける自信ない😭

# actionpack/lib/action_dispatch/middleware/cookies.rb#L620
-       request.cookies_rotations.encrypted.each do |*secrets, **options|
+       request.cookies_rotations.encrypted.each do |(*secrets)|
          options = secrets.extract_options!
          @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
        end

「@a55620fの2.7対応は__send__で書き直してますね↓」

# activesupport/lib/active_support/option_merger.rb#L15
    private
      def method_missing(method, *arguments, &block)
        options = nil
        if arguments.first.is_a?(Proc)
          proc = arguments.pop
          arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
        elsif arguments.last.respond_to?(:to_hash)
          options = @options.deep_merge(arguments.pop)
        else
          options = @options
        end
-       if options
-         @context.__send__(method, *arguments, **options, &block)
-       else
+       invoke_method(method, arguments, options, &block)
+     end
+
+     if RUBY_VERSION >= "2.7"
+       def invoke_method(method, arguments, options, &block)
+         if options
+           @context.__send__(method, *arguments, **options, &block)
+         else
+           @context.__send__(method, *arguments, &block)
+         end
+       end
+     else
+       def invoke_method(method, arguments, options, &block)
+         arguments << options if options
+         @context.__send__(method, *arguments, &block)
+       end
+     end

上の|(*secrets)|のかっことは関係ありませんが、Rubyのsuperのかっこありなしの違いの話↓をちょっとだけ思い出しました。

Ruby: `super`キーワードの4つの側面(翻訳)

⚓番外: RuboCopお手柄

# activesupport/lib/active_support/configuration_file.rb#L41
      def render(context)
-       erb = ERB.new(@content).tap { |erb| erb.filename = @content_path }
+       erb = ERB.new(@content).tap { |e| e.filename = @content_path }
        context ? erb.result(context) : erb.result
      end

つっつきボイス:「ブロック変数のerbがシャドウイングしてたのをRuboCopが見つけてくれたみたい」「最初RuboCopが誤動作したのかと思っちゃいました😆

後で変更前のコードにRuboCopをかけてみました。

configuration_file.rb:42:38: W: Lint/ShadowingOuterLocalVariable: Shadowing outer local variable - erb.
      erb = ERB.new(@content).tap { |erb| erb.filename = @content_path }
                                     ^^^

⚓Rails

⚓Rails+Amazon Rekognitionで「不適切な画像」を自動修正(RubyFlowより)

# 同記事より
has_one_attached :image

validate :image_moderation

def image_moderation
  # 画像がアップロードされてないor変更なしの場合はバリデーションしない
  return if image.blank? || !image.changed?

  # クライアントの初期化(シングルトンクラスやクラス変数などに移してもいい) -- 単なる概念実証(PoC)
  client = Aws::Rekognition::Client.new

  # Active Storageのattachmentを使うラベルを検出
  moderation_labels = client.detect_moderation_labels({ image: { bytes: attachment_changes['image'].attachable }}).moderation_labels

  # 安全でないコンテンツを検出したらバリデーションエラーを追加
  errors.add(:image, "contains forbidden content - #{moderation_labels[0].name}") if moderation_labels.present?
end

つっつきボイス:「Amazon RekognitionをRailsで使ってみた短い記事です」「今はもう画像解析系の処理を超簡単にやれるものがゴロゴロしてますね☺

参考: Amazon Rekognition(高精度の画像・動画分析サービス)| AWS

⚓semantic_logger: Railsのログをカスタマイズ(Awesome Rubyより)


同サイトより

# 同リポジトリより
logger.measure_info('How long is the sleep', payload: {foo: 'foo', bar: 'bar'}) { sleep 1 }

つっつきボイス:「semantic_loggerは以前のウォッチでURLだけ貼ったことがありました」「前からあったっけかこれ?🤔

参考: semantic_loggerの紹介 - Qiita

「リッチロギングフレームワーク、たしかに欲しいものではある」「お、NewRelicとかいろんなところにログを投げられるのね↓」「fluentdはないけど😆、JSONで投げればいいんだろうし」

  • ファイル
  • 画面
  • ElasticSearch(ダッシュボードとビジュアライズはKibanaで)
  • Graylog
  • BugSnag
  • NewRelic
  • Splunk
  • MongoDB
  • Honeybadger
  • Sentry
  • HTTP
  • TCP
  • UDP
  • Syslog
  • 既存のRuby製何でも
  • 自前の何か

「まあログは永遠の課題というか、簡単なものを書いているときにセマンティックなログフォーマットのことまで考えてロガーを使うのって割と面倒くさいですよね😆」「たしかに😆」「やばそうだと思ったら雑に全部大文字でメッセージ書いたりしますけど、セマンティックに書かないといけなくなるとプロジェクトにふさわしいログフォーマットを考えないといけなくなったり」

「ちなみにJavaにはいにしえの大昔からLog4jというロガーがありますね↓」「ありますね☺、今はバージョン2でしたか」「ガラケー時代に1.4を使おうとしたことはあるけど2は使ったことない😆


logging.apache.orgより

参考: log4j - Wikipedia

// logging.apache.orgより
logger.error((Marker) null, "This is the log message", throwable);

「log4jのサイトのコード例↑見てもわかりますけど、だいたいどのロガーもこういう書式になる😆」「だいたいログレベルとログメッセージでやるという😆」「まあそれ以上のものをロガーに求めませんけど、あそうだ、シングルトンでどこからでも取れて欲しい」「ですね☺

「そういえばIBM Javaなんてのもありましたし😆」「使ったことないです〜😆」「これでないと動かないドライバとか当時ありましてですね😆」(以下延々)

参考: IBMがJava 8を「少なくとも2025年までは確実にサポートする」とアピール - orangeitems’s diary

⚓書籍『ドメイン駆動設計 はじめの一歩』


同記事より


つっつきボイス:「こちらは永和システムマネジメントさんのブログですが、技術書典でこの本出すそうです」「3/1が2日目ということは2/29からかな?」

「人混みと行列苦手なので、技術書典はマジでVR参加したい🤣」「例のOriHime↓にお願いできたらいいのに😆」「私も技術書典は行ってますけど人多くてうんざりです😆」「同じじゃないですか🤣

そういえばOriHimeクリエイターの芳藤さんはこの間NHKの『逆転人生』にも出演されてましたね。

参考: 吉藤健太朗 - Wikipedia

追記(2020/02/19)

残念ながら今回は中止に…

⚓Railsの名前付けカンペ(Hacklinesより)


つっつきボイス:「おそらく自分用のチートシートというか、Railsに慣れてる人には今更かなと思いつつ」「ああRailsの命名コンベンション☺: それ以前にRubyにも命名コンベンションありますし」

後で見つけた以下のGist↓はRubyの名前付けについても載っていますね。

Stack OverflowでもRuboCopのRubyスタイルガイドしか触れられていませんでした。

参考: Ruby naming conventions? - Stack Overflow

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

⚓knock: Rails API向けJWT認証gem(Awesome Rubyより)

# 同リポジトリより
class ApplicationController < ActionController::API
  include Knock::Authenticable
end

class SecuredController < ApplicationController
  before_action :authenticate_user

  def index
    # etc...
  end

  # etc...
end

つっつきボイス:「JWTか〜、課金するようなAPIだと欲しいでしょうけど使われてるのかな?😆」「Awesome Rubyで『Devise vs Knock』って出てきたんですけど、リポジトリにこんなの↓が書いてあってほだされてしまいました😆」「yes、no、yes!🤣


同リポジトリより

「Devise gemとの競合はまあわからなくもないけど、普通ならAPIキー発行系は別にしたいというか、ここまでやるなら別サーバーにするでしょ😆」「JWT、あんまりやりたくない感😅

参考: JSON Web Token - Wikipedia

後で調べると、knockではJWTのruby-jwt gemを使ってますね。

⚓その他Rails


同記事より

「Sumo LogicのダッシュボードをRailsアプリに取り付ける記事だそうです」「賃貸情報のSuumoではなかった🤣」「Sumo Logicは日本法人あるんですね😳

参考: 日本に本格進出したSumo Logicに関する、知らない人は知らない意外な事実 - @IT


前編は以上です。

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

週刊Railsウォッチ(20200212後編)Rubyistが解説するUnicodeとUTF-8、Sorbetが速い理由、CSSの歴史、2019年の脆弱性まとめほか

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

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

Rails公式ニュース

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Publickey

publickey_banner_captured


週刊Railsウォッチ(20200218後編)rubyapi.orgで高速検索、RuboCopとJUnitFormatter、AWS Organizationsでの管理ほか

$
0
0

こんにちは、hachi8833です。いつの間にやらGoby言語の★が3,000になりました(ついでにバージョンちょっぴり上がりました)。V言語の作者のmedvednikovさんもを付けてくれていました😂。それだけですはい。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓Ruby

⚓rubyapi.org: Ruby APIドキュメントを高速検索(Hacklinesより)


rubyapi.orgより


つっつきボイス:「rubyapi.org?」「サイトを開くのが早いかも: いわゆるRubyメソッドとかを検索できるサイトですが、ドキュメントのサンプルコードをブラウザ画面で実行できる↓のが面白いなと思って(ただしExperimental)」「おぉなかなか有能😋」「実行はちょっと重いですがしょうがないかな」「過去バージョンについてもRuby 2.3まで切り替えられるみたいです」


rubyapi.orgより

「やはりというか、ブラウザ画面の実行は最後の行の結果しか出てこない😆」「まあここが違ってればドキュメント更新必要とかわかるでしょうけど☺

「前にウォッチにも貼った結城浩さんのツイート↓で『コピペで動くサンプルコードが欲しい』とあったのをちょっと思い出しました」「ああ、それで言うとRubyのHTTP周りのサンプルコードなんかは、使い方知ってる人じゃないとコピペしづらいつくりになってたような覚えがありますね☺

「ドキュメントって真面目に書くとどんどん行数増えてしまうんですよね😅

「UIだけ見ると例のAlgoliaっぽいかなと思いました」

後でWebサイトのリポジトリを見た限りではAlgoliaではなくElasticsearchでやっているようです。

⚓SorbetにFIXMEコメントなどというものはない(Hacklinesより)

Sorbetには、何らかのマジックコメントで特定の行のエラーを無視する方法はない。この点は私の知る他のどの段階的型チェッカー(TypeScript、Flow、Hack、MyPy)とも異なる。
自分がチームに参加した当初はどうかと思ったが、実務に使ってみるとこれが実にうまくいった。
同記事より抜粋・大意


つっつきボイス:「短い記事です」「SorbetにはFIXME的なコメントでアラートを黙らせる方法はないと😆」「使う人を選びそうな予感😆

Sorbetで型付けされたどんなRubyプログラムも、必ずT.untypedと戦わなければならない。SorbetユーザーひとりひとりがT.untypedのしくみとそのトレードオフを熟知しなければならない。特にT.untypedは感染力が強い。T.untypedな変数がやってきたが最後、その変数へのメソッド呼び出しもことごとく型なしとなるであろう。
同記事より抜粋・大意

⚓secure_headers gemに脆弱性


つっつきボイス:「脆弱性レベル10段階で4.3なので中くらいな感じ」「secure_headersってTwitterがやってるんですね 」「この辺は専門家じゃないと難しいな〜😅

# #418より
def show
  user_input_domain1 = URI.parse "https://google.com;script-src"
  user_input_domain2 = URI.parse "https://*;.;"
  user_input_domains = [user_input_domain1, user_input_domain2]

  override_content_security_policy_directives(frame_ancestors: whitelisted_domains)
end

# 上で生成される以下のレスポンスヘッダは、ユーザーがframe-ansestorを設定すれば`script-src`を変更できるので、XSSの可能性がある
frame-ancestors: https://google.com;script-src *;

「直接関係ありませんが、今日のWebチームミーティングに出た『メモ化のやりすぎはバグの元』みたいな話を思い出しますね: コードを短く書こうとしすぎてバグとか脆弱性につながったりすることってありそう☺

⚓html-template: 昔風のRubyテンプレートエンジン(RubyFlowより)

<!-- 同記事より: test.html.tmpl -->
<html>
<head><title>Test Template</title></head>
<body>
My Home Directory is <TMPL_VAR home>
<p>
My Path is set to <TMPL_VAR path>
</body>
</html>
# 同記事より
require 'html/template'

template = HtmlTemplate.new( filename: 'test.html.tmpl' )

puts template.render( home: ENV['HOME'],
                      path: ENV['PATH'] )

つっつきボイス:「1999年風のテンプレートエンジンって、そこまでしてレトロさを出したいのかと😆」「😆」「<TMPL_VAR home>みたいに書くテンプレート言語って、ないわけじゃないけど割と珍しい気がしますね🤔

「この記法、大文字じゃないけどXSLTにちょっと似てるかも🤣」「XSLTってありましたね🤣」「ほんのちょっぴり書いた😆」「どうしてもXSLTを書かないといけなくなったときにXSD(XML Schema Definition)勉強しましたけど、二度と書きたくない😆」「😆」「ちなみにXSDはXMLのデータ形式をバリデーションするのに使います: XMLがこれにパスしないとvalidかどうか信じられなくて脳が死にそうになる😆

<!-- https://www.w3schools.com/xml/xsl_intro.asp より -->
<?xml version="1.0"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <body>
    <h2>My CD Collection</h2>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Title</th>
        <th>Artist</th>
      </tr>
      <xsl:for-each select="catalog/cd">
        <tr>
          <td><xsl:value-of select="title"/></td>
          <td><xsl:value-of select="artist"/></td>
        </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>

</xsl:stylesheet>

参考: XSLT: 拡張可能なスタイルシートの言語変換 | MDN
参考: XML Schema Tutorial

「自分としてはなつかしのJSP(JavaServer Pages)にオールドスタイル感じる🤣: 中かっこ{}が出てきたり」「当時はJSPに邪悪さしか感じなかった🤣」「Javaをスクリプト言語っぽく使ってみましたというか😆

参考: JavaServer Pages - Wikipedia

<!-- http://www.wakhok.ac.jp/~tomoharu/db2003/text/db_c2.html より -->
   <%
     String name = null;
     if (request.getParameter("name") == null) {
   %>
         <p>not found!</p>
   <%
     } else {
         foo.setName(request.getParameter("name"));
     }
   %>

「ついでに以下↓も貼りました」「2020年度のRuby HTTPクライアントの最高峰とは😆

「自分はやっぱりHTTPClient↓が好きかな〜❤ウォッチ20180615)」「この記事にHTTPClientないけど、まあお好きなものをどうぞ☺


目次より

  • net/http(Rubyビルトイン)
  • Faraday
  • http.rb
  • rest-client
  • httparty
  • excon
  • Typhoeus
  • Curb
  • 目的に合ったクライアントを使おう

⚓RuboCopとJUnitFormatter


つっつきボイス:「RuboCopにJUnitFormatterというものが入ったらしいんですけど、これ何でしたっけ?」「JUnitは昔からあるJavaのテストスイートですけど、へ〜RuboCopでJUnit使う人がいるとは!」「なぜだろう😆

参考: JUnit - Wikipedia

JUnitとはJavaで開発されたプログラムにおいてユニットテスト(単体テスト)の自動化を行うためのフレームワークである。
Wikipediaより

# 同PRより
$ rubocop --format junit
<?xml version='1.0'?>
<testsuites>
  <testsuite name='rubocop'>
    <testcase classname='example' name='Style/FrozenStringLiteralComment'>
      <failure type='Style/FrozenStringLiteralComment' message='Style/FrozenStringLiteralComment: Missing frozen string literal comment.'>
        /tmp/src/example.rb:1:1
      </failure>
    </testcase>
    <testcase classname='example' name='Naming/MethodName'>
      <failure type='Naming/MethodName' message='Naming/MethodName: Use snake_case for method names.'>
        /tmp/src/example.rb:1:5
      </failure>
    </testcase>
    <testcase classname='example' name='Lint/DeprecatedClassMethods'>
      <failure type='Lint/DeprecatedClassMethods' message='Lint/DeprecatedClassMethods: `File.exists?` is deprecated in favor of `File.exist?`.'>
        /tmp/src/example.rb:2:8
      </failure>
    </testcase>
  </testsuite>
</testsuites>

JUnit Style Formatter
Machine-parsable
junitスタイルフォーマッタはJUnitフォーマットでの出力を提供する。
このフォーマッタはrubocop-junit-formatter gemを元にしている。
同PRより

「あ、HTMLとかJSONを出すようにJUnit形式でも出力したいってことか!😳: この記事↓でやっているように、JenkinsみたいなCIにはJUnit形式でも渡せるので、CIにJUnit形式のXMLで渡すといい感じに整形してくれたりするんでしょうね☺

参考: rubocop-junit_formatterを導入した | r7kamura on Patreon

「そんな機能があるんですか?」「ありますあります、XML系だとJUnitがこの手の形式の先駆けだったので」「Javaの話ではなかった😆」「記事ではCircle CIでもJUnit形式がサポートされてるってありますね」「JUnit、Javaのアサーション系のテストでは普通に使ってた☺

「ところでrubocop-junit_formatterとrubocop-junit-formatter、名前のアンスコとハイフンが1箇所違うだけ😆」「つらい名前😆」「メンテされてるのは前者、と」

⚓Noah Gibbsさん退職エントリと書籍


Ruby 3 JITの最新情報: 現状と今後(翻訳)

つっつきボイス:「RubyKaigiやTechRachoの翻訳記事↑などでおなじみのNoah GibbsさんがAppFolioを退社してスコットランドに移住するという退職記事を読んでたら、『Rebuilding Rails』という本を出してたことを知りました😳」「お、自分でRailsを組み立てて学ぼう的な本ですね😋

ご注意: 後でサンプルをよく見てみると、『Rebuilding Rails』は使われているRubyが2.0と相当古かった…😅。ご購入の前にご確認を。


『Rebuilding Rails』サンプルPDFより

なお同書のサンプルアプリは以下に公開されています。

⚓書籍『Mastering Software Technique』

Noah Gibbsさんの新しい本(執筆中)は、以下の『Mastering Software Technique』です。フォームで申し込むとサンプルPDFをダウンロードできます。


software-technique.comより

以下は昨年RubyKaigiでのNoah Gibbsさんです。同書はこのスピーチの内容の延長線にあるようです。

⚓その他Ruby


つっつきボイス:「数学範囲広すぎと思ったら、数式のパーサーでした☺」「構文解析怖くない😆」「まあ実際そのとおりで、言語の動きがわからなくなったらAST(抽象構文木)を見るのが一番確実ですし☺

参考: 抽象構文木 - Wikipedia

⚓DB

⚓PostgreSQLのwork_mem設定のコツ(Postgres Weeklyより)


つっつきボイス:「PostgreSQLのwork_mem設定で単位を指定しないとキロバイト単位になるそうです」「割とよくあるヤツ: ddコマンドとかも単位を付けないとえらく小さい単位になりますし🤣」「スワップ領域が超可愛らしいサイズに🐥

参考: dd (UNIX) - Wikipedia

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

⚓マルチアカウントのAWS Organizationsでユーザーとロールを管理する


つっつきボイス:「マルチアカウントのユーザーをどう管理するか問題、SSOもその手段のひとつ、と」「この記事だとSAMLの方がいい感じにできそうな気もするけど🤔

参考: Security Assertion Markup Language - Wikipedia


同記事より

「記事の人はSSO(シングルサインオン)で管理するより上みたいにやるのが好みだそうです」「Assume RoleでOrganizationsを管理するヤツですね」「Assume Role?」「特定のユーザーに一時的にポリシーを付与するようなことができます」「おぉ」

参考: Assume Roleの用途・メリット - Carpe Diem

「AWS Organizationsの管理は、何がベストかが場合によって違ってくるので結構悩ましいんですよね😅」「うーむ」「やりたいことが案件ごとに非常に多岐に渡っていていろいろ難しいですし」

「Organizationsは、Organizationsアカウントを分けておけば請求額を明示的に分けられるのは利点ですね😋」「請求を後から分けるのは難しいんでしょうか?」「構造上アカウントが分かれてないと分離は無理😇

「AWS Organizationsはmasterとその下の2階層でやっている分には比較的わかりやすいので自分は割と好きですね🥰」「あとAWS Organizationsだと子アカウントを作るときに認証を要求されないのがかなり便利😋: 普通のAWSアカウントを新しく作ろうとするとクレカ登録とか電話認証とかホントめんどくさい」「なるほど」

「Organizationsだと、後でサービスを子アカウントごとmasterから切り離して譲渡できるのもありがたい😂(認証は必要ですが)」「おぉ?」「AWSで最も面倒な作業のひとつは”AWSアカウントの移行”なんですよ: 動いているEC2インスタンスを全部AMI化してAMI共有したりとかS3バケットもコピーしたりとかマジ地獄☠」「むむむ」「なのでこうやって後で切り離せるようにしておくのは、特にBPSのような受託開発では重要ですね☺

「とにかくAWSのアカウント管理は何がベストプラクティスかは一概に言えなくて、案件に応じて考えるしかないんですけど、ひとつ重要なのは変に統合しないことでしょうね🧐: 統合されたものを後から切り離すのはとても大変なので、切り離す可能性のあるところで分けておくのは必須に近い」「なるほど!」

⚓その他インフラ

うれしくて貼っちゃいました😂

⚓JavaScript

⚓知らない間に強くなってたJSを急いでキャッチアップした


つっつきボイス:「JSをずっと触ってなかった人が久しぶりにやってみてわかったことをメモした感じの記事です」「さすがにprototype.js触ってる人はほとんどいないでしょうけど、jQueryは今でも触ってる人いますし☺」「letで驚いている😆」「コメントにも『「ES5の機能で感激するとはどんだけ長い間JS触ってなかったのか』という書き込みがありました😆」「まあオライリーのゾウさんJS本にもこの辺のことあんまり書いてませんし☺」「他のコメント欄も『オレもこれ知らなかった』みたいな声が続々寄せられてました」「JSをセカンダリ言語でやってる人たちにしてみれば知らない機能ありますよね☺


はみだし:「記事の末尾にきゃわいいイーブイの絵🐰」「ヤバい、ポケモンわからない😅」「自分も世代ズレてます😅」「自分は初代から😋

参考: イーブイ|ポケモン図鑑ソードシールド|ポケモン徹底攻略

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

⚓忙しい人のためのdraw.io


つっつきボイス:「BPS社内Slackで評判の記事でしたね」「これ割と有用😋」「draw.ioでプレースホルダ使えるのも知らなかった」「PlantUML↓使えるのも初めて知った」

参考: PlantUML: シンプルなテキストファイルで UML が書ける、オープンソースのツール

「む、見た感じdraw.ioのPlantUMLで書いた結果はひとつのオブジェクトになるっぽい?」「バラせないのかな…」「PlantUMLの図を出力してさらに細かくいじれたらいいんだけど、惜しいな〜😢

⚓言語・ツール

⚓PEG: あいまいさのない分析的形式文法


つっつきボイス:「最近ちょっとPEG(Parsing Expression Grammar)という形式で言語を記述してパーサーを生成して遊んでるんですけど、昔ながらのyacc/lexやEBNFよりイケてるようです」「人間が読むかコンピュータに読ませるかで変わってきますね☺」「PEGはEBNFや正規表現より厳密に書けるらしくて、自然言語の記述には固すぎて向いてない代わりにパーサーを記述するのに向いてるみたいでした😋

// mna/pigeonより: PEGのGo方言
{
package main

func toIfaceSlice(v interface{}) []interface{} {
    if v == nil {
        return nil
    }
    return v.([]interface{})
}

}

Input       ← #{ c.state["Indentation"] = 0; return nil } s:Statements  r:ReturnOp EOF 
                                            { return newProgramNode(s.(StatementsNode),r.(ReturnNode)) }
Statements  ← s:Line+                       { return newStatementsNode(s)}
Line        ← INDENTATION s:Statement       { return s,nil }
ReturnOp    ← "return" _ arg:Identifier EOL { return newReturnNode(arg.(IdentifierNode))}

Statement   ← s:Assignment EOL              { return s.(AssignmentNode),nil }
    / "if" _ arg:LogicalExpression _? ":" EOL INDENT s:Statements DEDENT 
                                            { return newIfNode(arg.(LogicalExpressionNode),s.(StatementsNode)) }


Assignment ← lvalue:Identifier _? "=" _? rvalue:AdditiveExpression
                                            { return newAssignmentNode(lvalue.(IdentifierNode),rvalue.(AdditiveExpressionNode)) }

LogicalExpression   ← arg:PrimaryExpression { return newLogicalExpressionNode(arg.(PrimaryExpressionNode)) }
AdditiveExpression  ← arg:PrimaryExpression rest:( _ AddOp _ PrimaryExpression )* 
                                            { return newAdditiveExpressionNode(arg.(PrimaryExpressionNode),rest)}
PrimaryExpression   ← arg:(Integer / Identifier) 
                                            { return newPrimaryExpressionNode(arg) }

Integer ← [0-9]+                            { return newIntegerNode(string(c.text)) }
Identifier ← [a-zA-Z] [a-zA-Z0-9]*          { return newIdentifierNode(string(c.text)) }

AddOp ← ( '+' / '-' )                       { return string(c.text),nil }

_ ← [ \t]+

EOL ← _? Comment? ("\r\n" / "\n\r" / "\r" / "\n" / EOF)

Comment ← "//" [^\r\n]*

EOF ← !.

INDENTATION ← spaces:" "* &{ return len(toIfaceSlice(spaces)) == c.state["Indentation"].(int), nil }

INDENT ← #{ c.state["Indentation"] = c.state["Indentation"].(int) + 4; return nil }

DEDENT ← #{ c.state["Indentation"] = c.state["Indentation"].(int) - 4; return nil }

参考: プログラミング言語を作る/yaccとlex

「Pythonから引退したGuido van Rossumさんも最近PEGで遊んでるようです」「後はこういうのをどこで使う機会があるかかな😆

⚓その他言語


つっつきボイス:「このブログを久しぶりに見かけたんですけど、今回はSSHキー生成の中身をyoshiさんの乱数記事↓的に追いかけていました」「ちょうど最近これに近いことやったな〜: OpenSSLコマンドで作られたX.509証明書の署名がちゃんとしてるかどうかを目で見て追いかけるという作業😆」「ひぇ〜😅」(以下マルチドメイン証明書などについて延々)

乱数について本気出して考えてみる

⚓その他

⚓お世話になりたい


つっつきボイス:「次と合わせて医療ネタが連続しちゃいましたが😅、タイに根管治療を専門にする歯科医がいるって初めて知りました」「私これ読んでタイに行こうかと真剣に考えました😆」「入れ歯製作なんかは未だに高度な職人芸が必要らしくて、3Dプリンタではまだうまくやれないらしいですね☺、その他のところでは機材がどれだけ最新かで決まる部分も大きいみたいですけど」

⚓番外

⚓思い出すのはブラックジャック10巻


つっつきボイス:「歯科のドリルとか、それこそブレのないロボットが向いてそう🤔」「日本だと免許とか資格とかクリアしないといけないものがいっぱいありますし😆

参考: “手術お助けロボット”による超マイクロ手術が初めての臨床実験に成功 | ナゾロジー


後編は以上です。

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

週刊Railsウォッチ(20200217前編)Railsのオプション引数退治、HSTSのデフォルトmax-ageが1年から2年に変更、semantic_logger gemほか

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

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

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

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

$
0
0

概要

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

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

Fullstaq Rubyとは

Fullstaq Rubyは、標準のMRI Rubyインタプリターのカスタムビルドです。メモリアロケーターの置き換え、セキュリティパッチの適用などさまざまな手を加えてあります。

Rubyを長くやっている人なら、なつかしのREE(Ruby Enterprise Edition)を思い出すことでしょう。REEはRuby 1.8.7やRuby on Rails 2.2といういにしえの代物です(ほぼ10年前!)。あの時代が懐かしくなります。REEは今でもRVMやrbenvでインストールできますし、レガシーアプリケーションの中には今も引き続き実行できるものもあれば、とっくに移行したものもあります。REEはパフォーマンス向上、メモリ削減、古いセキュリティ設定の調整などのため、Ruby 1.8.7にさまざまなパッチを適用していました。

それらの問題はMRI 1.9.xでほとんど解決し、MRI 1.9.xの利用が広まるにつれてREEは時代遅れになってきました。しかし現代の「vanilla(訳注: ピュアな)」MRIですら、比較的簡単に修正できるほころびがいくつもあります。中でも腹立たしいのはメモリ断片化によるメモリ肥大化です。

そんなわけで、REEの作者であるHongli LaiがFullstaq Rubyをリリースしたことはまったく不思議ではありません。

REEは死んだ、Fullstaq Ruby万歳!

Fullstaq Rubyが必要な理由があるとすれば

Evil Martiansが手掛けるプロジェクトのひとつで、深刻なメモリ肥大化が発生したことがありました。アプリケーションには膨大なIOがあり、高コンカレンシー設定を施したSidekiqプロセスも山のようにありました(1プロセスあたり20スレッド)。この設定はパフォーマンスの観点から最適です。理由は、ワーカーがさまざまなリモートAPIや自社内データベースやキャッシュにリクエストを送信することがほとんどだからです。しかしコンカレンシーをこれほど高く設定すると、メモリ断片化も大きく進みます。私たちのSidekiqプロセスのひとつひとつが「数GB単位で」メモリを食らうのです。

Sidekiqのコンカレンシー設定について詳しくはNate Berkopecの「Sidekiq in Practice part 1」記事をご覧ください。

私たちはMRI 2.6.3から、Fullstaq Ruby 2.6.3+jemallocに移行することを決定し、どんなふうに動くかをチェックすることにしました。

たしかに違う!

私たちは、production環境で動作する商用アプリケーションでFullstaq Rubyを試してみました。このアプリケーションは、支払いクライアントからのリクエストを24時間ぶっ通しでさばきます。

結論: どこもぶっ壊れず、ダウンタイムもゼロでした!

今度は監視グラフを見てみましょう。長時間実行しているプロセスのメモリ肥大化は、事実上消え去りました!

  • Webアプリケーションプロセスのメモリ消費はきわめて安定しました(1/4以下です!)。肥大化はその後も散発的に発生してはいますが、スパイク時のメモリ消費は50%以下にまで削減されたことが示されています。

Web pods memory consumption before and after switching to Fullstaq Ruby

Webポッドのメモリ消費推移

  • バックグラウンドジョブワーカー(私たちの場合はSidekiq)の重さも2/3までダイエットできました。Fullstaq Ruby移行前は1.5〜2 GBだったのが、移行後500〜700 MBになりました。

Sidekiq pods memory consumption before and after switching to Fullstaq Ruby

Sidekiqポッドのメモリ消費推移

  • 短命なプロセスのメモリ消費についてはこれといった違いは生じませんでした(cronジョブなど)。
  • レスポンスタイムやCPU利用率には何の違いも見いだせませんでした。

上のグラフから、メモリ消費がかさんだ原因はメモリ断片化であったことが証明されました。

Rubyのバイナリを差し替えるだけで相当の改善を得られたのです。

他の手段は?

jemallocをオプションとして選択できない、またはMRIを他のものに差し替えるわけにいかないのであれば、Ruby標準のglibc mallocの振る舞いを調整するMALLOC_ARENA_MAX=2という呪文をお試しください。結果はFullstaq Rubyとほぼ同じと言ってよいレベルです。

Fullstaq Ruby with jemalloc on the left and MRI with two malloc arenas on the right

Fullstaq Ruby + jemallocが左、MRI + mallocアリーナ2つが右
私たちの場合、mallocアリーナの個数に制約をかけた場合(右)では、Ruby + jemalloc(左)に比べて50〜100 MB程度メモリ消費が増えました。

MALLOC_ARENA_MAX=2のベンチマークについて詳しくは弊社のブログ「Cables vs. malloc_trim, or yet another Ruby memory usage benchmark」をご覧ください。

かくして私たちはFullstaq Rubyを使い続けることに決めました。

Fullstaq Rubyのインストール方法

記事執筆時点では、Fullstaq Rubyをインストールする方法はdebパッケージまたはrpmパッケージしかありません(直接またはリポジトリ経由でのインストール)。しかし私たちのアプリはKubernetesクラスタにデプロイするので、Dockerイメージが必要になりました。Fullstaq Rubyには公式の「コンテナエディション」がないので、独自のイメージをビルドしてみましょう。大した手間ではありません!

訳注: 翻訳記事公開時点では、Fullstaq Rubyサイトのコンテナエディションは”coming soon”となっています。

fullstaqruby.orgより

公式のRuby Dockerイメージで使われているLinuxディストリビューションに合わせてDebian 9を使うことにし、Rubyのバージョンを定義します。

FROM debian:stretch-slim

ARG RUBY_VERSION=2.6.3-jemalloc

続いて必要なソフトウェアをインストールし、Fullstaq Ruby APTリポジトリを追加し、Rubyそのものをインストールしてaptキャッシュをクリーンアップします。以上を1つのコマンドに集約することで、Dockerのレイヤのサイズを削減します。

RUN apt-get update -q\
    && apt-get dist-upgrade --assume-yes\
    && apt-get install --assume-yes -q --no-install-recommends curl gnupg apt-transport-https ca-certificates\
    && curl -SLf https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc | apt-key add -\
    && echo "deb https://apt.fullstaqruby.org debian-9 main" > /etc/apt/sources.list.d/fullstaq-ruby.list\
    && apt-get update -q\
    && apt-get install --assume-yes -q --no-install-recommends fullstaq-ruby-${RUBY_VERSION}\
    && apt-get autoremove --assume-yes\
    && rm -fr /var/cache/apt

Fullstaq Rubyではrbenvも依存関係としてインストールしますが、Dockerでは不要なので、以下のようにRubyの公式Dockerイメージと同じ方法でシステムの$PATHにRubyとgemのバイナリを追加しましょう。

ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME"\
    BUNDLE_SILENCE_ROOT_WARNING=1\
    BUNDLE_APP_CONFIG="$GEM_HOME"\
    RUBY_VERSION=$RUBY_VERSION\
    LANG=C.UTF-8 LC_ALL=C.UTF-8

# おすすめのパス: https://github.com/bundler/bundler/pull/6469#issuecomment-383235438
ENV PATH $GEM_HOME/bin:$BUNDLE_PATH/gems/bin:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin:$PATH

CMD [ "irb" ]

以上でできあがりです!

このイメージは既にビルドして公開してあります。quay.ioにある私たちのリポジトリからpullしていただいて構いません。

docker pull quay.io/evl.ms/fullstaq-ruby:2.6.3-jemalloc-stretch-slim

Dockerfileはgithub.com/evilmartians/fullstaq-ruby-dockerにあります。

私たちのアプリケーションのDockerfileでベースイメージを以下のように置き換えられます。

-ARG RUBY_VERSION=2.6.3
+ARG RUBY_VERSION=2.6.3-jemalloc

-FROM ruby:${RUBY_VERSION}-stretch-slim
+FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-stretch-slim

あとはstagingにデプロイし、最後にproductionにデプロイします。

皆さんもどうぞご自由にお使いください!

まとめ

  • Fullstaq Rubyへの移行はスムーズです。Rubyとgemを再インストールするだけですべて問題なくやれます。
  • アプリケーションサーバーやバックグラウンドジョブワーカーのプロセスが消費するメモリ量が劇的に削減されます。
  • 短期的なプロセス(cronジョブやスクリプトなど)でのメモリ消費は大して変わりません。
  • パフォーマンスもわずかに向上する可能性がありますが、ワークロードのプロファイル次第です。

関連記事

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

Ruby: Array#permutationでクイズを解いてみた

$
0
0

お題

Facebookで上の問題が流れてきたので、スレ(解答)を見ないようにしてRubyでやってみました。答えはたった1つというのがヒント。

なお、問題文は1から9までの数字を「1回だけ使う」のかどうか微妙にはっきりしませんでしたが、1回だけの前提で突っ走りました。

Array#permutation

まともにやれば9!(9x8x7…x2)回の実行回数が必要になることぐらいは見当が付きました。

帰りの電車の中で書き始めましたが、組み合わせを網羅するための順列アルゴリズムをスクラッチで書くのは思ったより面倒そう…😅

帰宅後きっとあるだろうと思ってググると、やはりArray#permutationがありました。

[2, 3, 11, 16].permutation.to_a
#=> [[2, 3, 11, 16], [2, 3, 16, 11], [2, 11, 3, 16], 
# [2, 11, 16, 3], [2, 16, 3, 11], [2, 16, 11, 3],
# [3, 2, 11, 16], [3, 2, 16, 11], [3, 11, 2, 16],
# [3, 11, 16, 2], [3, 16, 2, 11], [3, 16, 11, 2],
# [11, 2, 3, 16], [11, 2, 16, 3], [11, 3, 2, 16],
# [11, 3, 16, 2], [11, 16, 2, 3], [11, 16, 3, 2], 
# [16, 2, 3, 11], [16, 2, 11, 3], [16, 3, 2, 11],
# [16, 3, 11, 2], [16, 11, 2, 3], [16, 11, 3, 2]]

欲しかったのはこれです😋。なお、to_aしないとEnumeratorオブジェクトを返します。

[2, 6, 9, 12].permutation
#=> #<Enumerator: [2, 6, 9, 12]:permutation>

最初に書いたコード

とりあえず様子見で雑に書いてみました。問題文の式を事前に通分しておくことで除算を回避するという手もありますが、Rubyにある大好きなRationalで楽に書きました❤

def calc(a, b, c, d, e, f, g, h, i)
  tn1 = a.to_r
  td1 = (10 * b.to_r) + c.to_r
  tn2 = d.to_r
  td2 = (10 * e.to_r) + f.to_r
  tn3 = g.to_r
  td3 = (10 * h.to_r) + i.to_r
  tn1/td1 + tn2/td2 + tn3/td3
end

[1, 2, 3, 4, 5, 6, 7, 8, 9].permutation.each do |i|
  if calc(*i) == 1r
    puts i.to_s
  end
end

[5, 3, 4, 7, 6, 8, 9, 1, 2]
[5, 3, 4, 9, 1, 2, 7, 6, 8]
[7, 6, 8, 5, 3, 4, 9, 1, 2]
[7, 6, 8, 9, 1, 2, 5, 3, 4]
[9, 1, 2, 5, 3, 4, 7, 6, 8]
[9, 1, 2, 7, 6, 8, 5, 3, 4]

それっぽいものが出ました。

その2

  • 配列を[1, 2, 3, 4, 5, 6, 7, 8, 9]と書いてあるのが素朴すぎかなと思ったので(1..9).to_aに変えてみました。
  • 一応通分する形に変えてRationalを外しました。
  • 一時変数を減らしました。
  • resultの初期化を処理の直前に移動しました。
  • 処理の途中にputsto_sを挟むのもイケてない気がしたので、処理中に配列を変換しないようにし、putsto_sは最後の最後に回しました。
def calc(a, b, c, d, e, f, g, h, i)
  if a*(10*h + i)*(10*e + f) +
     d*(10*b + c)*(10*h + i) + 
     g*(10*b + c)*(10*e + f) == (10*b + c)*(10*e + f)*(10*h + i)
     [[a, b, c], [d, e, f], [g, h, i]]
  end
end

result = []
(1..9).to_a.permutation.each do |i| 
  ans = calc *i
  result << ans unless ans.nil?
end

puts result.to_s

[[[5, 3, 4], [7, 6, 8], [9, 1, 2]], [[5, 3, 4], [9, 1, 2], [7, 6, 8]], [[7, 6, 8], [5, 3, 4], [9, 1, 2]], [[7, 6, 8], [9, 1, 2], [5, 3, 4]], [[9, 1, 2], [5, 3, 4], [7, 6, 8]], [[9, 1, 2], [7, 6, 8], [5, 3, 4]]]

nil対応のためにeachブロックの行数がちょい増えてしまいましたが、しょうがないか。

その3

最初に「答えは1つ」と書いたことからおわかりかと思いますが、実は6つの配列は順序だけ異なった、同じ解であることにこのあたりで気づきました。上の結果を整形しました。

[
[[5, 3, 4], [7, 6, 8], [9, 1, 2]],
[[5, 3, 4], [9, 1, 2], [7, 6, 8]],
[[7, 6, 8], [5, 3, 4], [9, 1, 2]],
[[7, 6, 8], [9, 1, 2], [5, 3, 4]],
[[9, 1, 2], [5, 3, 4], [7, 6, 8]],
[[9, 1, 2], [7, 6, 8], [5, 3, 4]]
]

この重複をどうやって解消しよう?🤔

当初は「重複なしといえば集合」とばかりRubyのSetでやろうとしてじたばたしましたが、翌日になってsortしてuniqするというシェル芸でありがちなテクニックを思い出したので、そっちでやってみました。

def calc(a, b, c, d, e, f, g, h, i)
  if a*(10*h + i)*(10*e + f) +
     d*(10*b + c)*(10*h + i) + 
     g*(10*b + c)*(10*e + f) == (10*b + c)*(10*e + f)*(10*h + i)
     [[a, b, c], [d, e, f], [g, h, i]].sort
  end
end

result = []
(1..9).to_a.permutation.each do |i| 
  ans = calc *i
  result << ans unless ans.nil?
end

puts result.uniq.to_s

[[[5, 3, 4], [7, 6, 8], [9, 1, 2]]]

できました😋
最後はお楽しみの書式設定です。変数名を表示用に短くし、flattenでつぶして楽しました(答えがひとつと知ってしまっているので)。

r = result.uniq.flatten
puts "#{r[0]}/(#{r[1]}#{r[2]}) + #{r[3]}/(#{r[4]}#{r[5]}) + #{r[6]}/(#{r[7]}#{r[8]}) = 1"

5/(34) + 7/(68) + 9/(12) = 1

少々大げさですが、最後にrubocop -aをかけました。「calc()の引数多すぎ👮」は自動では修正できないので、配列をまとめて渡すように変えました。

# frozen_string_literal: true
def calc(ary)
  a, b, c, d, e, f, g, h, i = *ary
  if a * (10 * h + i) * (10 * e + f) +
     d * (10 * b + c) * (10 * h + i) +
     g * (10 * b + c) * (10 * e + f) == (10 * b + c) * (10 * e + f) * (10 * h + i)

    [[a, b, c], [d, e, f], [g, h, i]].sort
  end
end

result = []
(1..9).to_a.permutation.each do |i|
  ans = calc i
  result << ans unless ans.nil?
end

r = result.uniq.flatten
puts "#{r[0]}/(#{r[1]}#{r[2]}) + #{r[3]}/(#{r[4]}#{r[5]}) + #{r[6]}/(#{r[7]}#{r[8]}) = 1"

5/(34) + 7/(68) + 9/(12) = 1

おまけ

その後いつもお世話になっているkazzさんに軽くツッコんでいただきました。

  • select的なメソッド使えば一時変数要らなくなるかな
  • resultよりresultsにしよう

というわけでselectで書き直しました(整形は省略)。このままだとRuboCopに怒られそう👮🏼‍♀️

# frozen_string_literal: true
def calc(ary)
  a, b, c, d, e, f, g, h, i = *ary
  a * (10 * h + i) * (10 * e + f) + d * (10 * b + c) * (10 * h + i) + g * (10 * b + c) * (10 * e + f) == (10 * b + c) * (10 * e + f) * (10 * h + i)
end

results = (1..9).to_a.permutation.select do |i|
  calc i
end
results
#=> [[5, 3, 4, 7, 6, 8, 9, 1, 2], [5, 3, 4, 9, 1, 2, 7, 6, 8], [7, 6, 8, 5, 3, 4, 9, 1, 2], [7, 6, 8, 9, 1, 2, 5, 3, 4], [9, 1, 2, 5, 3, 4, 7, 6, 8], [9, 1, 2, 7, 6, 8, 5, 3, 4]]

おまけ

強い人がJuliaでワンライナーでやってました😳

関連記事

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`

Railsでnil? blank? empty? present?を使いこなそう

Haskell: ピーターからの問題を解いてみた

$
0
0

はじめに

👇こういう記事を見ると触発されて Haskell で解いてみたくなってしまいますね。

Ruby: Array#permutationでクイズを解いてみた

実際こういう数式系は Haskell が得意とする分野だと思います。

お題

要は下記の数式を満たす a,b,c,d,e,f,g,h,i の順列を求めろってことですね。

    \[\frac{a}{10b+c}+\frac{d}{10e+f}+\frac{g}{10h+i}=1\]

解く

数式はそのまま使えるので、まずは問題の数式をそのままラムダで書いてみます。

\[a,b,c,d,e,f,g,h,i] -> a/(10*b + c) + d/(10*e + f) + g/(10*h + i) == 1

できました。
次に、a〜iの全ての順列が欲しいです。
Haskellには Data.List というモジュールがあり、その中で permutations という配列から全ての順列を出す関数が用意されているのでこれと上記のラムダを使って解いてみます。

Prelude> -- モジュールのインポート
Prelude> :m Data.List
Prelude Data.List> filter (\[a,b,c,d,e,f,g,h,i] -> a/(10*b + c) + d/(10*e + f) + g/(10*h + i) == 1) (permutations [1..9])
[[9.0,1.0,2.0,5.0,3.0,4.0,7.0,6.0,8.0],[5.0,3.0,4.0,9.0,1.0,2.0,7.0,6.0,8.0],[7.0,6.0,8.0,5.0,3.0,4.0,9.0,1.0,2.0],[5.0,3.0,4.0,7.0,6.0,8.0,9.0,1.0,2.0],[9.0,1.0,2.0,7.0,6.0,8.0,5.0,3.0,4.0],[7.0,6.0,8.0,9.0,1.0,2.0,5.0,3.0,4.0]]

モジュールインポートを除いて、ワンライナーで解けました。
ちょっと結果の小数点が見にくいので、整数に直してみます。

Prelude Data.List> map (map round) $ filter (\[a,b,c,d,e,f,g,h,i] -> a/(10*b + c) + d/(10*e + f) + g/(10*h + i) == 1) (permutations [1..9])
[[9,1,2,5,3,4,7,6,8],[5,3,4,9,1,2,7,6,8],[7,6,8,5,3,4,9,1,2],[5,3,4,7,6,8,9,1,2],[9,1,2,7,6,8,5,3,4],[7,6,8,9,1,2,5,3,4]]

結果が見やすくなりました。
Haskell は数式を直観的に書けるので、コードもとても見やすいです(?)

以下コード

余談

Haskellには Hoogle という、型から関数を逆引きできるサービスがあります。
これは非常に便利で、少し慣れると思いあたる単語で関数を総当りで探すよりも目的の関数を見つけやすいです(体感)。
たとえば、今回使った permutations という関数は [a] -> [[a]] と検索すると上の方にヒットします。
[a] -> [[a]] を大雑把に説明すると「[a]を引数にとり[[a]]を返す。 aは多相型なのでどんな型でもよい」という感じの型を表します。

他の言語でもあったら便利そうですね。

週刊Railsウォッチ(20200225前編)RubyのShellwordsライブラリは知っておくべき、VCRはやはり有能、copを自作、Hix on Rails記事ほか

$
0
0

こんにちは、hachi8833です。次回の技術書典は残念ながら中止になりました。型システム祭りも延期だそうです。


つっつきボイス:「今日は1名Zoomで臨時リモートつっつき参加です」「はい聞こえま〜す」「five nineでつながってますね」「それは?」「無線用語😆: 了解度5で信号強度9📡」「Zoomやっぱりいいわ〜❤

参考: シグナルレポート(RSレポート)

「今やひととおりのイベントが中止ですし😇」「今度こそ技術書典行けるかと思ったのに😢」「常連ですが残念です😭」「代わりにオンラインでの開催「技術書典 応援祭」なんてのをやるみたい」「今回の出展者は次回優遇措置適用ですって」「出展者も本印刷したりしてますし」「主催者側の負担も半端ないはず」「出展料返却なしとあるけど会場費考えたら無理もない」「いろいろ大変だ…」

以下のツイートはつっつき後に見つけました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 3月の「公開つっつき」はお休みします

これまで毎月第一木曜に休まず開催してまいりました週刊Railsウォッチ「公開つっつき会」ですが、昨今のコロナウイルス流行を鑑みて3月は初の「開催なし」といたします🙇

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

公式の更新情報がなかったのでコミットリストから見繕いました。

今更ですが@kamipoさんのコントリビューション数がダントツですね。

参考: Rails Contributors - This year

つっつきボイス:「この週は久々に@kamipoさんの修正が少なめでした」「たまには一息入れないと☺

⚓attributes=assign_attributesのエイリアスにした

# activerecord/lib/active_record/attribute_assignment.rb#L13
+   alias attributes= assign_attributes
+

つっつきボイス:「修正はシンプルですね」「2つのメソッドが別々でもよかったんでしょうけど、今後また挙動が変わったときに合わせるのが面倒だからエイリアスにしたのかなと想像☺」「引用されてる#38401の方がメインだったのかも」

#38401まではassign_attributesattributes=もActive Modelで定義されていて引数をコピーしていた。assign_attributesがActive Record内でオーバーライドされるようになってそこではコピーが発生するが、attributes=がオーバーライドされていなかった。
つまりattributes=に代入する引数がネストまたはマルチパラメータだと引数が改変され、引数がコピーされていなかったということになる。
同PRより大意

⚓rails generateの改善

# railties/lib/rails/generators/actions.rb#L250
      def rails_command(command, options = {})
-       execute_command :rails, command, options
+       if options[:inline]
+         log :rails, command
+         command, *args = Shellwords.split(command)
+         in_root do
+           silence_warnings do
+             ::Rails::Command.invoke(command, args, options)
+           end
+         end
+       else
+         execute_command :rails, command, options
+       end
      end

つっつきボイス:「PRの内容がタイトルのshell out(=不意の出費を払う) とどうも意味がつながらなくて悩んだんですが、修正でrequire shellwordsが追加されているので、それにちなんでshell outってもじったのかなと思いました🤔」「ああShellwordsってライブラリあるある😆

⚓Rubyでシェル操作するならShellwordsを使おう

「Shellwordsって、コマンドラインの引数をいい感じに配列に分解したりエスケープしたりしてくれるっぽいですね」「そうそう、Shellwordsはシェルのコマンドライン引数をクォートしたりできます🧐

参考: Rubyから外部コマンドを実行するときはShellwordsモジュールが便利 - ブログのおんがえし

「Shellwordsはいわゆるコマンドインジェクションを防止するライブラリで、コマンドライン引数を扱うなら基本これを使うべきです😎」「そうでしたか!😳」「それをRailsコマンドの処理に使うようにしたと」

OSコマンドインジェクションの仕組みとその対策 | セキュリティ対策 | CyberSecurityTIMES

「Shellwords使わないと引数処理マジつらいですよ〜😭」「おぉ」「コマンド引数が完全に固定されてるとか自分で作った引数しか渡さないならいいんですが、ユーザー入力を含む文字列とかを引数に渡すのであれば、Shellwords使わないときっとインジェクション作り込んじゃいます💀

「一番安全なのはfork()自体に引数を渡すやり方」「おぉ?」「SQLで言うPrepared Statementみたいなもので、コマンドと引数をひとつの文字列にまとめるんじゃなくて引数を分離して渡す: これならどんなinfected stringぶち込まれても大丈夫💪」「あ〜なるほど」

参考: prepareStatementの使用 - データベース接続 - サーブレット入門

「とにかくShellwordsはRubyでシェルを操作するならぜひ知っておくべき🧐」「単に便利というレベルじゃなくてマストなんですね」「Shellwords使わないなら、自分のコードがインジェクションされない理由を全部説明できないといけない🧐」「Shellwordsって今まで知らなかったんですが、重要だったんですね」「まあ忙しいときはString#shellescapeで引数ごとにちまちまやることもありますけど、と思ったらこれも内部でShellwordsを呼んでた😆」「引数をまとめて処理するならShellwordsの方がラク😋

⚓シェルいろいろありすぎ

「ところで引数の扱いってシェルによって全然違ったりしますよね😇: 今はだいたいみんなbash使ってますけど、たまにtcsh使う人がいたりしますし」「私はずっとbash一筋ですけど、AIX(IBMのUnix)を使うはめになったときは強制的にkshでした😆」「ksh😆」「名前しか見たことない😆」「kshってKorn Shellの略でしたっけ?」「はい、kshマジわからなかった😆」「某大学のデフォルトシェルが何か変だなと思ったらtcshで絶句しました🤣

参考: tcsh - コマンド (プログラム) の説明 - Linux コマンド集 一覧表
参考: ksh

「macOSもCatalinaからデフォルトがzshになっちゃいましたね😢」「zshでかくてforkが重いからやめた方がいい気がしますけど😆」「🤣🤣」「ぜとしぇをデフォルトにするぐらいならfishあたりの方がよかったのでは😆」「同意です😆

参考: Z Shell - Wikipedia
参考: Friendly interactive shell - Wikipedia

⚓expand_cache_keyのアロケーションを削減

# activesupport/lib/active_support/cache.rb#L80
      def expand_cache_key(key, namespace = nil)
-       expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
+       expanded_cache_key = namespace ? +"#{namespace}/" : +""

        if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
          expanded_cache_key << "#{prefix}/"
        end
        expanded_cache_key << retrieve_cache_key(key)
        expanded_cache_key
      end

つっつきボイス:「expand_cache_key的なものをこの間も見かけた気がしますね☺ウォッチ20200203)」「dupをやめて+演算子に変えてますね」「frozenの文字列に+するとduplicateするヤツか😆」「まだ慣れない😅」「+ってdup倍以上速いそうです」「解説がないとわかんなさそう😆

参考: String#+@ (Ruby 2.7.0 リファレンスマニュアル)

「そういえば最近Twitterで『速くなるのはいいんだけど他の人が読みづらくなるコードはつらい』みたいなのを見かけましたね😆」「😆😆」「それ拾いました😋↓」「ライブラリコードだしいいのではという考え方もありますが、書くならコメントにも書いて欲しい気持ち😆

⚓NBSPチェックの正規表現を修正

ついでにmatch?include?に修正しています。

# activesupport/lib/active_support/configuration_file.rb#L34
        File.read(content_path).tap do |content|
-         if content.match?(/\U+A0/)
+         if content.include?("\u00A0")
            warn "File contains invisible non-breaking spaces, you may want to remove those"
          end
        end

つっつきボイス:「/\U+A0/マジか😆」「テストなかったのかしら😆」「本来ならテストと一緒に書いておきたいヤツ」

⚓NBSP文字とラテン系言語

「NBSP(ノーブレークスペース)って日本だと普通使わないかなと思うんですけど、どうでしょう?」「HTMLで&nbsp;が出てくることはありますね☺」「Excelとかから自動生成するとちょくちょく混じってくる😆

「ローカライズ時代の経験ですけど、ラテン系ヨーロッパの人がすごくNBSPを使うんですよ: Orquesta De La Luzみたいな一続きの固有名詞を途中で改行したくないときにOrquesta●De●La●Luzみたいに●のところにNBSPを置く慣習がありまして😅」「へぇ〜😳」「スタイルガイドでNBSPを禁止してあっても、フランスやスペインの人が隙を見てNBSPをちょくちょく入れてくるので仕方なく普通のスペース文字に置換したりしてました😭

参考: ノーブレークスペース - Wikipedia
参考: エンジニア・Webデザイナー必読: アプリケーションを国際化・多言語展開する前に知っておくべきこと - Qiita — 昔Qiitaに書いた記事です

⚓rails statsでTypeScriptファイルもカウントするようになった

# railties/lib/rails/code_statistics.rb#L41
-   def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/)
+   def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|ts|coffee|rake)$/)
      stats = CodeStatisticsCalculator.new

      Dir.foreach(directory) do |file_name|
        path = "#{directory}/#{file_name}"
        if File.directory?(path) && !file_name.start_with?(".")
          stats.add(calculate_directory_statistics(path, pattern))
        elsif file_name&.match?(pattern)
          stats.add_by_file_path(path)
        end
      end
      stats
    end

つっつきボイス:「お〜rails statsにJavaScriptの項目があったんだ」「今までtsが入ってなかったという😆

後でオレオレRailsアプリでrails statsやってみました↓。

+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |    344 |    232 |       5 |      30 |   6 |     5 |
| Helpers              |     64 |     44 |       0 |       3 |   0 |    12 |
| Models               |    100 |     39 |       3 |       6 |   2 |     4 |
| JavaScripts          |   2874 |   1740 |       0 |     110 |   0 |    13 |
| Libraries            |     47 |     44 |       0 |       1 |   0 |    42 |
| Model specs          |     91 |     43 |       0 |       0 |   0 |     0 |
| Request specs        |     15 |     11 |       0 |       0 |   0 |     0 |
| Routing specs        |     47 |     40 |       0 |       0 |   0 |     0 |
| Helper specs         |     24 |      2 |       0 |       0 |   0 |     0 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                |   3606 |   2195 |       8 |     150 |  18 |    12 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 2099     Test LOC: 96     Code to Test Ratio: 1:0.0

⚓primary db configが見つからない場合に正しくフォールバックするようにした

# activerecord/lib/active_record/railtie.rb#L135
-           next if db_config.nil?

            filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
-             db_config.spec_name,
-             schema_cache_path: db_config.schema_cache_path,
+             "primary",
+             schema_cache_path: db_config&.schema_cache_path,
            )

つっつきボイス:「最近この辺の修正をよく見かけるかも🤔」「元々”primary”を省略していたのを後付けでいろいろ直さないといけなくなった感」「マルチDBってやっぱり大変なんですね」「というより最初の設計で”primary”を省略できるようにすることが明示されてなくて後からやらないといけなくなったのかなと😆

⚓番外: ドキュメント修正

アソシエーション名が、joinされたテーブル名と対応していない場合、to_table:キーを持つHashを指定してそのテーブル名を指定すること。
同PRより

つっつきボイス:「add_referenceのAPIドキュメントに追記が入りました」「to_table:でやれるのか😳」「これ使ったことなかったけどいつからあったんだろう?」

後で調べるとRails 5.0.0.1でto_table:が入ったようです。


# activerecord/README.rdoc#L143
    class AddSystemSettings < ActiveRecord::Migration[5.0]
    class AddSystemSettings < ActiveRecord::Migration[6.0]

「トリビアですが、マイグレーションのサンプルコードの5.06.0に書き換えられました」「そのままコピペするとハマるヤツ😆


# activesupport/lib/active_support/inflector/methods.rb#L200
    #   classify('calculus')     # => "Calculus"
    #   classify('calculus')     # => "Calculu"

「意図的なスペルミスがうっかり修正されたのでrevertしたそうです😆」「😆

⚓Rails

⚓Hix on RailsのRailsチュートリ記事


同サイトより


つっつきボイス:「Railsでよくある感じの手順解説が並んでいて、昔だったらScreencastにされがちなノウハウが記事形式になってるのがいいなと思ったので」「こんだけみっちり書くモチベーションが凄い👍」「記事はどれも2019年12月以降なので新しいのも嬉しいです😋」「一覧表示のレイアウトとかTechRachoで参考にしたい❤」「TechRachoでやるなら記事もリニューアルしたいですね☺

⚓Interactorパターン

Factorial社では、早くから動詞(verb)を第一級市民にすることにした。新しいドメインがやってきたらそこにどんな動詞があるかを自問自答する。モデルのあらゆるCRUD/RESTコンベンションへの誘惑と戦い続け、ドメイン固有の動詞を使うようにした。「HelenはJohnからの最新の休暇リクエストをrejected = falseした」という言い方はしないだろう。「HelenはJohnからの最新の休暇リクエストをrejectした」と言うのが普通だ。
私たちはInteractorという新しい種類のオブジェクトを導入することでこれを実装した。新しくも何ともないコンセプトだが、私たちのアプリはRailsアプリとは似ても似つかないものになった。私たちのActive Recordモデルはきわめて小さくなってコールバックをまったく使っていない。ビジネスロジックのほとんどはInteractorに実装されている。コントローラがActive Recordと直接やりとりしなくなったことで、strong parametersの必要性もほぼほぼなくなった。アプリを動詞中心にしたことで、私たちのアプリは再利用が容易になり、もっと重要なことにコンポジション可能になった。
同記事より抜粋・大意


つっつきボイス:「上は『自分たちはこういう方針で設計している』みたいな記事なんですが、引用した法則1でInteractorパターンが登場していたのが気になりました」「お〜Interactorね☺」「この人たちはActive Recordのメソッド名が名詞形で単数複数あるのが好きでないらしくて、動詞の命令形で考えることにしたようです」「まあメンテできるならそういう方針でもいいんじゃないでしょうか☺」「kazzさんも『そこは設計の方針だから好き好きでいいよ☺』と言ってました」

そういえばinteractorというgemがありました(関連記事)。

InteractorパターンはHanamiに取り入れられているんですね。

参考: あーありがち - Clean ArchitectureとHanamiですっきりしてきた

「ふと最近のHanamiを見てみると、コミットの頻度が思ったより緩やかですね↓」「Hanamiは使ってる人は使ってるでしょうし、おそらくですけどHanami自体はあんまり多機能化の方向は目指してない気がしますね: Railsみたいに何でもやれるフレームワークが欲しい人はあまりHanamiは使わないかも🤔」「それもそうですね😅


hanamirb.orgより

⚓RuboCopでコードレビュー支援: Net::HTTPを使わせないcop(Hacklinesより)

# 同記事より
# cop/check_resilient_api_clients.rb

module RuboCop
  module Cop
    module ExternalServices
      class CheckResilientApiClients < Cop
        MSG = 'Use a more resilient API client'.freeze

        def on_send(node)
          add_offense(node, severity: :warning)
        end
      end
    end
  end
end

つっつきボイス:「なるほど自分たちでcopを書く話☺」「先週取り上げたNet::HTTP↓を使わせないためのcop作ったそうです👮‍♂️」「copを書くとDSL的なものやASTを扱ったりするのでいい勉強になりますね👍」「ああ確かに」「copではRubyを解析しないといけないのでRubyに対する理解が深まります😋

参考: Net::HTTP is not your API client - mwallba

# 同記事より
> ruby-parse -e 'Net::HTTP.get_response(uri)'
(send
  (const
    (const nil :Net) :HTTP) :get_response
  (send nil :uri))

参考: RuboCopの新しいルールを追加する方法(Custom Copの作り方) - アジャイルSEの憂鬱

⚓test doubleとVCRでテストを高速化(Hacklinesより)


つっつきボイス:「おぉVCR🥰」「VCRはリポジトリだけ見ててもあまりピンとこないんですけど、確か銀座Railsで実際にVCRを動かしているのを見ておおっなるほど❤という感じになりましたね: 動作をレコーディングして再生&やり直し可能にできるgem」「動いてるのを横から見るとこうやって使うのかと一発でわかる感😋

「VCRは、手続き的に『ここまで進んだらここまではできてる』みたいなものを上から下に書き下していくコードをテストするときにうまくマッチしますね☺」「おぉ」「なのでオブジェクト指向というよりは😆、手続き的な考え方」

「VCRは動作をリプレイできて、しかもそのリプレイをキャッシュしてくれるのがいいですね😋」「カセットというものを入れると1回呼び出したAPIのレスポンスを全部キャッシュしたものを返してくれる」「ふむふむ」「しかもカセットはファイルに保存できるので、カセットも一緒にgitにコミットしておけばテストでそのAPIを叩かなくてもよくなる🔨」「へぇぇ😳」「なのでclosed APIのテストなんかでとっても便利: 自分しかアクセスできないclosed APIに自分の環境でアクセスして取ってきたデータをベースにできる」「おぉ〜😍

「ほら、APIのスタブを書くのってとっても面倒じゃないですか😆」「はいたしかに😆」「APIのスタブやモックを作るということは、そのAPIの内部仕様を把握しないといけないですし、APIが巨大なXMLを返したりするとひたすらつらいですし、APIの仕様が変わってスタブを再編集とかやってられませんし😇」「ですです😇

「VCRはそういうAPIコールのレスポンスをファイルにも出せるので、そのカセットファイルがあればAPIにアクセスする権限のない他の人がテストするときにカセットからロードできる」「いいわ〜❤」「VCRはいろいろ有能💪: スタブとかモックとかAPI内部仕様とかを一切忘れてよくなるので、割り切りの落とし所としてはかなりいいと思いますね☺

参考: VCRを使って開発中にあほみたいにリクエストを飛ばさないようにする - かずおの開発ブログ(主にRuby)

参考: 永久保存版!?伊藤さん式・Railsアプリのアップグレード手順 - Qiita


元記事見出しより:

  • 特定の部分だけをテストしたい
  • VCR gemとカセットでテストを高速化
  • test doubleとメソッドスタブでテストを高速化
  • 重たいfeature testをどうにかする

RSpecで役に立ちそうないくつかのヒント(翻訳)

⚓開発速度と品質の落とし所は


つっつきボイス:「今回は割とThoughtBotのブログから拾いました」「ベロシティとクォリティ😆」「開発速度と品質のバランスをどうするかみたいな話ですね」「そもそもクォリティとは🤣」「🤣

拾い読み:

  • コードの品質とは
    • 「斧を振り回す凶暴なメンテナーがお前の家の場所を知ってると思ってコードを書け」とよく言われますが
    • 品質の高いコードは時間がかかり、顧客からメリットが見えにくい
  • 重要なのはチーム内コミュニケーション
    • 互いの意図や正しく推測できるレベルに持ち込む
    • プロダクトオーナーは設計上の決定の背景や理由を示す
  • 技術的負債を意識すること
  • コードの品質をいかに維持するか
    • 定期的なペアプロとコードレビュー
  • まとめ: 開発者とプロダクトオーナーが最初から率直にやりとりできるようにするのが大事

;⚓その他Rails


つっつきボイス:「guardは使いたい人が使うでいいかな〜☺」「チーム全員の環境にguardを入れるのは好きじゃない😆: guardって思わぬときに動き出したりすることがありますし、Docker環境の人もいればそうでない人もいたりすると邪魔になることもありますし😅」「たしかに🤔」「もちろん自分の環境を良くするために使うのは全然OK😋


前編は以上です。

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

週刊Railsウォッチ(20200218後編)rubyapi.orgで高速検索、RuboCopとJUnitFormatter、AWS Organizationsでの管理ほか

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

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

Rails公式ニュース

Hacklines

Hacklines

週刊Railsウォッチ(20200226後編)dry-rbを使うべき理由、最近のRubyオンライン教材、AWSから乗り換えた話ほか

$
0
0

こんにちは、hachi8833です。リモートワークの追い風が吹いているのかもしれません。自分は特にリモートワーク志向ありませんが。


つっつきボイス:「ビッグウェーブ😆」「リモートワークは人によって向き不向きがどうしてもあるので、そこ次第でしょうね☺」「ですね」「あとメンバー全員がリモートワークについて寛容でないと成り立ちませんし」「リモートワークの難しさってありますね」「自分はリモートで仕事し続けてますし、結果出してれば何も言うことはありませんし😆」「情報の共有が甘い人はたぶんリモートに向いてない🤔

「がっつり自炊はしないけど、蓄えはいろいろあるので折に触れて作ったりしますね🥘」「パスタは超便利🍝: 20キロぐらいまとめて買ってるので余裕」「自分はレトルトのパスタソースと乾麺と調味料とかかな〜」「冷凍パスタも好きです😋」「冷凍はちょっと割高かも」「味のバリエーション的にはとても助かります」

「十割そばはいいですよ👍」「知らない世界😅

「完全食も最近は割とおいしいのが出始めてるみたい」「昔の桂花ラーメンはキャベツ入れて完全食と言ってましたね😆

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓Ruby

⚓RubyGems.orgへの攻撃方法をひょんなことから発見した話


つっつきボイス:「gemを作っていた同僚がたまたまドメイン名をhttps://rubgems.orgとタイポしていたのを見かけて、もしかすると攻撃に使えるのではと思ってやってみたらできちゃった(ので関係者に知らせて塞いでもらった)んだそうです」「原文ではMITM(中間者攻撃)と書いてあるけど、これってそうかなぁ〜?😆」「私もそこがちょっと引っかかってました😆」「中間者攻撃は、文字通りクライアントからはわからないように通信の途中に割り込む手法なので、この記事にあるようなやり方を中間者攻撃って言うんだろうか?🤔」「もやもやする…😅

参考: 中間者攻撃 - Wikipedia

「記事の後半によると、脆弱性を知らせようとしたメアドが無効だったりと多少じたばたしたようです」「以下はrubygemsとbundlerに立ったissueです」

「スクワッティングという言葉を今更知りました😅」「ほら、『ドメイン 女性 振り向き』あたりで検索すると出てきそうな、おそらく世界で一番人目に触れたに違いないあの画像と関連するヤツですよ🤣」「あ〜、ひと頃やんなるほど目にしたアレ🤣

参考: サイバースクワッティング - Wikipedia

typo squatting = 1文字違いのドメイン名を押さえる行為

後でキーワードを日本語英語でいろいろ変えて検索してみましたが、あの振り向き画像を見つけられませんでした。別にいいんですが。

「rubygems.org本体が乗っ取られたならともかく、1文字違いのドメイン名とかまで無限に対処できませんし😆」「そうですね」「連絡された方も対応に困りそうなissue😆」「日本語ドメインみたいなUTF-8ドメイン名まで考えたらきりがない😆」「ドメインスクワッティングはわかるけど中間者攻撃と呼ぶのはやっぱり引っかかるなぁ😆


記事概要:

  • rubygems.orgを使った攻撃を試す
    • gemコマンドでやった場合
    • bundlerでやった場合
    • 結論
  • ダミーを置いたら100 IPほど釣れた
    • この問題を関係者に連絡するのに手間取った

⚓Rubyオンライン学習教材5つ(Hacklinesより)


つっつきボイス:「最近のRubyオンライン教材ってどうなってるかなと思って」「ほほぅ☺

記事の人が以下のサイトに5つの教材リストを置いてあります↓。

⚓dry-rbを使うべき理由(Hacklinesより)


つっつきボイス:「dry-rbはいいですね🥰: 半端なライブラリを再発明しないで済むためにも、dry-rbにどんなものがあるかぐらいは知っておきたい☺」「ですね☺」「こんなライブラリ欲しいな〜って思っているとdry-rbに既にあることが割とありますし😆」「とにかく存在を知っておくことが重要🧐


記事ななめ読み:

  • ビジネスロジックの置き場所にもいろいろあるが、短期プロジェクト(と一部の長期プロジェクト)に向いたやり方というものはある
  • 問題
    • MVCパターンではロジックをモデルに配置し、コントローラを薄くするのが常套手段
    • モデルが育つと分割がつらくなり、SOLIDの原則(特に単一責任の原則)も破られがち
  • dry-rbとは
    • 一種のgemエコシステムであり、必要なものをRubyアプリに取り込んでプラットフォームにできる
    • コントローラで使う例(dry-transactiondry-container
    • Operationクラス(2つのクラスを飲み込んでいる)
      • Containerクラス
      • Transactionクラス
    • 共通ロジックの再利用例
    • dry-valiodationによるスキーマバリデーション
    • 操作のステップ
  • まとめ
    • この方法が万能というわけではなくメンテ問題が魔法のように消えるわけでもない
    • しかしロバストネス、テスタビリティ、スケーラビリティをビジネスロジックに提供してくれる
    • ビジネストランザクションに責任を持つロジックが見つけやすくなる
    • スコープや関連付けといったRailsモデルにありがちなものをスキップできる

3年前に以下の記事を書いたときのdry-rbは16個でしたが今見ると21個に増えてますね。

Ruby: Dry-rb gemシリーズのラインナップと概要

⚓Net::HTTPをAPIクライアントにする必要はない(RubyFlowより)


つっつきボイス:「Ruby標準ライブラリのNet::HTTPはたしかにプリミティブすぎて使いづらい😆」「記事はFaradayとか他にもっといいものがあるんだからという感じ」「Faraday以外にもありますけど😆


記事ななめ読み:

  • 専用gemのない外部APIにアクセスしたいときがある
  • Net::HTTPでやるとタイムアウトエラーまみれになりがち
  • 本番でのいろんなコケ方を考えておく必要がある
    • リクエストがリダイレクトされる
    • ネットワークエラー
    • リクエストが多すぎてサーバーがあふれる
    • 結果が返るのが遅い
    • 転送速度の上限
  • Net::HTTPはベアメタルすぎ
  • Faradayでやれること
    • 認証
    • リトライの頻度を指数関数的に下げる
    • パラレルリクエスト


lostisland.github.io/faradayより

⚓その他Ruby


同リポジトリより


つっつきボイス:「Ruby製のハッキング/ペネトレーションテストツールだそうです」「WinRMってどういう意味かしら?」「わがんね😆

「それにしてもメタル風味溢れるジャケですね🎸」「この手のペネトレーションとかポートスキャナー的なツールはだいたい中二感がそこはかとなく漂います😆」「20年ぐらい前はこういう絵柄をよく見かけた気がするんですけど最近あまり見ないのは自分が知らないだけ?😆」「もしかすると流行が次に進んでたりして😆」「こういうのは万国共通なのかな?🤔

参考: 「お前らは厨二病を患ったことがある?」海外アニメオタクの反応 | 翻訳だだだ! 【海外の反応】
参考: 中二病[zhōng èr bìng] | 中国語を学ぶ~ん
参考: 中二病を意味する英語スラング Edgelord とは|英語ネットスラング辞典

私の中二はこういうの↓でした😇。1969年にあったはずのないカオナシ巨大ロボが目印です🥫

⚓DB

⚓データエンジニアの需要が倍増とのレポート(DB Weeklyより)


同記事より


つっつきボイス:「データベースエンジニアじゃないという 🤣」「ETLとか言ってるからデータアナリストとかそっち系でしょうね☺

参考: Extract/Transform/Load - Wikipedia

「この手の話は微妙で、ニーズがあるということは需要に対して供給が足りないわけですけど、うんとたくさん欲しいかどうかは別😆」「たしかに😆」「仮に1000人欲しいところに200人しか市場にいなければ倍率500%になりますけど、そこに10000人参入してきたら速攻あぶれるでしょうし😆」「パイの大きさ注意😆

「おそらくこの分野で本当に求められているのは、単にその辺に転がっている素材を集めて加工するだけの人よりも、ちゃんと実施計画まで立てて回せる人だと思いますし☺

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

⚓AWSホスティングをやめて売り出し中のUpCloudに乗り換えた話


upcloud.comより

記事より: EC2インスタンスのメール配信、ここがつらい

  • AWSでPTR(またはrDNS)レコードを設定するにはリクエストチケットを送る以外に方法がない
  • AWSのElastic IPアドレスはスパムで汚れがち
  • オープンリレーの疑いで(実際は違ったのに)ポート25番を突然ブロックされることがある

つっつきボイス:「UpCloudってヨーロッパ系のクラウドホスティングサービスみたいですね」

「あ〜たしかにAWSはメール送信というかポート25番にとても厳しいんですよ: それはわかる😢」「ちょっと変な動きするとブロックされる感じですか」「当然なんですが、AWSはスパムメールの温床として使われることについてとてもセンシティブなので、何かあったらカジュアルに即塞ぎにかかる可能性はありますね🧐」「メール送信が中心のサービスだとAWSでは大変そう😅

「記事ではElastic IPにも不満述べてますけど、引越し先のUpCloudがAWSと同じぐらい大きくなれば結局同じことになるのではという気がしますし😆: 記事にあるElastic IPの”bad reputation”というのがそれです」「おぉ?」

「Elastic IPはユーザーが取得して解放することができるので、悪い業者は取得したIPがスパムブラックリストに乗るまでスパムを送りつけて、IPがリストに乗ったら別のIPにさっさと乗り換えたりするんですよ」「あ〜なるほど」「なのでAWSでElastic IPを取得してみると既にブラックリストに乗ってるなんてことがあったりします😇」「取ったばかりでそれだったら悲しすぎ😭」「ユーザーが簡単にElastic IPを取れるようになってたら、そりゃそうやって使われるでしょうし😆

「陳情書を提出しないとPTRレコードを設定できないのはそのとおりで、手続きが面倒😤

⚓さくらのSSL証明書発行時間短縮


つっつきボイス:「社内Slackで喜びの声が上がってたので」「前は2時間だったのが10分に短縮されたと」「まあ2時間は最長の場合で、実際には30分ぐらいかかってましたけど😆

「ちなみに他のホスティングサービスからさくらのレンタルサーバーに移行するときにLet’s Encryptを使おうとすると厄介です: さくらのLet’s Encryptの認証はDNSじゃなくてHTTPで行うので、先にAレコードをさくらに向けておかないと証明書が取れないという😢」「あ〜」「なので移行前から証明書で暗号化してたサイトをさくらに移すときに、暗号化が効かなくなる期間がどうしても発生してしまう😇」「そうでしたか😅」「今度から10分まで短縮はされましたけど、やはり10分間は暗号化が効かないので、夜のうちにさっと済ますとかになりますね😆」「うーむ」

Let’s EncryptがVerisignと棲み分けできる理由: SSL証明書の「DV、OV、EV」とは何か

⚓その他クラウド

↑つっつき時は英語でしたが今日見ると日本語になってました。


参考: Linux カーネル の /dev/random について - myokotaの日記

つっつきボイス:「お/dev/randomの話😋」「昨年の乱数記事↓でも/dev/randomが話題になってましたね☺」「上の記事にもあるけどエントロピーが足りなくなるとブロックするとかあった🎲

乱数について本気出して考えてみる


「うちのラズパイまだ起動してなかった😆」「うちはまだ配線終わってません😆」「まあラズパイでも普通にLinux動きますし: ただし外部出力すると爆熱になる🔥」「どんな出力ですか?」「HDMI🔌: さすがにヒートシンク付けないと放熱がヤバい😇

⚓JavaScript

⚓fishery: ThoughtbotのJavaScript/TypeScript向けファクトリーライブラリ

// 同記事より
// factories/user.ts
import { Factory } from 'fishery';
import { User } from '../my-types';

export default Factory.define<User>(({ sequence, factories }) => ({
  id: sequence,
  name: 'Bob',
  address: { city: 'Grand Rapids', state: 'MI', country: 'USA' },
  posts: factories.post.buildList(2),
}));

つっつきボイス:「factory_botでお馴染みのThoughtbotがJavaScriptのファクトリーライブラリを出したそうです」

fishery: 漁業

「ははぁたしかにfactory_botライク」「この書き方が果たしてわかりやすいかどうかはともかく😆、factory_botを使い続けているRailsエンジニアには敷居が低いのかも?🤔」「JSだとまた違うんでしょうね」「JSのテスティングツールはいっぱいあってようわからん😆」「ファクトリーはデータ生成するんだから、どちらかというとフロントよりサーバーサイドで欲しいんじゃないかしら: クライアント側でビジネスオブジェクトを作るのはなくもないだろうけど、どのぐらい必要なんだろう?🤔

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

⚓Chromeの動画広告をGoogle自ら減らす方向に


つっつきボイス:「Google自らが動き出しました」「や〜YouTube見てると煩わしい動画広告だらけ🤣」「だらけ😆

「Firefoxとかが動画広告を締め出しにかかってきたからChromeも付いてこざるを得なくなったのかしら🤔」「おかげで今度は広告画像をJavaScriptでパラパラ漫画的に配信する輩が登場して、そっちの方が重いという、何だかなあという流れになってきてますし🤣」「🤣

⚓その他フロントエンド

つっつきボイス:「おほ、SPDYからHTTP/2に移行したのか」「すげえ」「LINEはエンジニアどっさり集めてますし」「そういえばLINEは割とJava文化圏なんですよ☺」「知らなかった〜」


「ほほぅ〜WebアプリでNFCタグとな」「NFCは前からありますけど、Webでやれるというのが新しい」

「NFCといえば、最近になってコンビニの端末がNFCクレジットカードにようやく対応し始めましたね😋」「おぉ」「なおNFCクレカは非接触なのでICクレカとは別物です」「このNFC Pay↓とかがそうなんですね」「NFCクレジットカードだとスキャナーとかを通さなくてよくなるので、SUICAの代わりにこれにすれば入退出も気にしなくていいし、こっちの方がいいんじゃね?って😋

参考: NFCとは?スマホやカードをかざせば決済ができる便利な機能とサービス|Have a good Cashless.~ いいキャッシュレスが、いい毎日を作る。~
参考: 日本で「NFC Pay」はいつ普及するのか:モバイル決済最前線 - Engadget 日本版

⚓言語・ツール

⚓git rebase --ontoを思い出せ


つっつきボイス:「今回は割とThoughtbotのブログから拾ったんですが、ブランチを付け替えられるgit rebase --ontoをこれで知りました」「もうコマンドラインでrebaseしてないし😆」「😆」「CLIはコンフリクトの処理がめんどくさすぎて😇

参考: git rebase --onto どこへ どこから どのブランチを - Qiita

⚓形式的な仕様記述

つっつきボイス:「はてブに上がっててみんな『なるほどわからん』という感じでした😆」「こうやって仕様を形式的に記述するのは、その中で全部完結できるといいんですよね😋」「😋」「こういう手法は昔からいろいろあるんですが、今ひとつ普及しませんね🤣」「🤣」「どちらかというとコンピュータサイエンス寄りの手法で、いわゆる『バグのないプログラムを作るには』みたいなヤツ」「宣言的に書く感じですね」

参考: 形式仕様記述 - Wikipedia

「そういえば自動車業界なんかでは、エンジン内部の制御に使うソフトウェアを書くときにそういう形式言語的なものを使ってるという話を小耳に挟んだことがあります👂」「バグがあると人が死ぬようなミッションクリティカルな方面では使っていることありますね: 宇宙関係やプラント、あと金融とか」「放射線医療機器とか」「そういう方面では形式仕様記述のニーズありますね☺

「まあWebではまずやらないと思いますが🤣」「と思います🤣」「基幹系でないWebでそこまで仕様ががっちり固まることはめったにないでしょうし」「まあ最近だと基幹系もビジネスロジックに合わせて変更を強いられたりしますけど😅

⚓その他言語


つっつきボイス:「ブクマが凄いことになってますね」「しかもほとんど無言ブクマ😆」「大学の授業の教材なんですね」「しかもプログラミング演習と銘打たれているということは大学1年とか2年向けの授業🎓

「本編も普通にいい感じですけど、特に2冊目のコラム編は楽しいトピックがいろいろ盛り込まれていて、世間話感サイコー❤」「サイコー😍」「ちょうど教授が授業中に雑談で話すような感じなんですよね: 自分が勉強会で話すときのスライドも話すことの2割ぐらいしか書いてなくて、後はそのとき思いついたことを話してますし😆」「😆」「読み物として面白い😋

「自分で作る教材だと、あんまり盛り込んだら読まれなくなるかなと思ったりすることもありますけど、こうやって広くパブリッシュされる本では面白さも重要な要素だと思います📖」「この間の文字コードの歴史みたいな話(ウォッチ20200212)は、今問題を解決したい人にとってはそんなに役に立たないけど😆、その分野に興味を持って勉強している人にとっては面白く読めたりしますし」「わかります🥰」「ターゲティングが肝心☺

⚓その他

⚓メモリダンプを模様で読む男


つっつきボイス:「これはありますね」「ありますね」「バイナリエディタのマッピングツールで色を眺めて『この辺からプログラムコードが始まる』『この辺からデータ』みたいに目ヂカラでわかる人、います」「私はできませんけど😆、そういう話はしょっちゅう見聞きしてました」「自分もできませんけど😆、壊れたデータをどうにかせざるを得なかったときにやりましたし」「この色とこの色とか、色の散らばり具合とか、バイナリも一応傾向がありますね」「ぐちゃらっとなってたら圧縮ファイルとか😆

「こういうのはゲームのセーブデータの解析してる人たちが強い😆」「あ〜そうそう😆」「ゲームの認証解除したり😆

私は復活の呪文の時代どまりなのでアウトオブスコープです😅

以下はつっつき後のツイートです。

⚓ここから始まった2題

なお、「MacintoshのGUIの父」という認識をされることについては「違います」と否定した上で、「でも、親子鑑定をしたら、祖父母の1人ではあるかも」とコメントしています。
同記事より

参考: ラリー・テスラー - Wikipedia

つっつきボイス:「かつてゼロックスでコピペを生み出した方でした」「当時そういうのをやってたのはゼロックスとかAT&Tぐらいだったのかも」「あとベル研とか」

「当時viのモード切替がめちゃめちゃ大変だったので、それが嫌でモードレスにやれるようにしたらしいと聞きました」「へぇ〜」「当時はviユーザーを仮想敵にしていたんじゃないかという噂もあるみたいです😆」「モード切り替えって人間に負担大きいですよね☺

「ここから始まったといえばこれも↓」「お〜村井先生の最終講義聞きに行きたかった😂」「書き起こしかなり長いです」「村井先生といえばワイルドで楽しい武勇伝が盛りだくさんな方ですけど、公開して大丈夫な話だったのかしら😆」「最終講義だし、さすがにオフレコ話はないか😆」(以下延々ここだけの楽しい話)

⚓番外

⚓吹く楽器は厳しそう


つっつきボイス:「楽器を演奏するための脳のエリアを損傷していないことを確認するために、手術中にバイオリン弾いてもらったんだそうです」「あ〜そういうこと😆」「意識があるまま脳を手術するのって昔の方がやってたかも😆」「脳そのものには痛覚がないって昔知ってびっくりした覚えあります🧠

参考: 脳は痛みを感じない | メディカルクリニック柿の木坂 | 都立大学 内科 神経内科


後編は以上です。

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

週刊Railsウォッチ(20200218後編)rubyapi.orgで高速検索、RuboCopとJUnitFormatter、AWS Organizationsでの管理ほか

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

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

RubyFlow

160928_1638_XvIP4h

Hacklines

DB Weekly

db_weekly_banner

Publickey

publickey_banner_captured

GitHub Trending

160928_1701_Q9dJIU

Rails 6: Docker/docker-compose/dipで`rails new`力を取り戻す

$
0
0

こんにちは、hachi8833です。いつからrails newが面倒になってきたのでしょう。

私の場合rails newする機会が多いので、Evil Martians流のDocker開発環境構築の次の段階として、自分用にDocker環境ビルドとrails newのためのしくみを作りました。

Rails 6のDocker開発環境構築をEvil Martians流にやってみた

概要

やりたいこと

  • Dockerで楽にrails newできるようにする
  • Rails 6 + Ruby 2.7以降のWebpackerを前提とする開発環境をDocker上に構築する
    • Evil Martians流のDocker開発環境構築に倣う
  • 無駄にカスタマイズしない
    • DockerでRailsとWebpackerを動かすために必要な最小限のカスタマイズに留める
  • 今後RailsやRubyやNodeやYarnがアップグレードされてもdocker-compose.ymlのバージョン番号を調整するだけで使えるようにする

前提とする環境

  • macOS(Docker for Mac)
  • Linux(Ubuntuなど)

なおWindows環境では試していません。

必要なもの

  • Docker
  • Docker Compose
  • dip(以下の記事をどうぞ)

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

リポジトリ

手順

1. 準備

  • リポジトリをgit cloneし、ディレクトリ名を適宜変更する
    • ディレクトリ名がDockerコンテナ名に使われる
    • .gitを削除して自分用にgit-flow initなどする
  • 必要に応じてdocker-compose.ymlのアプリ名やバージョンを修正する。
# PostgreSQL版の場合
x-var: &APP_IMAGE_TAG
  "my_app:1.0.0"
x-var: &RUBY_VERSION
  "2.7.0-slim-buster"
x-var: &PG_MAJOR
  12
x-var: &POSTGRES
  "postgres:12"
x-var: &NODE_MAJOR
  12
x-var: &YARN_VERSION
  1.21.1

なお、Ruby 2.7にはbundlerが同梱されているので、別途インストールはしない前提にしました。

  • 必要に応じてdip.ymlのrails new行にオプションを追加しておく。
  - dip rails new . -d postgresql --webpacker --skip-listen --skip-git

2. ビルド

ここまで行えば、後はプロジェクトディレクトリで以下を実行するだけです。

  • SQLite3版の場合
dip provision
  • PostgreSQL版の場合
dip provision
dip rails db:prepare
dip rails db:prepare RAILS_ENV=test # 必要なら

PostgreSQL版だとdip provisionの中でrails db:prepareを呼んだときにPostgreSQLへの接続に失敗する問題が解決できなかったので、db:prepareは手動で実行することにしました。

3. 起動

dip rails sしてhttp://localhost:3000をブラウザで開けばいつものWelcome画面が表示されます。

scaffoldなどで作ったページを開くとWebpackerが動き出します。

[Webpacker] Compiling...
[Webpacker] Compiled all packs in /app/public/packs
[Webpacker] Hash: 32e57f147dbdcbbf0c82
Version: webpack 4.41.6
Time: 3765ms
Built at: 02/27/2020 1:51:09 AM
                                     Asset       Size       Chunks                         Chunk Names
    js/application-bbe9c4a129ab949e0636.js    124 KiB  application  [emitted] [immutable]  application
js/application-bbe9c4a129ab949e0636.js.map    139 KiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]
Entrypoint application = js/application-bbe9c4a129ab949e0636.js js/application-bbe9c4a129ab949e0636.js.map
[./app/javascript/channels sync recursive _channel\.js$] ./app/javascript/channels sync _channel\.js$ 160 bytes {application} [built]
[./app/javascript/channels/index.js] 211 bytes {application} [built]
[./app/javascript/packs/application.js] 749 bytes {application} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 552 bytes {application} [built]
    + 3 hidden modules

Completed 200 OK in 7683ms (Views: 7664.0ms | ActiveRecord: 1.8ms | Allocations: 24008)

Docker向けにチューニングした点

基本的にEvil Martians流にしていますが、以下をカスタマイズしてあります。

1. listen gemをインストールしない(Mac向け)

listen gemはRailsでデフォルトでインストールされます。しかし、Docker for Macでこのgemがあると、ソースを更新してブラウザをリロードしても、Docker環境で起動したサーバーで、Dockerボリュームを変更が反映されないという既知の問題がありました。

そのため、--skip-listenを指定してlisten gemをインストールしないようにしています。listenがあっても大丈夫になったら--skip-listenを外すつもりです。

2. node_modules/ディレクトリをgitignoreする

rails newで生成される.gitignoreには、node_modules/やpublic/packs/、public/packs-testといったディレクトリをignoreする設定が含まれていません。

そのため、dipのrails new--skip-gitを追加してgit関連ファイルを生成しないようにしています。

スクリプトで.gitignoreに追加する手もありますが、定番のignoreエントリも含めてキットに.gitignoreを最初から入れておくことにしました。

3. node version manager ‘n’を追加

ビルド時点で最新のNode.jsをインストールし、アップグレードを行いやすくするために、nというバージョンマネージャをインストールする設定をDockerfileに追加しました。dip shでログインし、n ltsなどを実行することでNode.jsを最新にできます。

おまけ

なお、Evil Martians流ではyarnをnpmではインストールせず、curlとaptでインストールしていることに気が付きました。実際、Yarnの公式情報↓にはyarnをnpmでインストールすべきでないと書かれています。

参考: インストール | Yarn

関連記事

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


入退くんのメールが届かないときの対処法

$
0
0

こんにちは。harukaです。
今回2回目の投稿です。
最近有難いことに入退くんの利用教室数が多くなってきたのでたくさんのお問い合わせを頂いてます。
その中でも特に多いお問い合わせの内容をいくつかに分けて記事にしていこうと思います。

入退くんの通知方法

まず入退くんの通知方法は

  • メール
  • LINE

があります。

LINEでの通知方法については前に書いた記事にあるのでご覧ください!

メールが届いてるかどうかの確認方法

そもそもメールが届いていないってどうやって知るのでしょうか…
保護者様からの報告で知ることが多いかもしれませんが他に知る方法はあるんです!

教室入退室情報から確認する

入退くん管理画面の「教室入退室情報」から確認ができます。
その日の生徒の入退室時間も確認でき、日付や生徒の名前で検索もできる機能なんですが
よく見ると入退室の時間の右上にメールのアイコンがあります。
そのアイコンの色でメールが送られているかどうかがわかります。

アイコンの色は

送信済み
黄・赤
何らかの理由で送られていない

を意味しています。
そのアイコンをクリックしてもらえると詳細が出てきます。

メール送信履歴から確認する

入退くん管理画面の「メール送信履歴」からメールを送信した履歴を確認することができます!
そこに該当のメールアドレスの横の詳細を確認してください。

配送済み
問題なく届いています。
配送済みなのに届いていない場合は迷惑メールの設定により届いていない可能性があります。
背景が青色の配送エラー
サーバーの一時的なエラーの可能性があります。
時間をおいて確認してください。
背景が赤色の配送エラー
メールアドレスが誤っている可能性があります。
メールアドレスに間違いがないか確認してください。
背景が黄色の配送エラー
迷惑メールの設定、メールアドレスの入力間違いの可能性があります。

不達メール管理から確認する

これは届いていないメールアドレスを日付に関係なく確認ができます!
不達メール管理の画面は下の画像です。

入退くんではメールが届かなかった場合このリストに登録されます。

メールが届かないときにチェックすること

メールアドレスを確認する

届かないと言われたもしくは不達のリストに登録された場合にまず確認することです。
確認してするポイントとしては

メールアドレスに相違がないか
相違の例)文字が抜けている、文字が多く入っている等
有効なメールアドレスかどうか
無効なアドレス例)ドットが連続してるもの、ドットが@の直前もしくは先頭に入っている等

迷惑メール設定を見直す

Webメールの場合(Yahoo/Gmail等)

迷惑メールボックスを確認してもらい迷惑メールではないという設定を行ってください。

キャリアメールの場合(docomo/au/Softbankの場合)

《docomoをお使いの方》
dメニュー
→My docomo(お客様サポート)
→設定(メール等)
→メール設定(迷惑メール/SMS対策など)
《auをお使いの方》
Eメール(auメール)アプリを開く
→EメールMENU
→アドレス変更/フィルター設定
→迷惑メールのフィルター設定
《Softbankをお使いの方》
My Softbank
→ログイン
→メール設定
 

もし不明点等あったら各キャリアショップで聞いてもらえるとすぐ教えてくれます!

ドメイン設定に登録する

それぞれ前項の方法でメールの設定画面まで進むのは同じです。

《docomoをお使いの方》
【受信するメールの設定】
→【+さらに追加する】
→「nyutai.com」を入力
→【確認する】
→【設定を確認する】
《auをお使いの方》
【受信リストに登録/アドレス帳受信設定をする】
→「nyutai.com」を入力
→「必ず受信する」にチェックを入れる
→【受信リストの有効・無効設定】と【受信チェックの有効・無効設定】を有効にする
→【変更する】
→【OK】
《Softbankをお使いの方》
【迷惑メール対策】→【登録する】
→「nyutai.com」を入力
→【次へ】
→【登録する】

こちらももし不明点があったら各キャリアショップで聞いてもらえると確実です!

全て確認したら…?

不達メール管理の該当アドレスの右にある「送信を許可」を押してください。
入退くんは送信を失敗すると「送信を許可」を押すまで再送をしない仕組みとなってます。

なので全て確認が終わったら「送信を許可」を押して再送を許可してください!
ただ、押してすぐ再送されるものではないのでご注意ください。

まとめ

もし届かないという報告があったら

  • メールが送信できているか確認する
    • →できていなかったら理由を確認する
  • メールアドレスが間違えていないか確認する
  • 迷惑メール設定をしてもらう
  • ドメイン設定をしてもらう
  • 上記でも解決しない場合には別のメールアドレスを使ってもらう

上記でメールの問題は解決することが多いので是非試してみてください!

それではまた次の記事でお会いしましょう

週刊Railsウォッチ(20200302前編)RubyKaigi 2020は9月に延期、Railsのセキュリティパッチバージョニングが変更、dry-monadsほか

$
0
0

こんにちは、hachi8833です。テックカンファレンスの開催状況をまとめてくれた方がいました。ありがとうございます。


つっつきボイス:「こんなの見つけました」「お〜すげ〜😳、半分以上まっかっかじゃないですか🟥」「RubyKaigiは(つっつき時点では)まだ開催予定ですけど予断を許さない感じ…(臨時ニュース参照↓)」

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓臨時ニュース: RubyKaigi 2020は9月3日〜5日に延期

土曜日に延期のアナウンスがありました。詳しくはアナウンスをどうぞ。宿や交通の取り直しをお忘れなく。


esa-pages.ioより

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

この週はコミットリストから見繕いました。

⚓strict_loadingを追加

関連付けからのlazy loadingを防ぐ#strict_loadingを任意のレコードに追加した。
親レコードがstrict_loadingとマークされている状態で関連付けをlazy loadしようとするとエラーを発生する。余分なクエリを出さないよう関連付けをpreloadする場所を見つけるのに便利。
Changelogより大意

# Changelogより: 使い方
>> Developer.strict_loading.first
>> dev.audit_logs.to_a
#=> ActiveRecord::StrictLoadingViolationError: Developer is marked as strict_loading and AuditLog cannot be lazily loaded.

つっつきボイス:「has_manyの方向にアクセスしたときの振る舞いなのかな🤔」「どうなんでしょう😅」「使い方のコード例だとfirst取った時点でeager loadingされてそうな気がしないでもないけど…ちょい走り書き感😆」「preloadすべき場所を見つけたいときに便利ということみたいです」「このプルリク、久しぶりに👍とか❤がびっしり押されてるのでかなり喜ばれてる感じ😋

⚓SchemaCacheでMarshalオブジェクトからのシリアライズも選べるようになった

# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L139
+     def dump_to(filename)
+       clear!
+       connection.data_sources.each { |table| add(table) }
+       open(filename, "wb") { |f|
+         if filename.end_with?(".dump")
+           f.write(Marshal.dump(self))
+         else
+           f.write(YAML.dump(self))
+         end
+       }
+     end

つっつきボイス:「SchemaCacheってDBのスキーマかな?」「だと思います」「今まではYAMLからdumpしていたのがMarshallからのdumpもできるようになったということか」

このプルリクによって、シリアライズ戦略にMarshalYAMLのどちらかを選べるようになった。スキーマダンプのパスファイル名は、拡張子が.dumpならMarshal、.ymlならYAMLで、データベース接続ごとに定義する。デフォルトはYAMLのまま。
同PRより大意

「なるほどこの辺↓で拡張子を見てファイルを読み込んでる」「どうやらMarshalからのダンプは廃止の流れだったのにまたMarshallが入ってきたので、デフォルトはYAMLにしつつMarshalも選べるようにしたようです」「そのためにload_fromdump_toを追加したということね😋

# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L6
+     def self.load_from(filename)
+       return unless File.file?(filename)
+
+       file = File.read(filename)
+       filename.end_with?(".dump") ? Marshal.load(file) : YAML.load(file)
+     end

参考: 【Rails】SwitchPoint利用時にSchemaCacheを設定しSHOW FULL FIELDSを防ぐ - Qiita

SchemaCacheについて
Railsではrake db:schema:cache:dumpを使うことでdb/schema_cache.ymlにテーブルやカラムの情報が書き出されます。
このキャッシュを使うことでActiveRecordが型情報などを特定する手助けになります。
同記事より

⚓ActiveRecord::Baseサブクラスからのconnected_to呼び出しを禁止

振る舞いは変わっていないが、以前のAPI名だと「そのクラスでだけ接続を切り替えられる」かのようにミスリードする可能性がある。connected_toは現在の接続のコンテキスト(ロールなど)を切り替えるものであって、接続そのものを切り替えるのではない。
同PRより大意

# activerecord/lib/active_record/connection_handling.rb#L138
    def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
+     raise NotImplementedError, "connected_to can only be called on ActiveRecord::Base" unless self == Base
+
      if database
        ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
      end

つっつきボイス:「ActiveRecord::Baseのサブクラスでconnected_toを呼べなくするそうです」「connected_toって何するんだっけ?😆」「connects_toとかconnected_to?とかよく似た名前のメソッドがあってややこしいです😅」「親でしか許さないとなると継承とは一体何だったのか🤣」「🤣」「このconnected_toは接続のコンテキストを切り替えるものなのに、接続を切り替えると勘違いされやすかったのね😆」「メソッド名がイマイチなのかも😆

⚓テンプレートレンダリング時のハッシュアロケーションを削減


つっつきボイス:「こちらは引数を修正しつつアロケーションをちょっぴり減らしたそうです」「可読性の向上がメインみたいなのでこれでいいのかも☺: これでカリカリチューニングで可読性落ちたとかだったら残念ですし」

# activerecord/lib/active_record/railties/collection_cache_association_loading.rb#L4
  module Railties # :nodoc:
    module CollectionCacheAssociationLoading #:nodoc:
      def setup(context, options, as, block)
-       @relation = relation_from_options(**options)
+       @relation = nil
+
+       return super unless options[:cached]
+
+       @relation = relation_from_options(options[:partial], options[:collection])

        super
      end

-     def relation_from_options(cached: nil, partial: nil, collection: nil, **_)
-       return unless cached
-
+     def relation_from_options(partial, collection)
        relation = partial if partial.is_a?(ActiveRecord::Relation)
        relation ||= collection if collection.is_a?(ActiveRecord::Relation)

        if relation && !relation.loaded?
          relation.skip_preloading!
        end
      end

「ほほぅ、relation_from_options(**options)をいきなり呼ぶのをやめてるので、**optionsの展開のタイミングを変えてますね」「たしかに」「修正後はrelation_from_options(partial, collection)となってて、前のような雑な**じゃなくてオプションをきちんと展開するようになってるので、やはり可読性向上だと思います😋」「コメコメを追放して読みやすくしてくれたんですね🎉

⚓スキーマキャッシュ関連修正


つっつきボイス:「2つともスキーマキャッシュの話なので冒頭の#38432と関連してるっぽい」「このatomic_write↓っていうファイル関連メソッド初めて知りました」「Active Supportの機能でしたか」

# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L143
      def dump_to(filename)
        clear!
        connection.data_sources.each { |table| add(table) }
-       open(filename, "wb") { |f|
+       File.atomic_write(filename) { |f|
          if filename.end_with?(".dump")
            f.write(Marshal.dump(self))
          else
            f.write(YAML.dump(self))
          end
        }
      end

「ちなみに自分はRubyのopenメソッドって雑なのでキライ😆」「😆」「うろ覚えですけどopen使って外部APIのURLを叩くこともできた気がする」「マジで😅」「openっていろんな意味になったりするのが怖いので、atomic_writeみたいな書き方にするのはいいことだと思います😋

Ruby 2.7で調べると、Kernel#openでURLを開くにはrequire 'open-url'する必要があるようです。昔は違ったのかも?🤔

require 'open-url'
uri = 'https://ドメイン名/'
uri = URI.parse(uri)
open(uri).read 

上でURLにアクセスできましたが、以下のwarningが表示されました。URI.openURI#openだとwarningは出なくなりました。

warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open

⚓新しい属性に対応

以下のHTML属性が追加された:
* allowpaymentrequest
* nomodule
* playsinline

# actionview/lib/action_view/helpers/tag_helper.rb#L15
-     BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
-                             compact controls declare default defaultchecked
-                             defaultmuted defaultselected defer disabled
-                             enabled formnovalidate hidden indeterminate inert
-                             ismap itemscope loop multiple muted nohref
-                             noresize noshade novalidate nowrap open
-                             pauseonexit readonly required reversed scoped
-                             seamless selected sortable truespeed typemustmatch
-                             visible).to_set
+     BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
+                             autoplay checked compact controls declare default
+                             defaultchecked defaultmuted defaultselected defer
+                             disabled enabled formnovalidate hidden indeterminate
+                             inert ismap itemscope loop multiple muted nohref
+                             nomodule noresize noshade novalidate nowrap open
+                             pauseonexit playsinline readonly required reversed
+                             scoped seamless selected sortable truespeed
+                             typemustmatch visible).to_set

つっつきボイス:「また新しい属性が増えたようです」「改修はビューヘルパーだけど、HTMLの属性が増えたって言ってる?えぐい😆」「allowpaymentrequestってペイメント系のリクエストに関連してる感じ💰」「BOOLEAN_ATTRIBUTESには他にもどっちゃりboolean属性入ってるな〜😅」「フレームワークはこういうのに対応しないといけないから大変そう…」

参考: HTML Standard — whatwg.org

allowpaymentrequest
iframe要素の中でPaymentRequestインターフェイスを用いてpaymentリクエストを許可するかどうかを指定
nomodule
module scriptをサポートするuser agentの実行をscript要素で禁止するかどうかを設定
playsinline
video要素の動画コンテンツをplaybackエリアで再生するようuser agentにすすめる

⚓Railsのセキュリティアップデートポリシーが変更

# guides/source/maintenance_policy.md#L57
-the x-y-stable branch. For example, a theoretical 1.2.3 security release would
+the x-y-stable branch. For example, a theoretical 1.2.2.1 security release would

つっつきボイス:「Railsのセキュリティメンテナンスポリシーが変わってセキュリティパッチのバージョン番号が4桁構成になったそうです」「えっとメジャーバージョンとマイナーバージョンと、3つ目は何だっけ😆」「えっと、パッチバージョン」

参考: セマンティック バージョニング 2.0.0 | Semantic Versioning

「6.2.1を6.2.2とかにするのはちょいコワイけど、6.2.1.1とかにするならセキュリティだから当てなきゃという感じが伝わってきますね❤」「たしかに〜」「Railsのバージョンを上げたいわけじゃないけどセキュリティパッチは当てないといけないということはよくありますので☺

⚓Rails

⚓Dry-rbのモナドでServiceを改善する

# 同記事より
class Reservation::Create
  include Dry::Monads[:result]

  def initialize(user:, room:, start_date:, end_date:, notes: nil)
    @user = user
    @room = room
    @start_date = start_date
    @end_date = end_date
    @notes = notes
  end

  def call
    check_if_room_available
      .bind { create_reservation }
      .bind(method(:send_notification))
  end

  private

  attr_reader :user, :room, :start_date, :end_date, :notes

  def check_if_room_available
    Try(ActiveRecord::ActiveRecordError) { existing_reservations.exists? }.to_result.bind do |result|
      if result
        Failure('The room is not available in requested time range')
      else
        Success(nil)
      end
    end
  end

  def create_reservation
    reservation = Reservation.new(
      user: user, room: room, start_date: start_date, end_date: end_date, notes: notes
    )

    if reservation.save
      Success(reservation: reservation)
    else
      Failure('The reservation could not be created')
    end
  end

  def send_notification(reservation:)
    NotificationMailer
      .notify_room_booked(user: user, reservation: reservation)
      .deliver_later

    Success(reservation)
  end

  def existing_reservations
    Reservation.where(room: room).in_time_range(start_date: start_date, end_date: end_date)
  end
end

つっつきボイス:「kazzさんが最近Railsでパイプラインの実装で悩んでるので、Dry-rbのこの辺の記事が参考になるかと思って今ローカルで雑に翻訳してみました(許可取れたら公開します)」「自分がモナドやるときが来ようとは: 今この記事に沿ってちょっとやってみてるんですけど、Doでやるのとbindでやるところがやっと見えてきたのでモナドはこれから😆

「今ボク関数脳🧠」「この記事で使ってるDry-monadsはDry-transactionの後継ということを翻訳してて知りました」「モナドむずい😭」「大学で数学専攻しててもむずいということは相当手ごわいんですね😳

「そういえばdry-rbのリポジトリにdry-railsというのが作り中なのを見つけました」「dry-rbでRails的なものを作ってみる企画的な感じ☺」「思い切りWIP😆

「この記事では『Railsway Oriented Programming』という関数型由来の概念を援用していて、以下のスライドは割と前のですがその解説です」「どれどれ👀: successとfailという2本の線路で考えるところを絵で説明してるのがとてもいいですね👍」「😋」「エラー処理はraiseしちゃえばいいという考え方もあるんですけど、こういう世界だと単純にraiseするわけにいかなくて、かといって普通にやるとifの嵐になっちゃいますし😢

参考: Railway Oriented Programming | F# for fun and profit
参考: notes/Railway oriented programming.md at master · yukitos/notes — 上の日本語版

⚓コント・モナドロジー

「なははは😆、スライドのくまの会話、今の自分に染みるわ〜」「Maybeモナドと『たぶん』が入り混じったりしてて英語圏らしい笑い😆」「モナド簡単でしょ?とかひどい🤣」「『完全に理解した』出た😆」「endfunctorって何関手だったっけ😅

後で雑に訳を付けてみました。どことなくいしいひさいちを連想するセンスです。

くま: 関数をいくつかチェインしたいんだけど、エラーもキャプチャしたいんだくま。
ブレインズくん: そんなの簡単。モナドでやれます。
くま: モナド何だか難しそうなんだくま。
ブレインズくん: モナドは単に自己関手の圏におけるモノイドですよ。

くま: ⊂( ・∀・)ワケ ( ・∀・)つワカ ⊂( ・∀・)つラン♪
ブレインズくん: 何か問題でも?
くま: 自己関手がわかんないくま。
ブレインズくん: 簡単です。関手というのは圏と圏の準同型のことで、自己関手というのは単に圏自身に写像する関手のことです。
ブレインズくん: ほらこんなにシンプル!
くま: よし完全に理解したくま。

くま: で、ぼくはどうしたらいいくま?
ブレインズくん: モナドを全部理解する必要がないなら、Maybeを使えばいいのでは?
くま: たぶん(maybe)何をくま?
ブレインズくん: Maybeっていうモナド。
くま: だからたぶん(maybe)何て言うモナドくま?
ブレインズくん: だから、”Maybe”っていう、名前の、モナドなの。

くま: 「たぶんそのモナドの名前は…」じゃなかったのかくま?。
ブレインズくん: Maybeのモナドが名前は…ってヨーダみたいなしゃべり方しないでくれる?
くま: ヨーダみたいにしゃべってるのは君の方だくま。
ブレインズくん: とにかく話を戻すと、君に必要なのはきっとMaybe(definitely Maybe)のはず。
くま: つまりDefinitely Maybeなのかくま?

ブレインズくん: ちなみにぼくは『Definitely Maybe』より『<What’s the Story> Morning Glory?』の方が好きだけど。
ブレインズくん: 君にはEitherの方が合ってるかも。
くま: 何と何の『どちらか(either)』なのかくま?
ブレインズくん: Eitherはモナド。
くま: モナドと何の『どちらか(either)』なのかくま?
ブレインズくん: だからEitherだけ(just Either)。
くま: つまりJust Eitherって言うのかくま?

※ 『Definitely Maybe』と『<What’s the Story> Morning Glory?)』はどちらもオアシスのアルバムタイトル。

参考: オアシス (バンド) - Wikipedia

ブレインズくん: ああそうじゃなくって!JustはMaybeの一部なの。
くま: じゃJust Maybeって言うくまか?
ブレインズくん: 違う違う、そこはJustって言わないとだめ。JustかNothingだけ(just Nothing)。
くま: Just Nothingくま?でもさっきDefinitely Maybeって言ってなかったくまか?
ブレインズくん: いや今はEitherの話をしてるんですけど。
くま: Just NothingなのかDefinitely Maybeなのかはっきりして欲しいくま。
ブレインズくん: どっちも違います!いいからEither使えっつーの。
くま: (゜∀。)ワヒャヒャヒャヒャヒャヒャ


「あとRedditに『Dry-rbの問題はドキュメントだ』という書き込みを見つけました」「それほんに: APIの仕様もコード付きのユースケースも見当たらなくて、こんなことできるよってふわっと書いてあるぐらい😢」「さっきのブレインズくんの説明と大差なさそう😆」「今欲しいのは業務に近いユースケース😢」「『これらのpredicateはルールによってカプセル化され、述語論理を用いて互いにコンポジションできる。つまり共通の論理演算子を利用してバリデーションスキーマを構築できる』とか書かれても使い方がわからないって書き込みにありますね😅」「その意味までならわかるんですけど😆

Dry::Structはたまに使う。Dry::StructはVirtusを置き換えるものだと思っていたが、VirtusはミュータブルなのでForm Objectで便利なのにDry::Structはそうではないのが残念。結局Dry::Structはイミュータブルなstructに使っていて、ミュータブルなstructは手作りと(Trailbrazerの)Reformの合わせ技になっている。
Dry::TypesはDry::Structや手作りForm Objectと相性がいいのでしょっちゅう使っている。しかしDry::Typesはバリデーションのライブラリなのか型強制(coercion)ライブラリなのかアサーションのライブラリなのかはっきりしない。型に対する変更の順序が微妙に重要だったりするのもあって、めちゃくちゃ好きというほどではない。Rubyに型システムを追加しようと一人相撲している感もある。
Redditより大意

dry-structのissue #48を見ると「イミュータブルは仕様」とありました。

⚓Railsリンク集


つっつきボイス:「TechRacho記事もいくつか引用されていたQiita記事を見つけました: Railsガイドより先の設計とかの記事リンクだそうです」「ほほぅ〜頑張っていろいろ集めてる😋

「これにjoker1007さんの例の名記事↓も含めたいですね」「Service ObjectはRailsの標準ではないというのはまあ置いとくとして😆」「あとyasaichiさんの『Railsの正体』スライドも冒頭に置きたいです」「うん、あれは本当にいいスライドですよ❤

⚓WebカメラをActive Storageに直結する(Ruby Weeklyより)

# 同記事より
class VideoAttachmentService
  class << self
    def attach(model, video_path)
      model.picture.attach(
        io: File.open(video_path),
        filename: "player_video_#{unique_string}.webm"
      )
    end

    private def unique_string
      SecureRandom.urlsafe_base64(10)
    end
  end
end

つっつきボイス:「直結だそうです」「直結でがんがんストリーム配信しまくる?すげ〜😆」「記事では最初に画像のアタッチ、次に動画のアタッチやってますね」「既存の何かのプロトコルの焼き直しに見えなくもないですけど😆」「どうなんでしょう😆

⚓その他Rails


つっつきボイス:「お馴染みt-wadaさんのプレゼンです」「先週の『開発速度と品質』の話にも通じてそう(ウォッチ20200225)」

「中身見る前に言うと、自分の感覚では品質を下げたからといって開発速度が上がるとは限らないですね: カリッカリのところだと確かにトレードオフになるんですけど、特に開発の初期段階は質と開発速度はむしろ比例すると思ってます🧐」「おぉ」「結局、書くのが早い人は品質もいいし、書くのが遅い人に時間あげてもたいてい品質はよくならない😆」「😆」「言い換えると『開発がスタートする前に設計をどこまでしっかり固めてますか』ということ: このスライドは主に開発が進んでからの話みたいですけど☺

↓後でスライド見つけました。「システムを設計するための判断力をつける一番の方法は、自分で設計したシステムを長い間メンテすることだ(p59)」で考えさせられました。

参考: デブサミ2020、講演関連資料まとめ:CodeZine(コードジン)


今回は以上です。

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

週刊Railsウォッチ(20200226後編)dry-rbを使うべき理由、最近のRubyオンライン教材、AWSから乗り換えた話ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20200303後編)Ruby 2.7で引数のruby2_keywordsフラグを確認する、fake_apiでAPIプロトタイプ、groupdateで日付をグルーピングほか

$
0
0

こんにちは、hachi8833です。私もしばらくの間リモートワークになりました。

それはそうと確定申告が延長されましたね🎉

参考: 確定申告4月16日まで延長方針 - Yahoo!ニュース

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓Ruby

⚓Ruby 2.7の引数を確認する


つっつきボイス:「詳しくはkamipoさんのブログを見ていただきたいんですが、hashオブジェクトにRuby 2.7のruby2_keywordsフラグが付いているかどうかを確かめる公式の方法がこれまでなかったそうです」「これは渡されたものに対する評価方法がないってことですよね?たしかメソッドに対してはどういう引数があるのかをざっくり調べるメソッドは一応あるはず(名前忘れたけど😆)」

後で調べるとMethod#parametersがそれのようです。

「Ruby 2.7に入ったruby2_keywordsはキーワード的に使ってますね😋」「まあ上書きできるのかもしれませんが☺

後でruby2_keywordsを頼りにMethod#parametersをやってみました。

require 'ruby2_keywords'

module YourModule
  ruby2_keywords def delegating_method(*args)
    other_method(*args)
  end
end

class Foo
  include YourModule
end

Foo.new.method(:delegating_method).parameters
#=> [[:rest, :args]]

これはmameさんのハックを見て知った方法で、ようはフラグが付いてるhashオブジェクトかそうじゃないオブジェクトかで違う振る舞いをする処理を通らせてその結果を観測することでどっちだったかを確かめるという方法です。
同記事より

「観測!Ruby 2.7以降に合わせてすべてを完璧に修正する必要はなくて、観測して振る舞いが違うものに絞り込んで当面修正すればいいというのであれば、なかなかうまい方法かも😋」「観測されないものは存在しない😆

このフラグが付いてるhashオブジェクトかそうじゃないオブジェクトかで違う振る舞いをする処理がRubyの世界からはほとんど存在しないので、普段Rubyでコードを書いてる常人が自力では気づけんやろって方法で、一部のメソッド(initializeとかmethod_missingとか)をRubyのコードから直接呼び出すんじゃなくてCのコードから間接的に呼び出されるときにフラグが付いてるhashオブジェクトをdupするので、dupされずにそのままのオブジェクト(object_id)だったらフラグが付いてなかったってことでdupされて別のオブジェクトが観測されたらフラグが付いてたオブジェクトだったという技を使っています。
同記事より

「ここが振る舞いの違うところですか😳」「一気に書き下した感」「C言語の世界でのこんな微妙な振る舞いの違いを使うのすげ〜」「これでRailsのオプション引数退治をはかどらせてたんですね🍑

「そしてRuby 2.7.1では、フラグを見分けるruby2_keywords_hash?が正式に入るそうです」「お〜バックポートもすると😍」「ライブラリメンテナが泣いて喜ぶ機能😂」「つくづく凄い人たち…」

⚓ferrum: ヘッドレスChrome API


同サイトより

以下を謳っています。

  • Pure Chrome
  • Pure Ruby
  • No addiction

つっつきボイス:「フェラーム?」「あ、元素記号の鉄(Fe)か!ラテン語です」「クロム(Chromium)だから鉄ってシャレね☺」「CapybaraではculpriteというヘッドレスChromeドライバでやってるらしいですが、FerrumはCapybaraなしでもブラウザをRubyからいろいろ制御してクロールとかやれるということみたいです」

# 同リポジトリより
# Trace a 100x100 square
browser = Ferrum::Browser.new
browser.goto("https://google.com")
browser.mouse
  .move(x: 0, y: 0)
  .down
  .move(x: 0, y: 100)
  .move(x: 100, y: 100)
  .move(x: 100, y: 0)
  .move(x: 0, y: 0)
  .up

browser.quit

⚓RuboCop 0.80リリース(Ruby Weeklyより)

今日見るともう0.80.1がリリースされていました。


つっつきボイス:「RuboCop 0.80がリリースされてました」「Style/HashTransformKeysやらStyle/HashTransformValuesやらいろいろ追加されてるな〜」「copますます強くなる👮‍♂️」「この間取り上げたJUnitFormatterも入ってます(ウォッチ20200218)」「JUnitはJavaでは当たり前に使われてるテスティングツールですね☕

「ついでにRubyWeeklyでは以下のRedditも紹介されててて、『RuboCopの取締きびしすぎ』『いやそんなことない』『お前はよくてもチームはそうじゃねえ』『どの辺のルールがつらい?』とかスレがめちゃ伸びてます😆」「自分はありもののルールでやってるから気にしたことないですし😆」「最初からなるべく素のRuboCopでやれたら平和になれるかしら😆」「まあ変にいじるとアップグレードとかでややこしくなったり😆

⚓fake_api: APIのプロトタイプ作成(Ruby Weeklyより)

# 同リポジトリより
# app/fake_api/app_routing.rb
class AppRouting < FakeApi::Routing
  get('/projects').and_return           { create_list(:project, 5) }.with_status(202).with_headers({TOKEN: "SECRET"})
  get(%r{/projects/\d+$}).and_return    { object(:project) }
  post('/projects').and_return          { object(:project).merge({created: 'ok'}) }
  delete(%r{/projects/\d+$}).and_return { { result: :deleted } }.with_status(333)

  post('/auth')
    .and_return { { status: "OK" } }
    .with_cookies({x: "A"})
    .with_session({y: "B"})
    .with_headers({token: "C"})
end

つっつきボイス:「これはもしかして嬉しい人がいるかも?文字どおりAPIのプロトタイプをさくっと作れるみたいです」「(デモ動画を眺めて)API仕様に基づいてJSONを吐けるgemという感じかな😋: まだ本物のAPIサーバーがないけど仕様とデータだけ判明してて、APIないと開発つらいぜみたいなときなんかに結構使えそうな気がします👍」「やった😋」「使い捨てのAPIサーバーとか自分でスクラッチで立てるのイヤですし😆」「JSON手作りするのも😆

fake_apiはRailsエンジン型式になっていて、ファクトリーとルーティングを書けばRailsアプリの中で動かせる感じです。

⚓midas: Rubyでアノマリー検出(Ruby Weeklyより)


つっつきボイス:「用語が何だか難しめなんですが、動的な世界でアノマリー(異常値)を検出するgemみたいです」「元になったC++版のMIDASの挿絵↓を見ると侵入の検出とかを指してるようだ🤔」「下のa1〜a3はいかにも悪い人たちですし🦹🏻‍♀️」「割と難しい世界…」「ankaneさんは最近Rubyで機械学習方面やってるからその絡みかも🤔

グラフの異常値検出は、無数のシステムにおける疑わしい振る舞いを探す(侵入検出、レーティングの捏造、金融詐欺など)うえで重要な問題である。この問題は、静的なグラフに特化したアプローチの多くでは十分な研究がなされているが、現実のグラフは本質的に動的であり、静的接続に基づいた手法ではグラフやアノマリーの一時的な特徴を見落とす可能性がある。
(中略)
私たちの提案するMIDAS(Microcluster-Based Detector of Anomalies in Edge Streams)ではマイクロクラスターの異常値や、互いによく似た疑わしいエッジが突然エッジストリームに入ってくるのを、一定の時間とメモリで検出する。MIDASではさらに従来の手法にはなかった、原則に基づいた仮説のテスティングフレームワークを用いて偽陽性の確率の理論的束縛も提供する。最新のアプローチと比べて644倍高速かつ精度を48%向上させた。
github.com/bhatiasiddharth/MIDASより

直接の関連はありませんが『機械学習を用いた異常検知入門』スライドを貼ります。

「MIDASはギリシャ神話のミダス王なんでしょうね: 『王様の耳はロバの耳』の話と、触ったものが全部黄金になる力を神から授かったら飯も食えなくなった🥘という話の😆」「😆

参考: ミダース - Wikipedia

⚓groupdate: 日付のグルーピングgem(Ruby Weeklyより)

# 同リポジトリより
User.group_by_week(:created_at, time_zone: "Pacific Time (US & Canada)").count
# {
#   Sun, 08 Mar 2020 => 70,
#   Sun, 15 Mar 2020 => 54,
#   Sun, 22 Mar 2020 => 80
# }

つっつきボイス:「こちらは日付時刻のグルーピングgemだそうです」「あああ、こういうgroup_byってたしかにつらい😭」「Active Supportにはこういうのってないんでしたっけ?」「日付のgroup_byってepoch time的な数値でグルーピングしようと思えばできるんですけど、あんまり価値がない😆: 日でグルーピングとか分まで見てグルーピングとかって、やってみると結構エグいんですよこれが」「あ〜なるほど!」「created_atとかは秒以下の時刻も持ってますし、日でグルーピングとかって実はそんなに簡単じゃないんですよ〜😆」「このgemはタイムゾーンも扱えてgroup_by_dayとかgroup_by_weekとかgroup_by_hour_of_dayとか便利そうなのがありますね」「時刻の細かな扱いが多くて、時刻のレンジを切って分析することが多い案件なんかでは有用かも👍」「やった😋」「普通はあまりしませんけど😆、こういうのを知っていれば車輪の再発明しなくて済むかも☺

参考: UNIX時間 - Wikipedia

⚓その他Ruby

つおい😳

⚓DB

⚓iredis: Redisターミナルクライアント(DB Weeklyより)



同リポジトリより


つっつきボイス:「Redis用のインタラクティブなクライアントでiredisということみたいです」「お、redisのターミナルクライアント、よさげ😋」「あると嬉しい感じでしょうか?」「redisをRails経由で使うことはよくやってますけど…そもそもredisのGUIクライアントってありましたっけ?😆」「MySQLのMySQL WorkbenchやPostgreSQLのPgAdminみたいなGUIクライアントなヤツですよね、どうだったかな🤔」「つまりredisを直接触らなくてもやっていけるということで😆

後で探すと、RedisInsightというブラウザベースの公式ツールがあるそうです。今は無料版でも制約なく使えるようです。

サイト: RedisInsight | Redis Labs

参考: Redis向け GUI ツール RedisInsight を使う - tech.guitarrapc.cóm

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

⚓FirefoxのDoH

参考: Firefox、米国では「DNS over HTTPS(DoH)」が初期設定で有効に - ITmedia NEWS


つっつきボイス:「FirefoxがDNS接続をHTTPS化するそうですけど、言われてみれば今までDNSのクエリって暗号化されてませんでしたね」「普通に温かみのある平文でやってますし😆: やりたいのは改ざん防止かな?」「英語記事によると、ISPがその気になればユーザーがDNSにアクセスしたときの情報を取れるのを防ぎたいということみたいです」「ははぁそちらですか」「Mozillaは信頼できるプロバイダとしてCloudflareとNetDNSを使うとありますね」「その2つは信用できるのかしら😆」「Cloudflareは信頼を高めるために何かやってた覚えあります(ウォッチ20180518)」

参考: IPアドレスを保存しない高速パブリックDNSサービス「1.1.1.1」、APNICとCloudflareが無料提供 - INTERNET Watch

記事ななめ読み(抜粋・大意):

プライバシーにどんな脅威があってDoHを導入するのか
ユーザーのインターネット上での活動はDNSリクエストで露わになる。プロバイダ(ISP)やWiFiプロバイダがその気になれば無断でその情報を集められる。
暗号化DNSにする理由と、DNS以外にISPが情報収集に使えるしくみがあるかどうか
ユーザーのプライバシーは多くの脅威にさらされており、1つの技術ではカバーできない。だからこそ個別の問題に取り組むのであり、脅威が多いからといって解決を拒む理由にはならない。Mozillaに限らず、DNS以外の個人識別情報の漏洩を防ぐ適切な方法の定義に取り組んでいる。TLS接続におけるESNI(Encrypted Server Name Indication)もそのひとつだ。
DoHでDNSの中央集約化がさらに進むとネット全体にとってよくないのでは?
中央集約化がネットにとってよくないのは確かだ。現実世界ではISPのDNSサービスにロックインされ、5つの企業が米国のブロードバンドインターネットの80%を牛耳っている。FirefoxをDoH化するとトラフィックが大手ISPから離れるので、中央集約化を弱めることはあっても強めることはなく、企業のDNS設定も尊重しつつユーザーの選択肢を増やせる。
DoHは企業の”split-horizon” DNSなどとうまくやれるか
企業はDoHを簡単に無効にできるし、Firefoxは企業のポリシーを検出して自動で無効にすることもできるようになる。
FirefoxのDoH化は全世界でやるのか
当面米国内のみのリリースに注力する。現時点ではヨーロッパやその他の地域でのリリース計画はないが、DoHはあらゆる人々のプライバシー保護にとってよいものであると私たちは強く信じている。

参考: インターネット用語1分解説~DNS over HTTPSとは~ - JPNIC

⚓JavaScript

⚓プロポーザル: パイプライン演算子をJSに

// 同リポジトリより
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

result //=> "Hello, hello!"

つっつきボイス:「2年ほど前からあるプロポーザルですが😆」「みんな好きね〜|>パイプライン演算子🚰」「Elixirとか」「Ruby 2.7にもパイプライン演算子が入りかかったけど先送りになってましたね(ウォッチ20191210)」

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

「やっぱりパイプラインの記号は|>になるのかしら🤔」「|>はJSっぽくはないけど、言葉で書くよりうまい記号があればそっちでやりたいですし☺」「そういえば論理学方面の記号って、画数が少なくて書くのは楽だけど読むのがめちゃ大変なのがちょくちょくあってつらいです😭」「慣れると記号じゃない方がつらくなりますし😆

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

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

⚓「牧歌的 Cookie の終焉」


つっつきボイス:「jxckさんによるCookieの今後についてのエッセイなんですが、はてブでものすごいブクマ数になってて、しかもCookieについての説明としてもよくできてて、久しぶりに技術文の名文を読んだ気持ちになりました😂」「考えてみたら自分が知ってるのは牧歌的なCookieの方なのかも😆: Cookieはどうにでも使えちゃってたからトラッキングとかで使われがちだったけど、Cookieでやると何となくイカサマっぽく見えたり😆

「その記事を読んでて、IDFA↓というものを今頃知りました: Webとは別のiOS特有の広告トラッキングシステムだそうです」「まあ使う側はそいつが誰なのかはどうでもよくて、端末が同じかどうかが識別できればそれでいいんですけど、誤解されがち😆」「ですね😆

参考: IDFAについて、理解していますか?いまさら聞けないweb広告用語 | アド論 byGMO

「昔インテルのCPUも固有の番号を刻んだら袋叩きになってたことありましたし😆」「あ〜国民総背番号制とかそっちのイメージで騒ぎになってたような覚えが😆

PSN(プロセッサシリアルナンバー)でした↓。

参考: CPUID - Wikipedia

「あと今月のWeb+DB Pressを買ったらblog.jxck.ioの中の人の新連載が始まっていて↓、これもブログと同じぐらい特濃でおすすめです😋

最新号はjxckさんの『Origin解体新書』の他、mameさんの『コミッター詳解: Ruby 2.7の魅力』、ko1さんの『Rubyのウラガワ』など目白押しです。

⚓その他フロントエンド


つっつきボイス:「BPSのデザインチームがこのNeumorphというデザインで盛り上がってました」「へ〜このエンボスっぽいデザインですか😳: まだWeb界隈では見かけないな〜」「ひと頃のフラットデザインから逆に振れてきた感じなのかしら😆」「ちょっと前までフラットだ何だって言ってたのに😆」「きっとまた逆に振れる😆

「こうやって目立たせたいところが凸になってるのって意外と重要かもしれませんね☺」「色に頼らないのもよさげ😋」「ユニバーサルデザイン的に色覚の弱い人でも使いやすそうな気がしますし」「普段はデザインにあまり目が向かないけど、これはちょっと好き❤

「そう思うとCSSのz-indexって何だったんだろうって😆」「まあz-indexの軸とは違う感じ😆

参考: z-index-スタイルシートリファレンス

⚓言語・ツール

⚓習わぬ経を読む


つっつきボイス:「ちょっとできすぎな話かも😆」「ええ子や、パパの言うとおりよ〜👋

⚓その他言語


つっつきボイス:「これもほっこりしました☺」「三項演算子を使うかどうかの見極めってほんと難しいですね〜😆: うまくハマれば可読性上がることもありますし、メソッド末尾のreturnのところだったら三項演算子でもいいかな思うんですけど」「たいていこじらせる😆」「仕様が変わって三項演算子をifにしないといけなくなったときがめんどい😆

「後置のifは好きです」「ガード文に使う後置のifはいいですね😋

[Ruby/Rails] 例外で深くなったネストをGuard Clauseですっきりさせる

⚓その他

⚓『ちいさい言語学者の冒険-子どもに学ぶことばの秘密』


つっつきボイス:「こないだKindleでポチって読み中なんですが、いきなり面白かったので」「ほほぅ?」「言語学者の著者が主に自分の子をネタに言語学的な発見をする話なんですが、たとえば小さい子どもにこんな質問をすると↓」

  • 「か」に点々を付けると? –> 「が」
  • 「さ」に点々を付けると? –> 「ざ」
  • 「た」に点々を付けると? –> 「だ」
  • 「は」に点々を付けると? –> (ほとんどの子が、喉の奥をならすような文字に書けない音を出す)

「というふうにほとんどの子が『ば』と発音しない: 実は『ば行』の濁点は他の濁点と発音の規則が違っているということを、規則を知らない子どもが図らずも見つけ出したそうです」「だって半濁音の『ぱ』があるくらいですし😆、お子様はそのあたり混じりがちなのでは?」「実際、昔の日本語には『ば行』がなくて『ぱ行』だったという研究もあるそうです」

「ヘリコプターがヘリコとプター🚁」「apoptosisにpが入ってて発音しないって知らなかった」

「pter」は英語では巡り巡ってfeather(羽)に変わり果てたそうです。

⚓その他のその他

つっつきボイス:「なはは😆、ロックないとダメじゃん」「タワー型PCにキャスター要るかしら😆


「タッチタイピングが1位って意外😳」「他は納得: オーダーのOとか習わないと絶対やりませんし😆

参考: ランダウの記号 - Wikipedia

⚓番外

⚓スミソニアンに勝てる気がしない


つっつきボイス:「データ公開に加えて自由に再利用していいって太っ腹」「天才過ぎる」「米国のスミソニアン博物館って一日や二日では絶対見終わらないぐらい規模がでかいらしくて、死ぬまでに一度見に行ってみたいです👴

参考: スミソニアン博物館 - Wikipedia


後編は以上です。

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

週刊Railsウォッチ(20200302前編)RubyKaigi 2020は9月に延期、Railsのセキュリティパッチバージョニングが変更、dry-monadsほか

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

Windows7→Windows10で激重になったPCをSSD換装で延命した

$
0
0

BPSの福岡拠点として一緒にお仕事をさせていただいています、株式会社ウイングドアのウメバヤシです。

今年に入ってWindows7のサポートが切れたので、自宅にあった6年くらい前のノートPCを激重覚悟でWindows10にアップグレードしました。
すると案の定重くて使い物になりませんでした。。笑

PC自体は壊れてなかったので(HDDの回転音がちょっと怪しかったですが🤔)もったいないな〜と思い、余っていたSSDがあったので、換装してなんとか延命させようと頑張ってみたのでその過程を書いていきます。
(PCが手元になく、キャプチャが載せられなくてすみません)

環境

今まで

  • OS : Windows7 Professional 32bit
  • CPU : Celeron
  • メモリ : 2GB
  • HDD : 500GB

換装後

  • OS : Windows10 Pro 32bit
  • CPU : Celeron
  • メモリ : 4GB
  • SSD : 128GB

とりあえずメモリ増設

そもそもメモリが2GBしかなかったので、かなり重くなるだろうなと予測はしていました😅
タスクマネージャーで見ても常にメモリぱんぱん状態で、ネットもまともに見られません。

1,500円くらいでメモリを購入して増設。幸い、もともと2GBが1枚ささっているだけで、スロットが1つ空いていたので、購入したメモリは2GB1枚で済みました。

32bitなのでメモリは4GBがMAXです。3GBちょっと認識されました。

SSDクリーン

SSDはすでにいろいろ使い倒していて、他のOSが入っていたので、そのままだとWindowsでフォーマットできませんでした。なので一旦初期化します。
WindowsのSSD初期化は「diskpart」コマンドを使用しました。

SSDを外付けケースに入れてPCに接続後、管理者権限でコマンドプロンプトを起動して下記コマンドを実行。

// Diskpart起動
> diskpart

// ディスクのリストアップ
DISKPART> list disk

// 初期化するディスクの選択。nはディスクの番号を入力します(初期化するSSD)
DISKPART> select disk n

// 初期化(確認などなくすぐに初期化が実行されるので注意)
DISKPART> clean

SSDフォーマット

SSDの初期化が済んだらフォーマットします。

Windowsメニューを右クリックして、表示されるメニューから「ディスクの管理」を選択。
先ほど初期化したSSDがフォーマットできるようになっています。
フォーマットする際のパーティション形式は「MBR」と「GPT」とありますが、2TB以下なら「MBR」で問題ないと思います。

今回換装するPCはOSが32bitで、そもそも「GPT」をサポートしてなかったので、問答無用で「MBR」を選択。

HDDクローン

ここでこの作業のメイン、HDDのクローンをしていきます。

いろいろ方法はあると思いますが、今回はフリーで割と評判の良さそうな「EaseUS Todo Backup Free」というソフトを使用しました。
公式サイトでもダウンロードできますが、今回はWindowsユーザーならみんな大好き「窓の杜」からダウンロードしました。

使い方は結構簡単で、左のメニューから「クローン」を選択してクローン元とクローン先のディスクを選択するだけでクローンできます。

有料版ではそのほかにも機能があるみたいですが、クローンするだけなら無料版で十分でした。

ここで問題発生

今回使用するSSDの容量は128GBで、よくみるとPCのHDDの使用領域がすでに128GB超えていました。
ほぼネットしか見ないPCなのに何がこんなに容量食ってるんだ。。。

ぱっとみてもどこで食っているのか分からず。そんな時は下記ソフトがおすすめです。

ディスクドライブの容量を可視化してくれます。ポータブル版なのでUSBなどで持ち運べてとっても便利。

実行する時は管理者権限で実行してスキャンします。
スキャンにちょっと時間かかりますが、辛抱して待ちます。

スキャン完了後、ひと目で昔取ったiPhoneのバックアップファイルが原因だと判明。(200GBくらい食ってました。。)
iPhoneのバックアップファイルはサイズがかなり大きいので、PCでバックアップを取っている方は、ディスク容量を結構圧迫していると思います。

もういらないので、バックアップファイルをおもむろに削除。

改めて「EaseUS Todo Backup」でクローン

クローンに小一時間かかかるので放置。

クローン完了

無事1発でクローンが成功したので、ノートPCを分解してHDDをSSDに換装。
PCの分解方法は「型番 + 分解」とかで調べれば割と出てきます。
(当然メーカーのサポートは受けられなくなるので自己責任で)

起動!!

ドキドキしながらPCの電源を入れます。
BIOSの設定とかしないといけないかな〜と思いきや、そのまますんなり起動!
触ってみると、以前の激重が信じられないくらいサクサク快適になりました!
これであと何年かは頑張ってくれそうです。

起動して黒い画面が出る場合は、BIOSの設定で起動ディスクを変更すればいいと思います。

感想

臨終が間近に迫ったPCをメモリ増設とSSD換装で延命することができました。
ちょっとハードの知識があって、流れがわかれば割と簡単にできるので、ネット見るくらいの用途のPCならこの方法で復活させられそうです。
一昔前からパソコンショップなどでも、SSD換装のサービスが始まりだしたので、ショップに持っていけば何万円かでやってくれるところもあります。

今回のPCはドライバ関係が公式でWindows10に対応しているものがなかったのですが、とりあえず問題なく動いていたのでまあ良しとしました😊




株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

Rails 6のAction Mailboxを使ってみよう(翻訳)

$
0
0

概要

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

  • 英語記事: Rails 6 - Action Mailbox tryout – Saeloun Blog
  • 原文公開日: 2019/11/11
  • 著者: Romil Mehta
  • サイト: Saeloun — Ruby on Railsのコンサルティング会社で、Rails + React開発のほかに、React Nativeによるモバイルアプリ開発も手がけています。

Rails 6のAction Mailboxを使ってみよう(翻訳)

アプリケーションで大量のメールを受信するはめになることがままあります。そういうメールを扱わなければならなくなった場合、操作のためにメールをひとつひとつ処理しなければなりません。

人事部門のユースケースで考えてみましょう。ある組織から、オープニングの参加候補者たちに「レジュメを送って欲しい」というメールを1件送信したとします。参加候補者たち全員がレジュメを添付してメールに返信したら、届いたレジュメをクラウドにアップロードして、レジュメ1件ごとにデータベースのエントリーを作成する必要があります。

以下はそのためのステップです。

  1. メールを1件ずつチェックする
  2. レジュメをダウンロードする
  3. レジュメをクラウドにアップロードして、レジュメのテーブルにエントリを作成する

ダルそうな作業ですね。

Action Mailboxを導入する

Rails 6では、受信メールを処理するためのAction Mailboxが導入されました。Action Mailboxは、受信メールをコントローラによく似たメールボックスにルーティングしてRailsで処理できるようにします。Mailgun、Mandrill、Postmark、SendGridといった主要なプラットフォームはひととおりサポートされています。

インストールと実装

以下のコマンドを実行して、feedback_collectorという新しいアプリケーションを作ります。

> rails new feedback_collector

rails action_mailbox:installを実行してAction Mailboxをインストールします。このとき、受信メール保存用にActive Storageも同時にインストールされます。メールはここに保存され、処理済みかどうかがトラッキングされます。

続いてActive Jobが読み込まれてメールを処理し、処理が終わったメールは削除されます。削除されたメールについてもidやチェックサムをトラッキングするので、同じメールがまたやってきたときに処理を回避できます。

> cd feedback_collector
> rails action_mailbox:install
#=> Copying application_mailbox.rb to app/mailboxes
#=>      create  app/mailboxes/application_mailbox.rb
#=> Copied migration 20191021075823_create_active_storage_tables.active_storage.rb from active_storage
#=> Copied migration 20191021075824_create_action_mailbox_tables.action_mailbox.rb from action_mailbox

上のコマンドで生成されるマイグレーションは、action_mailbox用とactive_storgage用です。application_mailboxも同時に作成されます。

それではUserProductFeedbackのscaffoldを生成しましょう。

> rails g scaffold User name email
> rails g scaffold Product title
> rails g scaffold Feedback user:references product:references content:text

以下のマイグレーションを実行します。

> rails db:migrate

action_mailboxテーブルのスキーマにはstatusmessage_idmessage_checksumなどのカラムが含まれます。statuspendingprocessingfinishedbouncedのいずれかになります。message_idmessage_checksumは重複防止用です。

ApplicationMailboxクラスは以下のような感じになります。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
end

メールのルーティングは以下のように定義します。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
  routing  :all => :feedbacks
end

すべてのメールをFeedbacksMailboxにリダイレクトするルーティングを1つ指定しました。ルーティングは以下のように正規表現でも指定できます。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
  routing  /feedback\-.+@example.com/i => :feedbacks
end

上の正規表現はfeedback-Ahdhc12@example.comfeedback-5264yYxjg@example.comのようなメールアドレスにマッチします。

次はFeedbacksMailboxを作成します。

> rails g mailbox Feedbacks
#=> Running via Spring preloader in process 623
#=>       create  app/mailboxes/feedbacks_mailbox.rb
#=>       invoke  test_unit
#=>       create    test/mailboxes/feedbacks_mailbox_test.rb

FeedbacksMailboxクラスには、メールを処理するprocessメソッドがあります。このクラスでmailオブジェクトにアクセスできます。

メールを処理する前に何か操作を加えたい場合は、次のようにbefore_processingコールバックででやれます。

class FeedbacksMailbox < ApplicationMailbox
  before_processing :user

  def process
  end

  def user
    @user ||= User.find_by(email: mail.from)
  end
end

mail.fromuserを取れるようになりました。しかし製品へのフィードバックを保存するためにproduct_idが欲しくなります。

product_idを取得するには、返信メールの正規表現を次のようにproduct_idを含む形で指定できます。

  RECIPIENT_FORMAT = /feedback\-(.+)@example.com/i

返信メールがfeedback-1234@example.comの場合、上の正規表現によって1234product_idとして取得できます。

このメールを処理してユーザーからのフィードバックを保存しましょう。

class FeedbacksMailbox < ApplicationMailbox
  RECIPIENT_FORMAT = /feedback\-(.+)@example.com/i

  before_processing :user

  def process
    # フィードバックを作成する
    # mail.decodedはメールがマルチパートでない場合はbodyを返す
    # マルチパートの場合はmail.parts[0].body.decodedを使う
    # ここでは後者がフィードバックを含む
    if mail.parts.present?
      Feedback.create user_id: @user.id, product_id: product_id, content: mail.parts[0].body.decoded
    else
      Feedback.create user_id: @user.id, product_id: product_id, content: mail.decoded
    end
  end

  def user
    @user ||= User.find_by(email: mail.from)
  end

  def product_id
    # recipientsは複数の可能性があるので
    # RECEIPIENT_FORMATにマッチするものを検索

    recipient = mail.recipients.find { |r| RECIPIENT_FORMAT.match?(r) }

    # first_match(つまりproduct_id)を返す
    # 例: recipient = "feedback-1234@example.com"
    # これで1234を返す
    recipient[RECIPIENT_FORMAT, 1]
  end
end

mailオブジェクトにアクセスできるので、マルチパートのメールや添付ファイルありのメールも読み取れます。

development環境でテストする

development環境でテストするなら、http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/newをブラウザで開いて受信メールを配信するだけでやれます。toのメールアドレスに基づいてメールボックスにルーティングされ、メールが処理されます。

production向けの設定

Action Mailboxをproduction環境向けに設定するために、私たちの場合はconfig/environment/production.rbでingressを指定する必要があります。

ingresspostmarkにした場合で考えます。

config.action_mailbox.ingress = :postmark

さらに、Action Mailboxでpostmarkのingressへの認証リクエストに使う強力なパスワードの生成も必要です。私たちの場合、強力なパスワードはingress_passwordに暗号化済みcredentialとして保存する必要があります。

action_mailbox:
  ingress_password: PASSWORD

credentialに保存する代わりに、RAILS_INBOUND_EMAIL_PASSWORD環境変数に保存したパスワードを提供する手もあります。

今度は受信フックを設定して、受信したメールをactionmailboxというユーザー名と先ほど生成したパスワードを用いて/rails/action_mailbox/postmark/inbound_emailsに転送する必要があります。以下は私たちの場合のWebhook URLです。

https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails

動かせるサンプル

たとえば以下のサービスが使えます。

  1. Sendgrid(メールサービス)
  2. Freenom(ドメイン登録サービス)
  3. ngrok(ローカルWebサーバーを公開できるパブリックURLを提供する)

いずれも無料です。

セットアップ

SendGrid、Freenom、ngrokでアカウントを作ります。ngrokのインストールについてはngrokのガイドに記載の手順をご覧ください。

次はfreenomのドメイン登録リンクで無料ドメインを登録します。検索ボックスにactionmailboxなどのドメイン名を入力して「Check Availability」ボタンを登録します。オプション機能は無料なので好きな機能をオンにします。ナビゲーションバーの「Services > My Domain」セクションにドメインが表示されます。

それが終わったら、このドメインをSendGridで認証する必要があります。認証手順は以下のとおりです。

  1. SendGridの sender_authフォームをクリックする。
  2. 「DNS host」の「Other Host (Not Listed)」を選択し、「DNS Host」にfreenomと入力する。
  3. 「Next」をクリック。
  4. 「From Domain」ボックスにドメイン名を入力する。
  5. 「Next」をクリック。
  6. 「CNAME」レコードのフィールドが3つ表示される(「Freenom」ドメイン管理セクションでこれらを追加する必要あり)。
  7. 「My Domains」セクションの新しいタブで「Freenom」を開く。
  8. 「Manage Freenom DNS」タブをクリック。
  9. ここにCNAMEレコードを3つ入力する必要がある。
  10. MXレコードも作成する(「name」は空欄でもよい)。「type」をMX、「target」をmx.sendgrid.net、「priority」を10にそれぞれ設定。
  11. 「SendGrid」タブに戻ってチェックボックスを確認し、「Verify」をクリック。
  12. 失敗した場合は15〜20分ほど待ってから「Verify」を再度クリック。
  13. sender_auth/domainsリンクを開くと、設定したドメインのステータスがVerifiedになっている。

次は、development.rbに以下のSMTP設定を追加します。

config.action_mailer.smtp_settings = {
  :user_name => SENDGRID_USERANME,
  :password => SENDGRID_PASSWORD,
  :domain => OUR DOMAIN,
  :address => 'smtp.sendgrid.net',
  :port => 587,
  :authentication => :plain,
  :enable_starttls_auto => true
}
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false

rails sを実行してサーバーを起動します。

./ngrok http 3000を実行してブラウザの新しいタブで表示します。

> ./ngrok http 3000
ngrok by @inconshreveable

Session Status                online
Account                       ROMIL MEHTA (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://386e42cd.ngrok.io -> http://localhost:3000
Forwarding                    https://386e42cd.ngrok.io -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

上の「Forwarding」の値を見ると、サーバーのpublic URLが386e42cd.ngrok.ioになっています。この後の例で使いますので、URLをメモしておきます。

今度は、前述の「production向けの設定」で予告した手順を勧めます。

  • 「ingress」にsendgridを追加
config.action_mailbox.ingress = :sendgrid
  • ingressのパスワードを作成してcredentialに追加します。
action_mailbox:
  ingress_password: PASSWORD
  • SendGridの「Inbound Parse」セクションを開いて、「Add Host & URL」をクリックします。

ドメインを選択し、destination URLにhttps://actionmailbox:INGRESS_PASSWORD@SERVER_PUBLIC_URL/rails/action_mailbox/postmark/inbound_emailsを設定して、「POST the raw, full MIME message」チェックボックスをオンにします。

実例

以下を実行して、ユーザーに製品へのフィードバックを促すメイラーを作成します。

> rails g mailer Feedback
Running via Spring preloader in process 48769
      create  app/mailers/feedback_mailer.rb
      invoke  erb
      create    app/views/feedback_mailer
      invoke  test_unit
      create    test/mailers/feedback_mailer_test.rb
      create    test/mailers/previews/feedback_mailer_preview.rb

app/mailers/feedback_mailer.rbに以下のメール送信コードを追加します。

class FeedbackMailer < ApplicationMailer
  default from: FROM_MAIL_ADDRESS

  def send_email
    mail(to: ANY_USERS_EMAIL, reply_to: REPLY_TO_MAIL_ADDRESS, subject: 'Mailbox Test', body: 'Provide feedback for the product by replying to this mail')
  end
end

上のコード例のREPLY_TO_MAIL_ADDRESSにはfeedback-#{PRODUCT_ID}@#{SERVER_PUBLIC_URL}が、PRODUCT_IDには、フィードバックをお願いする製品がそれぞれ入ります。

以下のコマンドを実行すると、フィードバック依頼メールがトリガーされます。

> FeedbackMailer.send_email.deliver_now

上のコマンドを実行後、メールがユーザーに届きます。

feedback mailboxのルーティングは設定済みなので、ユーザーがメールに返信するとFeedbackMailboxprocessメソッドが呼び出されてメールが処理されます。

まとめ

本記事ではAction Mailboxの基礎、インストール方法、実装、設定方法について学びました。また、production環境に近いセットアップ例についても解説いたしました。

関連記事

銀座Rails#14で「出張Railsウォッチ」発表させていただきました

Viewing all 2925 articles
Browse latest View live