JavaScript読み込みの際に指定できるasyncについて

こんにちは!kossyです!



さて、今回はJavaScript読み込みの際に指定できるasyncについて、
ブログに残してみたいと思います。


asyncという単語の意味


まずは単語の意味から解説します。

async, await 構文 で馴染みのある方もいらっしゃるかもしれませんが、
asyncはAsynchronousの略語で、「非同期の」という意味になります。

ちなみに対義語は、Synchronousです。

同期・非同期については以下のYouTubeの動画が基礎的なところは参考になりました。
https://www.youtube.com/watch?v=QugDLcOo_EE

別のトピックですが、
よく非同期処理と並列処理(マルチスレッド)を混同する方がいらっしゃいますが、
実際には全く別の処理の仕方になっています。
javascriptができるのは非同期処理であって並列処理ではない - Qiita


前置き長くなりましたが、JavaScript読み込みの際にasyncを指定すると、
「.jsファイルを非同期で読み込み、即座に実行」するようになります。

通常、ブラウザはHTMLを解析する際、コードの上部から解析していきます。
ここで、headタグにscriptタグが存在する時、そのscriptタグを見つけた時点でHTMLの解析を一時的に停止し、
jsファイルのダウンロードと実行が同期的に行われます。

asyncを記述すると、javascriptファイルのダウンロードが同期処理ではなく非同期処理で行われるようになり、
HTMLの解析は停止されなくなります。




勉強になりました。

ActiveStorageを rails c で試す

こんにちは!kossyです!



さて、今回はrailsのActiveStorageをrails consoleで試す方法について、ブログに残してみたいと思います。




環境

Ruby 2.6.3
Rails 6.0.3
MacOS Mojave




前提として、ActiveStorageのinstallおよびmigrateを終え、適当なモデル(例ではUserモデルとします)を作成済みで、
has_one_attached :image をモデルに記述済みとします。

設定手順等は以下の記事をご参照ください。

attach・detach

has_one_attachedを記述すると、attach, detach, attached? 等のメソッドが使えるようになります。

attachは以下の方法で行うことができます。

$ bundle exec rails c 

$ file_path = Pathname.new("../sample.png")

$ User.first.image.attach(io: File.open(file_path), filename: 'sample.png', content_type: 'image/png')
  User Load (11.8ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
   (1.0ms)  BEGIN
  Tenant Load (1.0ms)  SELECT `tenants`.* FROM `tenants` WHERE `tenants`.`id` = 1 LIMIT 1
  ActiveStorage::Blob Load (0.7ms)  SELECT `active_storage_blobs`.* FROM `active_storage_blobs` INNER JOIN `active_storage_attachments` ON `active_storage_blobs`.`id` = `active_storage_attachments`.`blob_id` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'image' LIMIT 1
  ActiveStorage::Attachment Load (0.7ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'image' LIMIT 1
  ActiveStorage::Blob Create (41.5ms)  INSERT INTO `active_storage_blobs` (`key`, `filename`, `content_type`, `metadata`, `byte_size`, `checksum`, `created_at`) VALUES ('81hurrmwnr639jbncqdrp29bawgs', 'otenki.png', 'image/png', '{\"identified\":true}', 11292, 'ZxJ+WIvspRPOnzLjZ4WNsQ==', '2020-08-16 14:58:50')
  ActiveStorage::Attachment Create (1.1ms)  INSERT INTO `active_storage_attachments` (`name`, `record_type`, `record_id`, `blob_id`, `created_at`) VALUES ('image', 'User', 1, 3, '2020-08-16 14:58:50')
  User Update (0.7ms)  UPDATE `users` SET `users`.`updated_at` = '2020-08-16 14:58:50.197520' WHERE `users`.`id` = 1
   (26.0ms)  COMMIT
  Disk Storage (2.5ms) Uploaded file to key: 81hurrmwnr639jbncqdrp29bawgs (checksum: ZxJ+WIvspRPOnzLjZ4WNsQ==)
Enqueued ActiveStorage::AnalyzeJob (Job ID: a52b37af-0810-48bc-9474-b7c85bd8ddde) to Async(active_storage_analysis) with arguments: #<GlobalID:0x00007fe2ccbeab98 @uri=#<URI::GID gid://sfa-rails/ActiveStorage::Blob/3>>
=> true

detachは簡単で、imageに対して実行するだけです。

User.first.image.detach
  User Load (15.8ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
  ActiveStorage::Attachment Load (2.5ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'image' LIMIT 1
  ActiveStorage::Attachment Destroy (52.0ms)  DELETE FROM `active_storage_attachments` WHERE `active_storage_attachments`.`id` = 3
=> nil

ちなみに、detachと似たメソッドでpurgeメソッドもあります。

User.first.image.purge
  User Load (0.7ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
  ActiveStorage::Attachment Load (0.6ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 1 AND `active_storage_attachments`.`record_type` = 'User' AND `active_storage_attachments`.`name` = 'image' LIMIT 1
  ActiveStorage::Attachment Destroy (3.3ms)  DELETE FROM `active_storage_attachments` WHERE `active_storage_attachments`.`id` = 4
  ActiveStorage::Blob Load (0.9ms)  SELECT `active_storage_blobs`.* FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` = 4 LIMIT 1
   (0.5ms)  BEGIN
  ActiveStorage::Attachment Exists? (0.6ms)  SELECT 1 AS one FROM `active_storage_attachments` WHERE `active_storage_attachments`.`blob_id` = 4 LIMIT 1
  ActiveStorage::Attachment Load (0.6ms)  SELECT `active_storage_attachments`.* FROM `active_storage_attachments` WHERE `active_storage_attachments`.`record_id` = 4 AND `active_storage_attachments`.`record_type` = 'ActiveStorage::Blob' AND `active_storage_attachments`.`name` = 'preview_image' LIMIT 1
  ActiveStorage::Blob Destroy (14.0ms)  DELETE FROM `active_storage_blobs` WHERE `active_storage_blobs`.`id` = 4
   (0.7ms)  COMMIT
  Disk Storage (0.3ms) Deleted file from key: udidu0lp13b3h0u3pgmfvqzg0yuk
  Disk Storage (0.3ms) Deleted files by key prefix: variants/udidu0lp13b3h0u3pgmfvqzg0yuk/
=> nil

detachメソッドと異なるのは、DiskのStorage(ファイルの実体)も一緒に削除する点です。

ハマりどころやユースケースについては以下の記事が詳しかったです。

エンコード

APIモードを使っていると、encodeした画像のバイナリデータをそのままフロントに渡すケースもあるかと思います。

以下にencodeの手順を記載します。

$ file_path = Pathname.new("../sample.png")

$ User.first.image.attach(io: File.open(file_path), filename: 'sample.png', content_type: 'image/png')

$ Base64.encode64(User.first.image.download)
=> "iVBORw0KGgoAAAANSUhEUgAAAFkAAADwCAYAAABrGMiJAAAYUWlDQ1BJQ0Mg\nUH..."

上記のように標準モジュールのBase64のencode64メソッドの引数に、attachしたimageをdownloadしたものを渡せばencodeできます。



勉強になりました。


参考にさせていただいたサイト

Rails Console から ActiveStorage にファイルをアタッチする方法 - Qiita
ActiveRecord トランザクションと ActiveStorage をちょっとだけ仲良くさせる方法 - SmartHR Tech Blog
Active Storage の概要 - Railsガイド
ActiveStorage::Attached::One
APIモードのRailsでActive StorageとS3を使ってバックエンド、Reactでフロントエンドを実装したい - tomoyaf blog
【Rails 5.2】 Active Storageの使い方 - Qiita

Effective Ruby 第1章まとめ

こんにちは!kossyです!



さて、今回はEffective Rubyという書籍を読んだので、備忘録としてブログに要件だけ残してみたいと思います。
1回読んだだけでは全て頭に入らないので、、、泣

Effective Rubyは8章立てになっているので、8回に分けて更新することになると思います。



では、まずは第1章です。



第1章

1-1. Rubyは何を「真」と考えているかを正確に理解しよう

  • false, nil以外の全ての値は「真」である
  • 多くの言語とは異なり、Rubyでは、数値ゼロは「真」である
  • falseとnilを区別しなければならない時には、nil?メソッドを使うか、falseを左演算子とする "==" 演算子を使う

1-2. オブジェクトを扱うときにはnilかもしれないということを忘れないようにしよう

  • Rubyの型システムの構造上、全てのオブジェクトがnilになり得る
  • nil?メソッドは、レシーバがnilならtrue、そうでなければfalseを返す
  • 適切なら、to_s、to_iなどの変数メソッドを使ってnilオブジェクトを強制的に型変換しよう。
  • Array#compactメソッドは、レシーバのコピーから全てのnil要素を取り除いた形のものを返す。

1-3. Rubyの暗号めいたPerl風機能を避けよう

  • String#=~ではなく、String#matchを使う。String#matchなら、複数の特殊グローバル変数ではなく、MatchDataオブジェクトに全てのマッチ情報を返す。
  • 短い暗号めいた名前のグローバル変数ではなく、長くて意味のわかる別名の方を使う(例えば、$: ではなく、 $LOAD_PATH)。ただし、ほとんどの長い名前は、Englishライブラリをロードしなければ使えない
  • 暗黙のうちに$_グローバル変数を読み書きするメソッドを使うのを避けよう(たとえば、Kernel#print、Regexp#~など)。

1-4. 定数がミュータブルなことに注意しよう

  • 定数は書き換えられないようにするために必ずフリーズしよう。
  • 定数が配列やハッシュなどのコレクションオブジェクトを参照する場合、コレクションとその要素をフリーズしよう。
  • 既存の定数に新しい値が代入されるのを防ぐためには、定数が定義されているモジュールをフリーズしよう。

1-5. 実行時の警告に注意しよう

AngularのRouterLinkActiveOptionsについて

こんにちは!kossyです!



さて、今回はAngularRouterのOptionのひとつであるRouterLinkActiveOptionsについて、
ブログに残してみたいと思います。




環境
node 12.13.1
npm 6.14.4
Angular 8.0.6




routerLinkActiveとは?
routerLinkActiveディレクティブは、routerLinkで指定したURLと現在のURLが一致した時に指定したクラス名を付与できます。

参考: Angular 日本語ドキュメンテーション


routerLinkActiveOptionsのexactをtrueとすることで、routerLinkと現在のURLが完全に一致した時に、activeクラスを付与できます。

<mat-sidenav-container>
  <mat-sidenav mode="side" opened="true" class="mat-elevation-z20"> <!-- can use mode="push or over"-->
    <mat-nav-list>
      <mat-list-item routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        トップ
      </mat-list-item>
      <mat-list-item routerLink="/area/edit" routerLinkActive="active">
        編集
      </mat-list-item>

      <mat-divider></mat-divider>
      <h3 matSubheader>天気予報</h3>
      <mat-list-item *ngFor="let pref of (areasObservable | async)" [routerLink]="['/forecast', pref.city]" routerLinkActive="active">
        {{pref.label}}
      </mat-list-item>
    </mat-nav-list>
  </mat-sidenav>

/ にアクセスした場合
f:id:kossy-web-engineer:20200815122729p:plain

/area/edit にアクセスした場合
f:id:kossy-web-engineer:20200815122823p:plain



RouterLinkActiveを使えば、URLに応じて動的にクラスを切り替えることが簡単に実現できます。


参考にさせていただいた書籍
Angularデベロッパーズガイド 高速かつ堅牢に動作するフロントエンドフレームワーク - インプレスブックス

1日1データモデリングのすヽめ

こんにちは!kossyです!



さて、今回はプログラミングから少し離れて、最近私が実践している「1日1データモデリング」をご紹介できればと思います。



そもそもデータモデリングってなに?なんで必要なの?

この部分は詳しい記事がありましたのでこちらで解説するのは控えておきます。
https://qiita.com/Ayumu_walker/items/96a4e4d364d3f52f36bd



1日1データモデリングって?

読んで字の如く、「1日1回はデータモデリングをする」活動です。

データモデリングのスキルは一朝一夕で身につくものでもないですし、
場数を踏んで身につけるものだと筆者は考えています。

そこで、簡易的で構わないので身近なものをモデリングして
データモデリングの力を養おうというのが「1日1データモデリング」の狙いです。

よく書籍で紹介されるのは「レシート」ですが、他にも、

  • Web電話帳(タウンページみたいな)
  • You Tubeのコメント機能
  • 会社の組織図
  • アルバイトのシフト
  • Eコマースサイトのクーポン機能

のように、ジャンルや業態問わずなんでもモデリングの題材にできると思います。

私の場合、まずは自分の頭で考えて、簡単なテーブル設計とER図を考えます。その後インターネットで「〇〇 データベース」と検索して答え合わせのようなことをしていますが、
すでにやり尽くされたテーマだったりで意外と情報があり、「あーそこまで考慮しないとダメか、、、」と思考が足りないと感じることもまだまだ多いです。

まとめ

データモデリングシステム開発の工程で言うと「上流工程」に入り、プログラミングそのものよりも大事だと思うのですが、
プログラミングの書籍よりも数が少なく、情報が集めにくいです。(企業のテックブログも自社のデータベース設計はおおっぴらにしてないし、、、)

なので、「1日1データモデリング」のような取り組みを通して、「最適なデータモデルとはなにか」を常に考え続けることが、
スキルアップに必要なのではないかと考えています。


参考にさせていただいたサイト・書籍

Rails で 検索機能を実装する際にフルネームで検索できるように実装したい

こんにちは!kossyです!




さて、今回はRailsで検索機能を実装する際に、フルネームで検索できるようにする方法を
ブログに残してみたいと思います。




環境
Ruby 2.6.3
Rails 6.0.0
Mysql 5.7.23




前提
usersテーブルがあり、カラムとして

  • last_name
  • firlst_name
  • last_name_kana
  • first_name_kana

があるとします。
ので、full_nameというカラムを用意するのであれば今回紹介する方法を取る必要はありません。



実装手順

まずは全コード晒してみます。

app/controllers/users_controller.rb

require 'nkf'

class UsersController < ApplicationController
  KEYWORD_SIZE = 6

  def search
    _params = params.permit(:keyword)
    keywords = ["%#{NKF.nkf('-w --katakana', _params[:keyword].gsub(' ', ''))}%"] * KEYWORD_SIZE 
    query_str = 'concat(last_name, first_name) LIKE ? OR last_name LIKE ? OR first_name LIKE ? OR concat(last_name_kana, first_name_kana) like ? OR first_name_kana LIKE ? OR last_name_kana LIKE ?'
    
    @users = User.where(query_str, *keywords)
  end
end

require 'nkf' と記述しているのは、ひらがなが検索フォームに入力されても、カタカナに変換するためです。
https://qiita.com/y_minowa/items/c204992e4665a8687d4a

gsubは、文字列中の第一引数に合致するものを、第二引数に置き換えるStringクラスのメソッドです。
今回の場合は、空白(スペース)があった場合は取り除くようにしています。

%を入れているのは、あいまい検索を行うために入れています。
https://qiita.com/nakanishi03/items/2a6dbd72f9793b7e0ce4

keywordsは配列になっていて、例えばkeywordが田中だった場合、

["%田中%", "%田中%", "%田中%", "%田中%", "%田中%", "%田中%"]

のような返り値となります。

query_strはSQL句を文字列にしているもので、その中で使っているconcatは、文字列を連結させることができるmysqlの機能になります。(PostgreSQLの場合は || )
last_nameとfirst_nameをconcatする事で、フルネームでの検索を可能にしています。

\*keywordは配列を展開している動作になります。*は配列の前につけると配列を展開できる、Rubyのショートハンドになってます。
javascriptだとスプレッド構文がそれに当たりますかね。


これでひらがなでもカタカナでも漢字でもフルネームで検索ができると思います。

Railsでenumを定義している時、自動でscopeが定義される

こんにちは!kossyです!




さて、今回はRailsenumを定義すると、自動でscopeが定義されるので、
その使い方についてブログに残してみたいと思います。




環境
Ruby 2.6.3
Rails 6.0.3
MacOS Mojave



使い方

例えば、Articleモデルがあって、

class Article
  enum status: [:published, :draft, :ignore]
end

のようにenumでstatusを定義しているとします。

このとき、「Articleのなかでstatusがdraftになっているレコードを抽出したい」とした場合、

Article.where(status: :draft)
=> #<ActiveRecord::Relation [#<Article id: 1, status: "draft"]

のように取得できますが、enumで定義している場合、

Article.draft
=> #<ActiveRecord::Relation [#<Article id: 1, status: "draft"]

とする事で、whereを使った時と同様の結果を得られるようになります。

where.notで取りたい場合も、

Article.not_draft

とするだけで取得することができます。

これは、enumを定義すると自動的にクラスに対してscopeが定義されるためです。
ActiveRecord::Enum


勉強になりました。

Railsでsendgridのmessage_idを取得したい

こんにちは!kossyです!



さて、今回はRailsでsendgridをメール配信サービスとして使用していた時に、
message_idを取得する方法についてブログに残してみたいと思います。




環境
Ruby 2.5.1
Rails 5.2.3
MacOS Mojave




なお、sendgridの設定周りについての説明は割愛します。
configは下記の記事が参考になるかと思います。
https://sendgrid.kke.co.jp/docs/Integrate/Frameworks/rubyonrails.html



ActionMailer::DeliveryJobではなくNet::SMTP::Responseを返り値にする

通常、RailsでActionMailerを使ってメールを送信するときは、
deliver_nowかdeliver_laterを使うと思いますが、
https://edgeapi.rubyonrails.org/classes/ActionMailer/MessageDelivery.html

上記2つのメソッドでメールを送信した場合、返り値がActionMailer::DeliveryJobになります。

これだと、sendgridのmessage_idを受け取ることができないので、
smtp_configと実行するメソッドを変更する必要があります。

まずは config/environment/各環境のenvファイル のsmtpのセッティングを変更します。

  config.action_mailer.smtp_settings = {
    user_name: ENV['sendgrid_user_name'],
    password: ENV['sendgrid_password'],
    domain: 'hogehoge.com',
    address: 'smtp.sendgrid.net',
    port: 587,
    authentication: :plain,
    enable_starttls_auto: true,
    return_response: true # 追加
  }

return_responseをtrueにすると、サーバーからのresponseをそのまま返すようになります。

参考: http://aligach.net/diary/20180725.html

mail gem内でreturn_responseの値を見て分岐しているようでした。

github.com




そして、deliver_later(またはdeliver_now)ではなく、deliver_now!を使うようにします。

すると、

response = UserMailer.notify_private_announcement(user).deliver_now!

response.class
=> Net::SMTP::Response

ActionMailer::DeliveryJobではなくNet::SMTP::Responseを返り値にすることができます。

Net::SMTP::Responseインスタンスはattrとして、

response
=> #<Net::SMTP::Response:0x005652f6301890 @status="250", @string="250 Ok: queued as 1dMBeSp6Rj-Uh5TdzJKgmq\n">

statusとstringを持っています。
内、sendgridのmessage_idは、1dMBeSp6Rj-Uh5TdzJKgmq の部分になります。

この文字列を取得するには、

# この書き方なんとかしたい、、、

response.string[18..39]
=> "1dMBeSp6Rj-Uh5TdzJKgmq"

とすることで取得できます。

このmessage_idを取得し、sendgridのEventWebhookのsg_message_idと前方一致させることで、
メールの開封状況を取得することもできるようになります。

EventWebhookについては、
https://sendgrid.kke.co.jp/docs/API_Reference/Webhooks/event.html
https://sendgrid.kke.co.jp/docs/Tutorials/C_Manage_Events/using_event_webhook.html

が参考になるかと思います。

RailsにおいてDate.todayで日本時間が返らない原因を探った話

こんにちは!kossyです!



さて、今回は個人開発でLineBotをRailsを使って作成した時に、
Date.todayで日本時間が返らなかった時の備忘録を残してみたいと思います。



環境
Ruby 2.6.3
Rails 6.0.2
MacOS Mojave



config.active_record.default_timezoneの設定忘れ

単純なミスでした、、、

しかし、よく考えると時刻周りのconfigについて理解が曖昧だったので、調べてみました。



まずは公式ドキュメントをみました。
https://railsguides.jp/configuring.html

config.active_record.default_timezone:
データベースから日付・時刻を取り出した際のタイムゾーンをTime.local (:localを指定した場合)と
Time.utc (:utcを指定した場合)のどちらにするかを指定します。
デフォルトは:utcです。

引用: https://railsguides.jp/configuring.html


ふむ、デフォルトはutcだったから日本時間が返らなかったんですね。


他にはどんな影響があるのでしょうか。

config.active_record.default_timezoneの設定はDBを読み書きする際に、DBに記録されている時間をTime.utcで読むかTime.localで読むかを設定する。
:utcの場合DBに記録されている時間はUTC扱いで、この時DBサーバのタイムゾーン設定は考慮しない。
ActiveRecordインスタンスが持っているTimeWithZoneの値をUTCに変換し、その時刻をDBに書き込む。
:localの場合は、DBに記録されている時間はシステムのタイムゾーンとして扱う。
ActiveRecordインスタンスが持っているTimeWithZoneの値をシステムのタイムゾーンに変換し、その時刻をDBに書き込む。

config.active_record.default_timezoneは、DBのタイムゾーンと一致させておくべきだ。
DBのタイムゾーンUTCなら:utcにDBのタイムゾーンがSYSTEMなら:localにしておく。

新しくRailsのシステムを開発する場合は、DBのタイムゾーン設定とconfig.active_record.default_timezoneを合わせるように注意した方が良い。
もし、その設定に気付かず(忘れて)運用を開始してしまった場合は、MySQLタイムゾーンの設定をUTCにした方が良いかもしれない。

引用: https://qiita.com/joker1007/items/2c277cca5bd50e4cce5e

上記の記事が詳しく説明されていました。

時刻周りに無知の状態でWebアプリを開発していたら、事故りますねこれ、、、


皆さんも同じ轍を踏まないよう、くれぐれもご注意ください。

Railsでsendgridを使ってメールを一斉送信したい

こんにちは!kossyです!



さて、今回はRailsでメール配信サービスとしてsendgridを使っている場合に、
メールを一斉送信する方法について、ブログに残してみたいと思います。




環境
Ruby 2.5.1
Rails 5.2.3




なお、sendgridでメール送信を行う設定周りについては説明を割愛します。
configには以下の記事が参考になると思います。
https://sendgrid.kke.co.jp/docs/Integrate/Frameworks/rubyonrails.html



X-SMTPAPIを使う!

sendgridにはメール送信APIとして、SMTP-APIとWeb-APIがあるのですが、
SMTP-APIの機能に複数の宛先を選択する機能が用意されています。

今回はこのSMTP-API(別名X-SMTPAPI)の「複数の宛先を選択する機能」を使って、一斉送信をする例を紹介します。


例としてApplicationMailerクラスを継承したUserMailerクラスが定義してあるとします。

class UserMailer < ApplicationMailer

  def send_announcement_mail(subject, content, emails)
    @content = content
    xsmtp_api_params = { to: emails }
    headers['X-SMTPAPI'] = JSON.generate(xsmtp_api_params)
    mail(to: emails, subject: subject)
  end
end


headers['X-SMTPAPI']にjsonを格納する事で、簡単に一斉送信が可能になります。

send_announcement_mailメソッドの引数には、emailsが格納された配列を渡します。

呼び出し元のコードはこんな感じ。

class AnnouncementsController < ApplicationController
  def create
    @announcement = Announcement.new(announcement_params)
    if @announcement.save
      UserMailer.send_announcement_email(@announcement.subject, @announcement.content, User.all.pluck(:email)).deliver_later
    else
      # 例外処理
    end
  end
end

@announcementオブジェクトはattrとしてsubjectとcontentを持っていて、@announcementがDBに保存されたあと、
send_announcement_emailを呼び出し、subjectとcontentとUser全員のemailの配列をpluckメソッドを使って取り出して引数として渡しています。



to: emails とすると、宛先が見えてしまうのでは?と懸念された方がいるかもしれません。
が、公式リファレンスにその辺りのことがきちんと書かれていました。

このリクエストはSendGridで分解され、各宛先にはToヘッダに個別に宛先指定(他の人の宛先が見えることはありません)されたメールが届きます。

引用: https://sendgrid.kke.co.jp/blog/?p=4232


このように、sendgridのX-SMTPAPIを使えば、簡単にメールの一斉配信機能を実装することができます。
X-SMTPAPIには他にも機能があるので、興味のある方は、

https://qiita.com/wawoon/items/6de229ca5e891c00452b
https://sendgrid.kke.co.jp/docs/API_Reference/SMTP_API/building_an_smtp_email.html

上記の記事を読んでみてください。