【Rails】devise_mailで送信元メールアドレスを動的に変える
Railsでdeviseを使ってメール送信機能を作るとき、送信元のアドレスは通常いつも同じで済むことが多い。
セオリーなのは、 config/initializers/devise.rb
にある config.mailer_sender
の項目に送信元アドレスを書くことだろう。
# ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class # with default "from" parameter. config.mailer_sender = 'from@example.com'
また、Mailerの種類によって異なる役割のメールアドレスを送りたいときなどは、各Mailerクラスの冒頭に default
をつけることで、そのMailerで使われる送信元アドレスを上書きできる。
class UserMailer < ApplicationMailer default from: "info@udomomo.com" ... end
しかし先日、同じMailerによるメールでも送信元アドレスを出し分ける必要が生じた。そのサービスではユーザが使うドメインによってテンプレートを出し分けており、送信元アドレスもドメインによって出し分けたかったのだ。
これをdeviseの機能でどう実現すれば良いかしばらく悩んだが、 devise_mail
メソッドに opt
という引数があり、それを使うことで解決できた。
# mail_address変数に送信元アドレスを入れておく opt.store("from": mail_address) devise_mail(record, :mail_title, opt)
この方法は、devise_mailの実装を見ることで理解できる。
def devise_mail(record, action, opts = {}, &block) initialize_from_record(record) mail headers_for(action, opts), &block end ... def headers_for(action, opts) headers = { subject: subject_for(action), to: resource.email, from: mailer_sender(devise_mapping), reply_to: mailer_reply_to(devise_mapping), template_path: template_paths, template_name: action }.merge(opts) @email = headers[:to] headers end
devise_mail
は、メールヘッダの設定を行ったうえでActionMailerの mail
メソッドを使ってメールを送信しているが、その際 opt
引数に渡されたハッシュを merge
している。このmerge
とは2つのhashを合成するためのメソッドで、キーが重複した場合デフォルトでは後から指定した値で上書きされる。つまり、上書きしたいheaderの項目を opt
内で指定すれば、 devise_mail
を呼び出すタイミングで設定を変えることができる。
RailsのMailerはあまり使い慣れていないので、もっとスマートな方法があれば知りたい。
【書評】「におうコードの問題集 ソフトウェア設計に立ち向かう編」を読んだ
以前からソフトウェア設計に興味はあったものの、単にデザインパターンを暗記したところで役に立ちそうにないし、どう学べばいいだろう?というのがずっと悩みだった。そんなとき読んだこの本が、設計との向き合い方に対する一つの指針を示してくれたように思う。
わかりやすいサンプルコード
この本で主に扱っているのは、ソフトウェア設計をする際に避けては通れないSOLID原則について。SOLIDの5つの原則を、それぞれサンプルコードをリファクタリングするという形で体感していくという構成になっている。
既に設計をたくさん行っている人にとってはほぼ知っている内容かもしれないが、この本ではそれぞれの原則の特徴や、リファクタリング後に何がどう変わるのかが、他の本よりもすんなり理解できた。というのも、使われているサンプルコードにムダがなくわかりやすいのだ。決して入門書のHello World的なレベルではなく、さりとて複雑すぎる処理もしていない。APIやDBを叩くビジネスロジックという、設計をするうえでよく出てくるようなシチュエーションのコードがちょうどよい分量で出されており、サンプルコードとしてはとても良いと思う。
なお、サンプルコードにはCrystal言語というものが使われている。この言語は使ったことがなかったが、ものすごく雑に言うと静的型付け言語の要素を取り入れたRubyのようなものらしい*1。自分はJavaとRuby両方を触ったことがあるので、すんなり読むことができた。
設計が重要なときとそうでないとき
この本のもう一つの特徴は、 設計を語るうえでの前提の説明に丸一章割いている ことにある。
いくら設計手法を学んだところで、それであらゆるソフトウェアをパーフェクトに作れるようになるわけではないし、ソフトウェア関連の全ての問題を解決できるわけでもない。そこでこの本では「前提」の章で、設計とは何か・どういう場合に必要になるかを定義している。
この本では、扱うトピックを「クラス設計」に限定し、良い設計の基準を「後から機能の追加・修正をするのが簡単である」と定めている。またこのような基準になるのは、自然と「デプロイサイクルが短めの場合」に絞られる。これにあてはまらない状況のときはまた別のアプローチが必要になるが、前提がしっかり定められていることで、その後の具体的な設計を見ていく章でも考えるべきポイントが明確になって理解しやすかった。
この本は、単にソフトウェア設計の手法を示すだけではなく、設計とどう向き合えばいいかを考えさせる良いきっかけにもなった。過去にソフトウェア設計の本を読んだもののいまいち活かしきれていなかったが、この本を読んで自分が置かれているケースにあてはめて設計について考える意欲が湧いた。
過去のFirebase AnalyticsエクスポートデータをBigQueryにアップロードし直す
今個人で扱っているアプリケーションでは、Firebase Analyticsでデータを計測し、それをBigQueryにエクスポートした後に処理している。過去の日付分のテーブルを置いたままだと料金がかかるので普段は日付ごとに消しているのだが、過去にテストとして流したデータをもう一度BigQueryにアップロードして動作確認したいと思うことがあった。
テーブルはとっくに消してしまっているが、幸いBigQueryからエクスポートしたJSONデータが手元にあった。これがあればテーブルを復元することができる。
まず、JSONデータをGoogle Cloud Storageにアップロードしておく。次にデータセットを選択した状態で「テーブルの作成」を選ぶ。
「ソース」の欄でGoogle Cloud Storageを選ぶと、GCS上に上がっているファイルを選択できる。「送信先」の欄で作りたいテーブル名を入れておけば準備が完了する。
そのうえで、テーブルのスキーマを指定する必要がある。デフォルトではソースファイル(この場合はJSONファイル)からBigQueryが自動で検知してくれるようだが、それに頼るのはおすすめしない。なぜならFirebaseのエクスポートスキーマはネストが非常に深いからだ。一度自動検知で試してみたのだが、Recordの繰り返しの中にnullableなフィールドがあるため、全部のフィールドが表出していない行があるときなどは正常に検知されない。かといって、これを手動で指定するのも非常に手間がかかる。
そこで、bq
コマンドを使って現存するエクスポートテーブルのスキーマを取ってきてしまうのが速い。
bq show --format=prettyjson <projectid>:<datasetid>.<tablename>
上記のコマンドを入力すると、FirebaseからエクスポートされたテーブルのスキーマがJSON形式で返ってくる。
後は、BigQueryの「テーブルを作成」画面で、スキーマ欄の「テキストを編集」ボタンをONにし、入力欄に先程返ってきたJSONをそのままペーストすればよい。
大容量のファイルをsplitコマンドで分割する
先日、数GBある大きなログファイルをテストに使う機会があったが、同じ日付のファイルが複数あるときのテストがしたかったのでファイルを適当に分割する必要が生じた。自分でファイルの一部をコピペしてもいいのだが、splitコマンドを使うと手軽に分割できる。
元のファイルは 0014-20190809.json
という名前だった。これを分割するには以下のようにする。
split -d -a 3 --additional-suffix=.json 0014-20190809.json 0014-20190809_
-d
はsuffixを数字にする。デフォルトではsuffixに付与されるのはアルファベットだが、 -d
をつけることで数字にできる。また、 -a
はsuffixの文字数を設定する。上の例では3文字なので 000
から始まる。もちろんこの場合、1,000個より多いファイルに分割されるとエラーになる。
--additional-suffix
は、suffixの後に共通でつけたい文字列を指定する。今回は分割後もjsonファイルにしたかったので .json
をつけた。
また、今回は指定しなかったが、分割後の1ファイルごとの行数を指定して分割したいときは -l
オプションを使う。デフォルトでは1,000行ずつに分割される。
最後に分割したいファイルと、分割後のprefixを引数で渡せばよい。 0014-20190809_000.json
のようなファイルができあがる。
なおmacの場合、splitコマンドがBSDベースなので、-dオプションなどがない。coreutilsを導入し、GNU版のsplitを使うことをおすすめする。
アジャイル研修を受けて学んだ「アジャイルの価値観と原理原則」
先日、会社の開発チーム全員でアジャイル研修を受けた。プロのアジャイル講師が来て、1日かけて講義と実践を交えてアジャイルのやり方を説明してもらったのだが、いろいろな気づきがあった。
研修前の状況
自分のチームでは一応1週間ごとにスプリントを区切り、チケットにStory Pointをつけてタスクを割り振るスクラム開発を行っているのだが、最近自分だけがタスクを抱え込みすぎて、うまくチームで協力できていない感じがしていた。別にチームメンバーが冷たいというわけではなく「何かあったらすぐ相談してくれ」とよく言ってきてくれるのだが、どのラインから相談していいのか自分の中であまり理解できておらず、スプリントという仕組みをうまく使えていない自覚があった。そのため、個人的にはアジャイルに対する正しい認識を身につけることを目的として研修に参加した。
アジャイルの価値観と原理原則を理解する
アジャイルという言葉はあまりにも広まっており、いろいろな方法論やフレームワークが溢れかえっている。今回の研修でもINVESTやScrum 3355など多くの用語が出てきたが、単にやり方を覚えても今の自分にとっては表面的な理解になってしまうと思い、この辺りは丸のまま覚えようとしないように意識していた。
むしろ最も印象に残ったのは、アジャイルの価値観についての話だった。アジャイルとは特定の方法論ではなく価値観・原理原則を指す言葉であり、それを実践するためのやり方としてスクラムやカンバンなどがある。そしてその価値観は、アジャイルという言葉を生んだ17人のソフトウェアエンジニアによって、アジャイルマニフェストとして定義されている。
このマニフェストで4つの価値観が定義されており、さらにその下に12の原理原則がある。
この価値観と原理原則が、今回の研修の最も大きな学びとなった。アジャイルという手法やエンジニアという仕事について、中には自分のイメージが間違っていたところも見つけることができた。いくつか刺さったものを書いておく。
アジャイルチームは自律的な組織
最良のアーキテクチャ・要求・設計は、自己組織的なチームから生み出されます。
「自己組織的」とは、価値あるソフトウェアのリリースをゴールとし、そのために何をするかを常に自分たちで考えつつ、ときには柔軟に行動を変えるチームのこと。そして、そのようなことができる自律的なメンバーたちのことを指す。スクラム開発も、自己組織的なチームであることを前提とした手法である。
スクラム開発を表面的にしか知らないと、こんな誤解をしがちになる。というか自分もそうだった。
- スクラムマネージャーが上司、それ以外のチームメンバーが部下。
- チケットを切ることでタスクの割り振りを行う。自分のチケットの見積もりは自分で行い、それを期間内にこなす責任を持つ。
しかし、これは全く自律的ではない。自分のタスクをこなすという考え方の時点で内向きになっているうえ、視点がその期間のスプリントにしか向いていないからだ。
正しい捉え方はこんな感じ。
- チーム内には上司も部下もない。チーム全体として、価値あるソフトウェアをリリースするというゴールのために動いている。
- チケットを切る目的は、チーム全体としてそのスプリント期間にどれほどの開発ができるかを見積もるため。そのため、見積もりはチーム全員で合意する必要がある。
- 期間の途中に自分のタスクが終わったなら、チーム全体としての見積もりを達成するべく、率先して他の人のタスクを助けるべき。誰かのタスクが終わっていなければ、それはチーム全体の問題。
- タスクにないがリリースのために必要なことが明らかになったら、チームとしてどう動くかをすぐに相談するべき。
開発者は、チーム全体でひとつのソフトウェアを作っている。そのため、視点を自分に閉じず、チーム全体での目標達成をサポートしていく姿勢が必要になる。
直接の会話が重要
情報を伝えるもっとも効率的で効果的な方法はフェイス・トゥ・フェイスで話をすることです。
上記のようなチーム開発をするためには、緊密なコミュニケーションが欠かせない。最強なのは直接顔を合わせて話すことであり、それが一番誤解が生まれにくい。
自分がエンジニアになった頃は、エンジニアはどこでも働ける職業だというイメージがあった。しかし副業でリモート勤務をしてみてわかったが、顔を合わせていないと意思疎通がとても難しい。もちろんチャットやビデオ会議などはできるが、人に何か聞くためのコストは同じ場所にいるときより遥かに高い。
フルリモートで働けるという人は、最小限のやり取りで認識を合わせて期待どおりのものを作れるだけの実力とコミュニケーション力を持った人である。そしてチームメンバーと同じ場所で働いているなら、なおさら積極的にコミュニケーションを取ることが求められる。
エンジニアは信頼されてしまう存在
意欲に満ちた人々を集めてプロジェクトを構成します。 環境と支援を与え仕事が無事終わるまで彼らを信頼します。
ここでいう「信頼」とは、チームメンバーが価値あるソフトウェアを出すための行動をするだろうという前提に立つこと。そのため、この権限が必要なのでほしいと言えばすぐ渡されるし、これを最優先に行うべきだと言えばチームの次のスコープが変更されるのが理想の組織である。その代わり、もし成果が出せなかったり、もらった権限を使って何かやらかした場合は、自由がどんどん奪われていく。
会社の先輩が「エンジニアは入社直後に最大限の信頼を置かれ、その後減点主義で評価される仕事」と言っていたが、それはアジャイル開発を行うエンジニアは自律的であって当然という前提があるからだ。
活かせるまでの道は険しい
このように、アジャイルの価値観や原理原則を理解しなければ、どんな方法論を取ってもうまくはいかない。逆に言うと、方法論だけ学んでもうまくいかないし、価値観がわかったうえでそれを自分なりの方法論に落とし込むには試行錯誤が必要になる。
どのみちすぐに行動を劇的に変えられるわけではないし、しばらくは間違った解釈で的外れなことをしてしまうかもしれないが、アジャイルマニフェストを何度も読み返しつつ行動を見直していきたい。
「エンジニアリング組織論への招待」1-2章に学ぶエンジニアのコミュニケーションスタイル
最近、社内の同じ開発チームの人とのコミュニケーションに悩むことが多く、見かねた先輩がこの本を勧めてくれた。
実はこの本は前にも一度読んだのだが、その時は何の課題意識も感じていなかったのか大して頭に残っていなかった。それが今回もう一度読んでみると、とても大きな学びの連続だった。
この本では、1章は自分自身の中の考え方について、2章は1対1でのメンタリングについて、3章以降はチームの作り方について扱っている。今回は 普通の仕事術とは異なるエンジニアのチームならではの振る舞い方を学び直す のが目的だったので、2章まで読んだ。
不確実性を減らす
まず最初に、エンジニアリングの意義として 不確実性を減らす というキーワードが挙げられる。
ソフトウェア開発は、最終的な成果物の具体性が他の仕事に比べて極めて高い。企画案や資料であれば解釈するのは人間だが、ソフトウェアはコンピュータによって動かされるものであるため、曖昧な部分が少しでも残っているとバグや仕様のズレに直結してしまう。そのため、 エンジニア同士やステークホルダーとの間にある情報の非対称性や認識のズレに気を配らなければならず、これを解消するために密なコミュニケーションが必要になる。
振り返ってみると、自分の会社の強いエンジニアたちは不確実性の存在にとても敏感であり、不確実性を増やす言動(チケットにコメントを残さない、自分が書いた処理の理由を説明できないなど)にはとても厳しい。それが後から自分たちの首を絞めかねないからだ。
観測可能なもので判断する
これも不確実性の話に通じる。ソフトウェアが正しく動いているかをテストやログを通じて判断するように、自分が何か振る舞い方を変えるときも、変化を観測できるようなやり方をしなければいけない。例えば「心がける」「がんばる」というのは本当なのかどうかを観測できないが、「具体的に何かの行動を変える」ところまで落とし込めれば、周りからも変化が見えるようになる。
メンターの役割
不確実性と観測可能性という考え方は、メンター・メンティーが1on1をするときにも役立つ。メンティーが何か問題を抱えているとき、メンターがやるべきなのはその問題に答えを出すことや同情することではない。問題を分解していくことで不確実性を減らすこと、そして観測可能な形で行動を変えるようメンティー自身に考えてもらうことがメンターの役割である。
(ちなみにこれをメンターなしで一人の力でできる人は、速いスピードで自然に伸びていく。とはいえそこまでできる人は、自分自身を含めてほとんど見かけない)
コミュニケーションスタイルを変えていこう
自分はもともとエンジニアではない別の職種として今の会社に入った。そのときは、一つのプロジェクトは1-2ヶ月ほどで、メンバーは自分と多忙な上司だけであり、相談に行くときは入念に準備したうえで慎重に機会を伺っていた覚えがある。
しかし技術で問題解決をするチームの場合、一つの複雑なソフトウェアを長い時間をかけて共同で作りあげていく。そのため、コミュニケーションの小さなすれ違いが、不確実性を大幅に増やしてしまう。エンジニアという仕事は「コミュニケーションが苦手でも技術力があればやっていける職業」というイメージを持たれがちだが、実は他の職種以上に周りとの対話や気配りが必要な、とても人間臭い仕事であることがわかった。
この本を読んで、自分がエンジニアになる前のコミュニケーションスタイルをまだ予想以上に引きずっていたことに気付かされた。苦手なところではあるけれど、少しずつ変えていきたい。
マージコミットと通常のコミットのただ一つの違い
タイトルは先日、会社の先輩に突然出題されたもの。その時は答えられず教えてもらった。
コミットオブジェクトとは
Gitによって記録されるのは、SHA-1でハッシュ化されたものにすぎない。Gitの内部では、ファイルのコンテンツをハッシュ化したblobオブジェクトと、それを指すことでディレクトリ名やファイル名を記録できるtreeオブジェクトが生成される。このtreeオブジェクトを指すものがコミットオブジェクトであり、コミットオブジェクトを指定すればtreeオブジェクトがわかり、そこを見ることでblobを取り出せる。
しかし、それだけだとコミットの順番がわからない。そこでコミットオブジェクトは、生成時に直前のコミットオブジェクトのSHA-1ハッシュ値を指定する。こうすることで、あるコミットから親のコミットをたどることができ、コミットの履歴がわかるようになる。
コミットオブジェクトを見るには、 git cat-file -p <コミットオブジェクトのSHA-1値>
と指定すればよい。
$ git cat-file -p 5c8c49c tree 00906ce530257852785a7149b8df0c6a75a48ad2 parent cdba5fbd79ee3a484cfd45a97cfbbe90f77c1e6e author Udomomo <udomomo@example.com> 1563115462 +0900 committer Udomomo <udomomo@example.com> 1563115462 +0900 add bar.txt
このように、parentとして前のコミットが表示されるのがわかる。
マージコミットとは
これをふまえて考えると、マージコミットもtreeオブジェクトを指すという点では同じである。しかし、違う点が1つだけある。以下はmasterブランチにfeatブランチをマージしたときのコミットを取り出したものである。
git cat-file -p 4afea17 tree 76ed3224820042a02d6af1886a2fb3259f1d8cb5 parent f8d2537e168efec54ce288769e1f4ca460258330 parent 9b0e505f0fd9a9b540f9783ebcbef9f2be3bddb4 author Udomomo <udomomo@example.com> 1563115699 +0900 committer Udomomo <udomomo@example.com> 1563115699 +0900 Merge branch 'feat'
parentが2つあるのがわかる。それぞれ、masterブランチとfeatブランチの以前のコミットを示している。
これがマージコミットと通常のコミットの違いである。逆に言えば違いはこれしかない。Gitの内部では両方ともコミットオブジェクトであり、ブランチというのは利用者向けにわかりやすく作られた概念にすぎない。
もう1つ異なる性質のコミット
マージコミットと通常のコミットは親の数が違うが、この点でいうともう1つ異なるコミットがある。それはinitial commitである。initial commitを取ってきてみると以下のようになる。
$ git cat-file -p cdba5fb tree 09a13b897d3d0f528d487c704da540cb952d7606 author Udomomo <udomomo@example.com> 1563115391 +0900 committer Udomomo <udomomo@example.com> 1563115391 +0900 first commit
parentが1つもない。最初のコミットだけは例外的にparentを持たないことがわかる。