esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Rails 6.1 で導入される予定の delegated_type をつかってみてる話

こんにちわ。はじめまして。@kajisha です。 推しの Vtuber は、因幡はねる組長 かわいいおそろしいあくまでびでび・でびる様です。

最近は、風見くくさんのものまねが好きすぎてよく観てます。 初 3D 配信はほんとにクオリティが高いのでぜひ観てみてください。

www.youtube.com

このエントリでは、現プロジェクトでつかっている delegated_type について少し書いてみたいと思います。

delegated_type とは何か

以下の Pull Request で Active Record に導入されたものです。

github.com

Active Record にはクラス継承のしくみとして STI が提供されていますが、 単一のテーブルにサブクラスに必要な属性ももたせることになるので それぞれのサブクラスに必要な(そしてそれらはほとんどが nullable な)カラムを持つ巨大でスカスカなテーブルを作らざるを得ません。 もちろん、サブクラスの属性がほぼ同じ場合は STI でもあまり問題はないと思います。

しかしながら、現実はそうもいかないので、サブクラスごとに違う属性を持たせたくなることはままあります。 delegated_type はこのケースに対しスーパクラスに対応するテーブルとサブクラスに対応するテーブルのレコードの2つで継承関係を表現します。

以降で delegated_type を使って弊社組織の構造を実装してみるサンプルをもとにどんな感じなのか書いていきたいと思います。

弊社の組織構造を delegated_type をつかって表現してみる

それでは実際に弊社の組織構造をモデリングしてみます。今回は、組織構造を柔軟に扱うことのできる責任関係パターンで表わしてみます。 責任関係パターンの詳細は、Martin Fowler のアナリシスパターン―再利用可能なオブジェクトモデル (Object Technology Series) を読んでいただいた方がわかりやすいと思います。 (アナパタ、なんで絶版になっちゃたんですかねぇ…)

今回は、責任関係パターンで会社 -- 事業部 -- 社員 という構造をつくってみたいと思います。

f:id:kajisha:20201002093719j:plain
責任関係パターン

このパターンはあくまでも概念モデルなので、直接実装するのはややむずかしいです。 うまく説明できるかちょっとあやしいのですが、知識レベルの型は、クラスに相当する(クラスのクラス。メタクラスですね)ものであり、知識レベルのレイヤーでパーティ間の制約を表現します。 操作レベルは、知識レベルの制約を担保したオブジェクト群、と考えるとわりと近いのかもしれません。

しかし、このまま実装するのはルナティックモード*1すぎるので、ちょっと簡略化して以下のようにしてみました。

f:id:kajisha:20201002093758j:plain
実装

責任関係型は、責任関係に関連のタイプを列挙型として表現することにしました。パーティ型は、それぞれパーティのサブクラスとしています。 なお、これが責任関係パターンの正しい実装なのかというより、今回のサンプル用に変形したものである点は留意ください。 それでは、ひとつづつモデルをつくっていきます。

まず Rails アプリケーションを準備します。

bundle init

delegated_type はまだリリースされていないので master の Rails を指定します。

gem "rails", github: "rails/rails"
bundle exec rails new -d sqlite3 .

Party(パーティ) モデルの作成

bin/rails g model Party partyable:references{polymorphic}

ここで、polymorphic アソシエーションとして partyable を作成していますが直接 delegated_type なアソシエーションを一発で作成できないので一旦、ポリモーフィック関連として作成します。 なぜ polymorphic アソシエーションとして作成するのかと言うと、delegated_type は内部でサブクラスの情報を polymorphic アソシエーションとして扱っているからです。 ここはのちほど、delegated_type で書き直します。

すると以下のようなモデルが生成されます。

class Party < ApplicationRecord
  belongs_to :partyable, polymorphic: true
end

Compnay/Department/Employee(パーティのサブクラス) モデルの作成

次に Party のサブクラスをそれぞれ作成します。

Company モデル

bin/rails g model Company name:string
class Company < ApplicationRecord
end

Department モデル

bin/rails g model Department name:string
class Department < ApplicationRecord
end

Employee モデル

bin/rails g model Employee name:string email:string
class Employee < ApplicationRecord
end

delegated_type を Party モデルに指定する

class Party < ApplicationRecord
  belongs_to :partyable, polymorphic: true
end

を次のように書き換えます。

class Party < ApplicationRecord
  delegated_type :partyable, types: %w(Company Department Employee)
end

そして Company/Department/Employee が Party として振舞うことを示すために、Partyable concern を作成してサブクラス側に include させます。

module Partyable
  extend ActiveSupport::Concern

  included do
    has_one :party, as: :partyable, touch: true
  end
end

この concern を Company/Department/Employee に include させます。

class Company < ApplicationRecord
  include Partyable
end

class Department < ApplicationRecord
  include Partyable
end

class Employee < ApplicationRecord
  include Partyable
end

Accountability(責任関係) モデルの作成

責任関係モデルを作成します。責任関係型の列挙型はのちほど追加していきます。

bin/rails g model Accountability accountability_type:integer started_at:datetime ended_at:datetime commissioner:references responsible:references

このままでは commissioner, responsible に対応するテーブルが解決できないので、マイグレーションを以下のように修正します。 :commissioner:responsibleforeign_keyto_table オプションがミソです。これで parties テーブルを参照できるようになります。 これを付けないと、それぞれ commissioners, responsibles テーブルがあると判断されてしまうので、parties テーブルとのアソシエーションであることをおしえてあげます。

class CreateAccountabilities < ActiveRecord::Migration[6.1]
  def change
      create_table :accountabilities do |t|
      t.integer :accountability_type, null: false
      t.datetime :started_at, null: false
      t.datetime :ended_at, null: false, default: '9999-12-31 23:59:59.999Z'
      t.references :commissioner, null: false, foreign_key: {to_table: :parties}
      t.references :responsible, null: false, foreign_key: {to_table: :parties}

      t.timestamps
  end
end

Accountability にパーティ間の有効期間を表す started_atended_at を追加していますが、今回のサンプルでは本質ではないので使ってません。

つぎに、Party モデルと Accountability モデルのアソシエーションを追加します。

class Accountability < ApplicationRecord
  attribute :started_at, default: -> { Time.current }

  belongs_to :commissioner, class_name: 'Party', inverse_of: :commissioners
  belongs_to :responsible, class_name: 'Party', inverse_of: :responsibles
end
class Party < ApplicationRecord
  delegated_type :partyable, types: %w(Company Department Employee)

  has_many :commissioners, class_name: 'Accountability', inverse_of: :commissioner, foreign_key: :responsible_id, dependent: :destroy
  has_many :responsibles, class_name: 'Accountability', inverse_of: :responsible, foreign_key: :commissioner_id, dependent: :destroy
end

delegated_type により、いくつかの scope や predicate メソッドが生成されますが、ここでは説明を省略します。

実際にオブジェクトを作って確かめてみる

さて、準備はできました。それでは実際にパーティ間のアソシエーションをつくってみましょう。 なお、以下のシナリオでおもむろに Accountability に accountability_type の値を追加していますが、 実際は責任関係型がみたすべき制約をバリデーションとして書くべきです。

シナリオ1 会社と事業部の関係をつくる

永和システムマネジメントにアジャイル事業部がある、という関係をつくってみます。 さきほど責任関係のタイプは、列挙型で表現することにしたので、会社と事業部の関連をあらわす enum を Accountability に追加します。 企業と事業部の責任関係型を organization_structure としてみます。

class Accountability < ApplicationRecord
  attribute :started_at, default: -> { Time.current }

  belongs_to :commissioner, class_name: 'Party', inverse_of: :commissioners
  belongs_to :responsible, class_name: 'Party', inverse_of: :responsibles

  enum accountability_type: {organization_structure: 0}
end

では永和システムマネジメントにアジャイル事業部をぶらさげてみましょう。

company = Party.create!(partyable: Company.create!(name: '永和システムマネジメント'))
department = Party.create!(partyable: Department.create!(name: 'アジャイル事業部'))
Accountability.organization_structure.create!(commissioner: department, responsible: company)

シナリオ2 事業部と社員の関係をつくる

OK. それでは、アジャイル事業部に社員をひとり追加してみましょう。この責任関係型は、belong としてみましょう。ちょっと名前がイマイチですね…ゆるしてください。

employee = Party.create!(partyable: Employee.create!(name: 'wat-aro', email: 'wat-aro@example.com'))
Accountability.belong.create!(commissioner: employee, responsible: department)

よさそうじゃないですか。

関連をたどっていろいろデータが欲しいんだ!

ここからはいくつかのテーブルを経由して取得していくことになるので、ちょっと一筋縄でいきません。いくつか準備が必要です。 必要な道具は has_many through:, source: です。

企業にある事業部の一覧を取得したい

Company にある Department は、[:Company] <-- [:Party] <-responsible- [organization_structure:Accountability] -commissioner-> [:Party] --> [:Department] をたどって取得します。 最終的には

class Company < ApplicationRecord
  ...
  has_many :departments, ...
end

のようなアソシエーションで取得できることを目指しますが、 一気呵成にアソシエーションをたどれないので、ひとつづつ解決していきます。なぜたどれないのかは、最後の例で説明します。

organization_structure な Accountability を取得する

次のアソシエーションを取得します。 [:Company] <-- [:Party] <-responsible- [organization_structure:Accountability]

class Company < ApplicationRecord
  include Partyable

  has_many :organization_structure_accoutabilities, -> { organization_structure }, through: :commissioners, source: :party
end

organization_structure_accoutabilities から Party を取得する

上記で作成したアソシエーションからさらに以下のアソシエーションを取得します。 [organization_structure:Accountability] -commissioner-> [:Party]

class Company < ApplicationRecord
  include Partyable

  has_many :organization_structure_accoutabilities, -> { organization_structure }, through: :commissioners, source: :party
  has_many :organization_structures, through: :organization_structure_accoutabilities, source: :commissioner
end

Department を取得する

さいごに :organization_structures を経由して Department を取得します。

class Company < ApplicationRecord
  include Partyable

  has_many :organization_structure_accoutabilities, -> { organization_structure }, through: :commissioners, source: :party
  has_many :organization_structures, through: :organization_structure_accoutabilities, source: :commissioner
  has_many :departments, through: :organization_structures, source: :partyable, source_type: 'Department'
end

事業部に所属する社員の一覧を取得したい

Department に所属している Employee は、[:Department] --> [:Party] <-responsible- [belong:Accountability] -commissioner-> [:Party] --> [:Employee] をたどります。

Company の例と同様に、Accountability, Party, Employee と解決していきます。

class Department < ApplicationRecord
  include Partyable

  has_many :belong_accountabilities, -> { belong }, through: :party, source: :commissioners
  has_many :belongs, through: :belong_accountabilities, source: :commissioner
  has_many :employees, through: :belongs, source: :partyable, source_type: 'Employee'
end

企業に所属する社員の一覧を取得したい

ここまでで、企業の事業部、事業部に所属する社員を取得できるようになりました。これらのアソシエーションをつかって企業に所属する社員、というのも取れそうです。

これは Company モデルに以下のような employees アソシエーションを追加することで取得できます。

class Company < ApplicationRecord
  ...
  has_many :departments, through: :organization_structures, source: :partyable, source_type: 'Department'
  has_many :employees, through: :departments, source: :employees, dependent: :destroy
end

なぜアソシエーションを段階的に辿ったのか?

Company のインスタンスから employees を取得するときの Active Record が実行する SQL に理由があります。

SELECT "employees".*
  FROM "employees" INNER JOIN "parties" ON "employees"."id" = "parties"."partyable_id"
                   INNER JOIN "accountabilities" ON "parties"."id" = "accountabilities"."commissioner_id"
                   INNER JOIN "parties" "parties_employees" ON "accountabilities"."responsible_id" = "parties_employees"."id"
                   INNER JOIN "departments" ON "parties_employees"."partyable_id" = "departments"."id"
                   INNER JOIN "parties" "organization_structures_employees" ON "departments"."id" = "organization_structures_employees"."partyable_id"
                   INNER JOIN "accountabilities" "organization_structure_accountabilities_employees" ON "organization_structures_employees"."id" = "organization_structure_accountabilities_employees"."commissioner_id"
                   INNER JOIN "parties" "parties_employees_2" ON "organization_structure_accountabilities_employees"."responsible_id" = "parties_employees_2"."id"
  WHERE "parties_employees_2"."partyable_id" = 1
    AND "parties_employees_2"."partyable_type" = 'Company'
    AND "parties_employees"."partyable_type" = 'Department'
    AND "organization_structure_accountabilities_employees"."accountability_type" = 0
    AND "organization_structures_employees"."partyable_type" = 'Department'
    AND "accountabilities"."accountability_type" = 1
    AND "parties"."partyable_type" = 'Employee'

has_many through するときの INNER JOIN に同じテーブルがある場合、別名を付けるためです。もし、これを where で書いていた場合は、どのテーブルに対しクエリを書いているのかが、わからないため正しくレコードを取得できないことになります。

ここの部分は、わたしもこれでいいのかまだ自信がないので、もっとよい方法があればぜひ教えてください。

さいごに

いかがでしたでしょうか?わたしもまだ手探り状態なのでもっと良い方法があるかもしれません。そのときはまたなにか書ければいいなと思います。

以下にこのエントリで作成したサンプルを置いておきますので、ご興味あるかたはご覧いただければと思います。

github.com

*1:東方Projectのゲームにおける最高難易度モード

Rails / OSS パッチ会オンライン 2020年10月のお知らせ

2020年10月の Rails / OSS パッチ会を 10月8日(木)にオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS への upstream にパッチを送る会です。

会には RubyRails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。Google Meet あたりのテレビ会議システムを使います。

当日の招待 URL は Idobata の esminc/rails ルームで共有する予定です。

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントと (TV 会議システムによっては Google アカウント) が必要になると思います。

以下、前回の活動が関わる成果です。

koic: RuboCop

github.com

yahonda: activerecord-oracle_enhanced-adapter

github.com

その他の開催方針については以下の Gist に記していますので、ご参照ください。

gist.github.com

XP祭り2020に参加して野良LTしてきました

こんにちは。@junk0612 です。

シルバーウィークの初日9/19(土)に、XP祭り 2020 が開催されました。 それに参加してきましたので、感想などを書いていこうと思います。

野良LT

XP祭りには 野良LT という企画があります。 お昼の時間を利用して、飛び入りでLTしたり、午後のタイムテーブルの内容を眺めたり、勉強会の告知をしたりします。 もちろんお昼なので参加は任意だし、途中参加・途中抜けOKというゆるいけれどXP祭りらしい楽しい時間です。

実は、僕はもともとタイムテーブルの終わりの方にある LT の時間に参加登録をしていたのですが、登録者多数で抽選となり、残念ながら落選してしまっていました。 野良 LT の存在をすっかり忘れていて「発表したい内容だったけどしょうがないな、まあ気楽に楽しもう」なんて思いながらのんきに朝から参加したのですが、 開会の「まんざい」で野良 LT の話が出てハッとなり、午前の基調講演を聞きながら急いで間にあわせの資料を作って発表してきました。 発表資料はこちらです。

内容を一言で表すと「アジャイルって考え方とか取り組む姿勢が一番大事だよね」という感じです。 アジャイル開発は、世間にはよく「開発手法」という側面で紹介されることが多いですが、『達人プログラマー』 (「常に心に聖堂を思い描く」) や、『アジャイルサムライ』 (「信頼貯金を増やす」) 、『エクストリーム・プログラミング』 (「XPとはソーシャルチェンジだ」) などに書かれている通り、今直面している問題だけでなく、最終的な目標や周囲の状況を正しく捉え、そのなかでよりよい解決方法を探していく心構えこそがアジャイルの本質なんだろうな、ということを最近よく考えていたので、それを素直にそのまま書いています。

書いている途中で「そもそも『アジャイルソフトウェア開発宣言』だって価値の話をしていたじゃないか。もしかしてすごく今更なことを言っているのでは……?」という疑念が浮かびましたが、それはそれ。 今後も変わっていくであろう自分の中の考えをスナップショットとして残しておくことを優先しました。 準備もそれほどしていない中で拙い発表でしたが、温かい反応をいただけてよかったです。「ぜひこれを読んでほしいなー」というリンクもコメントでいただいて、発表したかいがあったなと思いました。

面白かった発表

意外に野良 LT の話が長くなってしまったのですが、参加した中で面白かった発表を手短に2つほどご紹介しようと思います。

エンジニアの創造力を解き放て! XPと歩んだ17年・技術・現場・仲間

基調講演です。大企業の中で17年もの間アジャイル開発の普及に挑んできた発表者の神部さんの経験や工夫の詰まった興味深い発表でした。 大企業ならではのとんでもない状況や事態がたくさんあり、信じられなかったと同時に、聴衆のみなさんが笑っているのを見てきっと似たような経験があるのだろうと思いました。 アジャイル開発が「ふつう」になるにはまだまだ頑張らないといけないんだなあ、と (LT の資料作成で慌てつつ) 身の引き締まる思いで聞いていました。 発表資料はこちらに公開されています。

近代史とアジャイル

われらが @fkino の発表です。 「近代 (資料内では1776年からにスポットライトを当てています) における産業の歴史の中で、アジャイルソフトウェア開発はどのような立ち位置にいるのか?」という、面白い視点での発表でした。 アメリカ独立時に制定された合衆国憲法の中にすでにアジャイルマニフェストの精神が見出されるところや、近代合理主義と脱近代合理主義の思想に触れ、ドラッカーの言葉を引用しながら「それには現代ではアジャイルという名前がついている」というところなど、詳しくない分野が多いながら聞いていて面白かったです。 発表資料はこちらに公開されています。

全体を通して

COVID-19 の影響で、様々なカンファレンスがそれぞれの形で実施されていますが、XP祭りはオンライン開催となって自宅からゆったり参加できてよかったです。 また以前のように対面で恐れることなく会話ができるようになることを祈りつつ、これからも頑張っていくぞ、と気持ちを新たにしたイベントでした。

たくさんのファイルを巨大なアーカイブファイルにしてアップロードする

たくさんのファイルを同時に扱う際には、その処理が使うリソースに注意したいものです。さいきん私が直面した課題は、AWS S3 のあるバケットに保存されている大量のファイルをひとつのアーカイブファイルにして別のバケットに配置するというものでした。ひとつのアーカイブに含まれるファイルの数は最大で70万くらいあり、各ファイルのサイズは 10MiB を上限にさまざまです。ちなみに、アーカイブ対象のファイルをすべて合わせると、140GiB 以下であることがわかっています。

もしも70万のファイルを一気にダウンロードしてZIPアーカイブしようとしたら、ある程度のディスク容量が確保できていることを事前に確認しておいたほうがよさそうです。どれくらい必要でしょうか。もしアーカイブ処理を同じマシン上で平行して実行すると、必要な容量はさらに掛け算で増えていきそうです。そういえば、できあがったアーカイブファイルは数十GiBのサイズになる可能性がありそうですが、そもそも、果たしてそんなに大きなファイルは一度にアップロードできるんでしょうか。心配事がいろいろあります。こういった場面では、一度にすべて片付ける代わりに、少しずつ処理していくパイプラインっぽいやり方で対応できるとよさそうです。

AWS S3 Multipart Upload で少しずつアップロードする

幸い、S3 は巨大なファイルを小さなパーツに分割して少しずつアップロードする "multipart upload" と呼ばれる手段を用意しています。"multiple upload" は、アップロード対象のファイルが完成していなくても開始できるようになっています。つまり、どこかにアップロードするために、アーカイブファイルをいったん手元のファイルシステムに保存する必要は必ずしもないということです。

RubyAWS SDK も、もちろんこの機能を使う API を備えています。以下はその一例です。

s3_client = Aws::S3::Client.new(region: 'ap-northeast-1')
archive_object = Aws::S3::Object.new(bucket, 'archive.zip', client: s3_client)
archive_object.upload_stream do |upload_stream|
  upload_stream.binmode

  # write to upload_stream (one side of a pipe, IO object for writing)
end

Aws::S3::Object#upload_stream で miltiple upload の開始を知らせると、AWS SDK はブロック引数を介して IO オブジェクトを返してきます。これは IO.pipe で作成されたパイプの書き込み側なのですが、とにかく、ここにどんどんファイルの中身を書き込んでいけばライブラリが勝手にファイルを適当なサイズ (デフォルトは 5MiB) で分割しつつ、少しずつS3バケットにアップロードしてくれます。べんり!

ディスクに書き込まずに ZIP ファイルを作る

ファイルをアーカイブするとき、できあがったアーカイブファイルをまるごと保存するためのディスク容量を用意する必要がないといいですね。実は、ZIPはそういう要求にうってつけのフォーマットになっています。

ZIP フォーマットの構造

ほかのアーカイブフォーマットと同じようにZIPファイルも、アーカイブしているファイルの名前やファイルサイズ、データ位置といったメタデータを保持している場所があります。"central directory header" と呼ばれる領域で、興味深いことに、"header" という名前に反して ファイルの末尾に配置されることになっています

つまり、ZIPファイルを作成し始める段階では、すべてのアーカイブ対象のファイルの情報を手元に持っていなくても問題ありません。それらの情報は最後に central directory を書き込むときに用意できていれば十分です。それまでは、ファイル名などのメタデータをとっておきつつ、単純に圧縮したファイルをひとつずつ頭から書き込んでいけば大丈夫です。ファイルの中身を圧縮してアーカイブに追加できた時点で、元のファイルは不要になります。ですので、ZIP形式のアーカイブを作る際に、対象のすべてのファイルをあらかじめ手元のファイルシステムに保存しておく必要はありません。

パイプと zip_tricks gem

今回は ZIP アーカイブを作成するために zip_tricks gem を選択しました。理由は、このライブラリではZIPファイルの出力先としてパイプを使えるためです。ZipTricks::Streamer.open はZIPファイルの出力先としてIOオブジェクトを受け取るのですが、このライブラリは受け取ったIOオブジェクトを rewind しないため、パイプを渡すことができます。

for_write, for_read = IO.pipe # for_read can be used to upload

ZipTricks::Streamer.open for_write do |zip|
  zip.write_deflated_file 'a-file.txt' do |input_stream|
    input_stream.write 'the content of a-file.txt'
  end
end

出力先にパイプが使えるということは、できあがったZIPアーカイブファイルを手元のファイルシステムに保存しないという選択肢がとれるようになります。そういえば、S3 の multipart upload のセクションで、Aws::S3::Object#upload_stream はブロック引数としてパイプのIOオブジェクトを提供していましたね。つまり zip_tricks を使うと、次のように、アーカイブの出力をそのパイプに直接書き込めるようになります。

archive_object.upload_stream do |upload_stream|
  upload_stream.binmode

  # upload_stream is a pipe
  ZipTricks::Streamer.open upload_stream do |zip|
    zip.write_deflated_file 'a-file.txt' do |input_stream|
      input_stream.write 'the content of a-file.txt'
    end
  end
end

サンプルコード全体

これで、手元でファイルを作成せずに、たくさんのファイルをアーカイブし巨大なアーカイブをアップロードできるようになりました。

require 'aws-sdk-s3'
require 'zip_tricks'

bucket = 'hibariya-sandbox'
s3_client = Aws::S3::Client.new(region: 'ap-northeast-1')

files_to_archive = %w[alpha bravo charlie delta] # whatever
archive_object = Aws::S3::Object.new(bucket, 'archive.zip', client: s3_client)

archive_object.upload_stream tempfile: false, part_size: 20 * 1024 * 1024, thread_count: 3 do |upload_stream|
  upload_stream.binmode

  ZipTricks::Streamer.open upload_stream do |zip|
    files_to_archive.each do |file_path|
      zip.write_deflated_file file_path do |input_stream|
        s3_client.get_object bucket: bucket, key: file_path, response_target: input_stream
      end
    end
  end
end

Aws::S3::Object#upload_stream に渡しているキーワード引数はすべて任意です。詳しくは 公式のドキュメント を確認してください。 ※話を簡単にするために、アップロード/ダウンロード両方に同じバケットを使用しています。

注意点など

AWS S3 の multiple upload を使う場合は、分割できるパーツの数や未完了のアップロードへの課金など、いくつか知っておいたほうが良い事項があります。おそらく、未完了のアップロードを定期的に掃除するためにS3バケットにライフサイクルポリシーを追加したくなるのではないかと思います。詳細は公式の記事をご確認ください。

https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html

Windowsのススメ WSL2を利用した快適な開発環境作り

こんにちは!コロナ禍の影響でライブハウス遊びができずに、気になるバンドのライブ配信をひたすら見続ける生活を送っている@hidenbaです。

アジャイル事業部では、各自が利用するPCは、予算に応じて2-3年のサイクルで買い換えを行い、各々が好きなPCを購入して利用しています。 以前はmacOSの利用率が圧倒的に高かったのですが、Dockerを利用して開発を行う際に、遅さがストレスになるという理由でLinuxを選択する 事が多くなってきているように思えます。かくいう私は、3年前まではMacを利用していましたが、同様の理由でLinuxを選択して、現在はThinkpad T490にArch Linuxを入れて利用しています。

私たちは、受託開発やお客様の業務支援を行っていますが、時として、お客様のセキュリティポリシーの都合上、お客様から貸与されたPCを利用して 開発作業を行うことがあります。そのときに支給されるPCはお客様が指定したPCになることが多く、OSの選択肢としてはmacOSがほとんどだったのですが、最近ではWindowsも選択肢に入るようになってきています。

WindowsRubyを使ったシステムの開発なんてできるのか?なんて思ったりもしたのですが、WSL2を利用することで快適に開発を行えるようになってきています。今日は、私がお客様から、貸与されたWindowsPCで開発できる 環境になるまでに、やったことをざっとあげていきます。それぞれの詳細については、公式サイトのドキュメントや詳しく解説されている記事が多数あるので割愛します。

ターミナルで作業するための準備

今回、お客様から貸与されたPCはDellXPS 13でした。初回起動後は、Windowsアップデートをして最新の状態にします。 次にコマンドラインから利用できるパッケージ管理ツールの「winget」をインストールします。 これで、コマンドラインを中心に作業ができるようなりました。

標準のPowerShellを使うのでもいいのですが、もうちょっと高機能で使いやすいWindows Terminalをインストールして以降は Windows TerminalPowerShellを利用して作業していきます

$ winget install "windows terminal"

必要なアプリケーションのインストール

必要なアプリケーションをwingetを利用してインストールしていきます。

内外とのコミュニケーションのパスを用意するためにSlackとTwitterのクライアントをインストールします。

$ winget install slack
$ winget install tweeten

エディタがないと生きていけないのでVSCodeをインストールします。

$ winget install vscode

WSL2の設定

WSL2を有効にするために、管理者権限でPowerShellを起動します。

$ Start-Process powershell.exe -Verb runas

仮想マシンプラットフォームオプションを有効にして再起動をかけます。

$ dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

※私の環境では、上記コマンドではWSLが有効化されなかったのでコントロールパネルから設定をしました

[コントロール パネル] -> [プログラムと機能] -> [Windows の機能の有効化または無効化] を開いて、 [LinuxWindows サブシステム] をチェックします。

WSLのバージョンを2に固定します。

$  wsl --set-default-version 2

カーネルコンポーネントの更新をしてWSL2が実行可能な状態にします。 https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel

WSL2にLinuxをインストールします。

$ winget install ubuntu

Docker環境の構築

Windows側にDockerをインストールします。

$ winget install "Docker Desktop"

VSCodeからWSL2環境に接続して開発を行うためにRemote Development拡張をインストールを行い、WSL2のubuntuからVSCodeを起動します。

$ code

開発に必要なリポジトリをWSL2のファイルシステムにcloneしてきてdocker-compose up等Docker環境を起動して開発を行うことができるようになりました!!

より快適な環境にしてくれるPowerToysのインストール

この状態だと、開発はできるがまだまだストレスが多い環境なので、キーバインドやウィンドウの配置を固定するためにPowerToysをインストールして好みのキーバインドにカスタマイズしています。

$ winget install powertoys
$ winget install ".NET Core"

現状の課題

この記事を執筆時点で早急にどうにかしなくては!と思っているのが、ssh-agentの設定です。現状はUbuntu上にkeychainをインストールして利用していますが、 VSCode起動時にパスフレーズを毎回聞かれたり、VSCodeのgit拡張からpushできずにVSCodeのターミナルからリモートリポジトリの操作をしています。 Windows側でssh-agentの設定をする方法があるみたいなので、近々試してみようと思っています。

最後に

十数年ぶりのWindowsに戸惑い多少ハマったりもしましたが、思っていた以上に簡単にWindowsでの開発環境を手に入れることができました。 みなさんも、機会があればWindowsでWSL2を利用した開発環境の構築をしてみるのもよいのではないでしょうか?

Kaigi on Rails に koic, 9sako6, yucao24hours が登壇!見どころをご紹介します

ここのところずっと RFC 1912 ばかり読んでいる inexperienced administrator の @yucao24hours です。

10 月 3 日開催予定の Ruby on Rails のカンファレンス、Kaigi on Railsスピーカー一覧タイムテーブルが公開されましたね。Rails のカンファレンスにぴったりの、汽車とレールを活かしたデザインがとてもかわいい!みなさん、もうチェックしましたか?

kaigionrails.org

わたしたちアジャイル事業部からも複数名がプロポーザルを提出しておりましたが、このたび @koic, @9sako6, @yucao24hours の計 3 名のプロポーザルが採択となり、発表の機会をいただけることになりました🎉

なお、今回のプロポーザルを提出するにあたっては、事業部内の有志のメンバーで集って「Kaigi on Rails のプロポーザルを考える会」を開き、お互いのプロポーザルについてレビューしあいました。 ぜひ以下の過去記事も併せてご覧ください。 blog.agile.esm.co.jp

今回のブログ記事では、登壇が決まったメンバー自らお伝えしたい発表の見どころや、発表をより楽しんでいただくための事前情報をギュッとまとめてお届けします。発表にかける思いの詰まったセルフライナーノーツをぜひお楽しみください。

『TDD with git. Long live engineering.』

発表者

@koic

発表者からひとこと

Martin Fowler, Kent Beck といったパイオニアPragmatic Bookshelf などによる出版によって、テスト駆動開発 (とその前に現れたテストファースト) と SCM は現場で広く親しまれるようになりました。

TDD と Git の登場には10年の隔たりがありますが、さらに10年が経過した今日における TDD と Git は有機的な繋がりを持った実践技法となっています。本編ではこれらの歴史と実践を背景に、リファクタリングのリズムに乗った TDD と相性の良い Git コマンドをもちいた TDD with git テクニックを紹介する予定です。

さらに GitHub によって、ブランチの意味合いも「開発トピックのために作るブランチ」「リリースのために作るブランチ」だけではなく「レビューのために作るブランチ」という側面が登場しました。これらによって実現されているソーシャルなリファクタリングと実戦的なコミットの適用についても取り上げます。こちらは、16 年間で 4,200 人以上が開発に関わっている rails/rails リポジトリの運用を参考にし、現場の開発でもちいている Git 術を TDD / リファクタリングと組み合わせて伝えます。

『コードレビュー100本ノックで学んだ Rails リファクタリング

発表者

@9sako6

発表者からひとこと

本発表では、Rails アプリケーションリファクタリングの Tips をお話しします。 これらの Tips は、実務で開発するなかで歴戦の先輩方にいただいた数百ものコードレビューから学びとったものです。 私の内に秘めておくのはもったいないので、言語化して発表することにしました。

大まかに「commit の意識改革」、「命名」、「分割」、「設計」、「テスト」という観点に分けて、それぞれいくつか具体例を交えながらお話しする予定です。 私のように Rails 歴が長くない方はもちろん、コードレビューや指導をする側の中・上級者の方にも参考にしていただけるのではないかと思っています。

『新ミドルウェア ActionDispatch::HostAuthorization と学ぶ DNS のしくみ』

発表者

@yucao24hours

発表者からひとこと

ウェブアプリケーションのコードを書くことが多い方にとっては、ふだんの開発作業時には DNS のしくみ ―名前解決がどのように行われるのか― を意識する機会はそう多くないかもしれません。(そのせいか、DNSドメインの世界に関しては「誤解を招く表現」や「事実からかけ離れた説明」がまことしやかに語られているさまが頻繁に見受けられるように感じます。)

かく言うわたしも少し前まではまさに "そこまで意識していなかった人間" で、これらのことについて真正面から向き合って勉強するようになったのはここ半年くらいなのですが、今となっては DNS をはじめとしたインターネット基盤技術について学ぶのが楽しくて仕方ありません。そこから溢れ出た「楽しい!」「おもしろい!」「みんなにも知ってもらいたい!」という純粋な気持ちをこれでもかとばかりにつめこんでプロポーザルをしたためましたので、こうして採択していただけて本当に感無量です。

今回の発表がウェブアプリケーションエンジニアのみなさんにとって、自分のアプリの "外" にちょっと目を向け、ふだんの開発をより広い視野で愉しめるようになるキッカケとなれたら... そして、DNS について正しい知識と理解を広めるための一助となれたらとても嬉しいです。


それでは、3 人の当日の発表にぜひご期待ください!

f:id:koic:20200910150145p:plain

仕事で遭遇したRailsのバグにパッチを送るまでの話

こんにちは、@color_boxです。 仕事でRailsを使っている時にバグに遭遇したのでそれに対してパッチを送りました。

送ったパッチはこちらです。 github.com この記事ではバグ発見からパッチ送信までの過程について書こうと思います。

続きを読む