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

Rails: Waterfall gemでコントローラとモデルを整理(翻訳)

$
0
0

概要

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

Rails: Waterfall gemでコントローラとモデルを整理(翻訳)

ファットモデルや巨大アクションはRails開発者にとって常に問題になっていました。Service Objectに助けてもらうようになったものの、ダメなコードをコントローラから素のRubyオブジェクトに移動しただけなのではないかと思ったりもします。しかしWaterfall gemでこの問題を解決できそうです。早速チェックしてみましょう。

Waterfallについて

Waterfallは、Service Objectの新しいアプローチのひとつです。Waterfallは関数型(functional)的なアイデアに支えられており、ブロックやその他のWaterfallオブジェクトを相互に渡すことで、簡単にチェインできるというものです。オブジェクトのどれかが失敗する(呼び出したときに文字どおり「堰き止められる(dam)」)とWaterfall全体が失敗し、トランザクションでロールバックすることも#reverseフローメソッドを用いて手動でロールバックすることもできます。

平均的なWaterfallサービスは次のような感じになります。

class MakeUserHappy < Struct.new(:user)
  include Waterfall

  def call
    with_transaction do
      chain { MakeUserHappy.new(user) }

      when_falsy { user.happy? }
        .dam { "#{user.name}はまるきり不幸です >_<" }

      chain { notify_happy_user }

      on_dam { |error_pool| puts error_pool + '恥を知れ'}
    end
  end

  private

  def reverse_flow
    make_user_unhappy(user)
  end

  def notify_happy_user
    ...
  end
end

このアプローチの最もよい点は、基本的なService Objectをいくつか作成してそれらを自由に組み合わせられることです。コントローラは次のような感じになります。

def make_user_happy
  authorize(@user)

  Wf.new
    .chain  { MakeUserHappy.new(@user) }
    .chain  { flash[:notice] = 'ユーザーは幸せです!' }
    .on_dam { |err| flash[:alert] = err }

  redirect_to user_path(@user)
end

もちろん#chainメソッドは必要に応じていくつでも追加できますが、コントローラが散らからないようシンプルを心がけています。

用例1: ネスト属性やaroundコールバック

コード例を見てみましょう。ここで行いたいのはネストした属性の除去と、予約(問い合わせ)自身とは別に予約の旅客を更新することです。また、更新の直前や直後に何らかのトラッキングを行う必要もあります。このトラッキングは最終的にオプションにする必要があります。

booking: 予約
enquiry: 問い合わせ

ネストした属性の除去が必須でなければ、around_updateコールバックあたりを使って予約を行い、ネストした属性を使えば済むのでもっと楽にできたでしょう。しかし、around_updateコールバックは属性を即座に代入するため、オブジェクトの古いステートを取得しようとすると困難が生じます。最終的に、このコールバックを状況に応じてスキップする方法を検討せざるを得なくなり、しかも現在のRailsにはきれいな解決方法がありません。結局トラッキングをシンプルにする方向で行うしかありませんでした。これでも既に相当複雑になっています。

そこで私たちは、手順ごとに個別のService Objectを作成してこれらをチェインすることに決めました。更新後のアクションは次のような感じになります。

def update
  authorize @enquiry

  Wf.new
    .chain { UpdateEnquiry.new(@enquiry, enquiry_params, partners_params) }
    .chain do
      redirect_to edit_enquiry_path(@enquiry), notice: '問い合わせの更新に成功しました。'
    end
    .on_dam do |err|
      redirect_to edit_enquiry_path(@enquiry), alert: "問い合わせ更新中のエラー: #{err}."
    end
end

UpdateEnquiryサービスは次のようになります。

class UpdateEnquiry
  include Waterfall

  attr_accessor :enquiry, :enquiry_params, :partners_params

  def initialize(enquiry, enquiry_params, partners_params)
    @enquiry = enquiry
    @enquiry_params = enquiry_params
    @partners_params = partners_params
  end

  def call
    with_transaction do
      TrackEnquiryUpdate.new(enquiry).call do
        chain { UpdatePartners.new(enquiry.partners, partners_params) }

        when_falsy { enquiry.update(enquiry_params) }
          .dam { enquiry.errors.full_messages.join('; ') }
      end
    end
  end
end

ご覧のとおり、#chainメソッドはブロックの内部でも簡単に使えます。これでトラッキングがずっと簡単になり、トラックしたい問い合わせを渡して、どこかのブロックで問い合わせか問い合わせの関連付けを更新するだけで済むようになりました。これはaround_updateコールバックと似ていますが、私たちのルールに沿って動作し、使うかどうかも自由に決められます。TrackEnquiryUpdateのコードは次のとおりです。

class TrackEnquiryUpdate < Struct.new(:enquiry)
  include Waterfall

  def call
    chain do
      enquiry.track(:remove_from_cart)
      yield
      enquiry.reload.track(:add_to_cart)
    end
  end

  private

  def reverse_flow
    ...
  end
end

私たちはService ObjectにStructを使うことにしました。Structには初期化ロジックがないので、コードがずっとスリムになります。Waterfallが堰き止められた場合のカスタムロールバックを実装できる#reverse_flowメソッドにご注目ください。

Ruby on Railsで使ってうれしい19のgem(翻訳)

次の例に進みます。

用例2: 支払い処理とロールバック

私たちの仕事に支払い処理は付きものです。APIがデータベースに支払いを1件作成した後でエラーを返すこともあれば、支払いを1件登録したのにデータベースへの保存に失敗することもあります。Waterfallはこのような問題を断ち切るときにも役立ちます。

How to Choose a Payment Platform for Your Project: PayPal, Stripe, Braintree

コントローラの内部は次のようになります。

def create
  payment_form = CardPaymentForm.new(card_payment_form_params)

  Wf.new
    .chain { EnrollPayment.new(payment_form) }
    .chain { head :ok }
    .on_dam do |errors|
      render json: { msg: errors.join(";\n ") }, status: 422
    end
end

EnrollPayment Waterfallは次のようになります。

class EnrollPayment < Struct.new(:payment_form)
  include Waterfall

  def call
    chain(charge: :charge) do
      ChargeStripe.new(charge_params)
    end

    chain(balance: :balance) do |flow|
      GetStripeBalanceTransaction.new(flow.charge.balance_transaction)
    end

    when_falsy do |flow|
      payment_form.attributes = charge_payment_params(flow.charge, flow.balance)
      payment_form.save
    end
      .dam { payment_form.errors.full_messages }

    chain { notify_after_payment }
  end

  private

  def charge_params
    payment_form.stripe_charge_params
  end

  def charge_payment_params(charge, balance)
    ...
  end

  def notify_after_payment
    ...
  end
end

API呼び出しとデータベーストランザクションの分離方法にご注目ください。分離したことで、Waterfallはこれらにカスタムロールバックを適用できるようになります。with_transactionブロックはデータベースロールバックを担当します。APIのロールバックについてはChargeStripe Waterfallの方をご覧いただく必要があります。

class ChargeStripe < Struct.new(:stripe_charge_params)
  include Waterfall

  def call
    chain(:charge) { Stripe::Charge.create(stripe_charge_params) }
  rescue Stripe::StripeError => e
    dam([e.message])
  end

  private

  def reverse_flow
    Stripe::Refund.create(charge: self.outflow.charge.id)
  end
end

#reverse_flowはカスタムロールバックで、現在のWaterfallがジョブを終了した後で親のWaterfallが堰き止められた場合にのみ実行されます。したがって、データベースでエラーが発生した場合、つまりGetStripeBalanceTransaction Waterfallが堰き止められた場合、ChargeStripe Waterfallは以前行われた支払いを返金します。

まとめ

Waterfallは、Service Objectの素晴らしい新実装であり、コントローラやモデルに属さないコードをきれいに片付けることができます。サービスは、瓦礫の山のような恐ろしいコードではなく、アクションの連続として明確に表現されます。

関連記事

Railsで重要なパターンpart 1: Service Object(翻訳)

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

Rails: Service Objectはもっと使われてもいい(翻訳)


PostgreSQL 9.6→10アップグレードのダウンタイムをpglogicalで最小化(翻訳)

$
0
0

概要

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

PostgreSQL 9.6->10アップグレードのダウンタイムをpglogicalで最小化(翻訳)

PostgreSQL 10がリリースされ、9.6クラスタを最新バージョンにアップグレードしてみたくなりました。しかし、以前アップグレードしたときのようにメンテナンス画面を開いてマイグレーションを実行するのに膨大な段取りが必要になるのでしょうか。そのときは、アプリをメンテナンスモードに切り替え、新しいダンプを取って新しいクラスタでリストアし、メンテナンスモードをオフにしました。

この方法だと、アプリは1時間ほど、もしかするともっと長い時間使えなくなるかもしれません。pglogicalを再読した後、9.6から10への切り替えをわずか数秒で完了できるpglogicalを試す決心を固めました。

概要

pglogicalは論理レプリケーションを実装していて、バージョンの異なるデータベース同士でもレプリケーションを行えます。これはPostgreSQL自身が提供するバイナリレプリケーションでは不可能です。PostgreSQL 10では論理レプリケーション機能のサポートがいくつか追加されましたが、9.6からのレプリケーションを行いたいので、いくつかの外部拡張に頼る必要があります。

pglogicalの必要条件のひとつは、レプリケーションされるすべてのテーブルに主キーが設定されていることです。主キーは単独カラムでなくても構いませんが、主キーの存在は必須です。また、レプリケーションエージェントが動作するために、両方のデータベースにsuperuserでアクセスできる必要もあります。DDLレプリケーションはサポートされず、TRUNCATE CASCADEはレプリケーションされません。特殊な条件ではないので、ほとんどのデータベースについてレプリケーションできるはずです。

ただし、主キーの必須要件には特別な注意が必要です。特に移行前のデータベースで、ActiveRecord gemでデータベースマイグレーションを管理していた場合、以前はschema_migrationsテーブルに主キーがありませんでした。その場合は次を実行します。

alter table schema_migrations add primary key (version);

アップグレードの方針は、PostgreSQLパッケージをpglogical拡張のサポート付きでインストールしてから、新しいPostgreSQL 10クラスタを作成して、新しいクラスタだけにスキーマをリストアするというものです。現在のクラスタの停止/再起動は、pglogicalを有効にしてインストールしたPostgreSQLを使って行うべきです。このクラスタはTCP/IP経由で他方に接続可能になっている必要があります。プロバイダ側(アップグレード前の9.6データベースを指す)とサブスクライバ側(新しいPostgreSQL 10データベースを指す)には、接続先のIPアドレスとポートをそれぞれ指定する必要があります。pglogical拡張を両方のデータベースで作成し、postgresql.confとpg_hba.confで論理レプリケーションを有効にし、両方のデータベースを再起動します。最後に、プロバイダ、サブスクライバ、そしてサブスクリプションを作成するいくつかのpglogical文が発行されてレプリケーションが開始されます。レプリケーションが完了したら、新しいクラスタのポートを古いクラスタに合わせて変更して古いクラスタを停止し、新しいクラスタを再起動できます。最後にアプリも再起動するとよいでしょう。特にrow型などのカスタム型がある場合、OIDが変わる可能性が高く、そうしたrow型が登録されているとアプリを再起動するまで期待どおりに動作しないためです。たとえばSequel gemを用いたDB.register_row_typeが使われている場合、これに該当する可能性があります。

最後の切り替えは可能な限り数秒以内に行われるので、ダウンタイムを最小限にできます。

手順のハンズオン

私たちのサーバーではアプリの他にDockerでPostgreSQLを実行しているので、本記事では手順の説明にもDockerを使いますが、他のセットアップでの手順にも簡単に応用できるはずです。Dockerでデモを行う利点は、(Dockerコンテナを)そのまま簡単に複製できることと、データベースの作成や実行にも対応できる点です。

本記事では、PostgreSQLクライアントがホストにもインストールされていることが前提です。

Dockerイメージと起動スクリプトの準備

以下のDockerfileをpg96/とpg10/サブディレクトリにそれぞれ作成します(Dockerコンテナ内でPostgreSQLを実行していない場合、自分の環境で複製するにはDockerfile内の手順をご覧ください)。

# pg96/Dockerfile
FROM postgres:9.6

RUN apt-get update && apt-get install -y wget gnupg
RUN echo "deb [arch=amd64] http://packages.2ndquadrant.com/pglogical/apt/ jessie-2ndquadrant main" > /etc/apt/sources.list.d/2ndquadrant.list\
  && wget --quiet -O - http://packages.2ndquadrant.com/pglogical/apt/AA7A6805.asc | apt-key add -\
  && apt-get update\
  && apt-get install -y postgresql-9.6-pglogical

RUN echo "host    replication          postgres                172.18.0.0/16   trust" >> /usr/share/postgresql/9.6/pg_hba.conf.sample
RUN echo "host    replication          postgres                ::1/128         trust" >> /usr/share/postgresql/9.6/pg_hba.conf.sample
RUN echo "shared_preload_libraries = 'pglogical'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "wal_level = 'logical'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "max_wal_senders = 20" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "max_replication_slots = 20" >> /usr/share/postgresql/postgresql.conf.sample
# pg10/Dockerfile
FROM postgres:10

RUN rm /etc/apt/trusted.gpg && apt-get update && apt-get install -y wget
RUN echo "deb [arch=amd64] http://packages.2ndquadrant.com/pglogical/apt/ stretch-2ndquadrant main" > /etc/apt/sources.list.d/2ndquadrant.list\
  && wget --quiet -O - http://packages.2ndquadrant.com/pglogical/apt/AA7A6805.asc | apt-key add -\
  && apt-get update\
  && apt-get install -y postgresql-10-pglogical

RUN echo "host    replication          postgres                172.18.0.0/16   trust" >> /usr/share/postgresql/10/pg_hba.conf.sample
RUN echo "host    replication          postgres                ::1/128         trust" >> /usr/share/postgresql/10/pg_hba.conf.sample
RUN echo "shared_preload_libraries = 'pglogical'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "wal_level = 'logical'" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "max_wal_senders = 20" >> /usr/share/postgresql/postgresql.conf.sample
RUN echo "max_replication_slots = 20" >> /usr/share/postgresql/postgresql.conf.sample

両方のサーバーはIPアドレス10.0.1.10の同じコンピュータ上で実行されることを前提とします。9.6のインスタンスはポート5432で、新しいクラスタは当初(切り替え前)ポート5433で実行されます。

cd pg96 && docker build . -t postgresql-pglogical:9.6 && cd -
cd pg10 && docker build . -t postgresql-pglogical:10 && cd -

本記事はDockerのチュートリアルではありませんが、Dockerを実際に使っている場合は、これらのDockerイメージをプライベートなレジストリにpushするとよいかもしれません。

最初の手順では、古い9.6クラスタを停止し、古いデータを持つクラスタのpglogicalを有効にして起動します(作業前には常にバックアップを取っておくようにしましょう)。クラスタデータは/var/lib/postgresql/9.6/main/に、設定ファイルは/etc/postgresql/9.6/main/にそれぞれ置かれているとします。/etc/postgresql/9.6や/var/lib/postgresql/9.6がない場合もスクリプトが新しいクラスタを作成してくれるので心配無用です(なお、最初に新しいデータベースを試してみたい場合は一時ディレクトリをいくつかマップしておくとよいでしょう)。

以下のスクリプトを/sbin/pg-scripts/start-pgに作成し、ファイルに実行可能属性を与えます。このスクリプトはコンテナのデータベースを実行します。

#!/bin/bash
version=$1
net=$2
setup_db(){
  pg_createcluster $version main -o listen_addresses='*' -o wal_level=logical\
        -o max_wal_senders=10 -o max_worker_processes=10 -o max_replication_slots=10\
        -o hot_standby=on -o max_wal_senders=10 -o shared_preload_libraries=pglogical -- -A trust
  pghba=/etc/postgresql/$version/main/pg_hba.conf
  echo -e "host\tall\tappuser\t$net\ttrust" >> $pghba
  echo -e "host\treplication\tappuser\t$net\ttrust" >> $pghba
  echo -e "host\tall\tpostgres\t172.17.0.0/24\ttrust" >> $pghba
  echo -e "host\treplication\tpostgres\t172.17.0.0/24\ttrust" >> $pghba
  pg_ctlcluster $version main start
  psql -U postgres -c '\du' postgres|grep -q appuser || createuser -U postgres -l -s appuser
  pg_ctlcluster $version main stop
}
[ -d /var/lib/postgresql/$version/main ] || setup_db
exec pg_ctlcluster --foreground $version main start

クラスタが存在しない場合は、このスクリプトによって新しいクラスタが作成されます。なお実際のレプリケーションに必要というわけではありませんが、このスクリプトは作業をシンプルにするため「trust」でsuperuser認証された新しいappuserデータベース作成もサポートします。これはテスト目的で新しいデータベースを生成したい場合に役立つことがあります。必要な場合はこのスクリプトを適宜調整してユーザー名や認証方法を変更します。

コンテナを実行する

9.6クラスタをポート5432で実行します(試すだけなら、別のポートで実行したり一時ディレクトリにマッピングしたりしても構いません)。

docker run --rm -v /sbin/pg-scripts:/pg-scripts -v /var/lib/postgresql:/var/lib/postgresql\
    -v /etc/postgresql:/etc/postgresql -p 5432:5432 postgres-pglogical:9.6\
    /pg-scripts/start-pg 9.6 10.0.1.0/24
# since we're running in the foreground with the --rm option, run this in another terminal:
docker run --rm -v /sbin/pg-scripts:/pg-scripts -v /var/lib/postgresql:/var/lib/postgresql\
    -v /etc/postgresql:/etc/postgresql -p 5433:5432 postgres-pglogical:10\
    /pg-scripts/start-pg 10 10.0.1.0/24

start-pgの第1引数はPostgreSQLのバージョン、第2引数と最後の引数はpg_hba.confが存在しない場合の作成に使うネットワークです。これは、appuserが「trust」認証方法で接続するのに使われます。

Dockerコンテナをsystemdサービスとして動かしてみたい方は、原文記事末尾のコメントでお知らせいただければ時間のあるときに補足します(実は難しくありません)。ドキュメントはネット上にいろいろありますが、私たちの独自サービスユニットファイルはこうしたチュートリアルと少し違っている部分があります。違いは、サービス起動時にポートが接続を受け付けるかどうかをチェックしているのと、(Docker)イメージが既にローカルにある場合はレジストリからpullしないようになっている点です。

PostgreSQL設定を編集する

Postgresql-pglogicalコンテナを使うファイルが古いクラスタで実行できるようになったら、postgresql.confファイルを更新してコンテナを再起動します。以下の設定は9.6クラスタと10クラスタのどちらの設定のベースとしても使えます。

wal_level = logical
max_worker_processes = 10
max_replication_slots = 10
max_wal_senders = 10
shared_preload_libraries = 'pglogical'

pg_hba.confには以下の行を含めてください(Dockerを使っていない場合や、デフォルト以外のネットワークでコンテナを実行している場合は、ネットワーク設定を変更してください)。

host    all     postgres        172.17.0.0/24   trust
host    replication     postgres        172.17.0.0/24   trust

サーバーを再起動すればレプリケーション開始の準備が整います。

データベースのレプリケーションを実行する

プロバイダのセットアップ

PostgreSQL 9.6データベースで以下を実行します。

# PostgreSQLでリストアするスキーマをダンプする
pg_dump -Fc -s -h 10.0.1.10 -p 5432 -U appuser mydb > mydb-schema.dump
psql -h 10.0.1.10 -p 5432 -c 'create extension pglogical;' -U appuser mydb
psql -h 10.0.1.10 -p 5432 -c "select pglogical.create_node(node_name := 'provider', dsn := 'host=10.0.1.10 port=5432 dbname=mydb');" -U appuser mydb
psql -h 10.0.1.10 -p 5432 -c "select pglogical.replication_set_add_all_tables('default', ARRAY['public']);" -U appuser mydb

# 私の場合シーケンスレプリケーションが動かなかったので、データベース切り替えの直前には別の方法をおすすめします
# psql -h 10.0.1.10 -p 5432 -c "select pglogical.replication_set_add_all_sequences('default', ARRAY['public']);" -U appuser mydb

これで、パブリックなスキーマのテーブルとシーケンスがレプリケーション対象としてマーキングされます。

サブスクライバとサブスクリプションのセットアップ

PostgreSQL 10データベースで以下を実行します。

# データベーススキーマの作成とリストア
createdb -U appuser -h 10.0.1.10 -p 5433 mydb
pg_restore -s -h 10.0.1.10 -p 5433 -U appuser -d mydb mydb-schema.dump
# pglogical拡張をインストールしてサブスクライバとサブスクリプションをセットアップ
psql -h 10.0.1.10 -p 5433 -c 'create extension pglogical;' -U appuser mydb
psql -h 10.0.1.10 -p 5433 -c "select pglogical.create_node(node_name := 'subscriber', dsn := 'host=10.0.1.10 port=5433 dbname=mydb');" -U appuser mydb
psql -h 10.0.1.10 -p 5433 -c "select pglogical.create_subscription(subscription_name := 'subscription', provider_dsn := 'host=10.0.1.10 port=5432 dbname=mydb');" -U appuser mydb

これで、レプリケーションのステータスを以下でチェックできるようになります。

select pglogical.show_subscription_status('subscription');

初期化が完了してデータベースが同期およびレプリケーションされると(データベースのサイズによってそれなりに時間がかかります)、切り替えを開始できるようになります。

シーケンス値のレプリケーション

この時点でデータベースのレプリケーションはほぼ完了です。シーケンス値のレプリケーション方法については見つけられませんでした。シーケンスに依存するシリアル整数主キーカラムを使っている場合は正しいシーケンス値も設定したいはずであり、さもないとシリアルシーケンスの次の値に依存する新しいレコードをINSERTできなくなってしまいます。その方法についてここで説明します。set-valueステートメントを生成した後で古いサーバーを停止する時間を確保できるよう、データベースへの大量書き込み集中に備えてギャップ値5000を挿入しています。おそらくこのギャップ値は、スクリプト実行してからサーバーが停止までの間にデータベースがどのぐらい急速に増大するかに応じて適宜調整が必要です。

psql -h 10.0.1.10 -p 5432 -U appuser -c "select string_agg('select ''select setval(''''' || relname || ''''', '' || last_value + 5000 || '')'' from ' || relname, ' union ' order by relname) from pg_class where relkind ='S';" -t -q -o set-sequences-values-generator.sql mydb
psql -h 10.0.1.10 -p 5432 -U appuser -t -q -f set-sequences-values-generator.sql -o set-sequences-values.sql mydb
# 新しいシーケンス値を新しいデータベース(例ではポート5433)に設定する
psql -h 10.0.1.10 -p 5433 -U appuser -f set-sequences-values.sql mydb

最後の切り替え手順

次は基本的に、PostgreSQL 10クラスタのポートを5432に変更します(古いクラスタのポートであれば何でも構いません)。続いて9.6クラスタを停止し(上の例ではCtrl-Cで停止)、新しいクラスタを再起動します。最後に、変換ルールでrow型のOIDに依存しているカスタム型がある場合は、データベースを使っているアプリも再起動するとよいでしょう。

ここでは、SQLステートメント発行の前に何らかの接続バリデーションを用いてプール内のコネクションを安全に切断できるアプリを仮定しています。そうでない場合は、postgresql.confやpg_hba.confをいじった後にデータベースを再起動したら、必ずアプリも再起動するのがよいでしょう。

クリーンアップ

新しいデータベースでの正常な動作を確認できたら、次のようにして片付けられます。

select pglogical.drop_subscription('subscription');
select pglogical.drop_node('subscriber');
drop extension pglogical;

この記事がダウンタイム最小限のデータベースアップグレードに役立つことを願っています。

関連記事

Rails開発者のためのPostgreSQLの便利技(翻訳)

PostgreSQL 10の使って嬉しい5つの機能(翻訳)

PostgreSQLの機能と便利技トップ10(2016年版)(翻訳)

Ruby 2.5新メソッド: #delete_prefix と #delete_suffix(翻訳)

$
0
0

概要

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

Ruby 2.5新メソッド: #delete_prefix と #delete_suffix(翻訳)

本記事は、BigBinaryのRuby 2.5シリーズのひとつです。

先ごろRuby 2.5.0-preview1がリリースされました

Ruby 2.4の場合

Projects::CategoriesControllerという文字列があり、Controllerという文字を削除したいとします。このような場合は#chompメソッドを使えます。

irb> "Projects::CategoriesController".chomp("Controller")
  => "Projects::Categories"

しかし同じ文字列からProjects::を削除したい場合、#chompに対応するメソッドがないので、subに頼る必要があります。

irb> "Projects::CategoriesController".sub(/Projects::/, '')
  => "CategoriesController"

Naotoshi Seoはこんな単純なタスクで正規表現を使いたくないと考え、こうしたタスクをカバーできるメソッドがRubyに必要であると提案しました。

このとき提案されたメソッド名には、remove_prefixdeprefixlchompremove_prefixhead_chompなどがありました。

Matzdelete_prefixというメソッド名を勧め、こうしてこのメソッドが誕生しました。

Ruby 2.5.0-preview1の場合

irb> "Projects::CategoriesController".delete_prefix("Projects::")
  => "CategoriesController"

これで、プレフィックスを#delete_prefixで削除し、サフィックスを#chompで削除できます。名前が対象的でないと感じられたため、#delete_suffixメソッドも追加されました。

irb> "Projects::CategoriesController".delete_suffix("Controller")
  => "Projects::Categories"

Elixir、Go、Python、PHPでどのようにこのタスクを扱っているかについては#12694の議論をご覧ください。

関連記事

Ruby 2.5新メソッド: Dir.children と Dir.each_child(翻訳)

週刊Railsウォッチ(20171215)Ruby 2.5.0-rc1リリース、Ruby 2.4.3セキュリティ修正、Ruby 3.0で変わるキーワード引数、HTML 5.2 RECリリースほか

$
0
0

こんにちは、hachi8833です。クリスマスが迫るとRuby周りが忙しくなりますね。

今週のウォッチ、いってみましょう。

臨時ニュース1: Ruby 2.5.0-rc1リリース


www.ruby-lang.orgより

ついさっき出ました。実は先ほど以下のセキュリティ修正でrbenvを更新したときにrc1の文字が見えていたのでついでにインストールしちゃいました。

臨時ニュース2: Rubyセキュリティ修正リリース

今朝発表がありました。

修正内容

以下の2点です。必要な方はお早めにアップデートしましょう。

プレスリリース(ダウンロードリンクあり)


私もローカル環境とオレオレRailsアプリをRuby 2.4.3にアップデートしました。

Rails: 今週の改修

commit差分から見繕いました。

System TestにFirefox headless driverを追加

# actionpack/lib/action_dispatch/system_testing/driver.rb#67
             browser_options.args << "--headless"
             browser_options.args << "--disable-gpu"

+            @options.merge(options: browser_options)
+          elsif @browser == :headless_firefox
+            browser_options = Selenium::WebDriver::Firefox::Options.new
+            browser_options.args << "-headless"
+
             @options.merge(options: browser_options)
           else
             @options
           end
         end

         def browser
-          @browser == :headless_chrome ? :chrome : @browser
+          if @browser == :headless_chrome
+            :chrome
+          elsif @browser == :headless_firefox
+            :firefox
+          else
+            @browser
+          end
         end

つっつきボイス: 「Firefox Quantamが速くなったらしいのと関係あるのかな」

参考: Firefox Quantumが激重になる問題が発生中

非推奨メソッドがpublicに変わらないよう修正

# activesupport/lib/active_support/deprecation/method_wrappers.rb#60
               deprecator.deprecation_warning(method_name, options[method_name])
               super(*args, &block)
             end
+
+            case
+            when target_module.protected_method_defined?(method_name)
+              protected method_name
+            when target_module.private_method_defined?(method_name)
+              private method_name
+            end
           end
         end

つっつきボイス: 「お、お、こんなバグあったとは」「非推奨メソッドが削除されるまでの間ということですね」

非推奨のBigDecimal#newを削除

# activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb#6
      module OID # :nodoc:
         class Decimal < Type::Decimal # :nodoc:
           def infinity(options = {})
-            BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+            BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
           end
         end
       end

つっつきボイス:BigDecimal#newじゃなくてBigDecimalになるのか」「RailsのActiveSupportなのかなと思ったらRubyの方だった」「newが非推奨になったのはなぜなんだろう?」「オブジェクトの同一性を担保するためなのかなと思ったけどnewあってもなくてもobject_id違うな↓」

追記: 「IntegerやFloatでnewできないのに合わせたんじゃないんすかね?」

RailsのSTIを修正

# activerecord/lib/active_record/relation.rb#55
-    def new(*args, &block)
-      scoping { @klass.new(*args, &block) }
+    def new(attributes = nil, &block)
+      scoping { klass.new(scope_for_create(attributes), &block) }
     end

つっつきボイス: 「STI: Single Table Inheritance」「この記事↓見るとSTI結構嫌われてる雰囲気ですね」「STIは有用なこともあると思うけど使ったことほとんどなかったナー: カラム増えるし」

参考: みんなRailsのSTIを誤解してないか!?

使われていないwebpack定数を削除

// activestorage/webpack.config.js#1
-const webpack = require("webpack")
 const path = require("path")

 module.exports = {

つっつきボイス: 「誰も使ってないので削除したそうです」

Rails

RailsのArelを使ってコンポジション可能なQuery Builderを書く(RubyFlowより)

# 同記事より
scene = Scene.new(scene_params)
SceneQueryBuilder.new(scene).by_season
SceneQueryBuilder.new(scene).by_episode
SceneQueryBuilder.new(scene).by_dialogue

つっつきボイス: 「Query Builderはよく使うし、いいパターンだと思う: ただ自分はArelじゃなくて生SQLで書くけど」「同じことをActiveRecordだけで書くのはつらくなりやすい」「Builderパターンを説明するときにはSQLを例に使うのが一番わかりやすいと思ってる: みんなもっと使おう」

[保存版]人間が読んで理解できるデザインパターン解説#1: 作成系(翻訳)

Railsのロガー記事2本: ActiveSupport::TaggedLoggingとベストプラクティス

# 元記事1より
#lib/vendor_payment_logger.rb

class VendorPaymentLogger < ActiveSupport::TaggedLogging
  def initialize(logger)
    super(logger)
    logger.formatter = formatter
  end

  def formatter
    Proc.new{|severity, time, progname, msg|
      formatted_severity = sprintf("%-5s",severity.to_s)
      formatted_time = time.strftime("%Y-%m-%d %H:%M:%S")
      "[#{formatted_severity} #{formatted_time} #{$$}]\n #{msg}\n"
    }
  end
end

つっつきボイス: 「最近ロガーで悩んでると聞いたので探してみました」「そうそう、某案件のロガー設計方針」「いつだったか、ロガーはオブジェクト指向的設計からはみ出しやすいって言ってましたね」「まさしく」

リレーションだけが更新されるとupdate_attributeでSQLが発行されない

Gobyの作者@st0012さんからの情報です。Rails 5のissue #25503が以下のやり取りの後未だにcloseしていないそうです。この挙動は2015年に0fcd4cで追加されたそうです。

問題点を見つけた。
changed?メソッドがリレーションの更新をチェックせず、属性の更新だけをチェックしてた。
リレーションが更新されたかどうかをチェックする何かうまい方法を見つける必要がある。個別のテストは動くのにグループになるとコケるテストがあったので、条件を少し追加する必要があった。
25503#issuecomment-250984166より大意


つっつきボイス: 「仕様なのかバグなのか?」「ActiveRecord::Persistence#update_attributeではリレーションの更新には触れてないですね」「ところで『update_attributeはバリデーションがきかないゴミだぞ』って誰か言ってた」「update_attribute(key, value) というAPIも残念」「バリデーションが効かないからゴミというわけではない(過去にやらかした経験上)」

参考: ActiveRecord の attribute 更新方法まとめ

Rubyアプリが劣化する様子をレストランになぞらえて説明する

Passengerでお馴染みのオランダPhusion社のブログ記事です。レストランに皿洗い機を導入後に処理量を増やすとどうなるかという形でサーバーのワーカーの扱いについて解説しています。


同記事より

テストにおけるstubのコスト

stubは速いしスイスイ通るし便利だけど使い所に注意という記事です。

# 同記事より
describe Order do
  let(:customer) { Customer.new }
  let(:order)    { Order.new(subtotal: 100, customer: customer) }

  describe '#total' do
    context 'a customer has fee' do
      before do
        customer.fee = 21
      end

      it 'returns the total price which includes fee' do
        expect(order.total).to eq(121)
      end
    end

    context 'a customer has no fee' do
      before do
        customer.fee = 0
      end

      it 'returns the total price without any fee' do
        expect(order.total).to eq(order.subtotal)
      end
    end
  end
end

つっつきボイス: 「単体テストだとstubを使う意味ないこと多いっすね」

Passwordless: Railsアプリでパスワードなし認証するgem

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: { case_sensitive: false }

  passwordless_with :email # <-- ここ
end

つっつきボイス: 「とりあえずPasswordlessという名前はわかりやすくていいかも: Deviseに比べれば」

[Rails] Devise Wiki日本語もくじ1「ワークフローのカスタマイズ」(概要・用途付き)

ankaneさんの膨大なメモ

groupdateなどさまざまなgemを出しているAndrew Kaneことankaneさんがいろんなドキュメントをリポジトリに置いているのを見つけました。内容はRails/PostgreSQL/セキュリティ/データサイエンスなどさまざまです。


つっつきボイス: 「BPSもDocbaseにMarkdownドキュメントを社内でじゃんじゃん共有しているけど、それと同じようなノリなのかも」「publicな場所に置いているのエライ」

Markdownで書けるドキュメントコラボレーションサービスを比較する

コーディングスタイルで揉める理由

『オブジェクト指向設計実践ガイド』などでお馴染みのSandi Metz氏の記事です。

  • なぜスタイルガイドを使うのか
  • どのスタイルガイドがいいのか
  • なぜチームで反対されるのか
  • 自分流でやりたいんだけどダメ?
  • チームで合意を取り付けるには
  • 新しいスタイルガイドが性に合わないときは?

つっつきボイス: 「これはどうしてもどこかで揉めるやつ」「永遠の課題」「基本はプロジェクトの既存スタイルに沿うことにして、迷ったらスタイルガイドに沿う、と言うだけなら簡単ですけどね」

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

ワーカープロセスを安全に切り替える

factory_girl改めfactory_botなどでお馴染みのThoughtbot社のブログ記事です。

# 同記事より
#!/usr/bin/env ruby

STDOUT.sync = true

def info
  "PID=#{Process.pid} WORKER_VERSION=1"
end

puts "TICK: Starting process... #{info}"

trap "TERM" do
  puts "TICK: Received SIGTERM #{info}"
  sleep 5 # 重い作業を終了中ということにする
  puts "TICK: Graceful shutdown #{info}"
  exit
end

loop do
  puts "TICK #{info}"
  sleep 2 # 重い作業を実行中ということにする
end

doorkeeper: RailsでOAuth2認証するgem

OAuth2に特化した認証gemです。Railsの他Grapeフレームワークにも対応しているそうです。

# doorkeeper-gem/doorkeeperより
class Api::V1::ProductsController < Api::V1::ApiController
  before_action :doorkeeper_authorize! # 全アクションでトークンを必須にする

  # 以下アクション
end

つっつきボイス: 「doorkeeper、結構定番らしいけど初めて知ったので」「これもいい名前かな」「そうっすか?Doorkeeper.jpと紛らわしそう」

Rails Developer Meetup 2017より


railsdm.github.ioより

大盛況だったRails Developer Meetup 2017のスライドからです。参加したかったー(´・ω・`)

ふつうのRailsアプリケーション開発

とても参考になります。

yuba: Railsの抽象化支援gem

generatorで生成できます。

# willnet/yubaより
rails generate yuba:service create_artist
rails generate yuba:form artist
rails generate yuba:view_model artist_index

rancher: コンテナを継続的に管理

deppbotとtachikoma.io: セキュリティや依存関係の自動更新

Ruby trunkより

Ruby 3でキーワード引数がかなり変わる見通し

詳細は今後多少変わるのかもしれませんが、既にMatzがRubyWorld Conference 2017などでRuby 3で本物のキーワード引数を導入すると言明しているそうです。

# 以下の呼び出しは「キーワード引数」を渡す
foo(..., key: val)
foo(..., **hsh)
foo(..., key: val, **hsh)

# 以下の呼び出しは「通常の引数」を渡す
foo(..., {key: val})        # {} で囲まれている
foo(..., hsh)
foo(..., {key: val, **hsh}) # {} で囲まれている

# 以下のメソッド定義は「キーワード引数」を受け取る
def foo(..., key: val)
end
def foo(..., **hsh)
end

# 以下のメソッド定義は「通常の引数」を受け取る
def foo(..., hsh)
end

Ruby 2のキーワード引数はHashオブジェクト(キーはすべてシンボル)の通常の引数であり、最後の引数として渡されている。この設計を選んだのは互換性のためだったが、かなり複雑になっていて、動作が直感的にならない多くのエッジケースの原因になっていた。
Ruby 3ではキーワード引数は通常の引数と完全に分離される(ブロックパラメータが通常の引数と完全に分離されているのと同様に)。
この変更によって互換性が失われる。キーワード引数を渡したり受け取ったりする場合は、常に({}などで囲まない)むき出しのシンボル値かdouble-splat**が必要になる。
次のような移行パスを考えている:
* Ruby 2.6か2.7あたりで警告を出すようにする: 通常の引数がキーワード引数と解釈可能な場合(またはその逆)
* Ruby 3.0で新しい文法に完全に移行する
#14183より大意


つっつきボイス: 「最初#14176 Unclear error message when calling method with keyword argumentsにしようかと思ったんですが、その後でこのissueが流れてきたので」「Railsも順次対応不可避…ゴク」「実は@st0012さんがちょっと前からGobyのキーワード引数周りの仕様で矛盾に突き当たって悩んでいるんですが、これを見たら無理もないかも」「本当のキーワード引数

Ruby 2.0.0リリース! – キーワード引数を使ってみよう

そこからsplat演算子(*)やdouble-splat演算子(**)の話題になりました。

「splat演算子って、引数とパラメータのどっちにも置けるからまたややこしい」「正直ぱっと見て分からないw」

参考: Rubyの配列展開 *[a, b, c]

Ruby

Ruby 2.5でStructにkeyword_init: trueでキーワード引数が使える

こちらもキーワード引数にちょっと絡みます。

Foo = Struct.new(:a, :b, keyword_init: true)
Foo.new(a: 1, b: 2) #=> #<struct Foo a=1, b=2>

つっつきボイス: 「これはとてもよい」「最初ハッシュが渡せるのかと思ったらキーワード引数だった」「とはいうもののStructにするより普通にクラス書いちゃうことの方が多い: Structって規模感で悩むのと、ダメな使い方になる可能性もあるんで」「Struct#newインスタンスを継承すべからずとかですね」

Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど

シングルトンクラスの本当の使われ所

私たちが普通クラスメソッドと呼ぶものは、技術的には、それぞれのシングルトンクラスで定義されたクラスオブジェクトの(シングルトン)インスタンスメソッドです。
同記事より


www.puzzle.chより


つっつきボイス: 「この図↑を見た限りでは、シングルトンクラスも加味すると継承パスはこんなふうに二本立てともみなせるということかな」

RubyConf 2017で発表したお: Rubyで2Dゲーム作った

記事のスライドの見せ方が見事です。


www.blacktm.comより


つっつきボイス: 「↑Apple IIがつい懐かしくって」

最近のYARV-MJIT


つっつきボイス: 「70fpsはoptcarrotのパフォーマンスでしょうね」

Rubyと型


つっつきボイス: 「Matzと_ko1さんの二人がこんなに手こずるなんて、型理論ってどんだけ難しいんだと思ってこの翻訳記事↓見つけました」

参考: 「型」の定義に挑む

「話がバートランド・ラッセルの数理論理学から始まってて、抽象度むっちゃ高い」「きわめつけは以下↓: 厳密に定義しすぎると発展を妨げるって」「(数学科出身につき)それ気持ちわかるわー」「エンジニアの発想じゃなくて数学者の発想だなーと思いました」

“型の定義とは何か?” この質問に明確かつ正確な回答を与えられれば、そこから生じる多くの誤解や無意味な議論を避けることができる。しかし、この質問に対して、そのような明確かつ正確な回答を持ってしまった場合、科学の前進を鈍化させ、”知識の成長を妨げ”、”調査の道筋を既知の狭いチャネル内に引き入れてしまう”ことにもつながりかねない。
postd.ccより

参考: Wikipedia: 型理論

JavaScript

LeaderLine: 指示線を描画するJSライブラリ


github.com/anseki/leader-lineより


つっつきボイス: 「これ大好きー: だいぶ昔にこういうのを力技で実装したときはうまくいかなかったのを思い出した(´・ω・`)」

parcel人気急上昇?

10日しないうちに★10,000に迫る勢いです。

github.com/parcel-bundler/parcelより

設定ファイルなしでも動くというのが殺し文句のようです。


つっつきボイス: 「JS環境選びってギャンブル」「今からでも遅くはない?」

参考: webpack時代の終わりとparcel時代のはじまり

図と例で学ぶawaitasyncFrontend Weeklyより)


nikgrozev.comより


つっつきボイス:asyncとかpromiseはほんと面倒」

JavaScript: 5分でわかるPromiseの基礎(翻訳)

mapforEachの違い

パフォーマンスも含めて比較した記事です。

// 同記事より
arr.forEach((num, index) => {
    return arr[index] = num * 2;
});
// arr = [2, 4, 6, 8, 10]
let doubled = arr.map(num => {
    return num * 2;
});
// doubled = [2, 4, 6, 8, 10]

つっつきボイス:map()forEach()はRubyの#map#eachとだいたい同じようなものだと思ってる」「他の言語でforeachを見た気がするけどどの言語だったかな…」

参考: Wikipedia-ja Foreach文

CSS/HTML/フロントエンド

HTML 5.2 RECがリリース

今年10月に出たHTML 5.1 2nd Editionの立場は…

目立つ部分だけ目次レベルでざっとdiff取ってみました(左がHTML 5.2、右がHTML 5.1 2nd Edition)

最近の非同期CSS読み込み(Frontend Weeklyより)

今ならrel="preload"でJSなしでできるということです。

<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">

はじめてのCSS Gridレイアウト(Frontend Weeklyより)

CodePenを多用して説明しています。

See the Pen CSS Grid: Calendar by Geoff Graham (@geoffgraham) on CodePen.


つっつきボイス: 「そういえばbabaさんが『gridって直下の要素しかgridできないのが不便なんですよね』って言ってました」「display: flex;が見えてあれ?と思ったけどliの中だけだった」

GoogleがSEOスターターガイドを改訂

肝心のガイドはまだ日本語になっていません。

Unicode Consortiumが追加待ちのグリフにフィードバックを募集


blog.unicode.orgより


つっつきボイス: 「文字が全部鼻に見えるw」「これレビューできる人世界に何人というレベルでは」

今気づきましたが、17372r-n4923-dam1-chart.pdfに旧かな文字まで並んでいて絶句してしまいました。もはや誰得。


17372r-n4923-dam1-chart.pdfより

その他

Microserviceアーキテクチャのパターン


microservices.ioより

昨日BPS社内のエンタープライズ・アーキテクチャ勉強会でMicroserviceのアーキテクチャに言及されたので探してみました。

エンタープライズアプリケーションアーキテクチャパターンを読む: 1.概要

Googleが動画を自動で漫画に変換するアプリを実験公開


research.googleblog.comより


つっつきボイス: 「うう、すげー」「コマ割りまでやってる」「今後結婚式に出席したらきっと山ほど見られますよ」

参考: Google、動画を自動で漫画化する実験アプリ公開。AIポートレートカメラマンやスクラッチ動画生成も

Recho: SlideShareなどにTwitterレスを表示できるChrome拡張


つっつきボイス: 「ほとんどニコ動」

go-torch: Goプログラムをブラウザでビジュアルプロファイリングするツール


つっつきボイス: 「この間もこんなグラフ見ましたね」「名前ど忘れ…frame graphだった」

参考: Go言語のプロファイリングツール、pprofのWeb UIがめちゃくちゃ便利なので紹介する

コードをフェンスの向こうに放り投げる

正規表現のよさげな本

番外

Ruby Tuesday


つっつきボイス: 「asakusa.rbが火曜日開催だから?」「そういうことか」

中国語にひらがなの「の」が絶賛混入中

人間デコーダー

写真は残念ながら削除されたようです。


つっつきボイス: 「この郵便屋さんものすごく優秀なんじゃ」「普通迷宮入りですよね」

むやみになつかしい感


つっつきボイス: 「大昔にTK-80↓の横でラジカセでFM聴いてたら実行/停止に応じていろんな音階が出たのをつい思い出しちゃって」

磁石とクリップ


今週は以上です。

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

週刊Railsウォッチ(20171208)最近のRailsフロントエンド事情、国際化gem、mallocでRubyのメモリが倍増、るびま記事募集ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Frontend Weekly

frontendweekly_banner_captured

悲報: JetBrainsのGoプラグインがGoLand登場で終了\(^o^)/

$
0
0

いつかその日が来るだろうなとは思っていたけど…

RubyMineを2017.2.4にアップグレードした後、いつものようにGobyを開いてみると、Go言語のシンタックスハイライトが全部消えてまっちろになっている。

再インデックスに時間かかっているのかなと思ったけど、プログレスバー表示されてないし。

もしやと思ってRubyMineの設定を開いてみるとこんなメッセージが。

RubyMine+Goプラグイン、Gobyメンテ環境として最強だったのにぃ…

とりあえずGoLandをダウンロードしてトライアルモードで確認することに。まずはVimプラグインをインストール。
続いてRubyMineからFile > Export Settingsで設定をエクスポートし、GoLandでFile > Import Settingsしたところ、シンタックスハイライト周りやVimキーバインドカスタマイズも含めてストンと入った。

確かに今までどおりのGo環境。でも今後はキーバインドとか別々に設定するのかと思うと…
GoLandにはRubyのシンタックスハイライトを追加できないので、RubyとGoをいっぺんに見たいときにIDEを2つ立ち上げないといけないのも悲しい。
有料でGoプラグイン復活希望。

関連記事

Goby: Rubyライクな言語(1)Gobyを動かしてみる

RubyMine+Go言語プラグインが超快適だった話

Ruby: Proxyパターンの解説(翻訳)

$
0
0

概要

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

パターン名は英語で表記します。

Ruby: Proxyパターンの解説(翻訳)

本記事では、Proxyパターンとその種類について学びます。パターンの種類ごとにRubyでの実装を行います。

Proxyパターンの目的

最初にProxyパターンの目的を押さえておきましょう。「Design Patterns: Elements of Reusable Object-Oriented Software」という書籍ではProxyを次のように説明しています。

別のオブジェクトの代理(サロゲート: surrogate)やプレースホルダを提供することで、オブジェクトへのアクセスを制御する

ProxyパターンはSurrogateとも呼ばれています。

同書で使われている例は私のお気に入りです。

あるドキュメントエディタに、グラフィックオブジェクトをドキュメントに埋め込む機能があるとします。画像によってはオブジェクトの作成にコストがかかるものがあります(巨大なラスタ画像など)が、ドキュメントは素早く開けられなければなりません。そのため、ドキュメントを開くときにはコストのかかるオブジェクトをすべて作成するのは避ける必要があります。(中略)解決方法は、実際の画像の代役として振る舞う画像プロキシのような別のオブジェクトを作成することです。このプロキシは画像のように振る舞い、必要に応じてインスタンス化の面倒を見ます。

依存関係は次のような感じになります。

Ruby - image proxy

TextDocumentは最初にImageProxyを用いて何らかのプレースホルダを表示し、ImageProxyは必要に応じて実際のImageを読み込みます。

適用範囲

プロキシは、オブジェクトへの参照をもっとスマートにする必要がある場合に常に有用です。次の応用方法があります。

  1. 仮想プロキシ(virtual proxy)
  2. 保護プロキシ(protection proxy)
  3. リモートプロキシ(remote proxy)
  4. スマート参照(Smart reference)

1. 仮想プロキシ

コストの高いオブジェクトを必要に応じて作成します

上述のImageProxyはまさしくこのタイプのプロキシです。Rubyで実装してみましょう。

2つのメソッドを持つTextDocumentクラスがあるとします。

class TextDocument
  attr_accessor :elements

  def load
    elements.each { |el| el.load }
  end

  def render
    elements.each { |el| el.render }
  end
end

メソッドはloadrenderの2つだけです。最初にドキュメントのすべての要素を読み込み、続いて要素をレンダリングしたいとします。

読み込みに非常に時間のかかるImage要素があるとします。

class Image
  def load
    # ... 非常に時間がかかる
  end

  def render
    # 読み込んだ画像をレンダリングする
  end
end

画像を含むドキュメントを読み込む場合は次のようになります。

document = TextDocument.new
document.elements.push(Image.new)
document.load # => 画像のせいで読み込みが遅い

画像の読み込みに時間がかかるので、ドキュメントの読み込みにも時間がかかります。

そこで、画像の遅延読み込み(lazy loading)を実装する仮想プロキシを作成します。

class LazyLoadImage
  attr_reader :image

  def initialize(image)
    @image = image
  end

  def load
  end

  def render
    image.load
    image.render
  end
end

これで、LazyLoadImageを使えばドキュメントの読み込みが一時停止しなくなります。LazyLoadImagerender呼び出しが行われるまで画像を読み込まないからです。

document = TextDocument.new
image = Image.new
document.elements.push(LazyLoadImage.new(image))
document.load # => 速い
document.render # => 画像読み込み中なので遅い

SimpleDelegatorを使って、Decoratorパターンで行ったのと同様にLazyLoadImageからImageへの正しい委譲を実装する方法もあります。

2. 保護プロキシ

保護プロキシは、元のオブジェクトへのアクセスを制御します

これはだいたい見ての通りです。元のオブジェクトを呼び出す前に何らかの保護ルールを適用したい場合、保護プロキシでラップできます。

class Folder
  def self.create(name)
    # フォルダの作成
  end

  def self.delete(name)
    # フォルダの削除
  end
end

class FolderProxy
  def self.create(user, folder_name)
    raise '管理者以外はフォルダを作成できません' unless user.admin?

    Folder.create(folder_name)
  end

  def self.delete(user, folder_name)
    raise '管理者以外はフォルダを削除できません' unless user.admin?

    Folder.delete(folder_name)
  end
end

この例では、プロキシと元のクラスのインターフェイスが異なっていることは私も認めざるを得ません。Foldercreatedeleteのパラメータは1つしかありませんが、FolderProxyuserもパラメータに取っています。これが保護プロキシの実装として最善かどうか私もわかりません。もっとよい例がありましたらぜひコメントでお知らせください ;)

3. リモートプロキシ

リモートプロキシは、異なるアドレス空間上にあるオブジェクトをローカル環境で代表します

たとえばRPC(リモートプロシージャコール)を使いたいのであれば、RPC呼び出しを扱うプロキシを簡単に作れます。ここではxml-rpc gemを例に取ります。

次のコードでRPC呼び出しを行えます。

require 'xmlrpc/client'

server = XMLRPC::Client.new2("http://myproject/api/user")
result = server.call("user.Find", id)

私たちの代わりにRPC呼び出しを扱うリモートプロキシを作成します。

class UserProxy

  def find(id)
    server.call("user.Find", id)
  end

  private

  def server
    @server ||= XMLRPC::Client.new2("http://myproject/api/user")
  end
end

これで、異なるアドレス空間上にあるオブジェクトへのアクセスに使えるプロキシができました。

4. スマート参照

スマート参照は、オブジェクトへのアクセス時に付加的な操作を実行する生のポインタを置き換えるのに使います

利用法の1つは、最初に参照が行われるときに永続オブジェクトをメモリに読み込むことです。

このタイプのプロキシを使うことで、レスポンスのメモ化(memoization)を作成できます。

たとえば、重たい計算を代行するサードパーティ製ツールを使うとします。そうしたサービスでは専用のgemを提供するのが普通です。gemがどのようなものか考えてみましょう。

class HeavyCalculator
  def calculate
    # しばらく何か計算する
  end
end

サードパーティ製gemなので、ここではメモ化を追加できません。かつ、HeavyCalculatorの呼び出しを長時間待ちたくありません。こんなときは、メモ化の追加を代行するスマート参照プロキシを作成します。

class MemoizedHeavyCalculator
  def calculate
    @result ||= calculator.calculate
  end

  private

  def calculator
    @calculator ||= HeavyCalculator.new
  end
end

これで、MemoizedHeavyCalculatorを用いてcalculateを必要なだけ何度でも呼び出せるようになりました。しかも実際の呼び出しは1回だけにとどまり、以後の呼び出しにはメモ化された値が使われます。

プロキシをスマート参照として使う方法を応用すれば、ログ出力を追加することもできます。何らかの見積もり機能を提供するサードパーティ製サービスがあり(当然サービスのコードは変更できません)、サービスの呼び出しごとにログ出力を追加したいとします。これは次のように実装できます。

class ExpensiveService
  def get_quote(params)
    # ... リクエストを送信
  end
end

class ExpensiveServiceWithLog
  def get_quote(params)
    puts "次のparamsで見積もりを取得しています: #{params}"

    service.get_quote(params)
  end

  private

  def service
    @service ||= ExpensiveServiceWithLog.new
  end
end

ExpensiveServiceはサードパーティのコードなので実装は変更できません。しかしExpensiveServiceWithLogを用いればどんなログ出力でも必要に応じて追加できます。ここでは簡単のためputsで出力しています。

その他の関連パターン

Proxyパターンのある種の実装はDecoratorパターンの実装と極めて似ていますが、両パターンの目的は異なります。Decoratorパターンはオブジェクトに責務を追加しますが、Proxyパターンはオブジェクトへのアクセスを制御します。

ProxyパターンはAdapterパターンに似ていることもあります。しかしAdapterパターンは適用先のオブジェクトに別のインターフェイスを提供するためのものです。Proxyパターンは、適用先のオブジェクトと同じインターフェイスを提供します。

追伸: プロキシオブジェクトは、元のオブジェクトが持つすべてのメソッドに応答するべきです。上の例ではプロキシクラスの元のオブジェクトが持つメソッドを単に実装しましたが、元のクラスにもっと多くのメソッドがある場合は、プロキシクラスでも同じコードが多数繰り返すことになるかもしれません。

Proxyパターンの実装とDecoratorパターンの実装はほぼ同じなので、プロキシから元のオブジェクトにメソッドを委譲できるSimpleDelegatorの記事をお読みいただくことを強くおすすめいたします。

お読みいただきありがとうございました。

関連記事

Ruby: Chain of Responsibilityパターンの解説(翻訳)

Railsで重要なパターンpart 2: Query Object(翻訳)

Railsで重要なパターンpart 1: Service Object(翻訳)

CSS GridがBootstrapよりレイアウト作成に向いている理由(翻訳)

$
0
0

概要

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

CSS GridがBootstrapよりレイアウト作成に向いている理由(翻訳)

CSS GridはWebレイアウト作成の新しい手法です。ブラウザでネイティブに動作するまともなレイアウトシステムが登場したのはこれが初めてです。

CSS Gridのメリットは、かの有名なBootstrapと比較することでいっそう際立ちます。以前ならJavaScriptを導入しなければ到底不可能だったレイアウトを作れるようになっただけではなく、コードのわかりやすさやメンテのしやすさも向上します。

本記事でその理由を解説します。

筆者は現在無料のCSS Grid上級編講座の準備も進めており、12月中に立ち上げを予定しています。

今のうちに講座にアクセスしたい方はこちらでメールアドレスをお知らせください。以下の画像をクリックして講座のプレビュー画面をご覧いただくこともできます。

それでは、CSS GridがBootstrapより優れていると私が考えている根拠トップ3をご紹介します。

マークアップがシンプルになる

BootstrapをCSS Gridに差し替えることで、HTMLの見通しがよくなります。これより大きなメリットは他にもありますが、CSS Gridを使ったときに最初に気づくのがこの点です。

わかりやすい例をご覧いただくために、あるWebサイトのダミーレイアウトを作成して、2つのバージョンで必要なコードを比較できるようにしました。以下がダミーレイアウトです。

原注: この例のデザインには少々気を利かせてありますが、デザインそのものはCSS GridとBootstrapを比較するうえで無関係です。したがって、コード例にはCSSの部分を含めないことにします。

訳注: このスタイルを含めたCodePenサンプルを参考までに作りました。

Bootstrapの場合

最初に、このWebサイトをBootstrapで作成するのに必要なマークアップを見てみましょう。

<div class="row>
    <div class="col-xs-12 header>ヘッダー</div>
</div>
<div class="row">
   <div class="col-xs-4 menu">メニュー</div>
   <div class="col-xs-4 content">コンテンツ</div>
</div>
<div class="row">
   <div class="col-xs-12 footer">フッタ</div>
</div>

お気づきいただきたい点が2つあります。

  1. rowごとに独自の<div>がある
  2. レイアウトの指定にクラス名が使われている(col-xs-2
  3. レイアウトが複雑になると、HTMLも複雑になる

レスポンシブなWebサイトならさらに複雑になってしまうでしょう。

<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1 menu">

CSS Gridの場合

今度は同じことをCSS Gridでやるとどうなるかを見てみましょう。HTMLは次のようになります。

<div class="wrapper">
    <div class="header">ヘッダー</div>
    <div class="menu">メニュー</div>
    <div class="content">コンテンツ</div>
    <div class="footer">フッタ</div>
</div>

<header><footer>のように意味を持つ要素を使ってもよいのですが、Bootstrapとの違いが際立つように例ではあえて<div>を使っています。

マークアップがシンプルになっていることがひと目でわかります。目障りなクラス名も、rowごとに必要だった余分な<div>タグもありません。要素は単なるgridのコンテナであり、その中にitemが置かれます。

Bootstrapと異なり、レイアウトが複雑になってもこのマークアップはさほど複雑になりません。

ただし、Bootstrapの例にはCSSを追加する必要はありませんが、言うまでもなくCSS Gridの例にはCSSが不可欠です。特に次のCSSを追加しなければなりません。

.wrapper {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: 40px 100px 40px;
}

.header {
  grid-column: span 12;
}

.menu {
  grid-column: span 4;
}

.content {
  grid-column: span 8;
}

.footer {
  grid-column: span 12;
}

この点についてはBootstrap推しの人に分があるかもしれません。Bootstrapではgrid作成のためにCSSを気にする必要がなく、HTMLでレイアウトを定義するだけで済むからです。

しかし次の説明でおわかりいただけると思いますが、マークアップとレイアウトの一体化は、実際には特に柔軟性において弱点となります。

柔軟性が非常に高い

画面サイズに応じてレイアウトを変更したいとします。たとえばモバイルではメニューをトップに移動するとしましょう。

つまり、上を下のように変えたいということです。

CSS Gridの場合

CSS Gridでは驚くほどシンプルにできます。メディアクエリを追加してその中でアイテムを自由に動かすだけで完了です。

@media screen and (max-width: 680px) {
  .header {
    grid-column: span 6;
  }
  .menu {
    grid-row: 1;
    grid-column: span 6;
  }
  .content {
    grid-column: span 12;
  }
}

このようにレイアウトを再編成できるということから、HTMLがどのように書かれているかをまったく気にする必要のない「ソース順序の独立性」を得られます。開発者やデザイナーにとって大きな勝利です。

CSS Gridによって、HTMLが本来求められている「コンテンツのマークアップ」に立ち返ることができます。ビジュアル表示はCSSの役割であり、HTMLの役割ではありません。

Bootstrapの場合

Bootstrapで同じことをしようとすると、実際にはHTMLを変更しなければなりません。menuタグが2つめのrowに置かれているので、最上位のrowまでえっちらおっちら移動してheaderのすぐ下に置かなければなりません。

これをメディアクエリで行うのはなかなか大仕事です。HTMLとCSSでは実現できないので、JavaScriptも使わざるを得ません。

この例は、現時点で私にとってCSS Gridの最大のメリットを示しています。

カラム12個の制約がない

これはさほど大きな問題ではありませんが、何度もこれで辛い目に遭いました。Bootstrapのgridは12カラムに分割されているので、レイアウトのカラム数を5個にしようとしたときに困り果ててしまいます。カラム数が7個や9個など、足して12にならないカラム数でも同様です。

CSS Gridならこんな問題は起きません。gridの総カラム数は自由に指定できます。カラム数7の場合は次のようになります。

これは次のようにgrid-template-columnsrepeat(7, 1fr)を指定するだけでできます。

.wrapper {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: 60px;
}

今なら、Bootstrapでも同じようにやれるハックがもしかしたらあるかもしれません。

Bootstrap 4ではFlexboxが使われていて、こちらでも同じことは可能ですが、まだベータ版です。

ブラウザのサポート状況

最後はやはりブラウザでのサポートもチェックしましょう。本記事執筆時点では、グローバルなWebサイトトラフィックの75%でCSS Gridがサポートされています。

ただしCSS Gridの利用を諦める前に、Morten Rand-Eriksenのメッセージに耳を傾けましょう。彼は、CSS Gridは後方互換性を考慮する方法を一新する機会であると主張しています。

CSS Gridはレイアウトモジュールです。ソースの順序に影響されることなくドキュメントのレイアウトを変更できます。言い換えると、CSS Gridは純粋な表示ツールであり、正しく用いられているので、ドキュメントのコンテンツのやりとりには影響を与えないはずです。ここから、シンプルかつ驚くべき真実が導き出されます。CSS Gridが古いブラウザでサポートされていないということは、ユーザーのエクスペリエンスに影響を与えてはならないということではなく、単に古いブラウザではエクスペリエンスが変わるというだけのことです。

言い換えれば、表示とコンテンツが分離されたことによって、すべてのユーザーが確実にコンテンツを閲覧でき、かつCSS Gridはサポート環境下においてレイアウトを改善することでエクスペリエンスを拡張するということです。

おしまいに

最後に、Mozillaを支援する開発者であるJen Simmonsの言葉を引用したいと思います。JenはCSS Gridを知るに連れて、私と同じ気持ちをCSS Gridに対して抱くようになりました。

CSS Gridを使えば使うほど、抽象レイヤーを追加するメリットはどこにもないという確信が深まりました。CSS Gridはレイアウトのフレームワークであり、ブラウザにしっかり組み込まれているのです。— Jen Simmons

CSS Gridの未来を確信し、CSS Gridをもっと知りたいとお思いの方は、ぜひメールアドレスをこちらにてお知らせください。準備ができ次第、近日リリース予定のCSS Grid講座に公開前にアクセスいただけます。


お読みいただきありがとうございました。私はPerと申します。インタラクティブなコーディングscreencastの新しいツールであるScrimbaの共同設立者です。ご質問やご意見がおありでしたら、お気軽にTwitterまでどうぞ。

関連記事

新版: やばい臭いのするCSSコード(翻訳)

いまさら聞けないBootstrap3の便利クラス

CSSを迷いなく書く極意: 単一ファイルコンポーネント(翻訳)

Webデザイナーがこっそり教える4つのテクニック

ベテランRubyistがPythonコードを5倍速くした話(翻訳)

$
0
0

概要

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

ベテランRubyistがPythonコードを5倍速くした話(翻訳)

私はかれこれ10年以上Rubyコードを書きまくっていますが、最近は修士科目の関係でPythonコードも書きまくっています。RubyとPythonは多くの点で違っていますが、パフォーマンス上の特性やコードの最適化といった面では似ています。本記事では、私が最近最適化したPythonコード片を題材に、Rubyコードの場合の高速化手順と比較してみることにします。

本記事について「Pythonインタプリタの高速化かと思った」というご感想をいくつもいただきました。それはそれできっとクールだったでしょうが、この記事はそちらではなく、インタプリタ言語を使うコードの高速化を扱います。ここで学んだことは、PythonにもRubyにも通用します。

そのコードをご覧に入れる前に、ご存じない方のために一応私のパフォーマンス方面について書いておきます。私の趣味のひとつがRubyのパフォーマンスであり、Railsアプリのベンチマークに使われるderailed benchmarks gemのメンテナでもあり、多くのプロジェクトでパフォーマンス関連のプルリクを大量に送りました。Railsのend-to-endリクエストをほぼ12%高速化するプルリクを送った後、なりゆきで「すべての文字列をfreezeする」運動の促進担当者になったこともあります。パフォーマンス向上といっても、そのほとんどはhash(Pythonではdict)やarray(Pythonではlist)の代入の削減によって得られたものです。

警告: 私はlistとarrayを互いに交換可能なものとして用いる傾向があります。メソッドと関数についても同様です。これらが同じでないことも、これを見てカッとなって眼から血を吹く人がいることも承知していますので、どうかご容赦ください。

私にとってパフォーマンスの改善はこれが初めてではありません。

何かを高速化する前には、仕組みを理解しておかなければなりません。次の課題を設定します。

コードの仕組みについて関心のない方は、最適化でできることを解説する次節「ロジックをDRYにする」までスキップしていただいて結構です。

本記事で最適化するコードは、チェスに似たゲーム盤上でクイーンの移動方法をすべて特定する必要のあるゲームのプレイで用いられるものです。かつ、このゲーム盤の高さと幅は変更可能とします(実際のチェス盤とは異なることもある)。また、ゲーム盤のステートはlistのlist(Rubyではarrayのarray)で表現します。

空白は0で表します。ゲーム盤のステートは次のような感じになります。

height = 7
width  = 7
board_spaces_occupied = [
    [  1,  0,  1,  1,  1,  0,  0],
    [  1,  1,  0,  1,  1,  0,  1],
    [  1,  1,  1,  1,  0,  0,  1],
    [  1,  0,  1,  0,  1,  1,  1],
    [  1,  0,  0,  1,  1,  1,  1],
    [  0,  0,  1,  0,  0,  1,  1],
    [  0,  1,  1,  0,  1,  1,  1],
]

詳細はもっと複雑ですが本記事とは無関係なので、さしあたってここまで理解できれば十分です。きっとRedditで「numpy使えばいいのに」とツッコミが入ることでしょう。numpyはこの特定の代入規則に合わなかったのです。

有効な移動先を特定するタスクを完了するため、次のPythonコードの変種を授業で与えられました。

BLANK = 0

def get_legal_moves_slow(move):
    r, c = move

    directions = [ (-1, -1), (-1, 0), (-1, 1),
                    (0, -1),          (0,  1),
                    (1, -1), (1,  0), (1,  1)]

    fringe = [((r+dr,c+dc), (dr,dc)) for dr, dc in directions
            if move_is_legal(r+dr, c+dc)]

    valid_moves = []
    while fringe:
        move, delta = fringe.pop()

        r, c = move
        dr, dc = delta

        if move_is_legal(r,c):
            new_move = ((r+dr, c+dc), (dr,dc))
            fringe.append(new_move)
            valid_moves.append(move)

    return valid_moves

def move_is_legal(row, col):
    return 0 <= row < height and\
           0 <= col < width and\
           board_spaces_occupied[row][col] == BLANK

元のコードは授業で使われたものですが、本記事に合わせて形式を少し簡単にしてあります。
heightwidthboard_spaces_occupiedはいずれもグローバルにアクセスできる変数です。

移動元として(5, 4)を渡すと、クイーンの移動先が2つしかないことが正しく示されるので、正常に動作していることがわかります。

print get_legal_moves_slow((5,4))
# => [(6, 3), (5, 3)]

Rubyistのために補足すると、このかっこは「タプル」であり、イミュータブルなarrayに含まれているとお考えください。Rubyならさしずめ[[6, 3], [5, 3]]のような感じになるでしょう。

このコードを高速化するには、そこで行われていることを理解する必要があります。ロジックを追ってみることにします。

クイーンは、自分のいる場所から縦横斜めに移動できます。移動距離は、途中に遮るものがない限り盤上でいくつでも移動できます。この動作を表すために、クイーンのすぐ周りにある空き位置をすべて見つけたいと思います。空き位置は、横方向の(row)移動と縦方向(column)の移動で表します。

directions = [ (-1, -1), (-1, 0), (-1, 1),
                (0, -1),          (0,  1),
                (1, -1), (1,  0), (1,  1)]

次に各空き位置を列挙して、クイーンがルールに違反せずにそこに移動できるかどうかをチェックします。

これを行うには、各移動方向をループして現在の横位置と縦位置に結合し、有効かどうかをそれぞれチェックします。

移動方向が有効な場合は、有効なマスの位置だけではなく、有効なマスへの到達方法(移動方向など)も保存します。この「移動方向」を繰り返しその先に展開して、他の有効な移動も見つけます。

fringe = [((r+dr,c+dc), (dr,dc)) for dr, dc in directions
        if move_is_legal(r+dr, c+dc)]

注意: rはrow(横方向)、cはcolumn(縦方向)を表します。drは横方向の差分、dcは縦方向の差分を表します。きれいな変数名は使われていません。なお文法をググりたいRubyist向けに補足すると、Pythonのこのようなlist列挙形式は「list comprehension」と呼ばれます。

これで、展開可能なfringe listを得ました。これをビジュアル表示する例を見てみることにします。

訳注: fringeは「付随」「メインでない」といったニュアンスです。コードと整合するよう英ママとしました。

完全に空になっているゲーム盤上に(3, 3)を置くと、そこから左斜め上のマスへの移動は(2, 2)になります。そこへの移動は、移動方向を表すarrayの最初の要素(-1, -1)を用います。これで、fringe listは次のようになります。

# [((row, column), (delta_row, delta_column))]
  [((2,   2),      (-1,        -1))]

その先を展開したい場合は、現在の位置に同じ移動方向(-1, -1)を適用して(1, 1)を得ます。

これで、利用できないマスに到達するまで、空きマスの周囲にある展開の必要な位置を即座に得ることができます。

次のコードは、展開を最後まで行います。

valid_moves = []
while fringe:
    move, delta = fringe.pop()

    r, c = move
    dr, dc = delta

    if move_is_legal(r,c):
        new_move = ((r+dr, c+dc), (dr,dc))
        fringe.append(new_move)
        valid_moves.append(move)

このコードでfringe listの各要素を列挙し、削除してから、移動が有効かどうかをチェックして、横方向の移動差分と縦方向の移動差分を適用して新しい位置を得ます。そしてこの新しい位置をfringe listに追加し、元の位置もvalid_moves listに追加し、後者が戻り値になります。

ここまでご理解いただけましたでしょうか。ここではループ内で同じゲーム盤に対してこのコードが何度も繰り返し呼び出されるので、ループの実行時間にまんべんなく影響します。

以上で、コードの動作と最適化方法について理解いただいたかと思います。

この後のセクションでは、RubyとPythonのどちらにも適用できたスクリプト言語の最適化手法のコンセプトについて解説します。Pythonコードを高速化する方法を理解できれば、任意のスクリプト言語を高速化できるようになります。最終的なコードやベンチマークは最後に示します。

ロジックをDRYにする

最適化の基本のひとつは、作業の重複を解消することです。ある要素をfringe listに1つ置くと、その位置はlist comprehensionで既に有効性がチェック済みになりますが、そのlistから他の要素を取り出すときに同じ要素について有効性を繰り返しチェックしてしまっています。つまり、8つのマスが有効な場合、各マスを2回ずつチェックしなければならなかったということです。

このチェックを1回に減らせば高速化できます。

オブジェクトよりロジックを優先する

スクリプト言語におけるオブジェクト作成は、メモリもCPUサイクルも消費します。このメモリを後でGC(ガベージコレクション)するときに、CPUサイクルを余分に消費してしまいます。

オブジェクトによっては、作成のコストが低いものがあります。複雑なオブジェクトは、その分作成やコピーに時間がかかります。list(Rubyで言うarray)やdict(Rubyで言うhash)は、stringよりもずっと作成コストが高くなります。同様に、stringよりもintegerの方が作成コストは低くなります。

このあたりの説明は難しいので、無理矢理な例を作りました。None(Rubyで言うnil)またはNoneでない値を1つ与えられ、これをlistに追加して後で処理するとします。次の例をご覧ください。

my_list = [value] # <== ここでlistに割り当てられる
if None in my_list:
    return

# ...

上の例では、listと関係ない場合であってもlistの割り当てが発生してしまいます。そこで、次のようにarrayの割り当てが発生する前に値をチェックすればずっと高速になります。

if value is None:
  return
my_list = [value]
# ...

もちろん、最初の例のようなコードを書く人はまずいないことは承知していますが、要点はご理解いただけると思います。

オブジェクトは遅く、ロジックは速い。これはRubyとPythonのどちらにも通用する真理です。

シリアライズ方法に注意

オブジェクト生成削減の際に考えておく点が2つあります。オブジェクト生成の実際のオーバーヘッドと、そのオブジェクトの参照方法や使用方法です。オブジェクトが割り当てられた後、どのように操作されるのでしょうか。

ここでは、データをシリアライズして内部から値を取り出し、タプルからの値の取り出しにコストがかかる様子を観察したいと思います。この例では、位置をタプルから展開しています。

move = (1, 0)
# ...
r, c = move

rcの取り出しコストはゼロではありません。次のよく似た2つの関数をご覧ください。

def function_one(move):
    r, c = move
    return r + c

def function_two(r, c):
    return r + c

タプルを渡すよりも、function_twoのように値をバラして渡す方がはるかに高速です。kernprofでベンチマークを取ってみたところ、function_oneではタプルから値を取り出さないと利用できないため、倍の時間がかかりました。

パフォーマンスを最大限に改善するためには、データ操作を可能な限り最小限に絞るのが理想的です。

ループ内のリテラルに注意

JITを持たないスクリプト言語で、次のコードが呼び出されるたびに何が起きるでしょうか。

valid_moves = []

呼び出しコストの非常に高いarrayが割り当てられます。コード例ではループ内でリテラルを明示的に使っていませんが、このコードがどのように実行されるかについても考慮が必要になることがあります。本記事のコード例では、このget_legal_moves_slow関数は何度も何度も繰り返し呼び出されます。上のコード例でforwhileは使われていないからといって、このコードがループ内に置かれないとは言い切れません。

この場合、改変が発生してこの関数が呼び出されるたびにvalid_movesが必要になります。変更されない静的な値がいくつもあることにお気づきでしょうか。

次の場合で考えてみましょう。

directions = [ (-1, -1), (-1, 0), (-1, 1),
                (0, -1),          (0,  1),
                (1, -1), (1,  0), (1,  1)]

比較的読みやすいこのちっぽけなコードが呼び出されるたびに、1つのlist、8つのタプル、16のinteger参照が割り当てられてしまいます。このlistは決して改変されないのですから、これを関数の外に追い出して、起動時に1回しか作成されないグローバル定数に保存すれば、割り当てを大幅に削減できます。

チェックせずにスキップせよ

同じチェックを2回繰り返さないことについては既に述べました。最も高速なコードは「コードを書かないこと」です。理想のチェック回数は、ずばりゼロ回です。

そのために安全でないコードを書けということではありません。しかし、絶対ありえないシナリオの存在に気づくことができれば、そのチェックは不要になります。

コード例のどこに適用すればよいでしょうか。次をご覧ください。

def move_is_legal(row, col):
    return 0 <= row < height and
           0 <= col < width and
           board_spaces_occupied[row][col] == BLANK

ゲーム盤のマスが空いているかどうかをチェックする前に、ゲーム盤からはみ出していないかどうかをチェックしています。

どうすればこのチェックを削除できるでしょうか。1つの方法は、ゲーム盤の周りに境界を設定して、移動を展開したときにマスが空いていないことがわかるようにすることです。しかしこの方法では他の計算の難易度が上がってしまいます。

ロジックを曲げずに行う方法は1つありますが、それについては後述します。ここでのポイントは、チェックロジックを削除可能かどうかを検討することです。

メソッドがなければ問題もなくなる

メソッド呼び出し(Pythonでは関数呼び出し)のコストはゼロではありません。あるメソッドが呼び出されると、インタプリタはそのメソッドが存在する場所を探索してからコードを呼び出さなければなりません。

メソッドはコードをクリーンかつ理解しやすくするうえで有用ですし、ボトルネックの99%はメソッド呼び出しではないので、メソッドを削ってしまえと書くのはためらわれます。メソッドの削除は最適化としては非常に微細なものですが、それでも一片の真実はあります。

メソッド探索の回数についても考えてみましょう。Pythonの実装でlistのインデックスが何回探索されるのかは私も知りませんが、Rubyの場合はメソッドとして探索されます。

board_spaces_occupied[row][col] == BLANK

したがって、上のコードでは探索が1回ではなく2回実行されます。1回目はboard_spaces_occupied[row]にアクセスします。listが返されると、colを介して2つ目の要素にアクセスします。

メソッドを削除して操作の回数を削減できれば、1つのデータ構造に対して操作を実行することで高速化できるはずです。

ありがたいことに、メソッド呼び出し回数やarrayのインデックス参照などは、(言語の)コア開発者によって最適化されているので十分速いのが普通であり、したがって最適化する意味がありません。

言い換えると、プログラムを歪めてまで探索やメソッド呼び出しの回数を減らしてはいけません。(プログラムを歪めずに)探索を簡単に減らせるなら、ぜひそうしましょう。

ベンチマークを取る

パフォーマンス厨の皆さんに警告: コードのパフォーマンスをチューニングするときには、必ず変更前/変更中/変更後にベンチマークを取りましょう。本記事でのアドバイスはほとんどの場合一般的に通用しますが、特定のユースケースには適していないこともありえます。だからこそベンチマークは絶対省略しないでください。ベンチマークの正しい取り方はまったく別の問題なので、別の機会にしようと思います。

ときにはルールを完全無視

オブジェクトの数が多い方が有利な場合もあります。キャッシュを使えば、メモリと引き換えにCPUを節約できます。移動方向を保存するarrayを定数に移すことについては既に説明しましたが、それを微細なレベルで行います。つまり、コードをメモリ上に強制的に常駐させるのです。これと引き換えに、オブジェクト再構築のためのCPUサイクルをまったく消費せずに済みます。オブジェクトがキャッシュされるからです。

どんなものをキャッシュできるのでしょうか。高さと幅が固定であることと、移動のルールが変更される可能性がないことはわかっています。ゲーム盤上の位置は変更される可能性があります。ここを考慮して、位置ごとの有効なすべての移動のlistを事前に算出しておくという手が使えます。

ゲーム盤のステートが変更される可能性に注意しなければならないので、各展開方向のlistを作成するときにこの点を考慮します。たとえば、(3, 3)から右方向への移動は次のようになります。

[(3, 4), (3, 5), (3, 6)]

(3, 5)で何か変更が生じるとどうなるでしょうか。このマスが空かどうかをチェックして削除することはできますが、その場合(3, 6)も到達不能になります。移動方向ごとにlistが1つずつあるので、埋まったマスが最初に見つかった時点で列挙を中断すればよいのです。

ここまでの最適化をまとめて行う

まず、移動方向をarrayに保存します。

STAR_DIRECTIONS = [  (-1, 0), (1,  0), # 上下
                     (0, -1), (0,  1), # 左右
                     (1, -1), (1,  1), (-1, -1), (-1, 1)] # 斜め

移動の順序は重要ではありません。この後をご覧いただければわかります。

次にマスが空いているかどうかの探索を(2回ではなく)1回の呼び出しで行い、個別のrow/columnの組み合わせではなくタプルに基くようにしたいと思います。そこで、位置をインデックスとして持つdict(Rubyで言うhash)を1つ作成します。

def build_blank_space_dict():
    blank_space_dict = {}
    for c in range(0, height):
        for r in range(0, width):
            blank_space_dict[(c, r)] = (board_spaces_occupied[c][r] == BLANK)
    return blank_space_dict

上を次のように使います。

def move_is_legal_from_dict(move):
    return 0 <= move[0] < height and
           0 <= move[1] < width and
           blank_space_dict[move]

次が最もコストの高い部分です。ゲーム盤での有効な移動をすべて事前に算出してキャッシュしたいと思います。この作業のコストは高いのですが、起動時に1回行えば済むので、再計算が不要になります。

def calculate_first_move_guess_dict():
    first_move_guess_dict = {}
    for r in range(0, height):
        for c in range(0, width):
            rc_tuple = (r, c)
            first_move_guess_dict[rc_tuple] = []
            for delta_r, delta_c in STAR_DIRECTIONS:
                valid_guesses = []
                dr = delta_r
                dc = delta_c
                move = (r + dr, c + dc)
                while move_is_legal_from_dict(move):
                    valid_guesses.append(move)
                    dr += delta_r
                    dc += delta_c
                    move = (r + dr, c + dc)

                first_move_guess_dict[rc_tuple].append(valid_guesses)
    return first_move_guess_dict

長くなりましたが、変更後のコードは上のようになります。ここで行っているのは、上述のget_legal_moves_slow()と基本的にまったく同じです。大きな違いは、rowやcolumnごとの可能な移動を二重ループの中でビルドしていることです。

move_is_legal_from_dict()で移動の有効性をチェックしてからlistに追加していますが、これに気づくことが重要です。これは位置がゲーム盤からはみ出しているかどうかのチェックなので、削除します。これで、後でゲーム盤のステートをチェックするときに同じチェックを繰り返さずに済みます(先ほど「後述する」と書いたのはこれです)。

お待ちかねの最終的なメソッドは次のようになります。

def get_legal_moves_fast(move):
    valid_moves = []
    for direction_list in first_move_guess_dict[move]:
        for move in direction_list:
            if blank_space_dict[move]:
                valid_moves.append(move)
            else:
                break # そこから先の方向への移動はすべて無効

    return valid_moves

有効な移動のarrayをfirst_move_guess_dict[move]で探索します。ゲーム盤が3×3の場合、位置(1, 1)の結果は次のようになります。

[[(0, 1)], [(2, 1)], [(1, 0)], [(1, 2)], [(2, 0)], [(2, 2)], [(0, 0)], [(0, 2)]]

サブarrayにある各要素をループで列挙し、blank_space_dict[move]でチェックします。有効な場合は有効な移動に追加し、無効な場合は内側のループをbreakします(その位置から先の方向はクイーンの移動ルール上無効であるため)。

最後に、有効な移動のタプルのlistを1つ返します。

パフォーマンスの比較はどのように行えばよいでしょうか。私はkernprofで確認しました。

$ kernprof -l -v perf.py
Wrote profile results to perf.py.lprof
Timer unit: 1e-06 s

Total time: 1.17439 s
File: perf.py
Function: get_legal_moves_fast at line 53

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    53                                           @profile
    54                                           def get_legal_moves_fast(move):
    55    100000        41991      0.4      3.6      valid_moves = []
    56    900000       378690      0.4     32.2      for direction_list in first_move_guess_dict[move]:
    57   1000000       491422      0.5     41.8          for move in direction_list:
    58    200000       106333      0.5      9.1              if blank_space_dict[move]:
    59    200000       116717      0.6      9.9                  valid_moves.append(move)
    60                                                       else:
    61                                                           break # rest of direction is invalid
    62
    63    100000        39242      0.4      3.3      return valid_moves

Total time: 5.80368 s
File: perf.py
Function: get_legal_moves_slow at line 69

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    69                                           @profile
    70                                           def get_legal_moves_slow(move):
    71    100000        79230      0.8      1.4      r, c = move
    72
    73    100000        78106      0.8      1.3      directions = [ (-1, -1), (-1, 0), (-1, 1),
    74    100000        75957      0.8      1.3                      (0, -1),          (0,  1),
    75    100000        94609      0.9      1.6                      (1, -1), (1,  0), (1,  1)]
    76
    77    900000       739500      0.8     12.7      fringe = [((r+dr,c+dc), (dr,dc)) for dr, dc in directions
    78    800000      1704089      2.1     29.4              if move_is_legal(r+dr, c+dc)]
    79
    80    100000        79558      0.8      1.4      valid_moves = []
    81
    82    500000       406481      0.8      7.0      while fringe:
    83    400000       471503      1.2      8.1          move, delta = fringe.pop()
    84
    85    400000       323095      0.8      5.6          r, c = move
    86    400000       321289      0.8      5.5          dr, dc = delta
    87
    88    400000       736811      1.8     12.7          if move_is_legal(r,c):
    89    200000       214678      1.1      3.7              new_move = ((r+dr, c+dc), (dr,dc))
    90    200000       215883      1.1      3.7              fringe.append(new_move)
    91    200000       189134      0.9      3.3              valid_moves.append(move)
    92
    93    100000        73752      0.7      1.3      return valid_moves

本記事をスマートフォンでお読みの方向けに、重要部分を以下に抜粋しました。

Total time: 1.17439 s
File: perf.py
Function: get_legal_moves_fast at line 53

Total time: 5.80368 s
File: perf.py
Function: get_legal_moves_slow at line 69

最適化前のメソッドの所要時間は5.80368秒、改善後のメソッドでは1.17439秒にまで短縮されました。パフォーマンスは約5倍向上しました。

これをさらに高速化することは可能でしょうか。

説明してませんでしたが、操作によってはコストの高いものがあります。私の見たところ、Pythonのlistに対するappend()は最適化があまり進んでいません。同じlistへのappend()を繰り返すのではなく、おそらく固定サイズのarrayを1つ初期化してそのインデックスに要素を追加し、続いてNone(Rubyで言うnil)をすべて削除してからarrayをリサイズする方法も考えられます。繰り返しになりますが、これが本当かどうかを確認するにはベンチマークをもれなく取ってください。

numpyが使えるのであれば、何か高速化の方法があるかもしれません。GVLを持たないプラットフォーム(RubyもPythonもこれには該当しません)であれば、移動方向ごとのループを並列化できるかもしれません。別のスレッドに置くためにスケジューリング時間を使う値打ちはおそらくありませんが。

また、私がPythonについて何か取りこぼしている点があるかもしれませんし、すべてを高速化できる方法を私が知らない言語もあります。これまで述べてきたのは、(Pythonの)itertoolsモジュール、functoolsモジュール、operatorモジュールについてです。

更に言うと、私のコードは元のものより複雑で、良くも悪くも凄いことになっています。作業内容も保存されるステートも多く、メモリ消費も増加しています。とは言うものの、私にとって重要なのは最後の「有効な移動を取得する」関数の呼び出し回数でした。この関数は読みにくいぐらい短く簡潔ですが、一方で各部分の協調動作について多くの知識を要求します。

私はコーディング時間のほとんどを、人間にとって読みやすいコードを書くことに費やしています。パフォーマンスが5倍アップするのは結構な話ですが、同僚のコードメンテ時間が10倍になったら何にもなりません。パフォーマンスではCPUやRAMを考慮しますが、同様に人間にとってのコストも考慮しましょう。私は普通、最初に遅いコードを書き、次にプロファイリングを行い、重要なホットスポットだけを最適化します。今回は、ここが私にとってのホットスポットでした。

お読みいただきありがとうございます。

関連記事

ベンチマークの詳しい理解と修正のコツ(翻訳)

メモリを意識したRubyプログラミング(翻訳)

Ruby 2.5のパフォーマンス改善点(翻訳)

RailsConf 2017のパフォーマンス関連の話題(1)BootsnapやPumaなど(翻訳)


Vue.jsサンプルコード(26)多数のアンケートを同一画面で順に表示する

$
0
0

26. 多数のアンケートを同一画面で順に表示する

  • Vue.jsバージョン: 2.5.2
  • [応募]ボタンを押すと、メッセージの主語を切り替えてアンケートを表示します。
  • 3つの回答の中から1つをクリックするとページを切り替えずに次の質問に進み、下の値リストを更新します。
    • (終了処理がないのでアンケートが終わると最初の質問に戻ります)
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード


ポイント: Vue.jsの他にlodash.jsdelayも使っています。

    methods: {
      next: function(e) {
        this.answers.push(this.a)
        _.delay(() => { this.index++; this.a = null }, 500)
      },
    },

バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

RailsアプリをAWS Elastic Beanstalkにデプロイする手順(翻訳)

$
0
0

概要

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

RailsアプリをAWS Elastic Beanstalkにデプロイする手順(翻訳)

前回の記事はElixirをElastic Beanstalkにデプロイする方法でしたが、今回は数あるフレームワークの中でもRailsを愛する会社であるSyndicodeより、RailsアプリをAWS Elastic Beanstalkにデプロイする方法のチュートリアルをお送りいたします。なお弊社ではRails開発者を絶賛募集中です。

Elastic Beanstalkについて簡単におさらいします。これはAWS(Amazon Web Services)上にアプリを設定する手順を自動化するクラウドデプロイメントサービスです。本チュートリアルではコマンドラインインターフェイス(CLI)を用いてAWS Elastic BeanstalkにRailsアプリをデプロイする方法をご紹介します。

1. Elastic BeanstalkのCLIをインストールする

Macの場合、HomeBrewでaws-elasticbeanstalkをインストールします。

brew install aws-elasticbeanstalk

Homebrewを使ったことがない場合やLinux環境の場合は、pip(Pythonのパッケージ管理ツール)でインストールします。

sudo pip install awsebcli

Windowsの場合は以下を実行します。

pip install awsebcli

2. Raisアプリをgit cloneする

ここではhttps://github.com/engineyard/todo.gitのサンプルアプリをgit cloneします。Elastic Beanstalk CLIで自分のRailsアプリを使いたい場合は、アプリをGitリポジトリに置く必要があります。

git clone https://github.com/engineyard/todo.git

3. IAMロールを作成する

AWSサイトの手順に従って、IAMロールを2つ作成します。必要なのはaws-elasticbeanstalk-ec2-roleaws-elasticbeanstalk-service-roleです。アプリや環境はElastic Beanstalk CLIで作成しますが、これらのIAMロールの作成だけはElastic Beanstalkコンソールで[Create New Application]をクリックして行います。この手順は一度だけ行う必要があります。以下を順に実行します。

  • Elastic Beanstalk consoleを開きます。
  • [Create New Application]を選択します。
  • ウィザードに従って[Permission]ページまで進みます。
  • [Next]をクリックしてIAM consoleを開きます。
  • [Allow]を選択してロールを作成します。

4. Elastic Beanstalk CLIのセットアップ

cd todo
eb init

AWS Access KeyとAWS Secret Access Keyが1つずつ必要です。ない場合は、IAM consoleでユーザーを1つ作成してcredentialをダウンロードします。リージョン、利用するアプリ([Create New Application])、プラットフォームのバージョンはデフォルト値で構いません。共有するToDoアプリには既にプラットフォームのバージョンが含まれています。独自のRailsアプリを使う場合は、Ruby 2.3 Pumaを必ず追加してください。

5. 環境を作成する

eb create todo_production

Elastic BeanstalkはSecurity Group、ELB、Auto Scalingグループを作成します。3分もすれば環境が整い、以下でアプリをチェックできるようになります。

eb open

環境の情報


Engine Yardのサンプルデプロイアプリより

以下にご注意ください。

  • Rubyは/opt/rubies/ruby-2.3.4の下に置かれます
  • Railsアプリは/var/app/currentの下に置かれます
  • ユーザー名はWebアプリ名が使われます

6. データベースを作成する

このToDoアプリの設定は、実際のアプリで使うには少々正しくない点があります。ToDoアプリのconfig/database.ymlではSQLite3データベースを使っているため、データベースを設定していなくても動いてしまいます。todo_production環境がEC2インスタンスを1つ持っているだけなので、さしあたってこれで十分です。しかしAuto ScalingグループでEC2インスタンスがもうひとつ作成されると、新しいインスタンスは独自のSQLite3データベースを持ちます。これは期待する動作ではないでしょう。データベースを1つにまとめるには、config/database.ymlを削除します。DATABASE_URLを使うので、このファイルは不要になります。

git rm config/database.yml
git commit -m 'Remove database.yml'

RDS consoleでRDSインスタンスを1つ作成します。MySQLまたはPostgreSQLを使えます。このToDoアプリのGemfileにはmysql2 gemとpg gemが両方入っているので、どちらもサポートされています。オプションのデータベース名フィールドには「todo」と入力します。論理データベースは、RDSインスタンスの作成後に作成されます。セキュリティのため、[Publicly Accessible]はNoに設定します。RDSインスタンスの準備が整ったら、Detailsアイコンをクリックして[Security Group]をクリックします。rds-launch-wizard-2 (sg-041b107e)のような文字列が表示されます。

[Security Group]ページで、[Inbound]、[Edit]の順にクリックし、PostgreSQL用のルールを追加します。sourceでCustomを選択してElastic Beanstalk環境のSecurity Groupを入力します。sgと入力するとSecurity Groupのリストが表示されます。

正しいSecurity Groupが見つからない場合は、グループ名にElastic Beanstalk環境idが使われています。環境idを取得するには、eb statusと入力します。私の場合、環境idがe-kq7hjkf7dtで、Security Group名がawseb-e-kq7hjkf7dt-stack-AWSEBSecurityGroup-44MI138FQVGとなっています。AWSEBLoadBalancerSecurityGroupを含む名前は選択しないでください。


Engine Yardのキャプチャ画像より

これでElastic Beanstalkによって作成されたEC2インスタンスがRDSインスタンスにアクセスできるようになるはずです。このElastic Beanstalk環境に紐付けられるRDSインスタンスを作成することもできますが、この環境をterminateするとRDSインスタンスもterminateしてしまうためおすすめできません。

7. DATABASE_URLを設定する

RDS credentialを使ってDATABASE_URL環境変数を設定します。形式はdb_type://username:password@hostname:port/db_nameです。たとえば、PostgreSQLインスタンスを作成した場合は次のcredentialを使います。

user: engineyard
password: mysecretpassword
hostname: eypostgres.cjb9zibjzcpd.us-west-2.rds.amazonaws.com
port: 5432
db name: todo

続いて以下を実行します。

eb setenv DATABASE_URL=postgres://engineyard:mysecretpassword@eypostgres.cjb9zibjzcpd.us-west-2.rds.amazonaws.com:5432/todo

次に、アプリをdatabase.ymlなしでデプロイしてページを開きます。

eb deploy
eb open

以上で、単独のRDSインスタンスを用いるElastic Beanstalk上でRailsアプリが動きました。

8. Secret Key Base

この環境変数を使わない場合は、SECRET_KEY_BASEを設定するか、encrypted Rails secretsを使う場合はRAILS_MASTER_KEYを設定します。bundle exec rake secretでsecret key baseを新しく生成します。

eb setenv SECRET_KEY_BASE=cccae61c0c117c787745b596655caa50062dc3fc739505df02e209d9e737a2f39ab484d20e63d5937e1c58901e81109523807f66be421728851fecc2262ed5a8

9. SSH

eb initを実行すると、public keyを追加できます。新しいkeyペアを作成することもできます。eb initを既に実行した場合は、--interactiveオプションを付けて再度実行することもできます。eb sshを実行して、自分の環境のEC2インスタンスに接続します。

10. 以上でおしまいです

AWS Elastic BeanstalkにRailsアプリをデプロイする簡単な方法をご紹介いたしました。サポートされるRubyバージョンやAppサーバーに制約があるため、一部については制御できないことがあります。Sidekiqなどのバックグランドワーカーについては改良が必要です。現時点では、.ebextensionsにファイルをひとつ作成し、SidekiqワーカーをRailsアプリと同じインスタンスで実行する必要があります。中規模アプリでは、Sidekiq専用のインスタンスを用意すべきです。

もっと詳しく知りたい方は、ぜひweekly newsletterをご購読ください。

関連記事

無料で使えるAWSアカウント用セキュリティ監査ツールの紹介(翻訳)

Rails 5.2を待たずに今すぐActiveStorageを使ってみた(翻訳)

Mac+HomebrewでPostgreSQLが起動しない場合の対応

$
0
0

小ネタかつ既出ですが、自分用にメモします。

  • macOS High Sierra
  • Homebrew 1.3.8

問題

PostgreSQLに接続しようとしたりpsqlを使おうとしたりすると、たまにソケットがないと言われて接続できないことがあります。
Macbookがフリーズして再起動した後に起きることがあるようです。

$ psql
psql: could not connect to server: No such file or directory
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

brew services listでチェックすると、PostgreSQLのステータスのstartedが赤く表示されています(正常の場合は緑)。

「コンソール」アプリを開くと、system.logで以下が延々と出力されています。

Dec 21 07:10:01 tori com.apple.xpc.launchd[1] (homebrew.mxcl.postgresql[34025]): Service exited with abnormal code: 1
Dec 21 07:10:01 tori com.apple.xpc.launchd[1] (homebrew.mxcl.postgresql): Service only ran for 0 seconds. Pushing respawn out by 10 seconds.
Dec 21 07:10:05 tori com.apple.xpc.launchd[1] (com.apple.preference.displays.MirrorDisplays): Service only ran for 0 seconds. Pushing respawn out by 10

自分の場合、おそらくフリーズのタイミングでpostmaster.pidに傷が入ったと思われます。

解決方法

結局postmaster.pidを削除してサービスを再起動することで解決できました。Linux環境でも効くと思います。

$ rm /usr/local/var/postgres/postmaster.pid
$ brew services restart postgresql
Stopping `postgresql`... (might take a while)
==> Successfully stopped `postgresql` (label: homebrew.mxcl.postgresql)
==> Successfully started `postgresql` (label: homebrew.mxcl.postgresql)

復活しますた。

参考: Stackoverflow Postgres could not connect to server

関連記事

MacのhomebrewでOpenSSLがビルドエラーになる場合の対処方法

Rubyのクラスメソッドをclass

$
0
0

概要

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

Rubyのクラスメソッドをclass << selfで定義している理由(翻訳)


https://pixnio.com/nature-landscapes/winter/landscape-sky-winter-snow-ice-water-tree-nature-outdoor-reflectionより

クラスメソッドは私の同僚の間で常に議論や反論の種になっています。クラスメソッドは的確かつ有用と考える人もいますが、実際にはコードの読みやすさや管理のしやすさを損ないがちな邪魔者だと感じる人もいます。私はRubyのオブジェクト指向的な本質を信奉しており、オブジェクトで考えることを(読むのも!)好んでいますが、私には後者が真実になる傾向があることに気づきました。よく言われるように、クラスメソッドがどうしても必要になることもあります。ファクトリーメソッドから、ActiveRecordモデルのカスタムクエリメソッドで使われる複雑なメタプログラミングインターフェイスにいたるまで、クラスメソッドを全否定することはできません。もちろんクラスメソッドの利用は控えめにすべきではありますが(詳しくはCode Climateのこちらの良記事をご覧ください)。

本記事ではクラスメソッドそのものの良し悪しについては言及せず、クラスメソッドが必要になった場合のクラスメソッドのスタイル上の記法について議論します。

コーディングスタイルとスタイルガイド

Rubyスタイルガイドでは、クラスメソッドはdef self.methodという記法を用いるのが望ましいとされています。このスタイルでは、より明示的な def ClassName.methodという記法が批判されていますが、より謎めいたclass << selfという記法も補助的にサポートされています。記法の実際の表示については該当のセクションをご覧ください。

組織内でスタイルガイドを共有し、それに従うことは重要です。Sandi Metz氏の良記事では次のように指摘されています。

(略)スタイル上の選択は多くの場合任意であり、純粋に個人の好みの問題です。スタイルガイドを選択することは、ほとんど重要でない点で意見が割れてしまった場合の合意を形成するということです。スタイル(そのもの)が重要だということではなく、スタイルが揃うことが重要です
Why we argue styleより

この指摘は実にもっともです。しかし、スタイルを正しく選択できることもやはり重要です。服装と同じく、コードのスタイルには開発者としての信条や価値観や哲学が反映されるのですから、この問題についても十分理解が必要です。私たちの誰もが多くのクラスメソッドを定義していますが、果たして私たちはクラスメソッドの動作を理解しているのでしょうか。


https://images.askmen.com/1080×540/2015/11/06-042951-men_s_fashion_must_haves.jpgより

シングルトンクラス

上の問いに答えるために、Rubyのオブジェクトモデルについて取り急ぎ調べる必要があります。一般に、Rubyのメソッドはクラスに保存され、データはオブジェクト(クラスのインスタンス)に保存されます。これはかなり一般的な知識なので、次の例でもう少し追ってみましょう。

an_array = [1, 5, 10]

an_array.averageを実行すると、Arrayやそのスーパークラスにaverageメソッドが定義されていないので、次のようにNoMethodErrorエラーが出力されます。

an_array.average
# NoMethodError: undefined method `average' for [1, 5, 10]:Array

Arrayにモンキーパッチを適用してaverageメソッドを定義してもよいのですが、このメソッドはan_array以外では不要なのであれば、次のようにすることもできます。

def an_array.average
  reduce(:+) / count.to_f
end

これで次のように動きます。

an_array.average
# => 5.333333333333333

同じメソッドをArrayの別のインスタンスで実行しようとすれば、次のようにまたNoMethodErrorが出力されます。

another_array = [1, 3, 7]
another_array.average
# => NoMethodError: undefined method `average' for [1, 3, 7]:Array

この理由は、Rubyが舞台裏でaverageメソッドを特殊なクラスに保存しているからです。an_arrayだけがその特殊なクラスを指しています。つまり自身のシングルトンクラスです。

an_array.singleton_class
# => #<Class:#<Array:0x007fcf27848750>>

an_array.singleton_methods
# => [:average]

Rubyでは、どんなクラスのどんなインスタンスにも必ず自身のシングルトンクラスがあり、そこにシングルトンメソッドが保存されます。先ほど定義したメソッドもシングルトンメソッドであり、シングルトンクラスに保存されています(ただし例外として、Numericオブジェクトはこれに該当しません)。

あるオブジェクトのメソッドを呼び出すと、Rubyは最初にそのオブジェクトのシングルトンクラスでメソッドを探索し、それから通常のクラスや先祖クラスのチェインを探索します。

クラスメソッドはすなわちシングルトンメソッドである

Rubyではクラスもオブジェクトなので、クラスメソッドはClassの特定のインスタンス上に定義された単なるメソッドです。次の例で考えてみましょう(gist)。

class Example
  def self.a_class_method; end
  def an_instance_method; end
end

実際の動作を見れば理屈はすぐわかります。

Example.is_a? Object
# => true

Example.class
# => Class

Example.singleton_class
 => #<Class:Example>

Example.instance_methods(false)
 => [:an_instance_method]

Example.singleton_class.instance_methods(false)
 => [:a_class_method]

instance_methodsの引数をfalseで呼び出すと、継承されたメソッドを除いたメソッドリストが返されます(instance_methods)。

記法を選ぶ

ついにRubyのクラスメソッドを正確に理解できました。これでコーディング方法について詳しく議論する準備が整いました。本記事のタイトルでおわかりのように、私はdef self.method記法よりclass << self記法を好んでいます。理由はおわかりでしょうか?

私はメソッドを、それらが属しているクラスの内部で定義したいのです。class << self記法ならこのアプローチ、すなわちメソッドを実際のシングルトンクラスのスコープ内で定義していることがはっきりと伝わります。しかしdef self.methodを使うと、複数のスコープにわたってメソッドを定義していることになります。通常のクラススコープの中にいるにもかかわらず、コードのどの場所でも特定のインスタンスにメソッドを定義できるRubyの機能を使っているのです。クラス定義内におけるselfは、その場所における現在の(クラス自身などの)Classインスタンスを指します。このため、def self.methodを使うとスコープを飛び越えることになり、私にはこれがおかしいと感じられるのです。

def self.method記法にはもうひとつ疑問があります。その理由は、privateやprotectedなメソッドを定義できることです。Rubyには、クラスメソッドをprivateとして宣言するためのprivate_class_methodメソッドが用意されていますが、protectedメソッドについては同等のものがありません。また、privateなクラスメソッドでは、各メソッドを個別に宣言しなければなりません(たとえば、クラスの途中で単純にprivateを用いることはできません: これはそのクラスのインスタンスメソッドに適用されるからです)。要は、class << selfの方が実際にはより明確だということです。

予想される反論

Sandi Metzの記事で指摘されているように、スタイルに関する議論はときとして開発者の間で感情的にこじれてしまうことがあります。class << self記法について予想される反論として次のようなものが考えられます。

  • 大きなクラスだとどれがクラスメソッドだかわかりにくい: 私もその点にはまったく同意です。コード量が450行もある神クラスなら、まずリファクタリングしてより小さなクラスに分解しつつdef self.method記法は変えないようにすべきでしょう。いくらスクロールしても終わらないほどコードが多ければ、スコープが変わる場所を簡単に見落としてしまいます。しかしそれでもそのクラスはリファクタリングすべきです
  • 明快でなくなる: これは一方的な見方ですし、根拠も不十分です。def self.methodはまったくもって明快ではありません。前述のようにdef self.methodの本当の意味を理解できれば、def self.methodはスコープが混じるために実際には曖昧になってしまいます。この動作の背後にある理論を把握することが理解の助けになります。

  • 素直にスタイルガイドに従えばいいんじゃね?: これに対する私の回答は、動作の詳細に注意を向けることが多くの場合重要だということです。これは重大な問題ではありませんし、def self.methodは「コードの匂い」にも該当しません。実際、これ自体議論になるかどうかというトリビアな問題です。それぞれの選択肢について動作を学んだり知識を得たりすることで、初めて議論に値打ちが生まれます。さらに、class << self記法は私にとってよりよいものであり、RubyやRubyのオブジェクト指向を安定して理解できると信じています。最後に、スタイルガイドは変更/改訂される(ただし慎重にですが)可能性があることもどうかお忘れなく。

本記事が皆さんにとって有益であることを願っています。このトピックについてお気づきの点やご質問がありましたらコメント欄までどうぞ。本記事が面白い/有益だと思っていただけた方は、ぜひ👏ボタンをクリックして応援してください。

関連記事

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

Railsの`Object#try`がダメな理由と効果的な代替手段(翻訳)

週刊Railsウォッチ(20171222)定番gemまとめサイト、active_record-mtiでテーブル継承、PostgreSQL 10の非互換変更点、Railsガイド攻略法ほか

$
0
0

こんにちは、hachi8833です。Ruby 2.5リリースまでもう少しです。

Rails勉強会@東京 第93回に初参加させていただきました。2年半ぶりの開催だったそうです。

お知らせ: 年末年始にかけて週刊Railsウォッチをお休みいたします: 次回は1月12日です。

今年最後のRailsウォッチ、いってみましょう。

Rails: 今週の改修

今週の更新情報は多めなので選別してみました。

セキュリティ関連ヘッダーを追加

# actionpack/lib/action_dispatch/railtie.rb#26
     config.action_dispatch.default_headers = {
       "X-Frame-Options" => "SAMEORIGIN",
       "X-XSS-Protection" => "1; mode=block",
-      "X-Content-Type-Options" => "nosniff"
+      "X-Content-Type-Options" => "nosniff",
+      "X-Download-Options" => "noopen",
+      "X-Permitted-Cross-Domain-Policies" => "none"
     }

つっつきボイス: 「X-で始まるヘッダって確かIE向けがほとんどだったと思うはRFC定義されていない独自拡張を表している」「X-Download-Options(IE向け)はまだわかるけど、↓の良記事見るとFlashがらみのヘッダが目につくなー: Railsでそこまで手を回すのってどうなんだろ?」「secureheadersにもこのヘッダ入ってるんで、セキュリティ関係者が入れときたいと思ってるらしいことはワカッタ」「10月のウォッチで扱ったgemですね」

db.createのエッジケースを修正

スキーマキャッシュの読み込み時には現在のマイグレーションバージョンをフェッチする。
しかしデータベースが存在しない場合に接続を取れずにエラーになる。これはデータベース作成時に問題になる。
データベースがない場合はスキーマキャッシュは不要なのでエラーを無視するよう修正。
#31311より大意

# activerecord/lib/active_record/migration.rb#56
-      def current_version(connection = Base.connection)
+      def current_version(connection = nil)
+        if connection.nil?
+          begin
+            connection = Base.connection
+          rescue ActiveRecord::NoDatabaseError
+            return nil
+          end
+        end

ActiveStorage::Blobからvariantを削除

# activestorage/app/models/active_storage/blob.rb#273
  def delete
-    service.delete key
+    service.delete(key)
+    service.delete_prefixed("variants/#{key}/") if image?
 @kaspth

つっつきボイス: 「variantって、作成した後削除し忘れてつまづきがちなやつ」「ところでblobって言葉ここに限らずいろんなところで見かけるんですが、どんな意味でしたっけ」「だいたいバイナリを表すことが多いっすね」「バイナリ・ラージ・オブジェクトの略なのか」「オブジェクト指向のオブジェクトではないw」

Railsのblobについての記述は以下にありました。

blobは、そのサービス上にあるファイルの位置を示すファイルとキーについてのメタデータを含むレコードです。
rails/activestorage/app/models/active_storage/blob.rbより大意

Railsの起動メッセージがきびきび表示されるようになった

起動直後に=> Booting Railsを表示するようになりました。

# railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt#3
require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
+
+if %w[s server c console].any? { |a| ARGV.include?(a) }
+  puts "=> Booting Rails"
+end

つっつきボイス: 「これは地味にうれしい」「今までなかったのが不思議ですね」

foreign_keysinformation_schemaを修正

初コミットおめでとうございます。

# activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#406
-          FROM information_schema.key_column_usage fk
-          JOIN information_schema.referential_constraints rc
+          FROM information_schema.referential_constraints rc
+          JOIN information_schema.key_column_usage fk
           USING (constraint_schema, constraint_name)
           WHERE fk.referenced_column_name IS NOT NULL
             AND fk.table_schema = #{scope[:schema]}
             AND fk.table_name = #{scope[:name]}
+            AND rc.constraint_schema = #{scope[:schema]}
             AND rc.table_name = #{scope[:name]}

つっつきボイス: 「これはMySQL向けだな: information_schemaがあるからすぐわかる」「JOINの方向を逆にしたのか」

SQLを発行したコードの行をオプションで出力

development.rbでconfig.active_record.verbose_query_logs = trueとすることでapp/views/news/show.html.erb:9:inのように出力されるようになりました。

# 26815より
Started GET "/news/popular" for ::1 at 2016-10-19 00:57:48 +0200
Processing by NewsController#popular as HTML
  Version Load (57.3ms)  SELECT  "versions".* FROM "versions" INNER JOIN "rubygems" ON "rubygems"."id" = "versions"."rubygem_id" LEFT OUTER JOIN "gem_downloads" ON "gem_downloads"."rubygem_id" = "rubygems"."id" AND "gem_downloads"."version_id" = $1 WHERE ("versions"."created_at" BETWEEN '2016-10-11 22:57:48.145796' AND '2016-10-18 22:57:48.145965') AND "versions"."indexed" = $2  ORDER BY gem_downloads.count DESC, "versions"."created_at" DESC LIMIT 10 OFFSET 0  [["version_id", 0], ["indexed", "t"]]
  ↳ app/views/news/show.html.erb:9:in `_app_views_news_show_html_erb___2784629296874387000_70222193538980'
  Rubygem Load (0.4ms)  SELECT  "rubygems".* FROM "rubygems" WHERE "rubygems"."id" = $1 LIMIT 1  [["id", 19969]]
  ↳ app/views/news/_version.html.erb:1:in `_app_views_news__version_html_erb__2744651331114605013_70222191156360'
  Version Load (0.8ms)  SELECT  "versions".* FROM "versions" WHERE "versions"."rubygem_id" = $1 AND "versions"."latest" = $2  ORDER BY "versions"."position" ASC LIMIT 1  [["rubygem_id", 19969], ["latest", "t"]]
  ↳ app/helpers/application_helper.rb:23:in `gem_info'
  GemDownload Load (0.3ms)  SELECT  "gem_downloads".* FROM "gem_downloads" WHERE "gem_downloads"."version_id" = $1 AND "gem_downloads"."rubygem_id" = $2 LIMIT 1  [["version_id", 882133], ["rubygem_id", 19969]]
  ↳ app/models/version.rb:247:in `downloads_count'

つっつきボイス: 「これはマジありがたい!: eager-loadingしまくってるとどうなるかというのはあるけれど」「そういえばこの間joker1007さんが『activerecord-cause gemは役割を終えた』みたいなことを言ってたのはこの修正のことかな?」「ちょうどこの間のウォッチでactiverecord-cause取り上げたところだった」

nobuさんの珍しいコミット

# 81b99b2 と 2d5700b
-    @cache_path = Tempfile.create(%w{tmp cache}, Dir.tmpdir)
+    @cache_path = Dir.mktmpdir(%w{tmp cache})

# bff3ee8 と 4022f33
-    @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tmp', 'cache')
+    @cache_path = Tempfile.create(%w{tmp cache}, Dir.tmpdir)

つっつきボイス: 「Rails勉強会@東京で話題になってたので」「nobuさんはRubyのコミッターだからRailsにコミットするのは確かに珍しいかも」「それ用のメソッドがRubyにあるから使おうよ、ってことですね」

なおRubyとRailsの両方で活発に活動している方はAaron Patterson氏を始め結構います。

[インタビュー] Aaron Patterson(前編): GitHubとRails、日本語学習、バーベキュー(翻訳)

Rails

active_record-mti: PGネイティブの継承テーブルをARで使うgem

Rails勉強会@東京で評判がよかったやつです。

# TwilightCoders/active_record-mtiより
ActiveRecord::Schema.define(version: 20160910024954) do

  create_table "accounts", force: :cascade do |t|
    t.jsonb    "settings"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", inherits: "accounts" do |t|
    t.string "firstname"
    t.string "lastname"
  end

  create_table "developers", inherits: "users" do |t|
    t.string "url"
    t.string "api_key"
  end

end

つっつきボイス: 「MTI: Multiple Table Inheritance」「PostgreSQLにテーブル継承できる機能があるのは知ってたし、6系の頃に使ったことあったけど多重継承できるのか↓: READMEのサンプルでは多重継承してないけど↑」「いやほんと、ぽすぐれのテーブル継承、普通に使えるし便利やで?」「STIだとカラム増えすぎるんですよね」

CREATE TABLE boatcar () INHERITS (boat,car);

「やっぱりぽすぐれいいな」「ビュー(データベースビュー)もいいし: Railsにcreate view、ほすぃ~」「同意」「同意」

ついでに9.6でこんな記述を見つけました。

親テーブル上の検査制約と非NULL制約はその子テーブルに自動的に継承されます。 他の種類の制約(一意性制約、プライマリキー、外部キー制約)は継承されません
PostgreSQL 9.6 – 5.8. 継承より

Rails: STI(Single Table Inheritance)でハマったところ

active_mocker: ARのモックを生成するgem

これもRails勉強会で話題でした。

# 同リポジトリより
require 'rspec'
require 'active_mocker/rspec_helper'
require 'spec/mocks/person_mock'
require 'spec/mocks/account_mock'

describe 'Example', active_mocker:true do

  before do
    Person.create # stubbed for PersonMock.create
  end

end

つっつきボイス: 「schema.rbが更新されるとモックがfailする、と」「ARでDBアクセスしないなら当然速くなるな」「ただDBMSに入れてloadし直すことによって挙動変わる場合はどうなるんだろう」「fasterなテストとして普段はactive_mocker: trueを回しておいて、定期実行では普通にDB使って回す方が、DBMSが型変換したりするケースとかも考えれば安全そう」

RubyWeeklyの「2017年人気記事リスト」(Ruby Weeklyより)

年の瀬を感じる企画ですね。TechRachoで翻訳した記事もいくつか見当たりました。


rubyweekly.comより


つっつきボイス: 「英語圏のネット系マガジンは軒並み1月までお休みですね: さすがクリスマス最優先な文化圏」「TechRachoでもこの企画やればいいのに」「来週やりましょう!」

get_schwifty: ActionCableでRailsビューの一部をバックグラウンドレンダリング(Ruby Weeklyより)

# 同リポジトリより
# app/cables/calculator_cable.rb
class CalculatorCable < BaseCable
  def fibonacci
    n = SecureRandom.rand(30..40)
    calculated = calculate_fibonacci(n)
    stream partial: "calculator/fibonacci", locals: { calculated: calculated, n: n }
  end

  private

  def calculate_fibonacci(n)
    return n if n <= 1
    calculate_fibonacci( n - 1 ) + calculate_fibonacci( n - 2 )
  end
end

つっつきボイス: 「これとよく似たgemがあったなー: render_asyncだ」「TechRachoで記事にしてました↓」

Rails: render_async gemでレンダリングを高速化(翻訳)

「render_asyncは素直にjQueryで遅延loadingしていてとてもわかりやすい: その代わりStreamとかはできない」
「そしてこちらのget_schwiftyはActionCable使ってStream APIで転送するので、render_asyncよりもさらにいい感じに出せる: ただしTCPセッション消費するからworker枯渇が怖いけど」

技術的負債調査のポイント10個


codeclimate.comより


つっつきボイス: 「こちらCode Climateブログの記事です」「お、Code Climateブログって記事本数は少ないけど質がとっても高いんで信頼できる: だいたいほぼ文句付けようのないレベル」「私も読んでて同じこと思ったので次で過去記事掘り起こしてみました」

古典技術記事探訪: CodeclimateやSemaphore.ciブログ

いずれも2014年の記事ですが、今も通用しそう。


つっつきボイス: 「1は例の定番記事『肥大化したActiveRecordモデルをリファクタリングする7つの方法』で一番多かった質問『どうしてクラスメソッドでできることをわざわざインスタンスメソッドにするの?』に答えたものだそうです」「前にも話したけど、クラスメソッドで作ったものを後でインスタンスメソッドに変えるのはほんとつらい」

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

「4の図↓もよかったので」「さっきのactive_mockerとかは、まさにこのピラミッドの左下を目指すためのもの」「単体テストほど軽く速くするということですね」


codeclimate.comより

Faktory: バックグラウンドジョブサーバー

ちょっと検索しにくい名前ですが。


contribsys/faktoryより

Q. productionで使えますか?
A. なにぶん新しいプロジェクトなので、リスクを許容できるのならば。APIが安定したら1.0をリリースする予定で、自分では安定していると思います。

Q. FaktoryにはRedis必要ですか?
A. 不要です。Faktoryはスタンドアロンの64ビットLinuxバイナリであり、ジョブを回すのにFaktoryワーカープロセスが必要です。「Redis -> Sidekiq」 == 「Faktory -> Faktory worker」という関係です。

Changelog: 技術系Podcast

上のPodcastもここです。Rails勉強会でこのPodcastをチェックしている人が結構いたので。


changelog.comより


つっつきボイス: 「みっちり文字起こししているところが凄いんですよ」「こういうのがありがたいっすね: 検索でも見つけやすいし」

Decoratorを比較


  • 素のdecorator
  • Module + Extend + Super decorator
  • Plain Old Ruby Object decorator
  • Class + Method Missing decorator
  • SimpleDelegator + Super + Getobj decorator

thoughtbotの記事です。2015年ですがまとまりがよかったので。


つっつきボイス: 「ちょっと前に見つけた別のDecorator記事が構成的にちょっと残念だったので」「うんうんそういうのよくあるw」

Railsガイドで怖気づいた人向けの攻略方法


sihui.ioより


つっつきボイス: 「いっぺんに読もうとすると挫折するので最初に全体像を把握しよう的なアドバイスですね」「まーでもわかる: 最近のRailsガイドは情報てんこ盛りでガイドという感じでなくなりつつあるかなぁ」

モブプログラミング


codeclimate.comより

codeclimate.comの2014年の記事です。公式サイトには来年4月にマサチューセッツ州でカンファレンスもあるそうです。


つっつきボイス: 「ペアプログラミングの次はモブプログラミング」「モブプロって、書いている人は自分の意見を交えないルールだったかな」「集団二人羽織的な?」

参考: モブプログラミング – Woody Zuill氏とのインタビュー

Ruby trunkより

Kernel#ppが2.5で標準に

11月のウォッチでお伝えしたKernel#ppがその後本採用になっていました。

net/protocol、net/smtp、tempfile、tmpdirを誰もメンテしないなら自分がやる

_ko1さん激賞のnormalpersonさんです。


つっつきボイス:net/protocolって初めて見た」

参考: Ruby HTTPクライアントの比較表

Ruby

awesome-ruby.com: 定番gemまとめ情報


markets/awesome-rubyより

1ページに全部書いてあるところが便利そうです。
同じ名前のニュースサイトがあって少々紛らわしいですが。

Light Cable: Railsなしで使えるActionCable実装

# 同リポジトリより
Rack::Builder.new do
  map '/cable' do
    # You have to specify your app's connection class
    use LiteCable::Server::Middleware, connection_class: App::Connection
    run proc { |_| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
  end
end

つっつきボイス: 「こういうAC実装が出てくるのは理解できる: Rackだけ使いたいときとか」「例のEvil Martiansがスポンサーになってますね」

Rubyからsymbolをなくせるか(Ruby Weeklyより)

# 同記事より
{"foo" => 1}[:foo] == 1 # trueだったらいいのに
{foo: 1}["foo"]    == 1 # trueだったらいいのに

つっつきボイス: 「だからHashWithIndifferentAccessが欲しくなる」「記事で引用されてるこれほんに↓: SmalltalkだとSymbolはStringを継承しているのにRubyはそうじゃない」

Rubyコードを関数型プログラミングっぽく書いてみた

# 同記事より
module APIDataCommons
  extend self

  def band_names(data)
    user_data(data)
      .fetch('favorite_bands')
      .map { |b| b['name'] }
  end

  def name(data)
    user_data(data).fetch('name')
  end

  private
    def user_data(data)
      data.fetch("user")
    end
end

つっつきボイス: 「あまり関数型っぽいコードには見えないかな?: あえて言うならmethod chainingをふんだんに使ったPromise的なコード」「社内Haskell勢のツッコミが待たれる」

JavaScript: 5分でわかるPromiseの基礎(翻訳)

Rubyデザインパターンとサンプルコード総ざらえ

説明は抑えめで、図とRubyコード中心です。


bogdanvlviv.github.ioより

class Task
  attr_accessor :name, :parent

  def initialize(name)
    @name = name
    @parent = nil
  end

  def get_time_required
    0.0
  end
end

つっつきボイス: 「1ページに収まっているので辞書的に便利そう」

JSON仕様「RFC 8259」と「ECMA-404 2nd Editon」リリース、UTF-8必須に


つっつきボイス: 「今話題のやつ」「404だとNot Foundに見えてしまうと誰かツイートしてた」「RFC 8259の方もどことなく蟹さんマークのNICチップっぽい名前🦀


卜部さんがこの仕様を元にすごい勢いでJSONパーサー作ってました。

Rubyでリサジュー曲線


つっつきボイス: 「何だか懐かしい感」「リサジュー曲線を見たのがとても久しぶりだったので」

参考: リサジュー図形

ずっと「リサージュ」と思い込んでました(´・ω・`)

SQL

Postgres Weeklyも1月までお休みだそうです。

PostgreSQL 10の互換性のない変更点(Postgres Weeklyより)

記事の日付は10リリース前の「16 May 2017」ですが、リリースノートと見比べるときによさそうです。


つっつきボイス: 「これは見ておくべき情報!」「ありがたい🙏

PostgreSQLのインデックス(Postgres Weeklyより)


citusdata.comより


  • B-Tree
  • Generalized Inverted Index (GIN)
  • Generalized Inverted Seach Tree (GiST)
  • Space partitioned GiST (SP-GiST)
  • Block Range Indexes (BRIN)
  • Hash

つっつきボイス: 「これも大事っすね: B-Treeで間に合うことが多いけど、データの種類や性質に応じたインデックスを選ばないとインデックスろくに効かなくなったりする」

俺のPostgreSQLチートシート(Postgres Weeklyより)


つっつきボイス: 「チートシートというほど網羅的ではないかな」「『俺のチートシート』ですね」「ポスグレって\で始まるコマンド体系がとっつき悪くって」「sudo -u postgres createuser --interactiveみたいに、名前にpgが入っていないcreateuserとかがシステム系コマンドっぽく見えてしまうのも残念」
「正直、MySQLのコマンド体系の方が親切だった分、PostgreSQLの普及が遅れた気がします」「まあ慣れればぽすぐれの方がコマンド短いから入力速いし: 結局ググるけどな!」

JavaScript

GrimmerとReactを公平に比較してみた

<!--元記事より-->
<button onclick={{action setRandomAnimal}}>Set Random Animal</button>
<ul>
  {{#each randomAnimals key="@index" as |animal| }}
    <li>{{animal}}</li>
  {{/each}}
</ul>
// 元記事より
import Component, { tracked } from '@glimmer/component';

const animals = ["Cat", "Dog", "Rabbit"];

export default class extends Component {
  @tracked randomAnimals = [];

  setRandomAnimal() {
    const animal = animals[Math.floor(Math.random() * 3)];

    this.randomAnimals = this.randomAnimals.concat(animal);
  }
}

つっつきボイス: 「GrimmerだとVue.jsっぽく書けるのか: コンパクトなのはちょっとよさそう」

npmパッケージを自作する(JavaScript Liveより)

{
  "name": "masks-js",
  "version": "0.0.1",
  "description": "A NPM package that exports functions to mask values.",
  "main": "build/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/brunokrebs/masks-js.git"
  },
  "keywords": [
    "npm",
    "node",
    "masks",
    "javascript"
  ],
  "author": "Bruno Krebs",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/brunokrebs/masks-js/issues"
  },
  "homepage": "https://github.com/brunokrebs/masks-js#readme"
}

つっつきボイス: 「npm作るコマンドとかあるのでgem作るのと同じぐらいの感覚: ↑こういうdescription書くのが面倒だけど」「gemspecもいろいろ書かないといけないですしね」

CSS/HTML/フロントエンド

HTTP Early HintsがRFC 8297に

Early Hintsの生みの親のkazuhoさん自らのツイートです。心なしかアバターが微笑んで見えます。

アクセシビリティのテストツール

aXepa11yGoogleChrome accessibility-developer-toolsなどを紹介しています。

子要素にフォーカスしたまま親要素を表示する

See the Pen :focus-within helpful a11y thing by Chris Coyier (@chriscoyier) on CodePen.

フロントエンドテクの紹介記事です。上のCodePenでマウスオーバーするとわかります。

Firefox Quantumが速くなった理由


hacks.mozilla.orgより


つっつきボイス: 「FirefoxというかMozilla組の追い上げ半端ないですね」「Mozilla Foundationそのものは緩く統括しているだけですけどね」

参考: 爆速進化したブラウザ「Firefox Quantum」は何がどう変化したのか?

その他

GeForce/TITANのデータセンター利用について

来年2月にChromeに広告ブロック機能を実装


リモートつっつきボイス: 「Googleが広告ブロック機能を提供しちゃうのか…」

CVEは誰でも出せる

番外

英米の名門校がずらり

体重も測れる超小型計測センサー

もしかしてエネルギー問題解決?

10平方ミクロン程度のグラフェン(原子一個分の薄さの黒鉛)から、腕時計を駆動できるほどの電力が得られる可能性があるそうです。

なお、グラフェンの製造が難しくて研究が遅れていたのが、あるとき黒鉛にセロテープを貼って引っぺがすだけで簡単に作れることがわかって一気に研究が進んだそうです。

参考: 驚異の素材グラフェンの「ゆらぎ」が、無尽蔵のクリーンエネルギーを生むかもしれない:米研究結果

これは凄い


つっつきボイス: 「おおぉ」「MIDIキーボードって実はこういうことするのにとても向いていますね」「物理コントローラが豊富にあるし」「MIDIもプロトコルとして成熟してるし」

藁で作った創作動物

https://layer13.tumblr.com/post/168746265269/ithelpstodream-in-northern-japan-the-wara-art


今週は以上です。

バックナンバー(2017年度)

週刊Railsウォッチ(20171215)Ruby 2.5.0-rc1リリース、Ruby 2.4.3セキュリティ修正、Ruby 3.0で変わるキーワード引数、HTML 5.2 RECリリースほか

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

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

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

WebサイトをPWA(Progressive Web App)に変える簡単な手順(翻訳)

$
0
0

概要

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

Webサイトを簡単にPWA(Progressive Web App)に変える方法(翻訳)

Progressive Web Appって何?

PWAは基本的にWebサイトの一種で、モバイルでアクセスするときにユーザーのデバイスに保存可能で、ネイティブアプリと同じように振る舞います。読み込み画面を備えていて、ChromeのUIを非表示にでき、接続が切れてもコンテンツが表示されます。最大のメリットはユーザーのエンゲージメント(engagement)を促進することです。AndroidのChromeブラウザ(他のモバイル用ブラウザについてはわかりませんが)がそのWebサイトをPWAとして検出すると、アプリをユーザーの好みのアイコンでデバイスのホームスクリーンに保存するようダイアログで促します。

PWAが重要な理由

PWAはクライアント向けビジネスで有利です。中国版Amazonとも呼ばれるAlibabaは、ブラウザにWebサイトをインストールするよう促すダイアログを表示するとユーザーのエンゲージメント率が48%向上することに注目しました(引用元)。

このことから、PWAに取り組む価値は十分あります。

このメリットを可能にしたのは、静的アセット(HTML、CSS、JavaScript、JSONなど)をユーザーのシステムに保存できるService Workerと呼ばれる技術です。これは、インストールされたWebサイトの振る舞いを記述するmanifest.jsonに沿って行われます。

PWAの例

以下のサイトは、本記事で説明しているのと同じ技術を使って私が作成したものです。

その他についてはpwa.rocksで多数の例をご覧いただけます。

セットアップ

WebサイトのPWA化は一見大変そうですが(Service Workerって何よ?)、それほど難しくありません。

1. 必要なもの: HTTPS(HTTPではなく)

PWAの一番面倒な部分は、http://ではなくhttps://を使うセキュアなドメインでWebサイトを動作させることです。
手動のHTTPS化は非常に面倒ですが、自前のサーバーを使っていればletsencryptを使って実に簡単かつ自動的にHTTPS化できます。しかも無料です。

2. ツール

2.1 lighthouse test

  • lighthouse testはGoogleが開発およびメンテナンスしている自動化テストサイトであり、Progressive、Performance、Accessibilityの3つの尺度に基いてWebサイトをテストします。各項目の点数はパーセントで表示され、それぞれ解決方法も示してくれます。

didiermotte.beをLighthouse testにかけた結果

2.2 realfavicongenerator.net

realfavicongenerator.netはPWAの表示レイヤの面倒を見てくれます。上述のmanifest.jsonファイルを生成し、Webサイトをモバイルデバイスに保存するときに必要な全バージョンのアイコンや、ページの<head>タグに追加するHTMLスニペットも生成してくれます。

注意: このサイトはアセットをサブフォルダに置くことを勧めますが、この方法だとPWA化が非常に面倒になります。作業を軽減するには、すべての画像やmanifestファイルをWebサイトのルートフォルダに置きます。

2.3 upup.jsでサービスワーカーを使う

Service WorkerはJavaScriptの技術です。私の頭では理解するのがつらいのですが、ありがたいことにとある賢いドイツ人女性が教えてくれたTal Atler氏(「オフライン優先」哲学を推進しています)が作成してくれたあるJavaScriptライブラリは、überへの接続が切れたときにもうまく振る舞えるようにしてくれます。Ola Gasidloさん、ダンケシェーン!

UpUpチュートリアルでさっと学ぶだけですぐ使えるようになります。

3. 手順

  1. RealfavicongeneratorでHTMLと画像のコードを生成し、自分のWebサイトに追加する
  2. HTTPSドメインを公開する
  3. lighthouse testを実行する
  4. 結果を分析する
  5. 問題をひとつずつ修正する
  6. 手順3.に戻って繰り返す
  7. どの項目もほぼ100点になるまで繰り返す: Progressiveは100点を目指す
  8. モバイルで実際の動作をテストする: うまくいけばAndroidで以下のようなポップアップが画面下部に表示され、Webサイトをモバイルのホームスクリーンに保存するよう促す

もっと知りたい方へ

必要なPWAの情報は以下の本にすべて盛り込まれています。

PWAで幸せになりましょう!

関連記事

CSS GridがBootstrapよりレイアウト作成に向いている理由(翻訳)

Railsのルーティングで多数のHTTP OPTIONSをうまく扱う方法(翻訳)

$
0
0

概要

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

: 本記事のようにルーティングでアスタリスク*を使うと、rails routesの出力ですべてのURLの網羅が保証されなくなります*の部分は展開なしで出力される)。大規模案件などでルーティングの見落としなどにつながる可能性もあるため、採用の際は今後の改修上のリスクなどの考慮が必要と考えられます。

Railsのルーティングで多数のHTTP OPTIONSをうまく扱う方法(翻訳)

Photo by Natalia Y on Unsplash

RailsのAPIセットアップ周りがいかに強力であっても、OPTIONSHEADといった他のHTTPメソッドのハンドラを実装しようとすると困ってしまいます。ルーティングするエンドポイントごとにこういうあまり使われないHTTPメソッドを指定するときは特にそうです。たとえばRailsのルーティングにsessionsというAPIリソースがあるとします。

namespace :api do
  namespace :v1 do
    resources :sessions
  end
end

Railsでは以下のルーティングが定義されます(ルート名やヘルパに応じて)。

GET     /api/v1/sessions(.:format)
POST    /api/v1/sessions(.:format)
GET     /api/v1/sessions/:id(.:format)
PATCH   /api/v1/sessions/:id(.:format)
PUT     /api/v1/sessions/:id(.:format)
DELETE  /api/v1/sessions/:id(.:format)

Railsでは、これ以外のマイナーなHTTPメソッド(OPTIONSHEADなど)はデフォルトですべてスキップします。しかしそれももっともです。マイナーなHTTPメソッドはめったに使われませんし、その必要が生じるとしても1つか2つのエンドポイントぐらいしかないので、既存のRailsルーティングAPIで問題は生じません。こうしたマイナーなHTTPメソッドの追加は次のように簡単にできます。

namespace :api do
  namespace :v1 do
    resources :sessions do
      collection do
        match '', via: :options, action: 'options'
      end
    end
  end
end

上で生成されるルーティングは次のようになります。

GET     /api/v1/sessions(.:format)
POST    /api/v1/sessions(.:format)
GET     /api/v1/sessions/:id(.:format)
PATCH   /api/v1/sessions/:id(.:format)
PUT     /api/v1/sessions/:id(.:format)
DELETE  /api/v1/sessions/:id(.:format)
OPTIONS /api/v1/sessions(.:format)

しかし、すべてのルーティングでHTTP OPTIONSHEADなどのハンドラが欲しいときはどうするのでしょうか?ルーティングひとつひとつにOPTIONSエンドポイントを追加して回るなど、とうていやってられません。

私たちの場合、クライアントへの通知用にAPIのリクエスト/レスポンスのJSON(hyper)schemaをHTTP OPTIONSで公開したいと思っていました。私たちが公開したいこの同じスキーマは、APIのテストにも使われるので、スキーマは(ドキュメントよりも)常に更新されます。

インターフェイスAPIを設計する

私はインターフェイスを新しく設計する場合、常にインターフェイスのAPIを最初に設計します。どんなAPIが理想的でしょうか。

class Api::V1::UsersController < ApplicationController
  before_action :authenticate_user!

  def create
    # POST /api/v1/usersのレスポンスハンドラ
  end

  def show
    # GET /api/v1/users/:idのレスポンスハンドラ
  end

  options do
   # OPTIONS /api/v1/usersのオプション本体を公開する
   # OPTIONS /api/v1/users/:idのオプション本体を公開する
  end
end

ブロックを受け取るクラスメソッドが1つあればよさそうです。最終的にそのメソッドがほとんどの静的データを扱うことになります。ブロック内部では、collection(/users)とresource(/users/:id)のルーティングを区別できる必要があります。

options do
  if is_index?
    # OPTIONS /api/v1/usersのオプション本体を公開する
  else
    # OPTIONS /api/v1/users/:idのオプション本体を公開する
  end
end

インターフェイスAPIを実装する

ありがたいことに、Railsには制限を加えながらすべてのルーティングにマッチさせる方法が用意されています。この場合、OPTIONSのHTTPメソッドを持つすべてのルートにマッチさせたいと思います。これを行うには、Railsルートに以下を追加するだけです。

match '*path', {
    controller: 'application',
    action: 'options',
    constraints: { method: 'OPTIONS' },
    via: [:options]
  }

次にapplicationコントローラで、このルーティングを扱うメソッドを定義し、URLに応じて適切なcontroller#optionsに委譲する必要があります。

def options
  # 適切なcontroller#optionsブロックに委譲する
end

今回の作業で最も面倒なのは、URLで示されたリソースをURLに基いてどのコントローラで扱うかを特定する部分です。Railsのrecognize_pathでは、関連付けられるメソッドを特定するのに2つのパラメータ(URLと、URLで使う実際のHTTPメソッド)が必要だからです。しかしこの場合は異なるHTTPメソッド(OPTIONS)を使うので、それなりに試行錯誤が必要です。

def route_details_for(url)
  [@route_details](http://twitter.com/route_details "Twitter profile for @route_details") ||= begin
    methods = [:get, :post, :put, :patch, :delete]
    method = methods[0]
    tries ||= 0
    route_details = nil
    begin
      route_details = Rails.application.routes.recognize_path(url, method: method)
      raise ActionController::RoutingError, '' if route_details[:action] == 'route_not_found'
      rescue ActionController::RoutingError => _
      method = methods[tries]
      retry unless (tries += 1) == 5
    else
      return route_details
    end
  end
end

試行錯誤の結果はそこそこシンプルになりました。URLを受け取り、それに関連付けられるコントローラとメソッドの情報をrecognize_pathで得られるまでループします。

その結果、{:controller => "api/v1/searches", :action => "create" }という形になり、関連するコントローラに変換して定数化しやすくなりました。

def controller_for(url:)
  route_details = route_details_for(url)
  name = route_details[:controller].titleize.gsub('/', '::').gsub(' ','')
  return "#{name}Controller".constantize
end

当初のAPI設計の実装の最後の部分は、関連付けられるコントローラ(controller_forで見つかります)からのオプションブロックを呼び出すことです。コントローラのこのメソッドをそのまま呼び出すこともできますが、より望ましいのはブロックのコンテキストを変更することです。それなら、ブロック内部のparamsなどに基いてリクエストコンテキストにアクセスできるようになります。これはRubyのBasicObjectメソッドであるinstance_execを使ってできます。これは、呼び出し元のコンテキストでlambdaprocやブロックを呼び出すことができます。このささやかなマジックについて詳しくはこちらをご覧ください。

def options
  return head :ok if controller_for(url: request.url).options.nil?

  return render({
    json: instance_exec(
      route_details_for(request.url),
      &controller_for(url: request.url).options
    )
  })
end

ここでは、recognize_pathから得たroute_detailsもパラメータとして渡していますが、これはブロックで必要になった場合に便利だと思って念のため渡しただけです。最終的に、当初の設計に極めて近い結果が得られました。

options do |route_details|
  if route_details.dig(:id)
    # /api/v1/usersのJSON (Hyper) schemaを公開する
  else
    # /api/v1/users/:id\のJSON (Hyper) schemaを公開する
  end
end

gemのバージョンについて

私たちのアプリでコンポーネント間コミュニケーション方法としてAPIが標準になるに連れて、このパターンがよく使われるようになりました。そこでこれをrails_http_optionsというgemにまとめました。

次の3つの手順で、好みのメタデータ(スキーマなど)をクライアントに公開できます。

  1. ApplicationControllerrails_http_optionsincludeします。これによって、HTTP OPTIONSリクエストの扱いを想定したoptionというpublicメソッドが追加され、その他にprivateメソッドもいくつか追加されます。
  2. 使いたいHTTP OPTIONSをrails_http_optionsで扱えるルーティングを追加します(必要ならconstrainsをもっと厳しくすることもできます)。
match '*path', {
  controller: 'application',
  action: 'options',
  constraints: { method: 'OPTIONS' },
  via: [:options]
}
  1. HTTP OPTIONSが意味のあるbodyを持つresourceにoptionsブロックを追加します。
options do
  {
    schemas: {
      accepts: Company.json_schema,
      returns: Company.json_schema
    },
    meta: { max_per_page: 100 }
  }
end

ブロック内では、リクエストされたリソースを指す関連スキーマを見つける方法を特定する必要があります。通常は、モデルのクラスメソッドやService Objectを使えばよいでしょう:)

関連記事

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

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

Railsのsecret_key_baseを理解する(翻訳)

RailsのCSRF保護を詳しく調べてみた(翻訳)


Ruby 2.5.0リリース!NEWSを読んでみた

$
0
0

Ruby 2.5.0リリースおめでとうございます。

取り急ぎNEWSを斜め読みしてみました。誤りを含んでいるかもしれないので気づき次第修正します。
主要な変更点についてはCRubyコミッタの村田さんによる記事「Ruby 2.5.0 リリース直前!何が変わるのかもう一度おさらいしておこう!」で詳しく解説されています。NEWSには載っていませんが、以下の記事で解説されているNODEとGCの切り離しも大きな変更だと思います。


2.4.0リリース以降の変更点

言語面の変更

  • トップレベルの定数参照を削除(Feature #11547
  • do/endブロック内でrescue/else/ensureを利用できるようになった(Feature #12906

  • 文字列の式展開にrefinementsが効くようになった(Feature #13812

コアクラスの変更点(主なもののみ)

Array

Data

  • 非推奨化: C拡張の基本クラスであり、Rubyレベルに公開する必要がないため(Feature #3072

Exception

  • 新メソッド:
    • Exception#full_messageで例外のString表現を取り出せるようになった: キャッチされていない例外をRubyが出力するときと同じフォーマットが使われる(Feature #14141 — experimental)

Dir

  • Dir.globに新しいオプションキーワード引数:baseを追加 (Feature #13056
  • 次のメソッドでGVLが解放されるようになった: Dir.chdir(ブロック引数なしの場合)、Dir.openDir.newDir.mkdirDir.rmdirDir.empty?
  • 新メソッド:

Enumerable

  • 以下のメソッドで引数に正規表現パターンを直接与えられるようになった (Feature #11286
    • Enumerable#any?
    • Enumerable#all?
    • Enumerable#none?
    • Enumerable#one?

追記: 以下も渡せるようです。#===に渡せるものならいけそうです。

[1, 2, 3].any?(1) # => true
[1, 2, 3].any?(Numeric) # => true

Rubyの===演算子についてまとめてみた

File

  • File.open:newlineオプションを与えると暗黙でテキストモードになるようになった(bug #13350
  • File::Constants::TMPFILEオプション付きで開いたファイルがFile#pathIOErrorがraiseされるようになった(Feature #13568
  • GVL(Giant VM Lock)がFile.statFile.exist?およびrb_stat()を使うその他のメソッドで解放されるようになった(bug #13941
  • File.renameでGVLが解放されるようになった(Feature #13951
  • 以下のメソッドで秒より細かいタイムスタンプをWindows 8以降でサポート(Feature #13726
    • File::Stat#atime
    • File::Stat#mtime
    • File::Stat#ctime
  • File::Stat#inoFile.indentical?でWindows 8.1以降のReFS 128bit inoをサポート(Feature #13731
  • 以下のメソッドでGVLが解放されるようになった
    • File.readable?
    • File.readable_real?
    • File.writable?
    • File.writable_real?
    • File.executable?
    • File.executable_real?
    • File.mkfifo
    • File.readlink
    • File.truncate
    • File#truncate
    • File.chmod
    • File.lchmod
    • File.chown
    • File.lchown
    • File.unlink
    • File.utime
    • File.lstat
  • 新メソッド:

Hash

IO

IOError

  • IO#closeが例外をraiseする場合のメッセージを「stream closd」から「stream closed in another thread」に改良した(bug #13405

Integer

  • Integer#stepでstep値が#>0と比較できない場合に#coerceメソッドからのエラーを隠蔽しなくなった(Feature #7688
  • Integer#roundInteger#roundInteger#floorInteger#ceilInteger#truncateが常にIntegerを返すようになった(bug #13420
  • Integer#powが指数算出用の剰余(modulo)を引数に取れるようになった(Feature #12508)(Feature #11003
  • 新メソッド

Kernel

Method

  • 新メソッド:
    • Method#===Proc#===と同様Method#callを呼ぶ)(Feature #14142

Module

  • Module#attrModule#attr_accessorModule#attr_readerModuleattr_writerがpublicになった(Feature #14132
  • Module#define_methodModule#alias_methodModule#undef_methodModule#remove_methodがpublicになった(Feature #14133

Numeric

  • 数値比較演算子(<<=>=>)が内部で#coerceメソッドからの例外を隠蔽しないようになった: coertionが不可能な場合は#coercenilを返す(Feature #7688

Process

  • getrusage(2)が存在する場合のProcess.timesの精度を改善(Feature #11952
  • 新メソッド:

Range

  • Range#initializeで開始値と終了値の#<=>の比較で“bad value for range” ArgumentErrorがraiseされた場合に例外を隠蔽しないようになった: 今後は#<=>呼び出しからの例外をそのまま通す(Feature #7688

Regexp

  • Onigmo 6.1.3-669ac9997619954c298da971fcfacccf36909d05にアップデート
  • 非包含演算子(absence operator)をサポート(#82
  • Unicodeの絵文字関連の文字プロパティを新たに5つサポート

RubyVM::InstructionSequence

  • 新メソッド:
    • RubyVM::InstructionSequence#each_child
    • RubyVM::InstructionSequence#trace_points

String

  • unfreezeされた文字列がString#-@でdupされないようになった: 既にfrozenされた文字列については互換性のため動作は変更されない(Feature #13077
  • -“リテラル”(String#-@)が同じオブジェクトを返すよう最適化された(ruby 2.1以降の"リテラル".freezeと同じ)(Feature #13295

  • String#casecmp/String#casecmp?は引数が文字列でない場合にTypeErrorをraiseする代わりにnilを返すようになった(bug #13312
  • String#start_with?で正規表現を引数に取れるようになった(Feature #13712
  • 新メソッド
    • String#delete_prefixString#delete_prefix!Feature #12694
    • String#delete_suffixString#delete_suffix!Feature #13665
    • 書記素(grapheme)クラスタを列挙するString#each_grapheme_clusterString#grapheme_clustersを追加(Feature #13780
    • String#undumpString#dumpされた文字列をunescapeする)(Feature #12275

Struct

  • Struct.newkeyword_init: trueオプションを与えるとメンバをキーワード引数として初期化できるようになった(Feature #11925

RegexpString

  • Unicodeバージョンを9.0.0から10.0.0にアップデート(Feature #13685

Thread

  • Thread#name=で設定したdescriptionセットがWindows 10で表示されるようになった
  • 新メソッド
  • Thread.report_on_exceptionがデフォルトでtrueになった: unhandled例外の終了スレッドを$stderrに出力する(Feature #14143

Time

  • Time#atで、第2引数の単位を第2引数で指定できるようになった(Feature #13919

KeyError

FrozenError

Stdlibの更新(主なもののみ)

Bundler

BigDecimal

  • BigDecimal 1.3.4に更新
  • 以下の機能を追加:
    • BigDecimal::VERSION
  • 以下の機能を非推奨化(1.4.0で削除予定)
    • BigDecimal.new
    • BigDecimal.ver
  • BigDecimal#clone#BigDecimal#dupで新規インスタンスを作成せずにレシーバ自身を返すようになった。

Coverage

  • ブランチカバレッジとメソッドカバレッジの測定をサポート(Feature #13901)。ブランチカバレッジはどのブランチが実行されていてどれが実行されていないかを通知する。メソッドカバレッジはどのメソッドが呼び出され、どのメソッドが呼び出されていないかを通知する。この新機能を使ってテストスイートを実行すると、どのブランチやメソッドがテストで実行されているかがわかるようになり、テストスイートのトータルなカバレッジをより厳密に評価できるようになる。

Coverage.startオプションで測定対象を指定できる。

Coverage.start(lines: true, branches: true, methods: true)

Rubyファイルの読み込みがある程度進むと、Coverage.resultでカバレッジの結果を取れる。

Coverage.result
#=> { "/path/to/file.rb"=>
#     { :lines => [1, 2, 0, nil, ...],
#       :branches =>
#         { [:if, 0, 2, 1, 6, 4] =>
#             { [:then, 1, 3, 2, 3, 8] => 0,
#               [:else, 2, 5, 2, 5, 8] => 2
#             }
#         },
#       :methods => {
#          [Object, :foo, 1, 0, 7, 3] => 2
#       }
#     }
#   }

結果のうち行カバレッジは変更されない(数値を含む単なるarray)。つまり各行のカウントは実行されたかnilかを表し、その行とは関連しない。

ブランチカバレッジの結果は次の形式を取る。

{ (jump base) => { (jump target) => (counter) } }

jump basejump targetはフォーマットされる。

[type, unique-id, start lineno, start column, end lineno, end column]

たとえば[:if, 0, 2, 1, 6, 4]は、2行目1カラム目から6行目4カラム目までの範囲のifステートメントを示し、[:then, 1, 3, 2, 3, 8]は3行目2カラム目から3行目8カラム目までの範囲のthen句を示す。

メソッドカバレッジは次の形式を取る。

{ (method key) => (counter) }

method keyはフォーマットされる。

[class, method-name, start lineno, start column, end lineno, end column]

たとえば[Object, :foo, 1, 0, 7, 3]は1行目0カラム目から7行目3カラム目の範囲にあるObject#fooを示す。先の例ではObject#fooは2回呼び出されている。

メモ: 互換性を保つため、オプションを付けないCoverage.startは行カバレッジだけを返し、オプションを付けないCoverage.resultは従来のフォーマットで結果を返す。

Coverage.result
#=> { "/path/to/file.rb"=> [1, 2, 0, nil, ...] }

DRb

  • ACL::ACLEntry.newIPAddr::InvalidPrefixErrorが抑制されないようになった

ERB

  • ERB#result_with_hashを追加: Hashオブジェクトで渡されたローカル変数を持つテンプレートのレンダリング(Feature #8631
  • erbコマンドでテンプレートファイルのデフォルトエンコーディングをASCII-8BITからUTF-8に変更(bug #14095
  • trim_modeが指定されている場合に改行(carriage return)が正しく削除されるようになり、Windowsで重複した空行が削除されるようになった(bug #5339)(bug #11464

IPAddr

  • IPAddrで不正なアドレスマスクを受け取らなくなった(bug #13399
  • IPAddr#ipv4_compatIPAddr#ipv4_compat?が非推奨化(bug #13769
  • 新メソッド追加:

IRB

  • バックトレースやエラーメッセージの逆順出力(Feature #8661 — experimental)
  • (experimental)binding.irbで自動的にirbがrequireおよび実行されるようになった(Feature #13099
  • binding.irbの呼び出し時にソースの前後が表示されるようになった(Feature #14124

Matrix

  • 新メソッド:
    • Matrix.combineMatrix#combineFeature #10903
    • Matrix#hadamard_product
    • Matrix#entrywise_product

Net::HTTP

  • Net::HTTP.newno_proxyパラメータをサポート(Feature #11195
  • Net::HTTP#min_versionNet::HTTP#max_versionFeature #9450
  • HTTPステータスクラスを追加
  • Net::HTTP::STATUS_CODESがHTTP Status Code Repositoryとして追加(Misc #12935
  • Net::HTTP#proxy_userNet::HTTP#proxy_passで、システムの環境変数がマルチユーザーで安全な場合にhttp_proxy環境変数を反映するようになった(bug #12921

open-uri

  • URI.openメソッドがopen-urlのKernel.openのエイリアスとして定義された。open-urlのKernel.openは今後非推奨化。

OpenSSL

  • Ruby/OpenSSLを2.0から2.1にアップデート。変更点はext/openssl/History.mdの「Version 2.1.0」に記載。

Pathname

Psych

  • Psych 3.0.2にアップデート
    • フォールバックオプションをキーワード引数に変換(#342
    • Psych.loadPsych.safe_loadJSON#parseと同様の:symbolize_namesオプションを追加(#333)(#337
    • Psych::Handler#event_locationを追加(#326
    • frozen string literal = trueに設定(#320
    • 時間のdeserializeでタイムゾーンオフセットを保持(#316
    • syck gemの非推奨メソッドエイリアスを削除(#312

RbConfig

  • RbConfig::LIMITS: C型の上限を指定。rbconfig/sizeofrequireすることで利用可

Ripper

  • Ripper::EXPR_BEGなど(Ripper#stateで使用)
  • メソッド追加
    • Ripper#stateスキャナのステートを指定(Feature #13686

RDoc

  • RDoc 6.0.1にアップデート
  • IRBベースのlexerをRipperに置き換えてドキュメント生成速度を大幅に向上(#512
    • ドキュメント生成速度が大幅に向上。
    • 今後の新文法のサポートが容易に。
  • 過去数年分のRubyの新機能を多数サポート
  • frozen_string_literal: trueの利用: ドキュメント生成時間を5%短縮。
  • did_you_meanのサポート。

Rubygems

SecureRandom

  • 新メソッド:
    • SecureRandom.alphanumeric

Set

StringIO

  • StringIO#writeで複数の引数を扱えるようになった

StringScanner

  • 新メソッド:
    • StringScanner#size/StringScanner#captures/StringScanner#values_atFeature #836

WEBrick

Zlib

  • Zlib::GzipWriter#writeで複数の引数を扱えるようになった
  • Procオブジェクトをbodyレスポンスとしてサポート(Feature #855
  • RubyGemとしてリリース(Feature #13173
  • Kernel#openで意図しない振る舞いを回避(Misc #Kernel#open

互換性の問題(featureやbug修正を除く)

  • Net::HTTP
    • Net::HTTP#startがデフォルトで:ENVp_addrに渡すようになった(bug #13351)これを回避するには明示的にnilを渡す
  • Socket
    • IO#read_nonblockIO#write_nonblockO_NONBLOCKファイルデスクリプションフラグを副作用として設定しないようになった(Linuxのみ)(Feature #13362
  • Random
    • Random.raw_seedがシード以外にも利用できるようになったことでRandom.urandomにリネームされた(bug #9569
  • Socket
  • ConditionVariableQueueSizedQueueを高速化のため再実装したことでStructのサブクラスでなくなった(Feature #13552

Stdlibの互換性問題 (featureやbug修正を除く)

  • Gem化(Gemification)

    • 以下の標準ライブラリをデフォルトgemへ移行促進。
      • cmath
      • csv
      • date
      • dbm
      • etc
      • fcntl
      • fiddle
      • fileutils
      • gdbm
      • ipaddr
      • scanf
      • sdbm
      • stringio
      • strscan
      • webrick
      • zlib
  • Logger
    • Logger.new(“| command”)が意図せずコマンドを開いていた。現在は禁止され、Logger#initializeは仕様どおりString引数だけをファイル名として扱うようになった(bug #14212)。
  • Net::HTTP
    • Net::HTTP#startがデフォルトで:ENVp_addrに渡すようになった(bug #13351)。これを回避するには明示的にnilを渡す。
  • mathn.rb
  • Rubygems
    • ubygems.rbファイルをstdlibから削除(Ruby 1.9以降は不要)

C APIを更新

プラットフォームのサポート変更

実装の改善

  • Hashクラスのハッシュ関数がSipHash13に変更された(ユーザーからは見えないかもしれないが念のため)(Feature #13017
  • SecureRandomでOpenSSLよりもOS提供のソースが推奨されるようになった(bug #9569
  • ミューテックスを書き直してサイズ縮小&高速化(Feature #13517
  • lazyなProc割り当てによってブロックを用いたブロック渡しのパフォーマンスを向上(Feature #14045
  • traceインストラクションの代わりにTracePointフック用の動的instrumentationでオーバーヘッドを回避(Feature #14104
  • ERBのテンプレートからのコード生成がRuby 2.4の倍速くなった。

その他の変更

  • (experimental)STDERRがttyから変更されていない場合にバックトレースやエラーメッセージを逆順で出力(Feature #8661
  • (experimental)STDERRがttyから変更されていない場合にエラーメッセージをボールド/アンダーラインテキストで出力(Feature #14140
  • –with-ext設定オプションで引数が省略不可になった: たとえば./configure –with-ext=openssl,+を付けて実行するとOpenSSLライブラリをコンパイルすることを保証し、そうでない場合はビルドが異常終了する。

ただし引数の末尾に必ず,+を付けなければならないことに注意。これを指定しないとOpenSSLだけがビルドされる(Feature #13302

関連記事

Ruby 2.4.0がリリースされました!

Ruby 2.1.0リリース!注目の新機能を見てみましょう

Ruby 2.0.0リリース! – キーワード引数を使ってみよう

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

$
0
0

概要

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

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

前書き

本記事は、フロントエンドのフレームワークに依存しないRailsプレゼンテーションロジックを現代的かつモジュール単位かつコンポーネントベースで扱う方法を独断に基いて解説するガイドです。3部構成のチュートリアルで、例を元に最新のフロントエンド技術の最小限に学習し、Railsフロントエンド周りをすべて理解しましょう。

Part 2Part 3もご覧ください。

混乱しがちな部分

今新たに実地のRailsフルスタック開発者になるということは、混乱の渦中で頑張るということです。Asset Pipeline/Sprockets/CoffeeScript/Sassを用いてフロントエンドを扱う「昔ながらのRails」wayは、2017年ともなると色褪せて見えます。Rails 3.1時代に選択された技術の多くは、現代の想定に応えられていません。昔ながらの手法にこだわり続ければ、この5年間にフロントエンド界隈で起こったあらゆる新技術を見送ることになってしまいます。全てを統べるJavaScriptパッケージマネージャnpmの台頭、頼もしいJavaScript文法であるES6の隆盛、連勝を重ねるBabelトランスパイラビルドツールかつてないほど成長を続ける CSSプリプロセッサPostCSS。フロントエンドのコードを「ページ」から「コンポーネント」にパラダイム変換したReactやVueなどのフロントエンドフレームワークが驚異的な成功を収めていることは申し上げるまでもありません。

これほどまでに複雑になったフロントエンド技術を1人の開発者の頭に押し込めようとすれば(キャリアの浅い人はなおさら)、認知機能が悲鳴を上げていわゆるJavaScript疲労になるのがオチです。

しかし、確立されたワークフローに疑問を感じるのは、時代に取り残された気持ちになるからとか、フロントエンド屋との技術トークがどんどん通じなくなるからとか、将来の見通しに不安を感じるからという理由だけではありません。つまるところ、プログラマーとは理性的な人間なのです。

Asset Pipelineの何が問題だったのか

昔ながらの方法が今も使えることについては触れないことにしましょう。今でも、Rails標準のフロントエンド向けセットアップ(とCoffeeScript)を使って成果を出せます。ビューテンプレート、スクリプト、スタイルシートは従来同様Asset Pipelineで結合/最小化されて配信されます。本番環境では2つの(少なくとも人間には)読めないファイルの形を取るので、重要性は変わりません。

しかし、開発者は普通次のような点に心を配っています。

  • 分離され、再利用可能かつテスト可能な、理解しやすいコードであること
  • コードの変更->結果の表示を短いサイクルで繰り返せること
  • 依存関係の管理が面倒でないこと
  • ツールがちゃんとメンテされていること

言うまでもなく、「昔ながらの」Railsは私たちのコードに何らかの構造を与えます。ビューテンプレート、JavaScript、スタイルシート、画像ごとに個別のフォルダが用意されます。しかしフロントエンドが複雑になればなるほど、これらを追っているうちに認知能力が低下(訳注: 参考)してしまいます。

「昔ながらの」フルスタックRails wayをおそろかにすれば、たちまちCSSやJSは死んだコードのグローバルなゴミ捨て場と化してしまうでしょう。

乗り換えを検討する理由の中には「スピード」もあります。Sprocketが遅いという問題については山ほどドキュメントがありますし、Herokuにいたっては、Asset Pipelineのパフォーマンス最適化方法に特化したガイドまで公開しています。そこではRailsアプリのデプロイで最も時間がかかるのがアセットの扱いであることを認めています。曰く「平均すると、依存関係をbundle installでインストールするときよりも20倍以上遅い」。

開発中にCSSを1行変更してページを再読み込みするときも、結果が表示されるまでに多少待たされます。この待ち時間はすぐに長くなります。

依存関係についてはどうでしょうか。Asset Pipelineを常に最新に保つのは大仕事です。プロジェクトにJavaScriptライブラリを1つ追加する場合、CDNから読み込んだコードをコピペしてapp/assetslib/assetsvendor/assetsに置くか、誰かがgem化してくれるまでぼんやり待つ方法があります。その間にも、JavaScriptコミュニティは同じことをnpm installコマンド、今ならyarn addコマンド一発で管理しています。アップデートも同様です。YarnはJavaScriptをBundlerのように便利に扱うことができます。

最後は、Asset Pipelineを支えるビルドツールであるSprocketsです。Sprocketsの最近のメンテ状況ははかばかしくありません。

Sprocketsはかれこれ5年以上ほこりを被ったままです(左)。同じ時期のWebpackは頻繁に更新されています(右)。

風向きが変わった

2017年、DHHとRailsコミュニティはフロント周りの変更に手を付け始めました。Rails 5.1ではwebpacker gemによるWebpack統合、Yarnを介したnode_modules、すぐに使えるBabel/React/Vue/PostCSSが(その気になればElmも)導入されました

しかしAsset PipelineとCoffeeScriptは今も彼らがメンテしています。rails newだけでプロジェクトを開始すると、昔ながらのRailsになります。JavaScript関連のトピックをググるときには、相変わらずコード例を脳内トランスパイルして隅々まで理解しなければなりません。

くよくよすることはありません。今日のRailsアプリではあらゆる現代的な手法を利用可能です。本シリーズで私たちと一緒に基礎を固めましょう。Rails/JavaScript/CSSの基本的な知識が多少あれば十分始められます。本シリーズでは設定やツールを最小限に抑えるため、最新のRails 5.1以降の機能も積極的に使います。

本チュートリアルのシリーズでは、Evil Martiansで培われた現代的で練り上げられたフロントエンド構築ベストプラクティスの一部を皆さまにご紹介いたします。

心の壁

Reactは私たちにコンポーネントで思考するよう指導します。その他の現代的なフロントエンドフレームワークもこれに準じています。モジュラリティは、BEMをはじめとする主なCSS方法論を支える哲学です。モジュラリティのコンセプトは「UIを構成するあらゆる論理的な部品は自己完結(self-contained)すべきである」というシンプルなものです。

Railsでは、ビューを論理的な部品に分割する方法が組み込まれています(ビューのパーシャル)。しかしパーシャルがJavaScriptに依存すると、おそらく他の現代的なコンポーネントと同様にapp/assets/javascriptsの下にある深いフォルダにアクセスしなければならなくなります。

パーシャルを使うときに一切合財をまとめることができれば、各パーシャルのスクリプトやスタイルシートを1箇所にまとめられるのではないでしょうか?

これからご紹介するアプローチは、React/Vue/Elmのアーキテクチャに依存していません。そしてそのように作られています。自分が使うツールの利用法を気兼ねなく学ぶことができますが、急いで学ぶ必要はありません。既にRailsで使えるツールを現代的なフロントセットの思考様式に徐々に合わせていけばよいのです。

Sass vs. PostCSS

SassはRailsに愛されています。しかし私たちの心はPostCSSに傾きつつあります。何より、PostCSSはRailsでCSSを処理するRuby組み込みのSassより36.4倍高速です。PostCSSは100%純粋なJavaScriptで書かれており、多くのプラグインを使って簡単に拡張/カスタマイズできます。cssnextというプラグインは、ブラウザでサポートされていない機能のポリフィルをすぐ使うことができますが、必要がなければ使わなくてよいのです。理由があれば、好みのプリプロセッサの上でPostCSSを使うこともできます。

私たちは何を作っているのか?

そろそろ実際に手を動かさないといけませんね。フロントエンドの新しいアプローチをご紹介するために、最小限の認証とActionCableを用いた何の変哲もない標準的なチャットアプリを作ることにします。アプリ名はevil_chatとしました。このサンプルアプリは複雑ではありませんが、それでも「フルスタック」の経験に十分なぐらいに洗練されています。

私たちのプロジェクトでは、Asset PipelineやデフォルトのRailsジェネレータが生成する大量の.scssファイルや.coffeeファイルたちとおさらばすることにします。テンプレートエンジンは従来どおりERBを使い、好みに応じてSlimやHamlを使える余地を残しています。

A new frontend folder in your app

左に見えるのがEvil Frontのフォルダ構造です

後でまたこのフォルダ構造を振り返ります。アプリのトップレベルにあるfrontendフォルダの中ですべてのことを行います。app/assetsは完全に置き換えられます。

一発ですべて理解できなくても問題ありません。ひとつずつ手順を追って進めましょう。

プロジェクトの開始方法

rails newだけでは不要なものを切り落とせないので、次の新しいマジックコマンドを使います(アプリ名はevil_chatとします)。

$ rails new evil_chat --skip-coffee --skip-sprockets --skip-turbolinks --webpack --database=postgresql -T

ご覧のとおり、CoffeeScriptやSprockets関連の機能は不要になります。本チュートリアルではテスティングまではカバーしないので、-Tオプションでテストファイル作成をスキップしています。作成後Herokuにすぐデプロイできるよう、--database=postgresqlでPostgreSQLを指定しています。

一番肝心なのは--webpackオプションです。これを指定することで、Webpackですべてのアセットをバンドルするwebpacker gemがRailsで使われるようになります。

  • node_modulesフォルダにはJS依存ファイルがすべて含まれます(誤って余分なファイルを大量にリポジトリにコミットしないよう、.gitignoreにも追記されます)。
  • package.jsonはすべての依存関係を宣言します。yarn.lockも同様なので、npm installではなく(より機能豊富な)yarn addでパッケージを追加できます。
  • .babelrcファイルは、ES6を、マーケットシェア1%以上のあらゆるブラウザに準拠するJavaScriptコードに変換します。
  • .postcssrc.ymlpostcss-smart-importプラグインやpostcss-cssnextプラグインで設定済みです。これらのプラグインのおかげで、cssnextに記載されている全機能を利用できます。

まだ何か忘れているような気がします。特に、Autoprefixerなどのツールのbrowserslistグローバル設定は、今後コードを正しくクロスブラウザ互換処理するうえで必要です。ありがたいことに、プロジェクトのルートディレクトリに以下のファイルを作成すれば簡単に修正できます。

$ touch .browserslistrc

おかげで要求される知識が随分少なくなりました(まったくというわけにはいきませんが)。

それではこのファイルを開いて> 1%という1行を追加しましょう。これさえ知っておけばブラウザの互換性を保てます。

最初の段階で正しくやっておきたいことがもうひとつあります。Railsジェネレータのデフォルトの振る舞いの再設定です。既にネタバレしていますが、app/assetsには今後何も置く必要がないので、以下の手順でこのフォルダを削除します。application.rbを開いて以下の行を追記します。

# config/application.rb
config.generators do |g|
  g.test_framework  false
  g.stylesheets     false
  g.javascripts     false
  g.helper          false
  g.channel         assets: false
end

Asset Pipelineに引導を渡すときが来ました。app/assetsフォルダを削除します。

しかし置き換え方法はどうすればよいのでしょうか。次の手順を実行します。

  • rails new--webpackオプションを追加したことでapp/javascriptというフォルダが作成されています。このフォルダをプロジェクトのルートに移動してフォルダ名をfrontendに変更します(名前は好みで構いませんが、frontendが一番わかりやすいと思います)。移動の際フォルダの中身は変更しないようにします。frontend/packsの中にあるapplication.jsは、このアプリのWebpack「エントリ」ポイントとして使われます。
  • application.html.erbを開き、javascript_include_tag "application"javascript_pack_tag "application"に置き換えます。メソッド名の単語が1つ変わることですべての違いが生じます。include_tagは、SproketsでコンパイルされたアプリレベルのJavaScriptファイルに参照を1つ挿入する昔ながらの方法ですが、pack_tagは先のエントリポイント(つまりfrontend/packs/application.js)から生成されたWebpackバンドルが使える新しい方法です。この段階で、<head>にあるpack_tag<body>の末尾、つまりyieldステートメントの直前に移動します。

  • stylesheet_link_tag 'application', media: 'all'stylesheet_pack_tag 'application'に置き換えます。WebpackとES6のimportステートメントの助けを借りて、今後はCSSをコンポーネント単位で使うことにします。これにより、CSSもすべてWebpackで管理されます。

  • 次に、バンドルするファイルを探索する場所をWebpackerに指定する必要があります(デフォルトのフォルダをリネームしたので)。Webpacker 3.0によると、Railsのconfigフォルダ内にあるwebpacker.ymlで設定できます。このファイルの最初の数行に、以下の要領でアプリのフォルダ構造の変更を反映します。

default: &default
  source_path: frontend
  source_entry_path: packs
  public_output_path: packs
  cache_path: tmp/cache/webpacker
  • ERBのパーシャルもfrontendフォルダ配下に置かれるので、application_controller.rbで次のように指定しないとコントローラから見つけられません。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  # 以下を書くだけでよい
  prepend_view_path Rails.root.join("frontend")
end
  • Webpackerが3.0になったことで、開発中にオンデマンドでアセットをコンパイルするための別プロセスが不要になりました。しかしJSコードやCSSコードの変更時にページを自動更新するには、rails sするときに従来同様webpacker-dev-serverを実行しておく必要があります。そのためのProcfileが必要なので作成しましょう。
$ touch Procfile

このファイルに以下の設定を書きます。

server: bin/rails server
assets: bin/webpack-dev-server

このProcfileを置くことで、Foremanなどのツールを使ってすべてのプロセスをコマンド一発で起動できるようになりますが、Evil MartiansのHivemindをぜひおすすめいたします。Hivemindの兄貴分であるOvermindは実行中のプロセスを一時停止せずにpryでデバッグできるツールですので、こちらもご覧ください。

訳注: 同社は昔のSFになぞらえた命名を好んでいるようです。Hivemindは集合精神、Overmindは「幼年期の終わり」に登場する神っぽい宇宙規模の集合知性を指します。

スモークテスト

それでは新しいセットアップが正しいかどうかテストしましょう。application.jspacksの下にあります)にシンプルなDOM操作コードを少し足し、Webpackerでちゃんと動くようにします。最初に、基本となるコントローラとデフォルトルーティングをそれぞれ生成する必要があります。

$ rails g controller pages home
# config/routes.rb
Rails.application.routes.draw do
  root to: "pages#home"
end

views/pages/home.html.erbの中身は完全にからっぽにしてください。次にapplication.jsの中身もからっぽにし、以下のコードに置き換えます。

// frontend/packs/application.js
import "./application.css";

document.body.insertAdjacentHTML("afterbegin", "Webpacker works!");

同じフォルダにapplication.cssファイルを作成し、CSSも効いていることを確認できるようにします(CSSはPostCSSで処理されます)。

/* frontend/packs/application.css */
html, body {
  background: lightyellow;
}

いよいよアプリの初立ち上げです。Hivemindをインストール済みであることが前提です。インストールしない場合はforemanなどのプロセス管理ツールをお使いください(私たちとしてはぜひHivemindの素晴らしさをご検討いただければと思います)。

$ hivemind

ではhttp://localhost:5000を開きましょう(Hivemindは$PORTに5000を設定するので、Railsも同じ環境変数で実行ポートを決定します)。次のように表示されるはずです。

A smoke test for our app

真っ赤なエラー画面が出ていなければ成功です

ここでWebpackのクールな点を1つご紹介します。application.jsファイルで"Webpacker works!"の部分を変更して保存すると、ブラウザの[更新]ボタンを押さなくても画面が自動更新されます。

実際のコードを書き始める前に、コーディングスタイルを定めましょう。

ところでJSのlintはどうする?

Prettierには著名なエディタがすべて統合されているので、ボタンひとつでコードを整形できます。ESLintにもあらゆる主要なエディタ向けのプラグインがあるので、即座に結果をビジュアル表示できます。

JavaScriptの文法は年単位で更新を繰り返すので、コーディングスタイルは多岐にわたり、書き始める前からスタイルが混乱しがちです。たとえばセミコロン使う派と使わない派の争いは終わりそうにありません。StandardPrettierのように、独自の色を出すコードフォーマッタもあります。私たちはPrettierにしました(Prettierはデフォルトでセミコロンを使いますが、いつでもオフにできます)。

lintはESLintである程度自動化するので、コーディングスタイルは常にチェックされます。また、メンテしやすいJSコードを書くベストプラクティスを豊富に収録しているAirbnbのJavaScriptスタイルガイドにも頼ることにします。

訳注: AirbnbのJavaScriptスタイルガイド日本語版は以下をどうぞ

JavaScriptスタイルガイド 1〜8: 型、参照、オブジェクト、配列、関数ほか (翻訳)

現時点ではwebpack-dev-serverしか含まれていないpackage.jsondevDependenciesを少々追加します。JavaScriptのlintに必要な部分をカバーすると、以下のような感じになるはずです。

{
  "name": "evil_chat_codealong",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "^3.0.1"
  },
  "devDependencies": {
    "webpack-dev-server": "^2.9.1",
    "babel-eslint": "^8.0.1",
    "eslint": "^4.8.0",
    "eslint-config-airbnb-base": "^12.0.1",
    "eslint-config-prettier": "^2.6.0",
    "eslint-import-resolver-webpack": "^0.8.3",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-prettier": "^2.3.1",
    "lint-staged": "^4.2.3",
    "pre-commit": "^1.2.2",
    "prettier": "^1.7.3"
  }
}

lint-stagedpre-commitは、後でgit addgit commitなどにフックするときに便利です。これは、残念なコードを誤ってリポジトリに登録しないようにするためのものです。

最後の仕上げに、ルートフォルダに.eslintrcファイルが必要です。適用するルールをESLintに指示するのに使います。

$ touch .eslintrc

.eslintrcに以下を書きます。

{
  "extends": ["eslint-config-airbnb-base", "prettier"],

  "plugins": ["prettier"],

  "env": {
    "browser": true
  },

  "rules": {
    "prettier/prettier": "error"
  },

  "parser": "babel-eslint",

  "settings": {
    "import/resolver": {
      "webpack": {
        "config": {
          "resolve": {
            "modules": ["frontend", "node_modules"]
          }
        }
      }
    }
  }
}

"extends"キー以下の要素の順序は重要です。ここで、最初にAirbnbのルールを適用し、Prettierのフォーマットガイドとコンフリクトするものがあれば常に最新のものを使うようESLintに指示しています。eslint-import-resolver-webpackへの依存を指定する"import/resolver"キーも必要です。これによって、JSファイル内のimportで指定したライブラリが、Webpackが管理するフォルダ(このアプリの場合はfrontendフォルダ)内に実際に存在するようになります。

CSSはどうする?

CSSにも何かlintツールが必要です。評判のよいツールであるnormalize.cssをnormalizeに使うことにします。CSSのエラーやスタイル違反の検出用にstylelintも使います。package.jsonに開発用の依存ライブラリを2つ追加しましょう。

"devDependencies": {
    ...
    "stylelint": "^8.1.1",
    "stylelint-config-standard": "^17.0.0"
  }

プロジェクトのルートフォルダに.stylelintrcを置いてlinterに指示する必要もあります。

$ touch .stylelintrc

ファイルの中身は次のとおりです。

{
  "extends": "stylelint-config-standard"
}

package.json(今度はdevDevdependenciesではありません!)の"dependencies"キーの配下に次のようにnormalize.cssを追加します。

"dependencies": {
    "@rails/webpacker": "^3.0.1",
    "normalize.css": "^7.0.0"
  },
  ...

次はgit hooksをいくつか導入し、git commit時に自動チェックが走るようにしましょう。package.json"scripts"を追加します。

...
"scripts": {
    "lint-staged": "$(yarn bin)/lint-staged"
  },
  "lint-staged": {
    "config/webpack/**/*.js": [
      "prettier --write",
      "eslint",
      "git add"
    ],
    "frontend/**/*.js": [
      "prettier --write",
      "eslint",
      "git add"
    ],
    "frontend/**/*.css": [
      "prettier --write",
      "stylelint --fix",
      "git add"
    ]
  },
  "pre-commit": [
    "lint-staged"
  ],
  ...

これで、コミット時にstagedファイルのエラーがすべてチェックされ、自動整形されます。

最終的なpackage.jsongistにあるような形になるはずです。ターミナルでyarnを実行して依存ライブラリをインストールします。

自動lintを早く使ってみたくてウズウズしているかと思います。frontend/packs/application.jsを開いてセミコロンを削除してから、git add . && git commit -m "testing JS linting"を実行すると、ただちにセミコロンが復元されます。だらしないコーディングスタイルともこれでお別れです。

Linterの設定

設定がすべて正しければ、プロジェクトのルートファイルにこれらのファイルがすべてあるはずです

最初のコンポーネント(React未使用)

本ガイドのPart 2で扱う内容の一部を軽くご紹介します。最初のコンポーネントを作ってみましょう。

最初にapplication.cssを削除します。これはスモークテスト以外では不要です。同様にapplication.jsのコードもすべて削除します。ここからは、application.jsにはimportステートメントだけを書きます。このエントリポイントにすべてが集約されます。アプリ全体で使うCSSやJavaScriptの置き場所が必要なので、作ってみましょう。このフォルダの名前はinitにしました。

$ mkdir frontend/init
$ touch frontend/init/index.js
$ touch frontend/init/index.css

続いてエントリポイント内部の新しいフォルダの登録が必要です。packs/application.jsに以下を追加します。

// frontend/packs/application.js
import "init";

新しいファイルに入れるコードも必要です。以下はinit/index.jsです。

// frontend/init/index.js
import "./index.css";

以下はinit/index.cssです。

/* frontend/init/index.css */
@import "normalize.css/normalize.css";

body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
}

ここでは、アプリのすべてのフォントに一般的なスタイルをいくつか適用しています。initフォルダはバンドル時に最初にチェックされる場所なので、ここにnormalize.cssをインクルードするのが自然です。同じフォルダを使って、後でポリフィルやエラー監視など、私たちのコンポーネントに直接関係のない機能をセットアップして必要が生じたら可能な限り読み込むこともできます。

initは特殊ケースです。このコンポーネントについてはどうでしょうか。

各コンポーネントは、3つのファイル(ERBパーシャルとそれ用のJS/CSSファイル)を含む1つのフォルダで構成されます。

私たちのコンポーネントはすべて、frontend配下のcomponentsフォルダ内に置かれます。ここに最初のコンポーネントを作成しましょう。レイアウトのテンプレートとみなしてこのコンポーネントをpageと呼ぶことにします。

$ mkdir -p frontend/components/page
$ touch frontend/components/page/{_page.html.erb, page.css, page.js}

私たちがこのコンポーネントのJSファイルをindex.jsと呼んでいないことにご注意ください。この名前はinitフォルダで予約されているからです。後でエディタでタブをいくつも開いたときに見つけやすいよう、JSファイルにはコンポーネントと同じ名前を付けることにしています。他のチュートリアルではindex.jsを使っていることが多いので、この手法はあまり見かけないかもしれませんが、後でコードを書くときに時間を大きく節約できます。

まだ私たちのコンポーネントに関連するJSロジックがないので、page.jsにはCSSファイルをimportする行しかありません。

// frontend/components/page/page.js
import "./page.css";

page.cssの方にはコンポーネントに関連するスタイルがあります。

/* frontend/components/page/page.css */
.page {
  height: 100vh;
  width: 700px;
  margin: 0 auto;
  overflow: hidden;
}

最後の_page.html.erbにはマークアップが含まれています。ERBや、コンポーネント同士をネストするのに使うyieldステートメントもすべて使える点にご注目ください。

<!-- frontend/components/page/_page.html.erb -->
<div class="page">
  <%= yield %>
</div>

application.jsimport "components/page/page";を追加して、新しいコンポーネントを参照できるようにするのをお忘れなく。

A structure for the first component

この時点でのfrontendフォルダ構造

それではhome.html.erbビューにERBコードを書いてみましょう。

<!-- app/views/pages/home.html.erb -->
<%= render "components/page/page" do %>
  <p>Hello from our first component!</p>
<% end %>

最初のコンポーネントが実際に動作することを確認しましょう。サーバーを再び起動してページを更新します。うまく行けば、以下のように表示されるでしょう。

A structure for the first component

最初のコンポーネントのブラウザ表示とコンソール出力

おめでとうございます。チュートリアルPart 1はこれでおしまいです。ぜひPart 2もご覧いただき、アプリの体裁を整えてチャット関連の機能に必要なコンポーネントを導入するところまでやってみてください。少ないコード量でコンポーネントをレンダリングするヘルパーや、コンポーネント作成を自動化するジェネレータも追加します。


Part 1 | Part 2 | Part 3

スタートアップをワープ速度で成長させられる地球外エンジニアよ!Evil Martiansのフォームにて待つ。

関連記事

Rails 5.2ベータがリリース!内容をざっくりチェックしました

5.1 beta1リリースノートに見るRails 5.1の姿

Vue.jsサンプルコード(27)選択肢に応じて入力項目を切り替える

$
0
0

27. 選択肢に応じて入力項目を切り替える

  • Vue.jsバージョン: 2.5.2
  • 上部の3つのラジオボタンをクリックすると、下の入力方法が変更されます。
    • 「クレジットカード」: [番号]と[年月日]フィールドが表示されます。年月日はDatePickerでも入力できます。
    • 「銀行振込」: [口座]フィールドが表示されます。
    • 「チケット」: [コード]フィールドが表示されます。
  • 画面をリロードすると最初の状態に戻ります。

サンプルコード


バックナンバー(Vue.jsサンプルコード)

Vue.jsサンプルコード(01〜03)Hello World・簡単な導入方法・デバッグ・結果の表示とメモ化

正規表現をチョムスキー言語学まで遡って理解する(翻訳)

$
0
0

概要

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

訳注: 原文タイトルは「正規表現を背後で支える言語学を理解する」といったニュアンスです。翻訳記事タイトルでは内容を把握しやすくするため「チョムスキー」を加えました。

正規表現をチョムスキー言語学まで遡って理解する(翻訳)

正規表現は、新人/ベテランを問わずプログラマーに恐怖心を呼び起こします。私が最初に正規表現(しばしばregexと略記されます)というものを目にしたときも、()だの*だの文字だの数字だので構成された祈祷書を読んでいるうちにめまいがしてきたのを覚えています。正規表現はナンセンスで理解不能なものに思えたのです。

正規表現は、高度なコンピュータサイエンス(CS)学科で扱われるのだろうと思っていました。それならば正規表現に取り組む気になったことでしょう。しかし私が正規表現に出会ったのは、学部4年になるまで先延ばしにしていた入門クラスだったのです。このコースの目的は、暗号学/人間とコンピュータのインタラクション/機械学習の概念を紹介することで、コードを1行も書いたことがない学生をCSに引きずり込むことでした。ところで機械学習はこの中で唯一の最新かつ最大の技術系バズワードですね。

レクチャーには数回ぐらいしか出席していませんでしたが、その中で出された課題の1つで私は頭を抱えてしまいました。CSに影響を与えたコンピュータサイエンティストか学者についてエッセイを1本書かなければならなかったのです。そのときはノーム・チョムスキーを選択しました。

そのときは知る由もありませんでしたが、チョムスキーについて学ぶうちに私は正規表現というウサギの穴に再び引きずり込まれ、いつしか正規表現が魔法のごとく別の何かに姿を変える様子にすっかり魅了されてしまいました。私が正規表現で夢中になったのは、正規表現のパワーの源たる、同じ名前の言語的なコンセプトの方でした。

正規表現を背後で支える言語学、すなわちほとんどのプログラマーに知られていない背景を知ることで、皆さまも正規表現を好きになっていただければと思います。ここでは特定のプログラミング言語での正規表現の使い方を解説するつもりはありませんが、言語としての正規表現をご紹介することで、皆さまが選んだプログラミング言語で正規表現がどのように機能しているのかを深く追求するきっかけになれば幸いです。

まずはチョムスキーに話を戻しましょう。チョムスキーのどのあたりが正規表現に関係あるのでしょうか?そもそもチョムスキーがCSと何の関係があるのでしょうか?

なりゆきコンピュータサイエンティスト

Wikipediaにはノーム・チョムスキーは言語学者、哲学者、認知科学者、歴史家、社会批評家、政治活動家と記載されていますが、コンピュータサイエンティストとは書かれていません。チョムスキーはそれらのすべての分野で重要な業績を残しているため、CSへの間接的な貢献は見落とされがちです。

チョムスキーの学術上の業績を調べるうちに、チョムスキーのコンピューティングへの興味が偶然であったという思いが強くなりました。このことから、チョムスキーの業績は一見CSとは無関係に見えたとしても、分野を問わずコンピューティングやIT業界に何かしら寄与しているという私の信念が裏付けられました。

とりわけチョムスキーによる言語学方面の功績は、CSの学際的な研究が与えたインパクトの好例です。チョムスキー階層は、コンピュータサイエンティスト/ソフトウェアエンジニア/ホビイストが日常的に書いているコードに転換されました。

そう、CSに正規表現なるものをもたらしたのは、この階層という概念だったのです。しかしチョムスキーから正規表現への飛躍を理解する前に、チョムスキー階層の概要について説明します。

言語の規則と秩序

チョムスキー階層とは、形式文法の秩序化です。それぞれの文法が、階層上で上位の文法の真部分集合となるような、形式言語の統語論的(syntactic)な規則を考えてみましょう。ある形式言語の文法は他のものよりも厳密であることから、チョムスキーは形式言語を彼の名を冠した階層に編成することを追求しました。

先ほど、形式文法は統語論的な規則であるということについて簡単に触れました。この規則は、与えられた形式言語で有効なあらゆる句(phrase)を与えます。文法は、言語を作り上げる規則を提供します。言語学者の言い方を借りれば、ある言語の形式文法は、非終端(nonterminal: 入力または中間文字列値)を終端(terminal: 出力文字列値)に変換できるフレームワークを提供します。

この目新しい語彙を解明するために、既成の形式文法を用いて非終端の集合を終端に変換する例を1つお目にかけましょう。なんちゃって形式言語「Parseltongue」(訳注: ハリー・ポッターシリーズの「蛇語」のこと)には次の形式文法があるとします。

  • 終端: {s, sh, ss}
  • 非終端: {snake, I, am}
  • 生成規則: {I → sh, am → s, snake → ss}

この生成規則を使って、「I am snake」という文を「sh s ss」に変換できます。この変換は次のように部分ごとに行われます。「I am snake」→「sh am snake」→「sh s snake」→「sh s ss」

蛇語の例から、非終端文字列が形式文法によって終端のみの文字列に構文解析される様子がわかります。しかし形式文法は言語を生成するだけではなく、ある文字列が形式文法に一致するかどうかを認識する装置でもあります。例の「I am a snake」という文字列はもれなく終端に変換できますが、「I am not a snake」という文字列は蛇語では記述できません。非終端の「not」は蛇語の終端に翻訳できないからです。

大事なことなので2回書きます。形式文法は形式言語を生成します。これは、形式文法の階層を作り出すことによって、チョムスキーは言語そのものも分類したということです。

面倒な前書きはここまでにして、チョムスキー階層における4種類の形式文法を見てみることにしましょう。制約が最も強いものから順に次の種類があります。

正規文法
入力文字列から出力文字列までの過去の情報を保持しない
文脈自由文法
入力文字列から出力文字列までの、直近のステート情報のみを保持する
文脈依存文法
入力文字列から出力文字列までの過去のステート情報をすべて保持する
制限なし文法帰納的可算文法
ステート情報をすべて保持することで、与えられた入力文字列から想定可能なあらゆる出力文字列を作成できる

この「ステート情報(state knowledge)」とは一体何だかおわかりでしょうか。Wikipediaのスコープの記載に基いて考えてみましょう。たとえば正規文法は、入力文字列を出力文字列に変換する処理において、文字列の過去のステートに関する情報を自身の「スコープ」内で保持しません。これは、文法が個別に非終端を終端(およびゼロ以上の非終端数列)に変換した後は、文法は文字列の過去のステートを忘れてしまうということです。

一方、制限なし文法の場合は、翻訳する文字列のすべての可能な状態を保持します。文脈自由文法や文脈依存文法は、両者の中間のどこかに位置します。

チョムスキー階層

チョムスキー階層(res.cloudinary.com)より)

チョムスキー階層の文法を詳しく知りたければ、オートマトン理論をのぞいてみる必要があるでしょう。ここからは、正規表現に関連する文法を中心に解説します。この文法には「正規文法(regular grammar)」というぴったりな名前がつけられています。

正規表現ではどうなるか

正規表現と正規文法は同等です。両者の形式は異なりますが、どちらも同じ統語論的規則の集合を表現し、同じ正規言語を生成します。

言語学的には、正規表現は次のように再帰的に定義されます。

  • 空集合は正規表現である。
  • 空文字列は正規表現である。
  • 入力アルファベット中の任意の文字xについて、xは正規言語{x}を生成する正規表現である。
  • 選択(alternation): xyが正規表現であれば、x | yは正規表現である。たとえば正規表現0|1からは正規言語{0,1}が生成される。
  • 連結(concatenation): xyが正規表現であれば、x - yは正規表現である。たとえば正規表現0-1からは正規言語{01}が生成される。
  • 反復(repetition)(またはクリーネ閉包): xyが正規表現であれば、x*は正規表現である。たとえば正規表現0-1*からは正規言語{0, 01, 011, 0111, ...}が無限に生成される。

正規文法は、先ほどの蛇語のような規則の組み合わせでできています。ある正規文法を使って入力文字列を構文解析して出力文字列にできるように、正規表現もおおむね同じように文字列を変換できます。正規表現で採用されているこの「選択/連結/反復」操作(先のアナロジーで言うなら、規則)のさまざまな解析例があります。

ここで少しだけ親愛なるノーム・チョムスキーに話を戻しましょう。チョムスキーの文法階層や正規文法は、入力文字列を出力文字列に変換するときの手順に関する情報を一切保持しません。これが正規表現においてどのような意味を持つかおわかりでしょうか。

正規文法のこの「忘却」は、文字列のある部分の翻訳は、その後に翻訳される別の非終端部分の翻訳に何の影響も与えないということです。出力文字列の作成において、文字列のさまざまな部分が互いに影響することはありません。

正規文法を支える言語学から、最初にプログラマーたちが正規表現をコードに組み込んだ理由について洞察を得ることができます。ここまでは言語を生成/認識する形式文法についてのみ議論してきましたが、正規文法が入力文字列の断片を1つずつ出力文字列に変換するという事実によって、パターンマッチャーとして使うことができるのです。プログラミングにおける正規表現では、生成規則を用いて入力文字列(パターン)を正規言語(パターンにマッチする文字列の集合)に変換します。

しかし、プログラミング言語の作者たちが言語学の分野で定義されているとおりに正規表現を実装していたのであれば、私はこの記事を書くことはなかったでしょう。コンピュータの正規表現は、言語学でいち早く登場した正規表現とは似ても似つかないものになっていますが、本記事で解説した言語学の正規表現は、コードに含まれる正規表現を理解するための有用な枠組みを提供してくれます。

似ていて違う正規表現とregex

訳注: 「Two Regular Expressions, Both Alike in Dignity」はシェークスピアの「ロメオとジュリエット」の前口上のもじりです。

ここからは、言語学的な意味での正規表現を正規表現、プログラミングにおける正規表現をregexと使い分けることにします。言語学的な正規表現とプログラミングの正規表現はかなり違っているにもかかわらず、世間ではどちらも正規表現と呼ばれてしまっています。何とも紛らわしいことです。

正規表現とregexの違いは、利用法から生じたものです。正規表現(正規文法)は形式言語理論の一部であり、自然言語における共有要素を記述するために存在しています。自然言語とは、人間が計画的に設計したものではない、長年に渡って進化を繰り返している言語を指します。言語学者は正規表現という言葉を理論のために用います(チョムスキー階層における形式文法の分類など)。正規表現は、人間が話す言葉を言語学者が理解するうえで役に立ちます。

一方regexは、与えられたパターンにマッチする文字列を検索する目的でプログラマーたち毎日使っているものです。正規表現は理論に寄っていますが、regexは実用に即しています。プログラミング言語は形式言語ですが、人間(ここではプログラマーたち)によって設計された特定の目的のための言語です。お気づきの方もいらっしゃるかと思いますが、プログラミング言語の作者たちはコードのregexの機能を増強しました。どんな拡張が施されたのか見てみましょう。

正規表現には選択/連結/反復の3つの操作があることを思い出しましょう。私はregexのプロではありませんが(regexpertとでも言うんでしょうか?)、Wikipediaの正規表現をのぞいてみただけでも、regexには3つの操作以上のものが実装されていることがわかります。

たとえばPOSIXのregex文法を使う場合、.orkは「orkという3つの文字で終わる4つの文字すべて」にマッチします。このピリオド.は、単純な選択/連結/反復よりも強力だと思いますか?

違います。実を言うと、最も高度なregexメタ文字(regex操作を呼び出す文字)ですら、正規表現の操作から派生したものなのです。仮に、アルファベットに含まれる26個の小文字だけを含む正規文法があるとすると、regexパターン.ork[a|b|c|...|z]orkのように正規表現の操作だけで記述できます。

regexにはメタ文字がやたらめったらあるので正規表現よりずっと強力な操作セットを備えているように思えますが、メタ文字は正規表現を定義するさまざまな操作を置き換える便利なショートカットに過ぎません。regexのメタ文字は、選択/連結/反復のよくある組み合わせをプログラマー好みの形に抽象化したものを提供しています。

ここまではregexというものを、正規表現に便利なショートカットや明確なユースケースを加えたものとして表しました。しかし、チョムスキー階層を思い出してみれば、正規文法は規則の中でも最も制約が強いものであり、スコープを持たないのです。ありがたいことに、regexは言語学の先輩である正規表現に比べて少しばかりユルいので、実用面でより強力なものになっています。

正規文法規則からはみ出す

チョムスキー階層によれば、正規文法には入力文字列を出力文字列に変換する際の情報が保持されないことを思い出しましょう。正規表現は正規文法と同等なので、正規表現には文字列が入力から出力に変わるときの中間ステートを記憶する場所がありません。言い換えると、ある正規表現に含まれる非終端の翻訳は、正規表現の他の部分の非終端の翻訳に影響しないということでもあります。

regexではこのようになっていません。regexでは後方参照をサポートするために、正規文法の重要な特性に違反しています。後方参照(backreferencing)が使えることで、プログラマーは正規表現を丸かっこ()で区切ってメタ文字でその部分を参照できます。たとえば、(la)\1というパターンは「lala」という文字にマッチするときに\1というメタ文字を使って「la」という文字の検索を反復します。

正規表現において文字列の各部分は互いに影響を与えられないため、regexの後方参照は先輩よりもずっと強力です。さらに重要なのは、後方参照には、1行の中である語を誤って2回続けて書いてしまった場合にその誤りを検索するという実用的な使いみちがあるという点です。実用という観点から見ることで、正規表現がプログラミングのregexでどのように改変されたのかを洞察できます。

regexの機能をさらに高めているのは、いわゆる「マッチの欲張り具合」を変更できる機能です。regexパターンのカテゴリである量指定子(quantifier)にはさまざまなものがありますが、これらは見た目が似ていても文字列の一部にマッチするときの挙動が大きく異なります。欲張り量指定子/最長一致量指定子(greedy quantifier)である*は文字列に可能な限り長く一致しようとしますが、ものぐさ量指定子/最小一致量指定子(reluctant quantifier)である?は文字列の一致する長さを最小限にしようとします。「abcorgi」という文字列が与えられていると、パターンが.*corgiの場合は文字列全体にマッチしますが、パターンが.?corgiの場合は「bcorgi」だけにマッチします。

所有量指定子(posessive quantifier)である+は文字列に対して最長一致を試みますが、欲張り量指定子の*と異なり、最大一致を見つける際に文字列の直前の文字をバックトラックしません。「abcorgi」という文字列が与えられている場合、.*corgi.+corgiはどちらも文字列全体にマッチします。所有量指定子と欲張り量指定子の結果は多くの場合同じですが、所有量指定子はバックトラックを回避するので効率が高まる傾向があります。

量指定子はメタ文字なので、技術的には正規表現の3つの操作である選択/連結/反復を組み合わせて作ることも可能です。しかし、量指定子の作り出すシンプルな抽象化によって、プログラマーが欲しいマッチの種類を素早く指定することができます。

まとめと関連書籍

長旅お疲れさまでした!チョムスキーと彼の名を冠したチョムスキー階層について学び、正規文法を深く深く掘り下げました。そして正規文法から始まって、正規表現の言語学上の定義を調べました。最後に、プログラマーが日常的にregexを使いたくなるよう正規表現とregexの違いに着目しました。

本記事ではチョムスキーから現代的なプログラミング言語まで正規表現の歴史を辿りましたが、regexのお話はこれでおしまいではありません。言語学的な正規表現やコンピュータのregexをもっと学びたい方向けに、学習のはかどる問いかけをいくつかご用意いたしました。

  • オートマトン理論とは何か?チョムスキー階層とどのように関連しているか?
  • regexはどのように実装されているか?さまざまなregexアルゴリズムにはどのようなトレードオフが存在するか?
  • 文字列マッチや操作を行う組み込みライブラリではなくregexを使うのが適切なのはどのような状況か?

また、正規表現の言語学的側面やコンピュータに関連する要素を私が研究していたときに用いた資料のリストもご用意いたしました。regexでお楽しみください!

本記事をお楽しみいただけた方は、ぜひ[いいね]ボタンのクリックや記事の共有をお願いいたします。
不明な点やご意見がありましたら、Twitterかコメント欄までどうぞ。本記事はMediumで最初に公開されました。

関連記事

Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号

正規表現の先読み・後読み(look ahead、look behind)を活用しよう

Ruby正規表現の後読みでは長さ不定の量指定子は原則使えない

JavaScript: Parcel.jsでReact.jsプロジェクトを作成する(翻訳)

$
0
0

概要

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

Parcel.jsでReact.jsプロジェクトを作成する(翻訳)

Parcel.jsはJavaScript界隈に登場した新しいbundlerです。誕生後2週間も経たないうちにGitHubで★11,000以上も獲得しています。本記事はParcel.jsで最初のReactプロジェクトを作成するためのガイドです。

Parcel.jsは、(マルチコア処理で驚異的な高速性が実現されるまでは)JavaScript bundlerとして最速であることと「設定ゼロ」を誇っています。詳しくはParceljs.orgをご覧ください。


  • 1. Parcel.jsは、インストールするだけですぐ使えるようになります。
npm install -g parcel-bundler
  • 2. プロジェクトディレクトリで以下のコマンドを実行し、package.jsonファイルを作成します。
npm init -y
  • 3. ReactとReact-domをインストールします。
npm install --save react react-dom
  • 4. 開発用の依存ライブラリをインストールします。
npm install --save-dev parcel-bundler babel-preset-env babel-preset-react
  • 5. Reactのプリセットを使うための.babelrcを作成します。
// .babelrc

{  "presets": ["env", "react"]}
  • 6. package.jsonに起動スクリプトを追加します。
// package.json

"scripts": {  "start": "parcel index.html"}
  • 7. index.htmlを作成し、index.jsを指すscriptタグを追加します(Gist)。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>React-Parcel-Example</title>
  </head>

  <body>
    <div id="root"></div>
  </body>

  <script src="./index.js"></script>

</html>
  • 8. index.jsを作成し、コンポーネントのimportとページのレンダリングを行えるようにします(Gist)。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';

import ExampleApp from './ExampleApp';

ReactDOM.render(<ExampleApp />, document.getElementById('root'));
  • 9. コンポーネントファイルとCSSファイルを作成します。
    • ExampleApp.jsは画像とタイトルを持つコンポーネントを単に返します(Gist)。
// ExampleApp.js
import React, { Component } from 'react';

import './ExampleApp.css';

class ExampleApp extends Component {
  render() {
    return (
      <div className="App">
        <img
          className="App-Logo"
          src="https://user-images.githubusercontent.com/19409/31321658-f6aed0f2-ac3d-11e7-8100-1587e676e0ec.png"
          alt="Parcel Logo"
        />
        <h1 className="App-Title">React-Parcel Example</h1>
      </div>
    );
  }
}

export default ExampleApp;
  • ExampleApp.css(Gist
/* ExampleApp.css */

body {
  margin: 0;
  padding: 0;
  font-family: Roboto, 'Helvetica Neue', Arial, sans-serif;
}

.App {
  text-align: center;
  margin: 2rem auto;
}

img {
  height: 100px;
}
  • 10. 以上でおしまいです。Parcelのdevサーバーを起動すると変更が即座に反映されるようになります。
npm run start

ここでご紹介したコードはGitHubリポジトリでご覧いただけます。また、実際にサンプルアプリを見ることもできます。


お読みいただきありがとうございました。JavaScriptとPythonの開発者およびメンターのVignesh Mです。ユーザー名は@vigzmvで通しています。もっと読みたい方はフォローをお願いします。

関連記事

JavaScript: 5分でわかるPromiseの基礎(翻訳)

JavaScriptスタイルガイド 1〜8: 型、参照、オブジェクト、配列、関数ほか (翻訳)

Viewing all 1838 articles
Browse latest View live