kent備忘ログ

お仕事と趣味生活と

JavaScript Primer 迷わないための入門書を読んで

前回の自動テストに続き、書籍によるJavascriptのインプットを行いました。

良かったところ

 一言にJavascriptといっても基本文法から関数やクラス、DOM操作や各種ライブラリなど、その使用範囲は多岐に渡ります。本書はその全てを網羅することを目的とせず、プログラミング経験者が、初めてJavascriptに触れる際に手にする入門書となっています。

またWeb版と書籍版の2つがあり、Web版では本家Javascriptの更新に合わせて内容も随時改訂が入ります。書籍は末尾に索引が入っています。

書籍内でも触れられていますが、Javascript自体のリファレンスとしては実質の公式ドキュメントとなっているMDN Web Docsがおすすめです。  


 序盤は基本文法から入り、他の言語と同じく変数や定数・演算子やデータ型・条件分岐や反復処理といった内容から解説していきます。随所に例となるコードが書かれており、解説したいものがどのような動きをするか書籍の中でわかるようになっています。

Web版では例となるコードの下に実行ボタンがあり、ボタンを押して実際にそのコードがどのように動くかを確かめることもできます。

後述の「難しかったところ」でも触れますが、次第に解説される内容は複雑で難解になってきます。本書は第1章〜第32章までで構成されており、一度読んだだけでは到底理解しきれないので、必要に応じて振り返りで読み返す必要があると思いました。


学んだこと
(第一部)基本文法

第1部〜第9部

第10部〜第21部

  • 条件分岐と反復処理、オブジェクトとプロトタイプオブジェクト
  • 配列と文字列(Unicode)について
  • 関数とスコープ
  • thisキーワード・ラッパーオブジェクトについて
  • クラス
  • 例外処理

第22部〜第28部

  • 非同期処理について
  • コールバック・Promise・Async Function
  • Mapオブジェクト・Setオブジェクト
  • JSONデータフォーマットについて
  • Dateオブジェクト・Mathオブジェクト
  • ECMAScriptモジュールについて
  • ECMAScriptのバージョンと歴史
第2部(ユースケース)

第29部〜第32部

  • アプリケーション開発の準備
  • Ajax通信についてでもアプリを用いた解説
  • Node.jsを用いたCLIアプリケーションの作成
  • Todoアプリケーションの作成

難しかったこと

 ブラウザがあればそのブラウザ上で動作し、気軽に環境構築が行えるJavascriptですが、今までのRailsのエラー表示画面と違って、ブラウザのデベロッパーツールの中で表示されているエラーから原因を究明しなければならず、その違いに慣れるまで苦労しました。

 また、これもRubyとの比較になってしまいますが、全体を通して基本文法の中で記述する記号や文字量が多く、構文エラーの原因となる事が多々ありました。いかにRubyの文法がシンプルで記述量の少ないものであるという事が再認識されました。
(Rubyはシンプルであるが故に、各文法をしっかり理解していないとコードを読み解く事が難しいという点もありますが…)

 各解説単元で振り返ると、Javascriptの性質でもある「暗黙的な型変換」が厄介なものであるという印象を受けました。開発者が意図していない型解釈をしてしまうと、プログラムのコード自体が意図しない動きとなってしまうのはバグの原因にもなりますし、エラー対策のために時間を取られてしまう原因にもなりそうでした。

 オブジェクトやプロトタイプオブジェクト、thisキーワード、非同期処理などについては理解がまだまだ浅いと思うので、再度実務で取り扱う事になった際に復習が必須だと思いました。

3月の振り返り(学習振り返りから、エンジニアとして働くことになった話)

RailstwitterのようなSNSアプリ制作を月の頭に終えた後、自社開発企業様でエンジニアとして働かせていただくことになりました!

3月上旬

先月に続き新たに別のユーザが自分のツイートに対しコメント・リツイート・いいねをした際に通知が行われる通知機能を実装しました。

この機能の実装にはモデルのポリモーフィックという性質がドンピシャにハマりました。ツイートの子モデルであるコメント・リツイート・いいねに対し他ユーザのアクションがあった際、3つのモデル別にアクションしたユーザとログインユーザを紐付けたレコードを作成します。

notification.rb

  class Notification < ApplicationRecord
    belongs_to :category, polymorphic: true

    enum action: { like: 0, retweet: 1, comment: 2 }
    scope :sorted, -> { order(created_at: :desc) }
  end

like.rb

  # モデルの中にメソッドとして実装
  def like_notification
    Notification.create!(category: self, user_id: tweet.user_id, action: Notification.actions[:like])
  end

あとはコメント・リツイート・いいねのアクションの中に上記メソッドの実装と、ビュー上に通知内容を表示する画面を実装すれば通知機能の完成です。


続いて、ロードマップの次のセクションに取り掛かろうとした際に、ゆうだいさんからお声掛けを頂く事になります。

HappinessChainに去年入会し、ロードマップも途中で、まだまだ転職活動も先の話だと思い込んでいた私には、まさに突然の出来事でした!勿論、自社開発企業に未経験転職を目指していた私は、二つ返事ですぐに紹介頂いた企業の社長様と面談する事になりました!

面談の中でも、今までどのような仕事をして来たのか、プログラミングの学習はどのように行なってきたのか等を聞かれましたが、
36歳の私自身が一番コンプレックスに感じていた、年齢の事に関しては何も聞かれることはありませんでした。

結果としてはその場でいつから入社して仕事出来るか聞かれ、こちらから後日正式な入社日を伝える形で面談を終えました!

5月より入社意向の旨を伝えました。今までとは全く違う環境下での実務になりますが、貪欲に業務内容と知識を吸収し、実りある実務経験を積みたいです!

3月中旬

引き続き、入社まで時間があるのでロードマップの課題に取り組みます。Rails課題の次は自動テストの課題に取り組みました。 今回の自動テストではライブラリとしてRspec, Capybara, FactoryBotを使用したテストになります。

テストにはモデルに保存されるレコードが正しく検証されているかのモデルスペック
アクションが正しく実行されるかのリクエストスペック(コントローラスペック)
Capybaraを使用したユーザが実行するであろう動作を検証するシステムスペック

モデルスペック(user_spec.rb)

  # ユーザモデルのテスト
  it '名前がなければ無効な状態であること' do
    user = FactoryBot.build(:user, name: nil)
    user.valid?
    expect(user.errors[:name]).to include('を入力してください')
  end

リクエストスペック(users_spec.rb)

 # ユーザのコントローラのアクションテスト
  describe 'ユーザ一覧' do
    it 'ユーザ一覧ページへの遷移が成功すること' do
      get users_path
      expect(response).to have_http_status(:success)
    end
  end

システムスペック(user_spec.rb)

# Capybaraを使用
  context 'when user login' do
    before do
      visit root_path
      click_link 'ログイン'
      fill_in 'Eメール', with: user.email
      fill_in 'パスワード', with: user.password
      click_button 'ログイン'
    end

    it 'ユーザログイン成功' do
      expect(page).to have_current_path root_path, ignore_query: true
    end
  end

モデルスペックとリクエストスペックがそれぞれモデルとコントローラのテストであるのに対し、システムスペックはユーザが起こすであろう動作を想定してテストするため、記述方法もCapibaraを使用する為独特になります。

実際にの課題アプリ上ではdeviseの動作との兼ね合いもあり、また、テスト結果を想定してexpect文を考える必要もある等、多少手こずるところもありましたが、無事全てのテスト結果をグリーンにする事ができ、LGTMを頂けました。

3月下旬

下旬からは次のプログラミング言語であるJavascriptに取り組んでいます。

どうしても今まで学んできたRuby言語との比較になっていましたが、おかげで動画解説の中でこの機能はRubyでいうこの機能と同じものだというようにイメージを掴みやすく学ぶ事ができました。

初めに動画教材を2本視聴しました。基本的なJavascriptの文法から簡単なデモアプリまでの実装を行いました。一方でJavascriptの性質として「暗黙的な型変換」があります。これは初めて知りましたが、第一印象としては厄介な性質を持つものだなと思いました。

現在はJavaScript Primerをインプット教材に学習中です。クラスや関数・thisキーワードや、難しい難しいと言われている非同期処理など、初めて学ぶことも多く、一度に理解するのは到底無理だと承知していますが、少しでも理解を深めようと奮闘中です!

Everyday Rails - RSpecによるRailsテスト入門を読んで

Railsの課題制作が長らく続きましたが、久々に書籍によるインプットを行いました。

良かったところ

 様々なテスト技法の種類がある中で、Railsでのモデルスペックのテスト、コントローラスペックでのテストといったように、その機能だけでテストできる単体テストから解説に入り、テストの基本を教わったところで、Webアプリ全体のテストをシステムスペックを使用して正常に動作しているか確認する統合的なテストの解説に入ります。

 個別の単体テストからシステム全体のシステムテストに入る流れは、Rspecのテストの流れを掴む上で非常に分かりやすい解説の仕方だと思いました。

 また、今回の教材の作者はRubyの学習の際にはお世話になった、チェリー本でお馴染みの伊藤淳一さんの為、サンプルアプリのコード中心でその周りに解説文が明記されているような形で、チェリー本の教材同様、コードの流れを見ながら解説を学んでいく形になりました。


 また、Rspecの基本文法としてdescribe, before, context, it文といった単語が出てきますが、各単語も単に用語の解説だけで終わるのでは無く、本書の中では必要に応じて使用され、テストコードの文章をDRYの原則に則り簡潔に書くために使用されます。

解説の中でFactoryBotやCapybaraといった、実際のユーザの使用ケースに合わせたテストが出来るツールが使用されます。

FactoryBotはテストを行う際のテストデータの作成、Capybaraはシステムスペックやリクエストスペックでテストしたいユーザのアクションを定義する事ができ、実際にWebアプリ上でのユーザの動きをテストする事が出来ます。


学んだこと

第1章〜第2章
Rspecのイントロダクションとセットアップ

  • Rspecについて
  • 今回のサンプルアプリについて
  • Rspecのセットアップ

第3章〜第4章
モデルスペックと意味のあるテストデータの作成

  • FactoryBotとfixtureの違い
  • アプリケーションにFactoryBotでデータを追加

第5章〜第6章
コントローラスペックとシステムスペック

  • コントローラスペックの基本
  • ユーザ入力のテスト
  • システムスペックの基本
  • Javascriptを使った操作のテスト
  • ヘッドレスブラウザを使う
  • システムスペックとフィーチャーペックについて

第7章〜第8章
リクエストスペックとDRYなスペック

  • リクエストスペックとシステムスペックの比較
  • GET, POSTリクエストをテストする
  • コントローラスペックをリクエストスペックで置き換えする
  • let句について
  • contextの共有・失敗の集約(aggregate_failures)

第9章〜第11章
速いテストとテスト駆動開発に向けて

  • モックとスタプ
  • 不要なテストを削除する
  • テストコードからシステムの実装へ

第12章
最後のアドバイス


難しかったこと

 今回は前回の課題まで行ってきたWebアプリ制作におけるコードの実装とは違い、コード実行上のバグを未然に防ぐためのテストをアプリ上に備える事がメインでした。

 単にアプリ開発のためにコードを記述して来た経験しかない段階では実際にテストコードを動かし、テストをパスするのか、はたまたエラーが出てしまうのか不安に思いながらコードを書いてテストを実行するような感じでした。(もちろんアプリ開発の際も書いたコードの思惑通りに動作するか不安を持つこともありましたが、)

 ただ、本書の最後で伝えていたテスト駆動開発というのはテストコードから先に書き始め、その後実際のWebアプリの実装に取り掛かるといった今までとは逆の手順で、エラーを潰しながら実装出来るという点では初回に一度実装するだけではなく、アプリの改良やアップデートの際も、一度書いたテストコードがパスするように変更を加えて行けば新たなバグを出すことなく、スムーズに実装を進められるのではないかというメリットもあると思いました。

2月の振り返り(deviseユーザプロフィール変更・同一テーブルの自己結合・同一ビュー内での一覧・詳細画面の表示)

先月に続いてRailstwitterのようなSNSアプリ制作でしたが、実装を通してrubyの文法の再確認やRailsの細かい実装テクニックについて、新しく学んだことも多かったです。

2月上旬

先月活用したdeviseのユーザプロフィールを変更できるように実装しました。

routes.rb

  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations'
  }

先月の課題にもあったユーザ登録の際に、ルーティングの設定でdevise_forの中にregistrations: 'users/registrations'を手動で設定した為、プロフィール登録だけでなく変更の際にも、deviseのビューファイル内のregistrations/editを使用して変更画面を呼び出す必要があります。

当初、後から追加したユーザコントローラ上でupdateのアクションを使いするものかと思っていましたが、よくよく考えたらユーザ登録の時点でdeviseを使用していたので、deviseに保存されたユーザデータを呼び出さなければ、更新できないなと納得しました。

続いて、ポストした文章の投稿時間がデフォルトの世界標準時のままだった為、日本標準時に変更しました。

始めに、configのapplication.rbに日本時間のタイムゾーン設定をします。

application.rb

config.time_zone = 'Asia/Tokyo'

続いて、同じくconfigのintializersディレクトリにtime_formats.rbというファイルを作り、その中に日本時間の表記設定をします。

time_formats.rb

Time::DATE_FORMATS[:datetime_jp] = '%Y年 %m月 %d日 %H時 %M分'

これで投稿時間が9時間前の時間ではなく日本の現在時刻が表示されるようになりました。

2月中旬

ユーザ同士のフォロー機能実装の為、同一のテーブル同士(自己結合)となる多対多のアソシエーションを学びました。

SNSアプリのように登録ユーザが他のユーザをフォローする際、お互いにユーザが他の沢山のユーザをフォローできます。従って多対多の関係になるので、中間テーブル(今回はフォローテーブル)が必要になり、外部キー制約を設定したユーザIDカラムを参照して、お互いのフォロー関係を管理します。

中間テーブルは親モデルであるユーザテーブルの所属になる為、belongs_toで連携します。

ただ、単にfollower_user, followed_userと記述してしまうと、存在しないfollower_user, followed_userテーブルを参照しに行ってしまいますので、下記のようにclass_name: 'User'と記述し、参照先にユーザモデルを指定します。

follow.rb

class Follow < ApplicationRecord
  belongs_to :follower_user, class_name: 'User'
  belongs_to :followed_user, class_name: 'User'
end

親モデルのユーザモデルファイルでは、フォローするユーザ側から見たデータ取得メソッドと、フォローされるユーザ側から見たデータ取得メソッドを設定することが出来ます。
これによってuser.followingsもしくはuser.followersでフォローしたユーザの人数・フォローされたユーザの人数の取得が簡単になります。

フォローユーザーから見た中間テーブル(followテーブル)を「follows」とする。「入り口はFKの'follower_user_id'」
followモデルに「belongs to :follower_user」と定義し、中間テーブルのfollowed_user_idカラムを「出口」として、フォローユーザのフォロー人数を集計する事を「followings」と定義(例:user.followings)
フォロワーユーザから見た中間テーブルを「passive_follows」とする。「入り口はFKの'followed_user_id'」
followモデルに「belongs to :followed_user」と定義し、中間テーブルのfollower_user_idカラムを「出口」として、フォロワーユーザのフォロワー人数を集計する事を「followers」と定義(例:user.followers)

user.rb

# フォローユーザからの視点
  
has_many :follows, inverse_of: :follower_user, class_name: 'Follow', foreign_key: 'follower_user_id', dependent: :destroy
has_many :followings, through: :follows, source: :followed_user
  
# フォロワーユーザからの視点
  
has_many :passive_follows, inverse_of: :followed_user, class_name: 'Follow', foreign_key: 'followed_user_id', dependent: :destroy
has_many :followers, through: :passive_follows, source: :follower_user

実際にWebアプリ上でもログインユーザのフォロー人数・フォロワー人数の取得を簡単に行うことが出来ました。

続いて、ツイート機能・いいね機能・コメント機能・リツイート機能を実装しました。
  • これらの機能については、始めに実装したツイートテーブルが親となり、一つのツイートには沢山のいいね・コメント・リツイートが付くことになる為、親子の関係になります。

  • ツイートにはツイート本文とツイートしたユーザIDが外部キーとして埋め込まれる形になり、アソシエーションによってツイートのレコードデータからユーザの特定が可能になります。

  • また、ツイートの子モデルではツイートIDと、いいね・コメント・リツイートしたユーザのIDが、外部キーとして埋め込まれることになります。
    ツイートレコードと同じように、各レコードデータからユーザやツイートの特定が可能になります。

実際に実装する際も、ツイート機能の実装が完了した後は、同じようなコードで実装がトントンと進みました。
おかげで、各機能ごとにメンターさんにレビューをレビューを頂く必要があった為連日、プルリクエスト申請の日々になりました。日々のレビューありがとうございます。

2月下旬

ユーザフォロー機能 ・ブックマーク機能の実装と、メッセージ機能実装を行いました。
始めに、先に実装してあったユーザテーブルとフォローテーブルのアソシエーションを活用し、ユーザ同士のフォロー機能を実装しました。

また、ツイートのブックマーク機能に関しても、先にいいねやリツイートの実装を学んでいた為、すんなりと実装することが出来ました。

2月最後の課題となったメッセージ機能(いわゆるDM機能)では、当初想定していた実装とはかけ離れた考え方も応用的で、複雑な実装を迫られることになりました。

当初、私はコメント機能と同じように、外部キーにユーザIDを持ったメッセージテーブルを作成し、ユーザ同士のアソシエーションを設定すれば良いと思っていました。

しかし実装を進めるうちに、作成したメッセージがどのユーザのものなのか?メッセージ一覧画面でログインユーザと、メッセージを送りつけたユーザを、どのように紐づけるのかわからなくなってしまいました。(今から考えれば情報が少なすぎるので当然と言えば当然ですが…)

必要に迫られてWebで検索した所、機能を実装するにあたり、

  • メッセージをやり取りするお互いのユーザは一つの部屋に紐づけられること
  • その部屋は沢山のユーザ同士がメッセージをやり取りするたびに作成される為、ユーザとメッセージ部屋は多対多の関係になること
  • ユーザとメッセージ部屋の紐付けを管理する為、中間テーブルとしてエントリーテーブルが必要であること。
  • その中で当然ながらメッセージのやり取りが行われるため、メッセージテーブルを中間テーブルとして作成し、メッセージにも外部キーとしてユーザIDと部屋のルームIDを紐づけること。

が必要であることがわかりました。

また、今回の要件ではメッセージ部屋の一覧画面と、メッセージ詳細画面を同一画面内で表示させるよう指示がありました。

今まで作ってきた一覧画面から一つのレコードデータを選択し、詳細画面に遷移させるルーティングとは別の動作になる為、一覧画面に遷移する際、どのようにしてパラメータIDを送信するか悩みました。

試行錯誤の末の解決策としては、メッセージ部屋のindexビューに遷移する際、httpのパラメータとしてuser.idを付与することで同一index画面内でもIDパラメータを渡すことが出来ました。

rooms_controller

# 通常indexビューに遷移するヘルパーメソッドはrooms_path → rooms_path(user.id)に変更。  
# その後、model#indexアクションにて、送信されて来たidパラメータが存在する場合のアクションを実装する。  
# .presenceメソッドでパラメータIDが付与されているか判断。if params[:id].present?と同義
Room.find_by(user_id: params[:id].presence)
  

今回に限らず要件に沿った動作を実現する為に、Railsは痒い所に手が届く柔軟な設計と幅広いテクニックがあり、奥が深いことを再確認しました!

1月の学習振り返り(selfキーワード復習、devise、OmniAuth)

1月に取り組んだ学習を振り返りです。先月に続いてRailsのWebアプリ制作でしたが、実装を通してrubyの文法の再確認やRailsの仕組みについて新しく学んだことが大半でした。

1月上旬

まず始めに、ruby学習時から曖昧にしてしまっていたselfキーワードについて学び直しました。
  • selfキーワードはレシーバ自身を表し、省略する事ができる。
    • インスタンスメソッド内で定義された場合はクラスのインスタンスそのものを表す。
    • クラスメソッド内で定義された場合はクラスそのものを表す。

当初、アクションのビジネスロジックをモデルファイルを使用せず、全てコントローラ上に記述していた為、コントローラ上のコード量が多過ぎになり、Rubocop先生からも怒られていましたが、selfキーワードを理解し、モデルファイルにビジネスロジックを移動させることで、コントローラ上のコードをすっきりさせる事ができました。

coupon.rb

# クーポンモデルのis_validカラムのbooleanをtrueからfalseにする
  def nullification
    return unless is_valid? # 値が既にfalseの場合は変更せず戻る

    update!(is_valid: false)
  end
続いて、表示されているビューと対応するアクションの場所についての復習になりました。

課題で3つの項目を1ページ内に表示させているレイアウトでしたが、当初、モデルのDBを更新・取得させるアクションはそのモデル名のコントローラ内でアクションを記述するものであると思い込んでおり、案の定、意図した通りに表示されず、どのアクションがどのビューに対応するのか学び直しとなったきっかけでもありました。

shopping_cart_controller.rb

class ShoppingCartController < ApplicationController
def index # 一つのインデックスの中に多数のインスタンスを記述。
  @shopping_cart_items = ...
  @total_price =...
  @discount_price =...
  @payment =...
end
if文を用いた条件分岐が増えてきた中で、コード記述量を減らす為、「&.」通称ぼっち演算子を学びました。

&.を使用することで、メソッドのレシーバの値がnilとなる場合でもエラーが発生しなくなります。 Ruby on Rails 5速習実践ガイドより

shopping_cart_controller.rb

class ShoppingCartController < ApplicationController# paymentがnilであってもエラーにならない
      @code = payment&.code
      @rate = payment&.rate
end

他にもテーブルへの一意性制約の設定に一度モデルのindexの作成が必要であることや、boolean型のカラム以外でも、モデルファイルにenum型を使用して真偽値を設定する方法を学びました。

coupon.rb

  enum status: { '有効': 0, '無効': 1 }

  def nullification
    return unless status == '有効'

    update!(status: '無効')
  end

schema.rb

    t.string "code", null: false
                  ︙
    t.index ["code"], name: "index_discounts_on_code", unique: true

プロモーションコード機能の表示・適用処理を追加できたので課題提出→LGTMを頂けました。

1月中旬

1月中旬からは次の課題に移りました。
今回はsns型のアプリの為、ユーザ登録・ログインにdeviseのgemを使用しました。そもそも触った事が無かったので、いざdeviseの仕組みから理解していこうと思いましたがとても奥が深いです。
未だ全てを理解出来ていませんが、わからないながらも手を動かして実装していく内に、既にユーザー管理に必要なアクションはgemで管理されており、恐ろしく簡単にユーザー登録・管理機能を実装できる事がわかりました。

その中でも当時の私自身のXポストでもdeviseで簡単にユーザ管理機能が実装できる理由として、

  • Deviseの実体はApplicationControllerにモジュールとしてincludeされている。
  • Deviseで作成したモデル(scope)のcontrollerは、本家Deviseの機能を継承している。
  • controllerのメソッドをコメントアウトしてオーバライドすればアクションをカスタマイズ出来る。

とざっくりと表現していました。

一方でフロント側になるビューでは初めてerbではなく、Railsのテンプレートエンジンであるslimを使用した実装を行いました。

erbだけしか使用した事が無かった最初は、使いにくいと感じていましたが、それでも使い続ける内にそもそものコード記述量が低いのもあり、だんだんと簡潔にコードが書けることにメリットを感じる事ができました。

_post_form.html.erb

        <%= f.text_area :content, class: "form-control border-0 shadow-none bg-light", placeholder: "いまどうしてる?"%>
          <div class=d-flex justify-content-between mx-4 mb-2>
            <i class=bi bi-image>
              <div class=actions>
                <%= f.submit "ポストする", class: "btn btn-outline-secondary rounded-pill" %>
              </div>
            </i>
          </div>

_post_form.html.slim

        = f.text_area :content, class: "form-control border-0 shadow-none bg-light", placeholder: "いまどうしてる?"
        .d-flex.justify-content-between.mx-4.mb-2
          i.bi.bi-image
          .actions
            = f.submit "ポストする", class: "btn btn-outline-secondary rounded-pill"

1月下旬

通常のログインの他に、他webサービスの認証機能を利用したOAuthについて学びました。
  • deviseにおけるOAuthに役割を持つgemは'OmniAuth'になります。
  • 解説はdeviseのGitHub wikiが参考になります。

OmniAuth自体、各Webサービスの認可に対応したgemを配布しています。
OmniAuth List-of-Strategies

今回は、GitHubを利用した認証を導入しました。今回初めて各Webサービスの開発者ページにアクセスしましたが、実際にOAuth認証機能の編集画面が用意されていたことにも驚きました。

omniauth_callbacks_controller.rb

# devise wikiより引用(実際は各Webサービスに応じてカスタムして使用)
  
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  # See https://github.com/omniauth/omniauth/wiki/FAQ#rails-session-is-clobbered-after-callback-on-developer-strategy
  skip_before_action :verify_authenticity_token, only: :facebook

  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"].except(:extra) # cookieのオーバフロー対策
      redirect_to new_user_registration_url
    end
  end

deviseを使用したユーザ登録・ログイン機能の追加・各Webサービス(今回はGitHub)を利用した認証ログインを追加できたので課題を提出→LGTMを頂けました。

12月の振り返り(複数テーブルによるアソシエーションと値の参照・更新についての理解)

12月に取り組んだ学習を振り返りです。表向きはRailsのWebアプリ制作でしたが、中身はDBに関するテーブル同志のお互いの関連付けと、それぞれのレコードの値取得方法や更新方法を調べることが大半でした。

12月上旬

まず始めに、既に常套手段となっているショッピングカートを用いた、商品の追加機能を実装しました。
  • Rails上でDBの値を取得・更新出来るように、モデルにお互いのテーブルの関連付けを行いました。
  • 今回は多対多のテーブル同士の間に中間テーブルを先に作成しておき、関連を持たせるお互いのmodelファイルにhas_manyとbelongs_toの記載します。

item.rb

class Item < ApplicationRecord
# アソシエーションの記載
  has_many :cart_items, dependent: :destroy
# アクティブストレージ対応の為記載(先月記事参照)
  has_one_attached :imageend

cart_item.rb

class CartItem < ApplicationRecord
  belongs_to :item
  belongs_to :cartend

cart.rb

class Cart < ApplicationRecord
  has_many :cart_items, dependent: :destroyend

また、お互いの関係が今回の場合のように一対多の場合は.buildメソッド、一対一の場合は.build_メソッドを使用すれば、所属テーブルの外部キーに親モデルのidを持たせることができます。

ターミナル(rails console)

@cart = Cart.find(params[:id])
@cart_items = @cart.cart_items.build # 一対多の場合、親モデル.子モデル.buildという形式
# インスタンスの中身
@cart_item
=> id: nil,
cart_id: 1,
item_id: nil,
name: nil,
quantity: nil,
created_at: nil,
updated_at: nil

# 一対一の場合(例えば商品と画像)
@item = Item.find(params[:id])
@image = @item.build_image # 親モデル.build_所属モデル

ただ、ここからテーブルの値取得方法に関して、RailsのクエリメソッドとSQLのクエリ文の違いに悩まされることのなりました。(そもそもSQLの理解が不足していた部分もあるかもしれませんが、現在も勉強中です…)

12月中旬

モデルの関連付けに続いて、URIとHTTPメソッドによるルーティングを学びました。

今回は商品をカートに追加するという動きの為、通常のCRUDのルーティングとは違った実装を迫られました。そこでmemberとcollectionという記述を学びました。

  • 2つの違いとして、memberは特定のレコード参照の為idを付与、collectionはidを付与しないところにあります。

routes.rb

resources :items do
  collection do
    get 'cart_items', to: 'cart_items#index'
  end
end

=> items/item:id/cart_items

resources :items do
  member do
    get 'cart_items', to: 'cart_items#index'
  end
end

=> items/cart_items

この関係を学んだことにより、単純なCRUD構造のページのルーティングだけでなく、ショッピングカート機能のようなアクションを用いた動作も実現できるようになりました。

12月下旬

商品の注文を確定させる為、購入の際行うチェックアウト機能を実装しました。同時に注文確認メールを送信するmailerの実装と使い方を学びました。
  • 始めに別のpaymentテーブルを用意し、そこに注文毎の注文者情報を保存させるようにしました。

  • その上でカート商品購入の際、氏名やメールアドレスを記入し、購入ボタンを押すと、記載されたメールアドレス宛に注文確認メールが送信します。

  • 送信機能には外部連携としてmailgunを使用。Active Storage実装の際に学んだ環境変数railsルートディレクトリ直下の.envファイルに記載し、herokuとGitHub Actionsにも値をセット、デプロイしました。

.env

MAILGUN_API_KEY=xx…
MAILGUN_DOMAIN=xx…
MAILGUN_PUBLIC_KEY=xx…
MAILGUN_SMTP_LOGIN=xx…
MAILGUN_SMTP_PASSWORD=xx…
MAILGUN_SMTP_PORT=xx…
MAILGUN_SMTP_SERVER=xx…
YOUR_HEROKU_APP=xx…

payment_controller.rb

# payment_controller.rb(メソッド化してモデルに切り出し)
 @payment.send_order_email

payment.rb

# payment.rb
 def send_order_email
    OrderMailer.creation_email(self).deliver_now
 end  

mailers/order_mailer.rb

class OrderMailer < ApplicationMailer
  default from: 'demo@abc.com'

  def creation_email(payment)
    @payment = payment
    mail(
      subject: '注文確認メール',
      to: @payment.email
    )
  end
end

views/order_mailer/creation_email.html.erb

<h2><%= @payment.name %> 様</h2>
<p>この度は、御購入ありがとうございました。<br>
  以下の内容でご注文承らせて頂きます。</p>
<p>決済番号:<%= @payment.id %></p>

<p>請求額:<%= number_to_currency(sum, format: "%u%n", unit: "¥") %></p>
ショッピングカート機能・チェックアウト機能を追加できたので課題提出→LGTMを頂けました。

11月の振り返り( production環境の設定とS3との連携)

風の強い時期ですが、学習を振り返りです。10月はSQLRailsに対するインプット月間でしたが、10月下旬から11月(12月に突入した今も継続中)ではRailsの課題アプリ制作を行いました。

10月上旬

まず始めに、制作物を本番環境で実行できるようにするため、herokuのアカウント作成とmini dyno・heroku postgresの連携、ターミナル上でのherokuコマンドを学習しました。
  • 続いてrails generateコマンドでモデルとコントローラ・ビューを作成し、DBに登録されているレコードの一覧表示、そこからクリックして詳細ページへの遷移を実装しました。
  • 同時にActive_storageを使用したモデルへの画像カラムの設定と、アクションに応じた画像の紐付けの機能を実装しました。

ターミナル

# Active_storageのインストール
rails active_storage:install
rails db:migrate

# gem "image_processing”をGemfileに記載後、bundle install
❯ docker-compose run --rm web bundle install
                                     ⋮

当初ローカルの開発環境では順調に実装が進み、試験的に用意したDBのデータを、アイテムの名前・説明文や画像等を意図した通りに表示させることが出来ました。

10月中旬

ここで初めて、途中まで制作した課題Railsアプリをherokuにアップロードしました。しかし、ここから本番環境への実装で大きく躓くことになります。
その1、画像ファイルの保存先としてAWS_S3との連携方法を知らなかった事による、本番環境での画像が表示されない問題。
  • この時はまだstorage.ymlやenviroments/production.rbといった環境設定ファイル設定値の記述方法を知らず、app/assets/imageフォルダに添付したテスト用画像をseedデータで流し込みDBに登録しようとした事、本番環境ではこのままデプロイし、(当然ですが)画像が表示されずに悩みました。

ローカルのdockerコンテナとherokuでは開発環境と本番環境の違いがあります。

Rails console

# ローカルのdockerコンテナ

❯ docker-compose run --rm web rails console
irb(main):001:0> Rails.env
=> "development"

# heroku本番環境

❯ heroku run rails console
Running rails console on <デプロイアプリ名>... up, run.1234 (Eco)
Loading production environment (Rails 7.0.6)
irb(main):001:0> Rails.env
=> "production"
  • 時間は掛かりましたが、以下複数のAWS_S3との連携方法を学びました。

credentials.ymlに環境変数を保存、それをマスターキーを使用して読み込む方法 。その上でRailsのdigメソッドを使用してstorage.ymlで呼び出す。

storage.yml

# 当初設定していたCredentials使用時のamazon連携設定

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: <%= Rails.application.credentials.dig(:aws, :s3, :region) %>
  bucket: <%= Rails.application.credentials.dig(:aws, :s3, :bucket) %>

これでheroku本番環境で意図した通り、画像が表示されるようになりました。

10月下旬

その2、production環境向けの設定方法を知らなかった事による、GitHub ActionsのCIと、herokuへのデプロイ両方に対応する環境設定で悩んだ事。
  • heroku本番環境で要求された通りの動作を一通り実装出来たので、GitHubでプルリクエストを作成しようとした際、今度はGitHub Actionsでテストが通りませんでした。

  • 今度はRailsのdigメソッドを使用する方法ではなく、heroku configで環境変数名と値をセットする方法とGithub Actionsのsecretに環境変数名と値をセットする方法を学びました。

aws_s3、heroku、GitHub Actions全対応の設定

# 最終的にcredential情報はheroku configに環境変数名と値をセット  
# GitHub Actionsではsecretsに環境変数名と値をセットする形で提出しました。
# 以下はAWS SDK for Rubyを使用しての呼び出し例

require 'aws-sdk-s3'

# AWS_S3にIAMユーザとしてアクセス  
s3_client = Aws::S3::Client.new(
  region: ENV['AWS_REGION'],
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
)

# AWS_S3のバケットにある画像ファイルをRailsプロジェクトのpublicディレクトリに取得  
s3_client.get_object(
  response_target: "public/test_image01.jpg",
  bucket: ENV['S3_BUCKET'],
  key: "rails-ec-demoimages/test_image01.jpg"
)

# publicディレクトリに保存した画像をDBの画像カラムにアタッチ  
item.image.attach(
  io: File.open(Rails.root.join("public/test_image01.jpg")),
  filename: "test_image01.jpg"
)
再び、2つのプラットフォームで本番環境の実装が可能となったので課題提出→LGTMを頂けた。

一方で、dotenv-rails gemの存在を知り、Railsアプリ直下の.envファイルに保存できることを学ぶ事となります。(これで開発環境でもS3と連携が取れます。)