20210221のRailsに関する記事は24件です。

routingをドメインごとに切り替える

まず任意のドメイン名を追加します。
参考リンク
https://qiita.com/tatsumin0206/items/8b4d0930c0f58cef7813

ホスト名に制約を加える

設定ファイルが自動的に読み込まれるようにconfig/initializerにhosts.rb(任意)を作成。

Rails.application.configure do
  config.admin1 = {
    admin: { host: "admin.example.com", path: "" }
  }
end

admin1というキーに対し、ハッシュをセットしています。

これを参照するには次のように書きます。

Rails.application.routes.draw do
  constraints host: config[:admin][:host] do
    namespace: :user, path: config[:admin][:host] do
      root "top#index"
      resources :session, only: [:create, :destroy]
    end
  end
end

constraintsは指定されたホスト名のみのアクセスを許可しています(ホスト名による制約)
namespaceでホスト名へのパスを設定しています。

namespaceを設定した時は以下の3つが追加されます。
1.URLのパスの先頭にadminが追加される
2.コントローラーの先頭にadminフォルダが付加される
3.ルーティングの先頭にadminが付加される

Rspecでのテストを実行

require "rails_helper"

describe "ルーティング" do
  example "管理者トップページ" do

    config = Rails.application.config.admin1 
    url = "http://#{config[:staff][:host]}/#{config[:staff][:path]}"
    expect(get: url).to route_to(
      host: config[:staff][:host],
      controller: "staff/top",
      action: "index"
    )
  end
end

簡単に解説します。

config = Rails.application.config.admin1

設定したファイルをconfigに代入しています。

url = "http://#{config[:admin][:host]}/#{config[:admin][:path]}"

②で設定したハッシュのキーをurlに指定しています。

 expect(get: url).to route_to(
      host: config[:admin][:host],
      controller: "admin/top",
      action: "index"
    )

route_toはルーティングのマッチャーです。
getリクエストでroute_to以下をハッシュで指定しています。
このホストにgetリクエストを送るとstaff/topコントローラーが指定され、indexアクションが働くという意味になります。

参考文献
Ruby on Rails6実践ガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がRailsとVue.jsでポートフォリオを作成してみた

こんばんは
アロハな男、やすのりです!

私は、自身のポートフォリオとして『Vue.js』と『Rails』を使用した、SPAアプリを作成しました。

この記事ではポートフォリオの内容や、実際に様々な機能実装をする上で『工夫したこと・苦労したこと』や、Webアプリ開発を通しての感想等をお伝えしていきたいと思います!

アプリ概要

一言で説明すると、ハワイに特化した『じゃらん』や『一休.com』 です。

Image from Gyazo

今のご時世では気軽に旅行することも難しいのですが、私自身や周りに旅行することが好きで、特にハワイが好きな方々がおり、実際に旅行した際の思い出やおすすめのお店等を共有し合うことがありました。

そこで、自身の思い出の場所やおすすめ店等を共有したいと思い、様々なサイトを探してみましたが、『管理人のおすすめ店をランキング形式で紹介』というものが多く、おすすめの場所が複数ある場合は、その分複数のサイトを共有しながら話し合うという様な形でした。

そういった経験から『もし、ハワイのお店やアクティビティ等がまとめられ、口コミ等でユーザーの評価がわかる様なサイトがあればお気に入りの場所もおすすめしやすくていいな』という想いから、このアプリを作成することにしました。

また、アプリ作成をする上で『説明がなくてもユーザーが使える見た目』であることや『ユーザーとしてアプリを使用する上で欲しい機能』を常に考え、『ユーザーが使いやすいアプリ』をコンセプトにアプリを作成しました。

下記ページにて公開中ですので、よろしければ実際に触ってみてください!
https://www.lets-enjoy-hawaii.com

使用技術等

  • フロントエンド
    • Vue.js 2.6.12
    • Vue Router
    • axios
    • JavaScript
    • HTML / CSS
    • Haml / SCSS※Vue.jsの導入に伴い使用中止
    • jQuery※Vue.jsの導入に伴い使用中止
  • バックエンド
    • Ruby 2.6.5
    • Rails 6.1.2.1
  • テスト
    • Rspec
    • FactoryBot
    • faker
    • rubocop
  • インフラ
    • AWS (VPC, EC2, S3, RDS, Route53)
    • https化
    • Nginx / Unicorn
  • ソースコード管理
    • GitHub

機能一覧

以降では、主要機能の詳細や工夫点等について紹介させていただきます!

入力フォームの入力不備表示機能

機能詳細
入力フォームの各項目に対して、モデルのバリデーション設定に反した入力がされるとエラーメッセージが表示され、入力欄の表示が変更されます。
※バリデーション設定に沿った入力がされた段階で、エラーメッセージや入力欄の表示が元に戻ります。

Image from Gyazo

工夫点

①入力不備の場所を判断できる様にする

入力項目が複数あるため、エラーメッセージを表示しただけでは、どの入力欄で入力不備が起こっているのか判断がつけづらいと考え、入力欄自体の表示も変更することにより、どの入力欄で不備が発生しているのかがすぐ判断できる様にしました。

②入力不備が解消されたかどうか一目でわかる配色にする

ページに使用している配色とは異なった色で入力不備の表示をすることにより、入力不備が解消されているかが一目でわかる様にしました。

会員登録済みメールアドレスお知らせ機能

機能詳細
会員登録時に入力されたメールアドレスが既に登録済みか判定し、登録済みであればエラーメッセージを表示させ、別のアドレスを入力するか、ログインを試すかを促す様にしています。

Image from Gyazo

懸念点

ユーザー登録数が増えてきた場合の処理速度問題

メールアドレスの入力がされ入力欄からフォーカスが外れた際に、axiosを使用した非同期通信を行い、メールアドレス判定用メソッドへリクエストを飛ばします。
現在は登録数が少なくレスポンスも早いですが、登録数が膨大になった際は時間がかかってしまうことが考えられますので、判定中なことをアイコンで表示させる等の処理が必要そうです。

詳しいコード解説等は別記事にて紹介しています。
RailsとVue.jsでメールアドレスが登録済みか判定する方法

登録済みのお気に入り・訪問記録へのメモ書き機能

機能詳細
登録済みのお気に入り訪問履歴に対して『〜が美味しかった!』や『次は〜動物をメインに見たい』等のコメントを、メモ書き感覚で保存することができます。

Image from Gyazo

工夫点

保存ボタンを押すことにより、コメント保存がされる様に設定

最初は各お店等へコメントでのメモ書きをする際に、『保存ボタン』をクリックしなくても、入力欄のフォーカスが外れた段階で保存処理をかければ、ユーザーの手間が1つ減るのではないかと考えていました。
しかしその仕様にした場合、『入力したけど、元々残していたメモ書きの方がやっぱりいい』や『誤ってメモ書きを削除してしまった』等といった場合に対応できないと考えを改め、『保存ボタン』も一緒に実装しました。

地図検索時、選択場所の表示変更機能

機能詳細
トップページの『地図から探す欄』と『島名から探す欄』にて島名等をホバーすると、対応した地図上の島の表示が変更されます。

Image from Gyazo

工夫点

①選択している島を視覚的にわかる様に設定

最初は地図上をホバーしても、マウスの表示が変わって地図を選択していることをお知らせしているだけでした。
しかし、地図上の表示も合わせて変更することによって、実際にユーザーが選択している箇所を把握することも容易になるのではないかと考え、地図上の表示変更機能を実装しました。

②島名にどこの島が対応しているのかわかる様に設定

島名検索をする際に、『オアフ島やハワイ島は聞いたことがあるけど、実際地図上ではどこになるんだろう』という声をいただき、実際に島名選択時にどの島と対応しているのかを地図表示を変更することでわかる様に設定しました。
これによって、『今回はオアフ島へ旅行に行くから、その横の島も時間があれば観光をする予定を立てよう』等の新たな計画を立てることもできるのではないかと思っています。

検索結果の並び替え機能

機能詳細
並び替え欄にある口コミランク順等をクリックすることで、表示されているお店等の検索結果一覧の並び替えをすることができます。

Image from Gyazo

工夫点

①初期状態の並び順をお気に入り数の多い順に設定

ユーザーが目的の場所を検索する際に評価の高い場所を優先的に見ていくと考え、口コミランク順かお気に入り数の多い順のどちらかを初期状態にしようと考えました。
その際に、『口コミ投稿をするより、お気に入りボタンを押す方が簡単でユーザーが使用する割合が高い』と考え、口コミ投稿に影響される『口コミランク順』ではなく、お気に入り数順を初期状態とすることにしました。

②並び替えボタンに使用頻度が高そうなものを用意

ユーザーが実際に並び替えをしたい項目にどういったものがあるかと考えました。
その際に『口コミランク順』と『お気に入り数順』は①でも説明した様に、ユーザーは評価の高いところを知りたいのではないかと思い、追加しました。
そして更に『口コミ数が多く、その場所に対しての感想が多い方が、よりその場所へのイメージがしやすい』のではないかと考え、『口コミ数順ボタン』を実装しました。

お店等への画像投稿機能(複数枚投稿可能)

機能詳細
お店等への口コミと一緒に、写真を投稿することもできます。
1回の投稿につき複数枚選択していただくことができ、現在どんな写真を選択しているのかをプレビューすることもできます。
そして、写真削除ボタンをクリックすることで、クリックした画像を選択画像から除外することができます。

Image from Gyazo

工夫点

写真削除ボタンをホバーした際に、ホバーした写真削除ボタンの表示を変更する。

写真を複数枚プレビューさせるためにVue.js上で繰り返し処理を行い各要素を表示させているため、そのままmouseoverイベントを発火させてしまうと、全ての写真削除ボタンの表示が変更されてしまう状態になっていました。
なので、削除ボタンにref属性を付与して、繰り返し処理のindex番号を与え、そのref属性とindex番号でホバーした要素を判断し表示を変更させることができました。

棒グラフ表示機能

機能詳細
お店等へ投稿された口コミの評価点の分布を、棒グラフで表示させています。
また、口コミが投稿されるとパーセントも更新され、グラフの表示も変動します。

Image from Gyazo

工夫点

パーセント表記に小数点を使用しない

各評価点のパーセンテージを計算した際に、どうしても割り切れず小数点が発生してしまいましたが、表示上はすっきりさせている方が見た際にわかりやすいと考え、小数点以下は四捨五入し表示をさせています。

Google APIを使用したお店等の画像取得機能

機能詳細
今のご時世ですので、実際に現地に行かなくても、お店の画像を取得し使用することができる『Google Street View API』を使用しました。

Image from Gyazo

問題点

①全ての場所は対応していない

画像自体はGoogle Streetにあれば取得することができますが、そもそもショッピングモールの中のお店等は表示されなかったりするので、画像が取得できずに掲載できないお店等も存在します。

②データベースに保存するカラムが増える

『Google Street View API』を使用するために、該当場所の座標等の情報をデータベースに保存しなければならないため、1つのレコードに使用される容量が増えてしまう。

以上のことから、可能であれば少しずつでも実際に現地で撮影した写真等を入手する必要があると考えています。

追加実装予定

GithubのIssuesに、実装内容と目的をまとめています。
https://github.com/Yasunori-aloha/lets-enjoy-hawaii/issues

ポートフォリオ開発で苦労したこと

特に苦労したことは、フロント部分とバック部分との連携でした。

当初はRailsだけで開発しており、フロント部分もRailsのビューを使用し実装していました。
しかし、現在主流なのは『フロント部分はVue.jsやReact等のJavaScriptフレームワークを使用してバック部分と切り分けて実装されるアプリケーション』であることや『フロントとバック両方の知識・技術があれば、実務に入った際のフロントとバック両チームのコミュニケーションの架け橋になれるのではないか』と考え、フロントをVue.jsへ切り替えを決心しました。

しかし、1度完成させていたものを新たに『APIモード』として作り替えるために、

  • APIモードでの必要情報の取り出し方
  • ビューをVue componentsとして移行

等々を新たに学習し直して、実装する必要がありました。

参考文献も少なく、スクール等では学べない内容でしたので、自分自身の成長により繋がったのではないかと思います。

ポートフォリオ開発を通しての得たもの

  • 自走力
    Railsだけでポートフォリオ完成させた段階では、どうやってVue.jsを連携させればいいのか全く知識がなかった状態からのスタートでしたので、導入の部分から実装部分に至るまで様々なエラー・不具合に遭遇してきました。
    しかし、Udemyの教材等を活用し、基礎的な内容から実践的なものまで学習を続けたことで、学習開始からポートフォリオへの導入まで約1ヶ月でVue.jsを実装することができました。
    そして、そのほとんど全てのタスクを自力で実装することができるまでになっていました。

  • デバック力
    ポートフォリオを作成する中で、様々なエラーに何度も遭遇してきました。
    ですが、その度にbinding.pryやdebugger等のデバックツールを使用して、『どこまで自身の想定内の動作をしているのか?』を明確にし、エラーの原因を絞り込み特定し解決、というサイクルを何度も経験し乗り越えてきました。
    今では、エラーに遭遇しても『今回はどういった理由でエラーが出てるのかな?』と、原因特定するこのサイクル自体を楽しんでいる自分がいました。

  • とにかく楽しむこと
    このポートフォリオを作成する中で、実装したい機能を実現するための方法を考えたり、考えたロジックをコードに落とし込んだり、エラーが発生した箇所はデバックをすることで原因を追及し解決する。
    こういった開発の流れ全てを楽しみながら開発を続けることができました。
    RailsだけでなくVue.jsも取り入れることで『フロント側ではこの情報が必要だからレスポンスで渡せる様にしよう』と考えたり、『バック側の処理でこの値が必要だからリクエストで送ってあげよう』と考えながら実装していくことでお互いの知識・技術も向上され、更にお互いのことを考えるための思考も養われたのではないかと思います。

最後に

ポートフォリオ開発を通して1番はじめに思い返されるのが『楽しくすることができた』ことです。
特に新たに実装したい機能が浮かんできて、その機能を実装するための方法を考えて、様々な教材で学習し実際に機能を実装できた時はとても大きな感動と達成感があったことを覚えています。

開発を始めたばかりの頃は、わからないことも多くエラーや実装方法で苦労することも多々ありましたが、実際に当初自身で実装したいと思っていた機能が実装することができて本当に良かったと思います。

まだまだ、開発過程で実装したいと思った機能がありますので、これからもアップデートを続けて、よりユーザーの使いやすいアプリケーションにできる様、そして自身の知識・技術が向上できる様に学習を続けていきたいと思います!

長い記事となってしまいましたが、ここまでご覧くださりありがとうございました!
もし、この記事ひいては開発したアプリケーションが皆様のお役に立つことができたのなら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル8章まとめ

この章でやること

  • ユーザーがログイン、ログアウトを行えるようにする
  • 基本的な仕組みでログインを実装する
  • ブラウザがログインしている状態を保持し、ユーザーによってブラウザが閉じられたら状態を破棄するといった認証システムを構築
  • その後ログイン済みのユーザー(current user)だけがアクセスできるページや、扱える機能などを制御する

今回作成する認証システムと認可モデルは、今後実装する機能の基盤となる仕組みとなる

8.1 セッション

HTTPはステートレス(Stateless)なプロトコル。
文字通り「状態(state)」が「ない(less)」ので、HTTPのリクエスト1つ1つは、それより前のリクエストの情報をまったく利用できない、独立したトランザクションとして扱われる
つまり、ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内にはない。
そのため一般的に、ユーザーログインの必要なWebアプリケーションでは、セッション(Session)と呼ばれる半永続的な接続を設定する

セッションを実装する方法として最も一般的なのは、cookiesを使う方法で、cookiesとは、ユーザーのブラウザに保存される小さなテキストデータを指す。
cookiesは、あるページから別のページに移動した時にも破棄されないので、ここにユーザーIDなどの情報を保存して、Webアプリケーションはcookies内のデータを使って、ユーザーが所有する情報をデータベースから取り出すことができる

ここではsessionというRailsのメソッドを使って一時セッションを作成し、ブラウザを閉じると自動的に終了するようにする
(次の章ではもっと長続きするよう設定する)

ログインページではnewで新しいセッションを出力し、そのページでログインするとcreateでセッションを実際に作成して保存し、ログアウトするとdestroyでセッションを破棄する

トピックブランチで作業し後でマージする

$ git checkout -b basic-login

8.1.1 Sessionsコントローラ

まずは、Sessionsコントローラとnewアクションを生成し、こんな感じのルーティングを作る

HTTPリクエスト URL 名前付きルート アクション名 用途
GET /login login_path new 新しいセッションのページ(ログイン)
POST /login login_path create 新しいセッションの作成(ログイン)
DELETE /logout logout_path destroy セッションの削除(ログアウト)
$ rails generate controller Sessions new

rails generateでnewアクションを生成すると、それに対応するビューも生成される。
createやdestroyには対応するビューが必要ないので、無駄なビューを作成しないためにnewだけを指定

Usersリソースのときは専用のresourcesメソッドを使ってRESTfulなルーティングを自動的にフルセットで利用できるようにしたが
Sessionリソースではフルセットはいらないので、「名前付きルーティング」だけを使う。
この名前付きルーティングでは、
GETリクエストやPOSTリクエストをloginルーティングで、
DELETEリクエストをlogoutルーティングで扱う

config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users
end

自動で生成されたテストを名前付きルートに更新する

test/controllers/sessions_controller_test.rb
require 'test_helper'

class SessionsControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get login_path
    assert_response :success
  end
end

rails routesコマンドを実行することで、いつでも現状のルーティングを確認することができる。

$ rails routes
   Prefix Verb   URI Pattern               Controller#Action
     root GET    /                         static_pages#home
     help GET    /help(.:format)           static_pages#help
    about GET    /about(.:format)          static_pages#about
  contact GET    /contact(.:format)        static_pages#contact
   signup GET    /signup(.:format)         users#new
    login GET    /login(.:format)          sessions#new
          POST   /login(.:format)          sessions#create
   logout DELETE /logout(.:format)         sessions#destroy
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

演習

GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。
→getはビューを表示する(ブラウザがアプリに取得を要求)
→postはアプリのデータを更新する要求する(DBを変えるなど)

ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか?

$ rails routes | grep sessions
sessions_new GET    /sessions/new(.:format)  sessions#new 
login GET    /login(.:format)  sessions#new 
POST   /login(.:format)  sessions#create logout 
DELETE /logout(.:format)   sessions#destroy

8.1.2 ログインフォーム

今度は新しいセッションで使うビュー、つまりログインフォームを整える

ログインフォームで入力した情報に誤りがあったときは、ログインページをもう一度表示してエラーメッセージを出力
前の章では、エラーメッセージの表示に専用のパーシャルを使い(_error_messages.html.erb)、そのパーシャルではActive Recordによって自動生成されるメッセージを使っていた。しかし、今回扱うセッションはActive Recordオブジェクトではないので、以前のようにActive Recordがよしなにエラーメッセージを表示してくれるということない。なのでフラッシュメッセージでエラーを表示していく

ユーザー登録フォームではform_withヘルパーを使い、ユーザーのインスタンス変数@userを引数にとった

<%= form_with(model: @user, local: true) do |f| %>
  .
<% end %>

セッションフォームとユーザー登録フォームの最大の違いは、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもない。
したがって、新しいセッションフォームを作成するときにform_withヘルパーに渡さなければならない情報は、若干異なる

form_with(model: @user, local: true)
Railsでは上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースのスコープ(ここではセッション)とそれに対応するURLを具体的に指定する必要がある

form_with(url: login_path, scope: :session, local: true)

このコードをログインフォームに組み込む

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p> #ユーザー登録ページへ誘導
  </div>
</div>

生成されるHTMLフォーム。

<form accept-charset="UTF-8" action="/login" method="post">
  <input name="authenticity_token" type="hidden"
         value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
  <label for="session_email">Email</label>
  <input class="form-control" id="session_email"
         name="session[email]" type="email" />
  <label for="session_password">Password</label>
  <input id="session_password" name="session[password]"
         type="password" />
  <input class="btn btn-primary" name="commit" type="submit"
       value="Log in" />
</form>

フォーム送信後にparamsハッシュに入る値が、メールアドレスとパスワードのフィールドにそれぞれ対応したparams[:session][:email]とparams[:session][:password]になることが推測できる

name="session[email]"
name="session[password]"
↓↓
params[:session][:email]
params[:session][:password]

演習

リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。
→formタグでmethod:postで送信している

8.1.3 ユーザーの検索と認証

ログインでセッションを作成する場合に最初に行うのは、入力が無効な場合の処理

①入力が無効な場合の処理を作成
②ログイン失敗時のエラーメッセージの配置
③ログイン成功時の土台部分の作成

まず、①を行う前に、Sessionsコントローラのcreateアクションにnewビューが出力されるよう定義

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    render 'new'
  end

  def destroy
  end
end

デバック情報をみると以下の情報が表示

paramsハッシュでは、次のようにsessionキーの下にメールアドレスとパスワードがあります。

---
session:
  email: 'user@example.com'
  password: 'foobar'
commit: Log in
action: create
controller: sessions

ユーザー登録の時と同様、これらのパラメータはネストしたハッシュになっている。
{ session: { password: "foobar", email: "user@example.com" } }

つまり、これらのパラメータをpostリクエストでcreateアクションに送信すると、
createアクションにて、paramsハッシュで以下のようにデータを受け取る。

params[:session][:email]
params[:session][:password]

これを一つにすると
params[:session]
ちなみに、上記のemailとpasswordの部分は細かく見ると
{ session: { params: "foobar", email: "user@example.com"} }
このようになっている。
つまり、ハッシュの中にハッシュがあるhash to hashを採用している。

createアクションの中では、データをparamsハッシュから簡単に取り出せる。

実際に、createアクションでデータを受け取るには、
find_byメソッド
authenticateメソッド
を使う。

authenticateメソッドは、認証に失敗するとfalseを返すので、その点を踏まえてコントローラに処理を記述。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase) #送信されたメアドでユーザーを取り出す
    if user && user.authenticate(params[:session][:password]) #もしuserが存在し、パスワードが一致した場合
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end

  def destroy
  end
end

user && user.authenticate(params[:session][:password])という文の
&& (論理積(and))は、取得したユーザーが有効かどうかを決定するために使う。
この場合は、入力されたメールアドレスを持つユーザーがデータベースに存在し、かつ入力されたパスワードがそのユーザーのパスワードである場合のみ、if文がtrueになる=言葉でまとめると「ユーザーがデータベースにあり、かつ、認証に成功した場合にのみ」

User Password a && b
存在しない 何でもよい (nil && [オブジェクト]) == false
有効なユーザー 誤ったパスワード (true && false) == false
有効なユーザー 正しいパスワード (true && true) == true

演習

Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.2で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate('foobar'))

>> user = nil
=> nil
>> !!(user && user.authenticate('foobar'))  #nilとfoobarパスワード正
=> false
>> user = User.first
>> !!(user && user.authenticate('foobaz'))  #user.firstとfoobazパスワード誤
=> false
>> !!(user && user.authenticate('foobar'))  #user.firstとfoobarパスワード正
=> true

8.1.4 フラッシュメッセージを表示する

前章のエラーメッセージはUSERモデルが自動で生成してくれた
しかしセッションではActive Recordのモデルを使っていないため、使えない
そこで、ログインに失敗したときにはフラッシュメッセージを表示するようにする

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'
    end
  end

  def destroy
  end
end

上のコードには誤りがある。実は上のコードのままでは、リクエストのフラッシュメッセージが一度表示されると消えずに残ってしまう
表示したテンプレートをrenderメソッドで強制的に再レンダリングしてもリクエストと見なされないため、リクエストのメッセージが消えない。

8.1.5 フラッシュのテスト

この小さなバグ(フラッシュメッセージが消えない問題)は、「エラーをキャッチするテストを先に書いて、そのエラーが解決するようにコードを書く」に該当する状況
なので、ログインフォームの送信について簡単な統合テストを作成する

$ rails generate integration_test users_login
      invoke  test_unit
      create    test/integration/users_login_test.rb

以下の流れでテストを書く

  1. ログイン用のパスを開く
  2. 新しいセッションのフォームが正しく表示されたことを確認する
  3. わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
  4. 新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
  5. 別のページ(Homeページなど) にいったん移動する
  6. 移動先のページでフラッシュメッセージが表示されていないことを確認する
test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  test "login with invalid information" do
    get login_path    #ログイン用のパスを開く
    assert_template 'sessions/new'  #新しいセッションのフォームが正しく表示されたことを確認する
    post login_path, params: { session: { email: "", password: "" } } #わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
    assert_template 'sessions/new'  #新しいセッションのフォームが再度表示され、
    assert_not flash.empty?  #フラッシュメッセージが追加されることを確認する flashが空でないか
    get root_path    #Homeページ にいったん移動する
    assert flash.empty? #homeページでフラッシュメッセージが表示されていないことを確認する
  end
end

今時点testはred (移動してもフラッシュが消えないバグが解決してないため)

$ rails test test/integration/users_login_test.rb

上の例のように、rails testの引数にテストファイルを与えると、そのテストファイルだけを実行することができる。

テストをパスさせるには、flashをflash.nowに置き換えることで解決する
flash.nowは、レンダリングが終わっているページで特別にフラッシュメッセージを表示することができる
flash.nowのメッセージはその後リクエストが発生したときに消滅する

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash.now[:danger] = 'Invalid email/password combination' #flash.nowに変更
      render 'new'
    end
  end

  def destroy
  end
end
$ rails test test/integration/users_login_test.rb
$ rails test

演習

8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。
→/loginから適当に入力し、動作確認済

8.2 ログイン

cookiesを使って一時セッションでユーザーをログインできるようにし、ログイン状態を保持したままデータを送信できるようにする。

今回はブラウザを閉じるとcookiesの有効期限が自動的に切れるようにするが、後にブラウザを閉じても保持されるセッションを追加する。

以前はセッション実装する際、様々なコントローラやビューで多くの数のメソッドを定義する必要があったが
現在はRailsのモジュール機能を使うだけでそうしたメソッドを一箇所にパッケージ化できる

Module Helper#メソッドの集合(ヘルパー)を定義
Sessionsコントローラ生成時には既にセッション用のヘルパーモジュールも自動生成されていて、Railsのセッション用ヘルパーはビューにも自動的に読み込まれる。

つまり、Railsの全コントローラの親クラスであるApplicationコントローラにこのモジュール(ヘルパー)を読み込ませることで、どのコントローラでも使えるようになる。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper
end

8.2.1 log_inメソッド

定義済みのsessionメソッドを使って、単純なログインを行えるようにする。
(sessionメソッドはSessionsコントローラとは無関係なので注意。)

sessionメソッドはハッシュのように扱える。

session[:user_id] = user.id

上記のコードでは、ユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーidが自動生成される。
この後のページで、session[:user_id]を使って、ユーザーIDを元通りに取り出すことができる。

後に詳解するcookiesメソッドとは対照的に、sessionメソッドで作られた一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了する。

同じログイン手法を様々な場所で使い回せるようにする為に、Sessionsヘルパーにlog_inという名前のメソッドを定義しておく。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)       #user引数を定義
    session[:user_id] = user.id   #sessionメソッドで[:user_id]にuser引数のidを代入
  end
end

sessionメソッドで作成した一時cookiesは自動的に暗号化され、上記のコードは保護される。

sessionメソッドについて参考
https://qiita.com/zettaittenani/items/a75f0da8f44cfe0f85c0

ここが重要だが、攻撃者がたとえこの情報をcookiesから盗みだすことができたとしても、それを使って本物のユーザーとしてログインすることはできない。

ただし、それはsessionメソッドで作成した「一時セッション」にしか該当せず、cookiesメソッドで作成した永続的セッションでは断言できない。
(ブラウザ閉じて消えるsessionメソッドなら大丈夫だが、ブラウザ閉じても消えないcookiesメソッドだと危ないってこと)
何故なら、cookiesの場合セッションハイジャックという攻撃を受ける可能性があるから。

これは後に詳解する。

log_inというヘルパーメソッドができたので、ユーザーログインを行ってcreateアクションの中身を完成させる。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user  
    # sessions_helperのlog_inメソッドを実行し、sessionメソッドのuser_id(ブラウザに一時cookiesとして保存)にidを送る
      redirect_to user                 # ログインしたユーザーのページにリダイレクト
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
  end
end

今の状態でもログインできるが、ログインしたかどうかのメッセージが表示されないので、このままだとユーザーからしたら本当にログインできたのかがわからない。(ブラウザセッションを直接確認すればわかるが)

なので、後にセッションに含まれるIDを利用して、データベースから現在のユーザー名を取り出して、画面で表示する。
さらに、アプリケーションのレイアウト上のリンクに、現在ログインしているユーザー(自分)のプロフィールを表示できるようにもする。

演習

有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです!(コラム 1.2)
先ほどの演習課題と同様に、Expiresの値について調べてみてください。
→開発者ツール(画面でF12)→アプリケーション→Cookieで見つけれる
sessionの値は大量の文字列

8.2.2 現在のユーザー

ユーザーIDを一時セッションの中に安全に置けるようになったので、今度はそのユーザーIDを別のページで取り出す
そのためには、current_userメソッドを定義して、セッションIDに対応するユーザー名をデータベースから取り出せるようにするcurrent_userメソッドの目的は、次のようなコードを書けるようにすること

<%= current_user.name %>

また、このようなコードで、ユーザーのプロフィールページにリダイレクトできるようにもする

redirect_to current_user

この時、ユーザーを検索する方法としては

User.find(session[:user_id])

が思いつくが、ユーザーIDが存在しない状態でfindを使うとエラーが発生してしまう。
たとえばユーザーがログインしていない場合、sessionハッシュにidがない(NULL)ので、
結果的にidが存在しないユーザーを探してしまうのでエラーになってしまう。

この状態を修正すべく、createメソッド内でメールアドレスの検索に使ったのと同じfind_byメソッドを使う。

User.find_by(id: session[:user_id])

この方法だと、IDが無効(ユーザーが存在しない)の場合でもメソッドはエラーを発生せずにnilを返してくれる。
(Usersテーブルから目的のidの値を持ったユーザーを探している為)

findとfind_byの違いが分からなかったので、調べました。
https://qiita.com/tsuchinoko_run/items/f3926caaec461cfa1ca3
idの値が分かっていて、そのidのデータを取得したい場合・・・find
idの値が不明で、id以外のカラムを検索条件としたい場合・・・find_by
ということのようです。

find_byメソッドの手法を使って、current_userを次のように定義

def current_user
  if session[:user_id] #もしsession[:user_id]=ログインしてたら
    User.find_by(id: session[:user_id]) #同じidのUserを探す
  end
end

セッションにユーザーIDが存在しない場合、nilを返す。(falseではない)

存在しない場合でも何回もDBへ問い合わせしていないので早い

いる場合は、ログインユーザーのidとDBのidが同じユーザーを返している。

さらに、Rubyの慣習に従って、User.find_byの実行結果をインスタンス変数に代入する。

こうすることで、1リクエスト内におけるDBへの問い合わせは最初の一回だけになり、以後の呼び出しではインスタンス変数の結果を再利用するだけになる。
これが、Webサービスを高速化させる重要なテクニック。

if @current_user.nil?  #もし@current_user.nil? なら
  @current_user = User.find_by(id: session[:user_id]) #@userにセッションと同じuserを探して代入
else
  @current_user  #いなければ、?
end

既に@current_user(ログインユーザー)がいればユーザーを表示し、
いなければユーザーを@current_userに代入できる。

上のコードは or演算子「||」を使えれば、たった1行で書ける

@current_user = @current_user || User.find_by(id: session[:user_id])

Userオブジェクトそのものの論理値は常にtrueな為、@current_userに何も代入されていない時だけ、find_byが読み出される。つまり、DBへの無駄な読み出しが行われなくなる。

Rubyではさらに短縮形で書く。

@current_user ||= User.find_by(id: session[:user_id])

||=は、手前の@current_userがあれば@current_userに代入、なければUser.find〜を代入、という意味となる。

この概念をor equalsと呼ぶ。

コラム 8.1. 「||=」とは何か?

この「||=」(or equals)という代入演算子はRubyで広く使われているイディオムであり、Ruby開発者を志すならこの演算子に習熟することが重要

多くのコンピュータプログラムでは、次のような記法で変数の値を1つ増やすことができる

x = x + 1
そして、Ruby(およびC、C++、Perl、Python、Javaなどの多くのプログラミング言語)では、上の演算を次のような短縮形で表記することもできる

x += 1
他の演算子についても同様の短縮形が利用可能

  $ rails console
  >> x = 1
  => 1
  >> x += 1
  => 2
  >> x *= 3
  => 6
  >> x -= 8
  => -2
  >> x /= 2
  => -1

いずれの場合も、●という演算子があるときの「x = x ● y」と「x ●= y」の動作は同じ

Rubyでは、「変数の値がnilなら変数に代入するが、nilでなければ代入しない(変数の値を変えない)」という操作が非常によく使われ,
or演算子 || を使えば、次のように書ける

  >> @foo
  => nil
  >> @foo = @foo || "bar" #nilの論理値はfalseになるので、@fooへの最初の代入「nil || "bar"」の評価値は"bar"に
  => "bar"
  >> @foo = @foo || "baz" #既に@fooがあるので、評価値は"bar"に
  => "bar"

nilの論理値はfalseになるので、@fooへの最初の代入「nil || "bar"」の評価値は"bar"になる。
同様に、2つ目の代入「@foo || "baz"」("bar" || "baz"など)の評価値は"bar"に
Rubyでは、nilとfalseを除いて、あらゆるオブジェクトの論理値がtrueになるように設計されている
さらにRubyでは、||演算子をいくつも連続して式の中で使う場合、項を左から順に評価し、最初にtrueになった時点で処理を終えるように設計されているので、こうなる
なお、||式を左から右に評価し、演算子の左の値が最初にtrueになった時点で処理を終了するという評価法を短絡評価(short-circuit evaluation)と呼ぶ。

上記の演算子をコンソールセッション上で実際に実行して比較してみると、@foo = @foo || "bar"はx = x O yに該当し、Oが||に置き換わっただけである

  x    =   x   +   1      ->     x     +=   1
  x    =   x   *   3      ->     x     *=   3
  x    =   x   -   8      ->     x     -=   8
  x    =   x   /   2      ->     x     /=   2
  @foo = @foo || "bar"    ->     @foo ||= "bar"

これで「@foo = @foo || "bar"」は「@foo ||= "bar"」と等価であることがわかる
この記法をcurrent_userの文脈で使うと次のような簡潔なコードになる

@current_user ||= User.find_by(id: session[:user_id])

上記での記法をhelperに組み込む

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す(いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])  
#@current_userがいれば@current_userに代入し、いなければsessionのユーザーを探す
    end
  end
end

演習

Railsコンソールを使って、User.find_by(id: ...)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。

irb(main):002:0> User.find_by(id: "4")
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
=> nil

userがいない場合User.find_by(id: "4")はnilを返す

先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップに従って、||=演算子がうまく動くことも確認してみましょう。

リスト 8.17: sessionのシミュレーション
>> session = {}
>> session[:user_id] = nil
>> @current_user ||= User.find_by(id: session[:user_id])
<ココに何が表示されるか?>→ユーザがいないのでnilが返る
>> session[:user_id]= User.first.id
>> @current_user ||= User.find_by(id: session[:user_id])
<ココに何が表示されるか?> →ユーザーがいるので、一番目(first)のユーザーが@current_userに入る
>> @current_user ||= User.find_by(id: session[:user_id])
<ココに何が表示されるか?> →上と同じ
irb(main):003:0> session = {}
=> {}
irb(main):004:0> session[:user_id] = nil
=> nil
irb(main):005:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (7.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
irb(main):006:0>  session[:user_id]= User.first.id
  User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
irb(main):007:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", crea...
irb(main):008:0>  @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", crea...

8.2.3 レイアウトリンクを変更する

ユーザーがログインしているときとそうでないときでレイアウトを変更する

この時点でメニューに対する統合テストを書くのがいいが、ここでもいくつか新しい概念を覚える必要があるので、テストの作成は後回しする

さて、レイアウトのリンクを変更する方法として考えられるのは、ERBコードの中でif-else文を使用し、条件に応じて表示するリンクを使い分けること

<% if logged_in? %> もしログインしてれば
  # ログインユーザー用のリンク
<% else %>       ログインしてなければ
  # ログインしていないユーザー用のリンク
<% end %>

このコードを書くためには、論理値を返すlogged_in?メソッドが必要なので、まずはそれを定義する。

ユーザーがログイン中の状態とは「sessionにユーザーidが存在している」こと、つまりcurrent_userがnilではないという状態を指す。
これをチェックするには否定演算子が必要なので、! を使う

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す(いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?  #上で定義したcurrent_userメソッドを否定演算子にし、trueかfalseを返すようにする
  end
end

理論値を返すメソッドの準備ができたので、リンク新しく作るリンクは4つですが、そのうち次の2つのリンクにを整える

今回は未定義のリンク
<%= link_to "Users", '#' %>
<%= link_to "Settings", '#' %>
ログアウト用リンク
<%= link_to "Log out", logout_path, method: :delete %>
引数としてハッシュ(method:)を渡している。このハッシュでは、HTTPのDELETEリクエストを使うように指示

プロフィール用リンクについても同様に変更
<%= link_to "Profile", current_user %>
なお、上のコードは省略系↓↓のようにも書ける
<%= link_to "Profile", user_path(current_user) %>

しかしこの状況ではcurrent_userを使う方が、Railsによってuser_path(current_user)に変換され、プロフィールへのリンクが自動的に生成できるので便利
次に、ユーザーがログインしていない場合は、ログイン用パスを使って、リンクを作成
<%= link_to "Log in", login_path %>

ここまでの結果をヘッダーのパーシャル部分に適用

app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>      #もしログインしてれば
          <li><%= link_to "Users", '#' %></li> #userページへlink
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li> #プロフィールページへ
              <li><%= link_to "Settings", '#' %></li>   #設定ページへ
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li> #ログインしてなければ、ログインパスを表示
        <% end %>
      </ul>
    </nav>
  </div>
</header>

dropdown-toggle"などのbootstrapによるCSSのドロップダウン機能を有効にするため、Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリの他にjQueryも読み込む必要がある
以前、gemfileでbootstrap-sass gemでインストールしたが、一部は含まれていなかったため。

RailsのアセットパイプラインはWebpackとYarnのどちらともうまく動きます。そして上述のJavaScriptをインクルードするには、WebpackとYarnの両方が必要。
なのでまずは、①jQueryとBootstrapのJavaScriptライブラリを、アプリケーションにインストール

$ yarn add jquery@3.4.1 bootstrap@3.4.1

②アプリケーションでjQueryを有効にするには、Webpackの環境ファイルを編集して、以下の内容を追加する必要がある。

WebpackにjQueryの設定を追加する

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
module.exports = environment

③application.jsファイルでjQueryをrequireし、Bootstrapをimportする

app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery")
import "bootstrap"

これでようやくログイン機能が動くようになる

演習

ブラウザのcookieインスペクタ機能を使って(8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。
→見た目の確認

もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。
→見た目の確認

その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの[閉じたときの状態に戻す]機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう→見た目の確認

8.2.4 レイアウトの変更をテストする

統合テストを書いて演習で確認した動きをテストで表現し、今後の回帰バグの発生をキャッチできるようする

手順は以下の通り

  • ログイン用のパスを開く
  • セッション用パスに有効な情報をpostする
  • ログイン用リンクが表示されなくなったことを確認する
  • ログアウト用リンクが表示されていることを確認する
  • プロフィール用リンクが表示されていることを確認する

上の変更を確認するためには、テスト時に登録済みユーザーとしてログインしておく必要がある
当然ながら、データベースにそのためのユーザーが登録されていなければならないが、Railsでは、このようなテスト用データをfixture(フィクスチャ)で作成できる。
このfixtureを使って、テストに必要なデータをtestデータベースに読み込んでおくことができる

現時点のテストでは、ユーザーは1人でOK。

fixtureには有効な名前とメールアドレスを持ったユーザーを一人用意する。
さらに、Sessionsコントローラのcreateアクションに送信されたパスワードと比較できるよう、
password_digest属性も追加する。
その為に、digestメソッドを独自に定義する。

has_secure_passwordを使うとbcryptパスワードが作成されるので、
bcryptパスワードハッシュ化→fixture用のパスワード にする。

なお、Railsでは次のコードでパスワードをハッシュ化している。

BCrypt::Password.create(string, cost: cost)

string=ハッシュ化する文字列
cost=コストパラメータ

costパラメータはハッシュを算出する為の計算コストを指定する。
コストパラメータ値が高いほど復号化が困難になる為、本番環境ではコストパラメータを設定する。

なお、テスト中は計算コストを下げて軽くしたい。
それには、secure_passwordのソースコードが参考になる。

cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                              BCrypt::Engine.cost

少々込み入っているが、コストパラメータをテスト中は最小にし、本番環境ではしっかりと計算する方法がわかれば十分

上記のdigestメソッドは今後活用するので、Userモデル(User.rb)に置いておく。
この計算はユーザーごとに行う必要はなく、インスタンスメソッドで定義する必要はない。

そのため、digestメソッドはUserクラス自身に配置してクラスメソッドとする

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :  #コストをテスト中は最小に
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
end

digestメソッドができたので、有効なユーザーを表すfixtureを作成できるようになった

test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>   #User.digest #user.rbで定義したメソッド

fixtureではERBを利用して、先ほど定義したdigestメソッドを使用している。
(password)をstringで文字列に変換してハッシュ化している

fixtureではハッシュ化されていない生のパスワードは参照できない。
理由として、password属性が存在しない点が挙げられる。
(has_secure_passwordの機能を使って、passwordとpasswordconfirmationの値をデータベースにpassword_digestの値として追加していたから)

つまり、passwordという文字列をテスト用のfixtureのpassword_digestに与えることで、
ハッシュ化前のパスワード(password)を読み出すことができる。
(password_digestの平文がpassword)

有効なユーザーをfixtureで作成出来たので、テストでfixtureのデータを参照する。

test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael) #fixtureで定義したmichealを利用
  end

  test "login with valid information" do
    get login_path #loginする
    post login_path, params: { session: { email:    @user.email,  #createアクションにfixtureで定義したemailとpasswordをparamsへ送信
                                          password: 'password' } }
    assert_redirected_to @user    #@userページへリダイレクトしたか確認
    follow_redirect!            #リダイレクト
    assert_template 'users/show'   #showページが表示されているか
    assert_select "a[href=?]", login_path, count: 0  #ログインパスがないか
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end
end

テストはグリーンになるはず

演習

1リスト 8.15の8行目にあるif userから下をすべてコメントアウトすると、ユーザー名とパスワードを入力して認証しなくてもテストが通ってしまうことを確認しましょう(リスト 8.26)。通ってしまう理由は、リスト 8.9では「メールアドレスは正しいがパスワードが誤っている」ケースをテストしていないからです。このテストがないのは重大な手抜かりですので、テストスイートで正しいメールアドレスをUsersのログインテストに追加して、この手抜かりを修正してください(リスト 8.27)。テストが red (失敗)することを確認し、それから先ほどの8行目以降のコメントアウトを元に戻すと green (パス)することを確認してください(この演習の修正は重要なので、この先の 8.3のメインのコードにも修正を反映してあります)。

test "login with valid email/invalid password" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email:   @user.email, #Fill_inにこれを入れたらtestはredになった
                                          password: "invalid" } }
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

2.“safe navigation演算子”(または“ぼっち演算子)と呼ばれる&.を用いて、リスト8.15の8行目の論理値(boolean値)のテストを、リスト 8.2812 のようにシンプルに変えてください。Rubyのぼっち演算子を使うと、obj && obj.methodのようなパターンをobj&.methodのように凝縮した形で書けます。変更後も、リスト 8.27のテストがパスすることを確認してください。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user&.authenticate(params[:session][:password])  #user.を&.に書き換える
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end

問題なくテストはパスする

8.2.5 ユーザー登録時にログイン

今のままでは、新規登録の終わったユーザーがデフォルトでログインしていないのでユーザー登録後にはログインしている状態にする
そのためにはUsersコントローラのcreateアクションにlog_inを追加するだけでOK

app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user  #save後にlog_inメソッドを発動
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

log_in @userの動作をテストするために、logged_in?ヘルパーメソッドとは別に、is_logged_in?ヘルパーメソッドを定義しておくと便利
このヘルパーメソッドは、テストのセッションにユーザーがあればtrueを返し、それ以外の場合はfalseを返すメソッドにする
残念ながらヘルパーメソッドはテストから呼び出せないため、current_userを呼び出せません。sessionメソッドはテストでも利用できるので、これを代わりに使う。
※helperファイルで定義したmethodはapp側でしか使えない。test_helperはtest側でしか使えない

test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all

  # テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil? #sessionがあればtrue
  end
end

テスト側に反映

test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?  #signup後loginしてるか
  end
end

これで、テストすればgreen

演習

リスト 8.29のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。
→redになる log_inされなくなるのでred

現在使っているテキストエディタの機能を使って、リスト 8.29をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタのコメントアウト機能については『開発基礎編: テキストエディタ』の 「コメントアウト機能」などを参照してみてください。
→VScodeでは、「command+a」でALL選択 「command+/」でコメントアウトできる

8.3 ログアウト

ログアウト機能を追加する

ログアウトは簡単で、deleteメソッドを使えば可能
session.delete(:user_id)
上のコードで、現在のユーザーをnilに設定できる。

次に、log_inおよび関連メソッドのときと同様に、Sessionヘルパーモジュールに配置するlog_outメソッドを定義

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
  .
  .
  .
  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

helperからコントローラーへ記載

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy  #destroyメソッドを定義
    log_out   #helperで定義したlog_outメソッドを使う
    redirect_to root_url
  end
end

ログアウトメソッドをテストで使う

test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "login with valid email/invalid password" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email:    @user.email,
                                          password: "invalid" } }
    assert_not is_logged_in?
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end

test_helperでis_logged_in?ヘルパーメソッドを定義し、利用できるようにしたおかげで、
is_logged_in?を書くだけで、sessionが空ならfalse(これをassert_notで空ならtrueにした)というテストを実現できた。

これでtestはパスする

演習

ブラウザから[Log out]リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.35で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。
→確認
cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。
→確認

gitに追加する

$ rails test
$ git add -A
$ git commit -m "Implement basic login"
$ git checkout master
$ git merge basic-login
マージ後、リモートのリポジトリにpush

$ rails test
$ git push
最後に、いつものようにHerokuにデプロイ

$ git push heroku

以上

2/22追記

gitのマスターへマージ後、rails testでエラーというかテストが実施されないエラーが発生。
そのためherokuへアップデートもできない。

エラー内容(テストができない)

/Users/kiyomasa/environment/sample_app/app/controllers/application_controller.rb:2:in 
`<class:ApplicationController>': uninitialized constant ApplicationController::SessionsHelper (NameError)

調べてみると
https://teratail.com/questions/228180
この方、もしくは
https://stackoverflow.com/questions/17984330/why-do-i-get-uninitialized-constant-applicationcontrollersessionshelper-name/17984474
この方と同じ状態になりました。

applocation_controller.rb
class ApplicationController < ActionController::Base
    include SessionsHelper  #スペルミスは確認
end
sessions_helper.rb
module SessionsHelper  #moduleのスペルも間違ってなさそう

    # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end  

  # 現在ログイン中のユーザーを返す(いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end

end

時間を置いて、VSCODEを再起動したらテストができるようになりました。... 原因不明でした。
もしそうなってしまった方は試してみるといいかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MiniMagickで画像がresizeできなかった時の対処法

PF作成中に生じたエラーです。オチは非常に簡単なものだったので前提条件は省きます。

erbファイルに以下の様に記述してサーバーを起動し、quiz_image(image_tagで表示されるはずの画像)を確認しようとしました。<%= image_tag quiz.quiz_image.variant(resize: '300×300'), class: 'card-img-top' %>だけ見てくだされば大丈夫です。

_quiz.html.erb
<% if quiz.quiz_image.attached? %>
  <%= image_tag quiz.quiz_image.variant(resize: '300×300'), class: 'card-img-top' %>
<% end %>
<div class="card-body">
  <h3 class="card-title"><%= quiz.title %></h3>
  <p class="card-text"><%= quiz.sentence %></p>
  <% quiz.tags.each do |tag| %>
    <%= tag.name %>
  <% end %>
  <%= link_to '問題詳細へ', quiz, class: 'btn btn-primary'  %>
</div>

しかし、画像が表示されません。そこでターミナルを確認すると以下の様に書いてありました。

MiniMagick::Error (`gm convert /var/folders/43/2kbncrm93jsb1tpd11tsys0r0000gn/T/ActiveStorage-24-20210221-35258-1rq06qb.jpeg[0] -auto-orient -resize 100×100 /var/folders/43/2kbncrm93jsb1tpd11tsys0r0000gn/T/image_processing20210221-35258-1uhkzzk.jpeg` failed with error:
gm convert: Option '-resize' requires an argument or argument is malformed.
):

最後の一文をざっくり翻訳すると「resizeメソッドの文法がなんかおかしいよ」とあります。(malformedとは奇形という意味です)

みたところ変なところはないのでとりあえずググってみました。すると以下の記事を発見しました。

https://askubuntu.com/questions/629808/resizing-with-imagemagick-error-message

どうやら
'300×300'
ではなく
'300x300'
が正しい様です。一見同じ様に見えますが'×(かける)'と'x(エックス)'が違います。

書き直して再確認するとうまく行きました。どうやらASCIIには×という文字は存在しない様で、xで代用しているみたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails: scopeはnilを返す使い方をしてはいけない

はじめに

Railsで開発している時、scopeを使うシーンは多いと思います。
しかし、実務で使用している際、予期しない動作をすることがあったので、調べたことをまとめておきます。

実行環境

  • macOS Catalina 10.15.7
  • ruby 2.6.6
  • Rails 5.2.4.3

事前準備

今回はnameというカラムを持つ、Userというモデルを使用していきます。
また、hogeという名前とfugaという名前を持つ2つのレコードが入っている想定です。

> User.all
=> User Load (1.1ms)  SELECT `users`.* FROM `users`
#<User id: 1, name: "hoge">,
#<User id: 2, name: "fuga">

scopeでUserを検索する

まずは通常通りscopeでUserを検索してみましょう。
scope自体の使い方の説明が主目的ではないので、簡単に特定の名前で検索するscopeを考えます。

class User < ApplicationRecord
  scope :find_hoge, -> () { find_by(name: "hoge") }
end

このscopeを使うことで、namehogeであるuserを検索することができます。
発行されるsqlをみても、きちんとnamehogeであるレコードを検索できていることが分かるかと思います。

> User.find_hoge
  User Load (0.8ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'hoge' LIMIT 1
=> #<User: id: 1, name: "hoge">

scopeでnilを返してみる

さて、それでは次に結果がnilを返すscopeを作成してみます。

class User < ApplicationRecord
  ...

  scope :find_nobody, -> () { find_by(name: "nobody") }
end

上記のscopeではnamenobodyであるレコードを検索していますが、該当するレコードが無いためnilを返すと思われます。
実際に実行してみましょう。

> User.find_nobody
  User Load (1.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'nobody' LIMIT 1
=> User Load (1.2ms)  SELECT `users`.* FROM `users`
#<User id: 1, name: "hoge">,
#<User id: 2, name: "fuga">

あれ?sqlが2回実行されていますね。
しかもallをした時と同様の挙動をしてしまっています。
どうやら結果もnilではないようです。

> User.find_nobody.nil?
  User Load (1.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'nobody' LIMIT 1
=> false

原因

この挙動の原因は、scopeの仕様にあります。

scopeはメソッドチェインなどで複数の条件を結合して使用することが想定されているため、結果がnilとなった場合は該当scopeの検索条件を除外したクエリを発行するという仕様になっているようです。

今回のケースではscopeの中身を直接実行してみると、想定通りnilを返していることが分かります。

> User.find_by(name: "nobody")
  User Load (1.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'nobody' LIMIT 1
=> nil

そのため、find_nobodyの条件を除外したクエリ、つまり検索条件の無いallの結果が返ってきてしまっているのです。

実際に、scopeを連結してみると、上記の仕様がよく分かるかと思います。
find_nobodyfind_hogeをチェインして使用してみましょう。

> User.find_nobody.find_hoge
  User Load (3.9ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'nobody' LIMIT 1
  User Load (2.0ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'hoge' LIMIT 1
=> #<User: id: 1, name: "hoge">

nobodyについて検索した後、hogeのみの条件で検索するクエリが発行されていますね。

対応策

ここまで見てきたように、nilを返す可能性がある場合はscopeで実装してしまうと予期しない結果となることがあるので、クラスメソッドで実装しておくのが良いかもしれません。

class User < ApplicationRecord
  ...
  ...

  def self.find_nobody
    find_by(name: "nobody")
  end
end

> User.find_nobody
  User Load (0.7ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'nobody' LIMIT 1
=> nil

無事にnilが返ってきますね。

まとめ

scopeを使うことで頻繁に使用する処理などをまとめておけるのが便利です。
しかし、nilを返す場合には今回紹介したように想定と異なる動作をする場合があるので、注意する必要がありそうです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ゲストログイン用のユーザー作成〜ポートフォリオに最適〜

アプリケーションにテストログイン機能をつけた話し

まだまだやりたいことはいっぱいあるけれど、AWSへのデプロイが終わったので見てもらうことを考えてゲストユーザーでのログイン機能をつけることにしました。

諸先輩方のアプリケーションを見てもついていない人はいないぐらい実装率が高いので今や当たり前の機能になっていると感じます。
そもそもログインして見てもらうのにわざわざ登録させるのも気が引けるし、せっかく時間を割いて見にきてくれるのだから、おもてなしの心があるべきだと思います。

ではdevise導入済みで話しを進めていきます。

1・app/controllers配下にusersがない場合

まずはコントローラーに新しいファイルを作成。

$rails g devise:controllers users

⬇️できたファイル

app
 ┣controllers
      ┣users
         ┗confirmations_controller.rb
         ┗passwords_controller.rb
         ┗registrations_controller.rb
         ┗sessions_controller.rb

users直下に4つのファイルができました。

2・routes.rbに記述

routes.rb
# devise_for :usersの部分に追加
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
    sessions: 'users/sessions'
  }

〜略〜

# resources :users, only: [:show] がある場合はその下に記述
devise_scope :user do
 post 'users/guest_sign_in', to: 'users/sessions#new_guest'
end

3・sessions_controller.rbにてメソッドを定義

sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new_guest
    user = User.guest
    sign_in user
    redirect_to root_path, notice: "ゲストユーザーとしてログインしました。"
  end
end

フラッシュメッセージはお好みで。

4・userモデルにメソッドを定義

user.rb
# モデルに定義なのでselfからスタート

def self.guest
  find_or_create_by!(email: 'test@test.com') do |user|
    user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
    user.nickname = 'ゲストユーザー'
    user.gender_id = '4'   ⬅️ActiveHashの入力
    user.genre_id = '9'    ⬅️ActiveHashの入力
  end
end

ActiveHashを利用している場合はuser.gender_id = '4'などの記述で問題ありませんでした。
ゲストログインも通常のユーザー登録をする流れと同じ流れを辿るので、バリデーションがかけられていると未入力項目に対してエラーが出てしまう。
ここでしっかりと項目をうめておく。
passwordは最初SecureRandom.urlsafe_base64としていましたがAWSへのデプロイ時にエラーになり、ログを見るとバリデーションが不正とのこと。
記述を変えたら本番環境でも成功しましたので、もし引っかかってしまったら試してみて下さい。

5・ビューファイルを変更

new.html.erb
<%= link_to 'ゲストログインはこちら', users_guest_sign_in_path, method: :post %>

ボタンを作る場所などレイアウトに合わせて適用。

参考にさせて頂きました

@take18k_tech
簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)

@rie1224
Railsでテストアカウントでの簡単ログインの実装方法

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォームオブジェクトを作る際にハマったこと

モデルの関係性

大まかなモデルは以下のモデルで、あとで出てくるprefectureはアクティブハッシュ を採用している。
userモデル :ユーザー情報
itemモデル :商品情報
orderモデル :購入記録、user_id、item_id を保存したい
order_addressモデル :購入者住所、orderモデル生成時にできるorder_idを保存したい
order_formモデル(フォームオブジェクト)
(prefecture)モデル

オブジェクトフォーム

class OrderForm
  include ActiveModel::Model
  attr_accessor :user_id, :item_id, :postal_code, :prefecture_id, :municipality, :address, :building_name, :phone_number

  with_options presence: true do
    validates :user_id
    validates :item_id
    validates :order_id
    validates :postal_code, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "Input correctly"}
    validates :prefecture_id, numericality: { other_than: 0, message: 'Select' }
    validates :municipality
    validates :address
    validates :phone_number, format: {with: /\A\d{10}\z|\A\d{11}\z/ , message: "Input only number"}
  end

  def save
    order = Order.create(user_id: user_id, item_id: item_id)

    OrderAdress.create(postal_code: postal_code, prefecture_id: prefecture_id, municipality: municipality, address: address, phone_number: phone_number, building_name: building_name, order_id: order.id)
  end
end

疑問点

order_idも保存したいからattr_accessorのなかに記述するのでは?

結論

『order_id』はorderが生成されたと同時に作られるから、そこにはかけない
attr_accessorの部分はhtmlファイルのフォーム部分で保存したいものの記述
のため、フォームでの段階では保存されない

前提として 保存の順番は orderテーブル  →  order_adressテーブル (order_idが必要)

だから、attr_accessorの段階ではorder_adressテーブルに保存したいorder_id
は作られていないから、そこにはかけない

saveの意味
def save の部分中身の意味はorderコントローラーの記述も見ないとわからない

フォームオブジェクトを振り返ってみて出てきた疑問

今回は1対1の関係性でフォームオブジェクトを使っていたが、
1対多のときは使うことができるのか?使い方は?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails における_pathと_urlの違い

現在、RailsTutorialを学習中の駆け出しエンジニアの者です。

railsヘルパーの一種であるpathとurlの違いを理解できていませんでしたので、自分なりに調べてみました。

_pathは相対パス、_urlは絶対パス

(例)
root_path => '/'
root_url  => 'http://www.example.com/'

new_user_path => '/users/new'
new_user_url => 'http://www.example.com/users/new'

相対パス

_pathは相対パスを示しています。相対パスはアプリケーションから見て、どのファイルやフォルダの位置にあるのかを示します。上の例であるnew_user_pathで説明しますと、viewフォルダの中のusersフォルダの中のnew.html.erbを呼び出していることになります。

絶対パス

_urlは絶対パスを指定しています。絶対パスとは、そのページをインターネットからアクセスする際のurlを示します。 なので、今いる位置に関係なく呼び出すことができますし、外部のサイトを呼び出すこともできます。

使用方法の違い

Railsの開発においての_pathと_urlの使用方法の違いを説明します

_pathの使用方法

_pathはredirect_to以外でよく使用します。

例えばlink_toなどで使用します。

_urlの使用方法

redirect_toのときに使用する。
なので、Railsにおいてはコントローラの際に使用することが多いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

詳細画面での進捗率ゲージの作成

はじめに

オリジナルアプリでクラウドファンディングサイトを作成した際に、
jQueryを使って進捗率ゲージを作成したので紹介します。
初学者なのでより良い方法が他にもあるかもしれませんが、どなたかの参考になれば嬉しいです。
Image from Gyazo

環境

・Rails 6.0.0
・Ruby 2.6.5
・jQuery

作成手順

【view】

details.html.haml
.contents-pacent
  .contents-pacent__box1
    進捗率
  .contents-pacent__box2
    - @percent = number_to_percentage(@total.to_f/@project.target_amount*100,precision: 1)
    = @percent
    .contents-pacent__box2__graph1
      .contents-pacent__box2__graph2
        %input{name: "percent", type: "hidden", value:@percent, class: 'percent' }

①ゲージを挿入したい部分にクラスを設定します。
 →.contents-pacent_box2_graph2の部分

②inptを用いてDBのインスタンス変数の値を取得します。
 →inputのvalueに変数を設定することでjQueryで変数を使えるようにします。
  ※今回は変数の詳細は割愛します。
 参考記事:https://qiita.com/Kohei_Kishimoto0214/items/d919b00d75dec0699cf0

【CSS】

_projects_details.scss
.contents-pacent__box2__graph2{
  background-color: #ea662d;
  height: 15px;
  border-radius: 6px;
  max-width: 100%;
}

③CSSは以上の通りです。
 進捗率が100%以上になっても突き抜けてしまわないように
 max-width: 100%;を設定しています。

【jQuery】

project_detail.js
$(function() {
  let percent = $( ".percent" ).val();
  window.onload = function(){
    $(".contents-pacent__box2__graph2").css({ 'width' : percent } );
    }
});

③viewのinputの値を取得します。
④リロードのタイミングで.contents-pacent_box2_graph2のwidthの値を@percentからとるように設定します。

最後に

詳細画面は以上のようなシンプルな設定でゲージの作成ができました。
しかしトップ画面の並んでいるプロジェクトそれぞれに進捗率を反映させるのには少し手間取りましたので
次回紹介できればと思っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アセットパイプラインについて解説

アセット(assets)とは

railsのapp/assetsディレクトリにはimages、javascripts、stylesheetsの3つのディレクトリがあります。
それぞれ画像ファイル、javascriptファイル、cssファイルを置くためのディレクトリです。

これらの事をアセット(assets)と呼びます。

アセットパイプラインとは

アセットパイプラインはstylesheetsディレクトリ以下の複数のファイルを1つに結合し、1個のcssファイルを生成する事をいいます。アセットパイプラインの最大の特徴はファイルの変換と結合です。

sassとは

SassとはCSSを拡張したスタイルシート言語のことです。
ブラウザではSassを理解できないため、サーバー側でsassをcssに変換してやる必要があります。この処理の事をコンパイルと呼びます。よく使用される拡張子.scssはsassの一種です。scssはapp/assets/stylesheetsに配置します。

なぜファイルを結合する必要があるのか

なぜ複数のファイルを結合するかというと、ブラウザとサーバー側のHTTP通信の回数を減らすためです。これによってサーバーへの負荷を減らすことができます。

アセットパイプラインの利用方法

stylesheetsディレクトリのapplication.cssを開くと以下の記述があると思います。

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */


まずはこれらを解説していきます。
コメント中の*はディレクティブと呼ばれ、アセットパイプラインの設定を行なっています。

 *= require_tree .

はアセットパイプラインが処理するファイルの設定範囲を設定しています。.(ドット)はstylesheetsディレクトリ以下の全ファイルを処理対象として指定しています。

ここを対象のディレクトリにしたい場合は

 *= require_tree ./対象のディレクトリ名

とします。

*= require_self

次に*= require_selfですが、これはself=自分自身、つまりはapplication.css自身をアセットパイプラインの処理対象とする事を宣言しています。

スタイルシートを目的別に分ける方法

まずstylesheetディレクトリにadmin.cssを作成します。

*
*= require_tree ./admin
*/

/config/initializers/assets.rbに以下を記述します。

Rails.application_config.assets.precompile += %w( admin.css)

これによりadmin.cssがプリコンパイルの対象に加わります。

次にstylesheetsディレクトリにadminディレクトリを作成し、layout.scssを作成しスタイルを記述します。

続いてviews/lauouts/application.html.erbをadmin.cssに変更します。

sample_app/app/views/layouts/admin.erb

<!DOCTYPE html>
<html>
  <head>
    <title><% title_helper %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

link_tagをadminに変更しています。

/application.controller

layout :set_layout

private def set_layout
    if params[:controller].match(%r{\A(admin)/})
      Regexp.last_match[1]
    else
      "customer"
    end
end

上記によりset_layoutメソッドはadminという文字列を返します。この文字列がレイアウトとして使用されます。あとはこれを繰り返すことで利用別ごとにスタイルシートを作成できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ActionTextに検索機能を適用する方法(検索機能を自作する)

はじめに

ActionTextとは、Rails6から追加された機能で下記写真のようにリッチテキストを編集するためのエディタが簡単に作れるようになります。
スクリーンショット 2021-02-21 12.40.10.png
しかし、一方で課題もありActionTextを適用していると検索機能を簡単に実装できるgemであるransackを使用できなくなってしまいます。

そのため、今回はgemを使わず自作で検索機能を作成し、ActionTextを適用しているカラムも検索できるようにしていきたいと思います。

今回の課題

postsテーブルのtitleカラムとcontentカラムに検索機能を適用したいためransackを導入。しかし、ActionTextを適用したcontentカラムにはransackが適用されず検索できない。

原因は何か

まず今回の検索対象であるPostモデルを確認します。

app/models/post.rb
# == Schema Information
#
# Table name: posts
#
#  id         :bigint           not null, primary key
#  category   :integer          default("knowhow"), not null
#  status     :integer          default("draft"), not null
#  title      :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#  user_id    :bigint
#
# Indexes
#
#  index_posts_on_user_id  (user_id)
#
class Post < ApplicationRecord
  has_rich_text :content

  belongs_to :user

  validates :title, presence: true
  validates :title, length: { minimum: 2, maximum: 30 }
  validates :content, presence: true
end

contentカラムにActionTextを適用するためにhas_rich_text :contentを記述しています。
ここで重要なのはPostモデルには「contentカラム」が存在していないということです。

ではcontentカラムのデータはどこに格納されているのでしょうか?
実際に、下記のように投稿して確かめてみます。
スクリーンショット 2021-02-21 13.32.03.png
コンソールを確認すると以下のようになっています。
スクリーンショット 2021-02-21 13.30.14.png
「QiitaにActionTextに関する記事の投稿」というデータはpostsテーブルのtitleカラムに格納されており、「ActionTextを使用しているとransackが使えない?」というデータはaction_text_rich_textsテーブルのbodyカラムに格納されていることがわかります。

実際に、action_text_rich_textsテーブルを確認すると以下のようになっています。

db/migrate/create_action_text_tables.action_text.rb
# This migration comes from action_text (originally 20180528164100)
class CreateActionTextTables < ActiveRecord::Migration[6.0]
  def change
    create_table :action_text_rich_texts do |t|
      t.string     :name, null: false
      t.text       :body, size: :long
      t.references :record, null: false, polymorphic: true, index: false

      t.timestamps

      t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
    end
  end
end

つまり、今回postsテーブルのcontentカラムに検索機能を適用できなかったのは、postsテーブルにcontentカラムが存在せず、他のテーブルにデータが渡っていたことが原因であると推測できます。

解決方法

原因は「contentカラムのデータがaction_text_rich_textsテーブルに渡っていたこと」だと推測できたので、action_text_rich_textsのテーブルをpostsテーブルに内部結合し該当カラムを取り出せるようにすれば解決できるのではないかと考えました。
画像.png


結論

Postモデルに以下を記述することでcontentカラムを検索することができるようになりました。
ちなみに今回はSQLの知識も使っているのでこちらの記事を参考にしていただけると幸いです。

app/models/post.rb
class Post < ApplicationRecord
  has_rich_text :content

  scope :search, -> (search_param = nil) {
    return if search_param.blank?
    joins("INNER JOIN action_text_rich_texts ON action_text_rich_texts.record_id = posts.id AND action_text_rich_texts.record_type = 'Post'")
    .where("action_text_rich_texts.body LIKE ? OR posts.title LIKE ? ", "%#{search_param}%", "%#{search_param}%")
  }
end

では、細かく分解して内容を確認していきましょう。

① scope :search, -> (search_param = nil) {}

まずはscopeについてです。
scopeはActiveRecordの機能の一部で、モデルに定義するとクラスメソッドのように呼び出せます。
もちろんコントローラに直接書くこともできるとは思いますが、ファットコントローラを避けるために今回はPostモデルにsearchというscopeを定義しています。

scope :スコープ名, -> { 条件式 }

引数にはsearch_params = nilを指定しています。

② return if search_param.blank?

この文章は検索フォームに何も入力されていない状態で検索が実行された場合に早期リターンできるように定義しています。

returnに何も記述していないのは、未入力で検索が実行された場合にnilを返すためです。

なぜあえてnilを返しているかというと、scopeメソッドはクラスメソッドとは違って、nilの場合にallメソッドが実行されるからです。

これにより、未入力状態で検索が実行されてもPost.allしている状態になるので全ての投稿を表示したままにできます。

③ joins("INNER JOIN action_text_rich_texts ON action_text_rich_texts.record_id = posts.id AND action_text_rich_texts.record_type = 'Post'")

ここではjoinsメソッドで引数内をPostモデルに内部結合しています。
内部結合しているテーブルはaction_text_rich_textsテーブルです。

またON句を使用して結合条件を定義しています。
今回定義した結合条件は以下の通りです。

結合条件の内容
 action_text_rich_texts.record_id = posts.id 

record_idpostsidカラムが一致すること

 action_text_rich_texts.record_type = 'Post'

racord_typePostモデルと紐づいていること
④ .where("action_text_rich_texts.body LIKE ? OR posts.title LIKE ? ", "%#{search_param}%", "%#{search_param}%")

ここではwhereメソッドを使用して、テーブル内の条件に一致したレコードを配列の形で取得しようとしています。

このままではわかりづらいので引数の中身をさらに2つに分解して考えてみます。

contentカラム(bodyカラム)の検索
.where( action_text_rich_texts.body LIKE ?, "%#{search_param}%" )
titleカラムの検索
.where( posts.title LIKE ? , "%#{search_param}%" )

ここでは3つポイントがあります。

1. [モデル].where( [カラム名] LIKE [パターン] )

LIKE述語はカラムのデータが、指定したパターンと一致した場合にTrueを返します。Trueが返された行は検索の対象となります。

2. LIKE ? , "[値]"

「?」はプレースホルダーと呼ばれるもので、第2引数の値を「?」で置き換えています。

3. %#{search_param}%

ここでは検索フォームに入ってきた値をRubyで埋め込んでいます。

また「%」は「0文字以上の任意の文字列」という意味の特殊記号なので、「%」で囲ってあげることにより曖昧な検索が可能となっています。


これらの処理によって、検索フォームに入力された値に合致するものをtitleカラムやcontentカラム(bodyカラム)から検索できるようになりました!

その他

検索機能のコントローラやビューの該当箇所は以下の通りです。

コントローラー
  def index
    @posts = Post.order(created_at: :desc)
    @posts = Post.search(params["q"]).order(created_at: :desc)
  end
ビュー
= form_tag posts_path, method: :get, class: 'ui action input fluid' do
  = text_field_tag :q, '', placeholder: 'キーワードで検索'
  = button_tag :class => 'ui icon button' do
    %i.search.icon

#ビューにはSemantic UIとHamlを使用しております

あとがき

僕は自作アプリの作成を決めた時、ransackを使用して検索機能を実装しようと考えていました。
そのため、今回の検索機能の自作はある種事故的なものではありましたが、以前会社の研修で学んだSQLの復習にもなったし、ロジックを考えて組み立てていく過程はとても楽しかったです!

ActionTextを使用している方がどれほどいらっしゃるかはわかりませんが、この記事がなんらかの参考になれば幸いです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[memo]テストコード準備と実行

テストコードの準備

# Gemを追加 Gemfile
# group :development, :testというグループの中に記述
% gem 'rspec-rails', '~> 4.0.0'

# ターミナル
% bundle install

# ターミナル
% rails g rspec:install

# .rspecに設定を追加
--require spec_helper
--format documentation

テストコードを記述するファイルを用意

# ターミナル
# Userモデルのテストファイルを生成
% rails g rspec:model user

テストコードの実行

# ターミナル
% bundle exec rspec spec/models/user_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】RSpecでDeviceメール認証をクリアする方法

実装例

userのサンプルデータを作成する段階で認証済にします。
factorybotのuser do内に追記するのみで、
テストコードには変更を加えませんので汎用的に使えるかと思います。

factorybot.rb
FactoryBot.define do
  factory :user do
    :
    :
    #下記1行を追加するだけ
    confirmed_at { Date.today }
  end
end

この状態でfactorybotを実行すれば
メール認証済の状態でサンプルデータを作ることができます。
参考:github.com/henrydjacob

その他の解決方法

テストコード内のbefore処理に記述する方法もありました。
参考:Rspecのrequest(Integration test)でDeviseのメール認証を回避する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】envメゾッドで開発、本番、テスト環境での挙動をわける方法

Railsで用意されているenv

  • produciton(本番環境)
  • test(テスト環境)
  • development(開発環境)

実装例

Rails.env.development?と記述すれば開発環境の場合の処理を記載できます。
例えば、例外処理で下記のような記述を行った場合は、開発環境以外でのみ読み込まれる。
参考:Rails 5の404/500エラーページ、簡単作成手順

controller/application
#開発環境ではない場合に読み込む
unless Rails.env.development?
  rescue_from ActiveRecord::RecordNotFound, with: :render_404
  rescue_from ActionController::RoutingError, with: :render_404
  rescue_from Exception, with: :render_500
end

しかし、このままだと、本番環境のみでなく、テスト環境でも読み込まれるため、
RSpec等実行する際のエラー文を読み込めない場合があります。
下記のような記述にすれば解決。

controller/application
#開発環境またはテスト環境ではない場合に読み込む
unless Rails.env.development? || Rails.env.test?
  rescue_from ActiveRecord::RecordNotFound, with: :render_404
  rescue_from ActionController::RoutingError, with: :render_404
  rescue_from Exception, with: :render_500
end

同じ要領で下記のように使うことも可能。

controller/hoges_controller
    # 本番環境で使っているMySQLではRANDOMが使えずRANDが必須
    if Rails.env.development? || Rails.env.test?
      # SQLite(開発、テスト環境ではRANDOM)
      @hoges = Answer.order("RANDOM()").limit(3)
    else
      # mySQL対応(本番環境ではRAND)
      @hoges = Answer.order("RAND()").limit(3)
    end

さいごに

RANDOMの例で記載した、unless Rails.env.development? || Rails.env.test?
という記述は、元々if Rails.env.production? とelse記述していたところ、
本番環境でもdevelopmentの方が読み込まれてしまい修正した経緯があります。
(いずれもほぼ同じ意味という認識で実行したのですが、、)
もし原因分かる方がいらっしゃいましたらご教示いただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで画像プレビュー機能を実装するシンプルなコード

Ruby on RailsのアプリでJavaScriptを使ってプレビュー機能を実装するコードを解説します。

こんな感じで画像ファイルを選択すると、プレビューが表示される機能を目指します。
Image from Gyazo

実行環境

Rails 6.0.3.1
macOS Catalina バージョン10.15.7

ビューのコード

ビューはきわめてシンプルです。

app/views/messages/_form.html.erb
<%= form_with model: @message, id: 'new_message', local: true do |f| %>
  <%= f.text_field :content, placeholder: 'type a message' %>
  <%= f.file_field :image %>
  <%= f.submit '送信' %>
  <div id="image-list"></div><%# プレビューを表示する部分 %>
<% end %>
<%# ↓投稿編集ページ用の画像表示箇所↓%>
<%= image_tag @message.image, id: 'image' if @message.image.present? %>

JavaScriptのコード

コードは以下の通りです。流れをシンプルにして説明すると、

  • 画像ファイルが選択されるとその画像ファイルに対してURLが生成
  • 生成したimg要素のsrc属性にそのURLをセット
  • プレビュー表示用のdiv要素の中に子要素のdiv要素とimg要素を追加する

この流れでプレビュー画像を表示します。

app/javascript/packs/preview.js
// プレビュー表示機能は新規投稿("/new/")か投稿編集("/edit/")ページでのみ有効にする
if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    // プレビューを表示するための要素を取得
    const ImageList = document.getElementById('image-list');

    const createImageHTML = (blob) => {
      // 画像を表示するためのdiv要素を生成
      const imageElement = document.createElement('div');
      // 表示する画像を生成
      const blobImage = document.createElement('img');
      // img要素のsrc属性の値をセット
      blobImage.setAttribute('src', blob);

      // 生成したHTMLの要素をブラウザに表示させる
      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    };

    document.getElementById('message_image').addEventListener('change', function(e){
      // 画像が表示されている場合のみ、すでに存在している画像を削除する(編集ページ用)
      const imageContent = document.querySelector('img');
      if (imageContent){
        imageContent.remove();
      }

      // 発火したイベントeの中の、targetの中の、filesという配列に格納された画像を変数に代入
      const file = e.target.files[0];
      // 画像のURLを生成
      const blob = window.URL.createObjectURL(file);

      createImageHTML(blob);
    });
  });
}

ビューファイルとJavaScriptのファイルを見比べやすいように横並びにした画像を用意しました。
Image from Gyazo

最後に、application.jsに以下の記述を追記して、turbolinksはコメントアウト(または削除)し、preview.jsを読み込むことを忘れないようにしましょう。

app/javascript/packs/application.js
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("./preview") //このコードを追記
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験】独学+メンターでここまで出来た!Web知識ゼロからモダンな技術アプリ開発までに利用した5つのサービス【Rails / React / AWS / Docker / CircleCI】

0. はじめに

こんにちは!辻野(@ddpmtcpbr)と申します。

当記事は、「Webエンジニアへのキャリアチェンジを目指している開発未経験者が、モダンな技術を備えたアプリを開発するまでの学習過程」についてまとめたものです。

現在筆者は非IT系企業の社員として働いており、Web開発エンジニアとしての実務経験はありません

そんな筆者がWebエンジニアとしてのキャリアチェンジをするためのポートフォリオとして、本アプリを開発しました。

学習開始から現時点までにおいて、プログラミングスクール等には通っておらず、学習はほぼ全て独学&一部メンターサービス利用の布陣で進めてきました。

独学中心でアプリ開発に挑戦したい、ポートフォリオを作成してWebエンジニアへのキャリアチェンジを進めていきたい、と考えている方々にとって、参考になればと思います。

最初に、今回私が開発したアプリの概要を紹介します。

アプリ名: 積読解消アプリ 「Yomukatsu!」

image.png

あなたの積読解消をサポートします」をスローガンに掲げたSPA風Webアプリです(”風”の詳細は後述)。

読書メンタルマップという手法を用いて、ユーザーの書籍完読に向けたモチベーション維持をサポートします。

Web URL: https://yomukatsu.com/

【3分動画】Yomukatsu 字幕解説

アプリの使い方を3分でまとめています。

使用技術

  • Backend: Rails ( API mode / Rspec / rubocop) + Nginx ( upstream puma-socket )
  • Frontend: React ( create-react-app / Redux / Material-UI / eslint&prettier)
  • Infra: AWS ( ECS Fargate/ ECR / RDS / ALB / Route53 ), Netlify, Docker&docker-compose, CircleCI

各項目の詳細は後述しています。

インフラ構成

yomukatsu_cloud_architecture.png

モダンな技術を採用したWeb系企業が提供している、中・大規模なアプリケーションを想定したインフラ構成にしています(そのため、個人開発アプリとしてみるとちょっと仰々しいかもです(;’∀’))

詳細は後述しています。

機能一覧

ユーザー利用機能

  • Twitterアカウントを利用したユーザー登録(OAuthによるSNS認証)
  • ゲストログイン機能
  • Google Books APIを用いた書籍検索機能
  • Google Books、Amazon、楽天ブックスへのリンクボタン配置
  • Twitterシェア機能
    • ハッシュタグ「#yomukatsu」付きTweet
    • Twitter card 表示
  • Twitter card用にリサイズした書籍画像をAWS S3へ保存・管理
  • Redux による state 管理を活用したローディング画面
  • Slack Incomming Webhookを利用したお問い合わせ機能
  • Route53 による独自ドメイン + SSL化

非ユーザー利用機能

  • Netlify の Pre-reidering 機能活用による動的なOGP情報の保持( Twitter card 表示用)
  • Docker による開発環境の完全コンテナ化
  • CircleCI による自動 CI/CD パイプライン構築
    • CI: Rspec, rubocop, eslint&prettier
    • CD: AWS ECR
  • その他セキュリティ対策(XXS, CSPF等)

まず触ってみてもらうのが一番良いかと思います!今回はフロントエンドに React を採用しているため、メニューモーダルの開閉や通知バー表示など、アニメーション的な表現も実装できているのが伝わるかと思います。

ゲストユーザー機能もありますので、気軽に利用してみてほしいです!レスポンシブにも対応しています。(推奨ブラウザはChrome、Safariになります)

アプリURL: https://yomukatsu.com/

※現在α版としてのリリースのため、配信内容が予告無く変更される可能性がございます

あわせて、 Githubも公開していますので、よかったら参考にしてください。

Github URL: https://github.com/ddpmntcpbr/rails_react_docker

この記事について

当記事は3章構成になっております。

まず、「1.自己紹介」で、簡単に自己紹介をさせていただきます。

次に、「2.開発アプリ解説」で、今回の開発アプリ開発について、コンセプト決定の流れから、実装機能/技術スタックについて詳しく紹介します。「転職用PF作りたいけど、どんなアプリを作ればいいか分からない」という方にとって、参考になることがあれば幸いです。

最後に、「3. 学習ロードマップ」で、Web知識ゼロだった筆者が、当アプリの開発にまでに利用した5つの教材およびサービスについて、時系列に沿って紹介したいと思います。特に独学では、「まず何を学べば良いのか」「どんな教材を選べば良いのか」というところから自身で考える必要があります。そういった方々にとって参考になり得る情報と思います。

1. 自己紹介

1.1 筆者スペック

  • 20代後半 男
  • 工学部機械系 修士卒 → 非IT系 日系製造業 技術開発職(現職)
  • 大学から現職において、データ分析ツールとしてプログラミングを経験(matlab / python)
  • Webエンジニアへのキャリアチェンジを目指し、社会人になってから独学を開始

大学入学以降、授業や研究データの分析ツールとしてプログラミングに触れる機会はありました。もともと自動化や効率化に興味があったことからプログラミングにだんだんとのめり込み、研究室ではプログラミングに多く触れられそうな研究テーマ(データ分析系)を選びました。

したがって、「プログラミング自体が完全に未経験」というわけではありませんでした。しかし、いわゆる”Web系”の知識は社会人以降の独学を開始するまではゼロ、という状態で、最初は HTML すら知らないところからのスタートでした( ̄▽ ̄;)。そのため当記事では「Web知識ゼロ」という表現をしています。

キャリアチェンジ志望の理由は、ざっくりと言えば、

1. もっとプログラミングがしたい
2. モノづくりで誰かの役に立ちたい
3. 非効率・非生産的な仕事を無くしたい

ここについては当記事の趣旨ではないので、詳細については省略します。

2. 開発アプリ解説

2.1 コンセプト方針

転職用PFアプリのコンセプトを決めるにあたり、満たすべき用件としては、下記3点を考えました。

(a) 実際の企業が採用しているようなモダンな技術を盛り込む
(b) 具体的な解決課題を明確にする
(c) サービスの利用が個人で完結する

(a) 実際の企業が採用しているようなモダンな技術を採用する

未経験からエンジニア転職において、高品質なポートフォリオは必須と考えました。

「モダンな技術をポートフォリオに組み込むことで、技術力の高さをアピールする」というのが基本的な考え方になるとは思いますが、個人的な解釈としては、ポートフォリオで証明すべきは「技術力」ではなく「自走力」だと考えています。

ぶっちゃけた話、実際の現場を経験したエンジニアと比べれば、未経験者間での能力差というものはどんぐりの背比べのようなものだと思います。多くの企業が「実務経験1年以上」をエンジニアの採用項目にあげていることからも、実務経験というのはそれだけ重い価値があり、未経験者とは大きな隔たりがあるのだと思います。

したがって、ポートフォリオの技術レベルの高さそのものはあまり重要ではなく、そこに到達するまでの過程の方が大事であり、「自走力=必要な情報は自らキャッチアップして吸収する能力」があることを示す方が、企業人事側としては採用しやすいのではないか?と考えました。

「スクールの制作アプリをそのまま提出する未経験者が足切りされてしまう」という話は多く聞きますが、これはそのアプリの技術力が低いからではなく、そのアプリから当人の「自走力」が主張できないから、だと私は考えています。私が独学ベースにこだわったのは、単純にお金の問題だけではなく、独学ベースでアプリを開発することができれば、自然とそれが「自走力の証明」につながると考えたからです。

今回のアプリにおいては、「独学:メンター=8:2」 くらいの割合で、上記技術を採用できるラインまで行くことができています。「メンター利用は独学からは外れるのでは?」という指摘もあるかもしれませんが、

  • プログラミングでは、個人ではどうにもならないようなエラーに遭遇してしまうことが多々ある
  • 実際に企業に入ってからは、先輩エンジニアの方々に分からないことを質問しながら業務を進めることになるため、「質問力」も重要な能力である
  • 完全独学オンリーだと、誤った癖が身についてしまっているリスクが高くなる

といった観点から、独学者が適宜メンターサービスを利用することは、かえって採用人事側にとって安心感を与える材料になるのでは、と考えたので、自信を持って「メンターサービスを使いました」と主張しています。

※ 具体的なモダンな技術リスト

  • Rails API + JSフレームワーク(React.js)の構成
  • Dockerで実行環境を完全コンテナ化
  • Herokuではなく、AWS でアプリをデプロイ(ECS Fargate & ECR)
  • Circle CIによるCI/CDパイプラインの構築

(b) 具体的な解決課題を明確にする

さて、前章とは一見真逆のことを言いますが、アプリ開発において、モダンな技術を採用することそのものには本来何の価値もない、と考えています。

なぜなら、アプリの目的はあくまでも「ユーザーにとって価値を提供できるか」であり、技術はそれを実現するための手段でしかないはずだからです。(保守・運用面でのメリットも考えられますが、保守・運用の最終目的もユーザーへの価値提供であることから、この点も包含した解釈になります)

これは、技術というものを下に見ているというわけでは決してありません。むしろ「新しい技術をどんどん使ってみたい!」という技術に対する好奇心、探究心は人一倍強い自負があります。現職はIT系ではありませんが、技術開発職という立場で業務に取り組んでおり、知的好奇心を満たせるという意味では、今の仕事に面白みを感じています。

しかしながら、かつては行き過ぎた技術先行思想によって「手段の目的化」が発生し、「最新技術を駆使した誰にとっても役に立たない技術」を開発してしまったという苦い過去の経験があったりもします。結果として「やっぱり技術は人の役に立ってなんぼ」というのが、約3年間技術職として働いてきて培った、技術者としての小さな矜持だったりします(この辺の話は直接お会いした方にはお話できるかと思います)

転職用PFであれば、技術ありきな考え方になることはある程度は仕方がないことでしょう。しかし「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」くらいのことは転職用PF作成においても考えていいんじゃないかな?と思いました。あるいは、もう少し目線を下げて「自分が欲しいものを作ろう」という程度でも十分でしょう。大事なのは、まず課題があり、それを解決する手段として技術があるという順番だと思いました。

もちろん今回の開発アプリは転職用PFが趣旨である以上、中には「技術力を証明したいから」という理由で選定した技術もあり、全てに対して課題が明確だったわけではありません。また、初心者の個人開発アプリがいきなりバズることは現実的には厳しいとは思います。しかし、そこを目指す姿勢があるかというのは、エンジニアとして本格的にキャリアを進めていく上では、長期的には大きな差異になると考えています。

また、

  • 課題が明確な方が採用した技術や実装した機能の根拠も明確にできるため、開発の方針を立てやすい
  • 自身がユーザー目線に立てることで、改善点を見つけやすい
  • 純粋にモチベーションを維持しやすい

といった点でもメリットもあると感じましたので、この方針は間違っていなかったと思います。

(c) サービスの利用が個人で完結する

「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」を、もう一歩深く考えた方針です。

例えば Rails を対象として考えたとき、一般的な転職用PFとしては、TwitterライクなSNS系アプリや、メルカリライクなEC系アプリが多いかと思います。

理由は、ユーザー認証、CRUD操作、DB間のリレーションなど、基本的なサーバーサイド技術を一通り抑えられるものであるから、だと思います。「Railsの一般的な知識を持っていることを証明する」手段と考えれば、妥当な方針でしょう。

しかしながら、未経験者が転職用に開発した上記アプリが実際にユーザーに継続して使われるということは、まず無いでしょう。SNS系アプリは「ユーザー数が多ければ多いほどサービスとして質が高まる」性質があるため、アプリとして軌道に載せること自体が非常に難しいです。EC系アプリは BtoC であれば出品企業がいないとサービスが成り立たない、CtoCであればより SNS として要素が強まる & いよいよメルカリで十分、という壁があります。

これらアプリの難しさは、ユーザーどうしがつながることを前提としている点にあります。裏を返せば「個人で完結するアプリであれば、活路はある」とも言えると考え、この方針でコンセプトを詰めていくことにしました。

2.2 コンセプト内容

上記3点を念頭に置きながら、自分自身の生活の中で"課題"を探し、最終的にたどり着いたものが「読書メンタルマップ術の電子化」というコンセプトでした。

そもそも皆様は、読書メンタルマップ術というものをご存知でしょうか?

読書メンタルマップ術とは、ハーバード大学の先生が提唱している積読解消術です。読破したい書籍に対して、

1. 完読したい本について、それを読む“理由”や“目的”を3つ、紙に書きだす
2. 飽きてきたら、それを見返す

を繰り返すことで、完読までモチベーションを維持するというシンプルな読書手法です。

積読というものは、だいたい「最初は読む気があったけど、次第に読む気がなくなってしまった」書籍です。この「最初の読む気」を事前に明文化・保存しておくことで、いつでも最初の頃に新鮮な気持ちを取り戻せるようにしておこう、というイメージになります。

自分自身、実際に活用している技術ではあるのですが、少し困ったことがあります。それは、電子書籍との相性が悪いことです。

通常であれば紙とペンを用いるものですが、例えば出先でスマホやタブレットで電子書籍を読んでいるような状況では、必ずしもこれらの道具があるとは限りません。特に私は、外に出る時はあまりものを多く持ちたく無い性分なので、外ではスマホと財布くらいしか持っていないことが多いです。

仮に持っていたとしても、例えば電車の中で紙とペンを出して色々と書き始めるのは、少し億劫だったりします。

これを全部ペーパーレスで完結できるようなアプリがあったら便利だな」と思ったのが、このコンセプトを思いついたきっかけになっています。

もちろん、これだけであればスマホのメモ帳だけでもできてしまうものですが、このアプリには、

  • Google Books APIを活用した書籍検索・保存機能
  • メンタルマップ作成のヒント機能
  • Google Books, Amazon, 楽天ブックスへのリンクボタン配置(特に書籍レビューはメンタルマップ作成の大きなヒントになる)
  • Twitterでの読書仲間への気軽なシェア機能

といった機能が備わっており、より読書メンタルマップ術を使用しやすい環境を整えています。

先ほどの「2. 具体的な解決課題を明確にする」に照らし合わせて考えると、このアプリの解決課題は、ユーザーの積読を解消すること、もっと言えば、読書メンタルマップ術をペーパーレスで実行できるすることで電子書籍で読書するユーザーにとっても扱いやすくすること、になります。

また、「3. サービスの利用が個人で完結する」も満たしています。当アプリには、ユーザー同士がつながる機能は一切実装されていません

代わりとして、Twitterとの連携にはかなり重きを置いて実装機能を決めました。具体的には、

  • Twitterアカウントを利用したユーザー登録
  • ワンタップでハッシュタグ付きツイート
  • 充実した Twitter カード表示

を機能として実装しています。原則的には個人でサービスが完結しつつも、ユーザーどうしの繋がりはTwitter内の既存のネットワークに乗っかる、ことを狙ったコンセプトになっています。

2.3 技術スタック

再度、インフラ構成を載せます。

yomukatsu_cloud_architecture.png

この内容について、ひとつひとつ解説します。

Back-end

Rails API + Nginx の組み合わせにしています。

サーバーサイドフレームワークとしては他にも Laravel, Django, Node.js などもあります。恐らく大体のことは、どれを選んでも実装・実現できる、と思うのですが、その中で今回 Rails を選んだ理由は、

  1. 採用している企業数が多い
  2. 日本語の教材が豊富なため学習のハードルが低い
  3. 国内コミュニティが発達しているため、インターネット上での日本語ドキュメントが豊富

最初の学習言語として Rails を選択する初学者の方は多くいるかと思います。その一方で、「Railsはオワコン」という説が各所で言われていることについて、不安に感じる初学者の方もいると思います。

この点に関して、あくまで個人的な見解を述べますと、初学者であった自分が、少なくとも最初に学ぶ言語/FW として Rails は間違いではなかった、と考えています。

正直、初学者である私には、「なぜ Rails がオワコンであるのか」について技術ベースで語ることはできません。しかし、

  • 現時点で Rails を採用している企業の絶対数は多く存在すること
  • 日本国内において、Rails に替わるサーバーサイドのデファクトスタンダードな技術が、まだ定まっていないこと

は事実と言ってよいかと思います。

もしかしたら、長期的に見れば日本国内でも Rails を採用する企業が減っていく流れにはあるのかもしれません。しかし、微分値と絶対値はセットで捉えないと判断を誤ることになります。初心者が目指すべきは「今すぐに仕事を得られる技術を身に着けること」であり「将来必要になってくる技術」ではない、自分が今学ぶべきはやはり Rails である、と判断をしました。

また、オワコンというのは裏を返せば、技術的に枯れていて、初心者にとっては学びやすい言語/FWである、とも言えます。

特に日本国内での(過去含めた)使用者が多いため、日本語のドキュメントが多く存在します。事実、Rails開発で遭遇するエラーは、Google検索すれば何かしらの日本語記事がヒットします。

最先端の技術は過去の技術の欠点を補う要素を持って生まれてきているのは事実ですが、検索しても欲しい情報が見つからなかったり、あったとしても英語ドキュメントだけだったりします。私自身は、このアプリの開発を通じてWebフレームワークに基本的な概念が身についてきているため英語でも大丈夫になってきましたが、ベースラインすら乏しい状態の初学者がいきなり英語のドキュメントを読み解いていくのは、二重にしんどいです。

以上が、私が最初の言語/FWとして Rails を選んだ根拠になります。

主要gem
  • devise_token_auth: APIモードでの devise。トークン認証を簡単に実装
  • twitter_omniauth:Twitter認証を簡単に実装
  • active_model_serializer: Rails APIからのレスポンスJSONを制御
  • imageMagic: 画像のリサイズを実行。特に、Twitter card用に書籍画像をリサイズする際に使用
  • aws-fog/carrierwave: リサイズした書籍画像を AWS S3 に保存
  • rspec: デファクトスタンダードになっているRubyテスト用フレームワーク
  • rubocop: Rubyの静的コード解析

TwitterアカウントでのOAuth認証は、過去の実装例が少なく、非常に苦労したところでもありました。しかし、「Twitterとの連携を重視」という今回のコンセプト上では絶対に欲しい機能と考え、頑張って実装しました。

AWS S3については元々採用予定はなかった(Google Books APIの画像リンクをそのまま引っ張ってくる予定だった)のですが、Twitter card で書籍画像を表示させる時にどうしても画像サイズを適切に制御する必要が出てきたので、imageMagic と合わせて Rails で画像リサイズ & S3保存、を実装することにしました。

Front-end

今回フロントエンドとしては、JSフレームワークであるReact.jsを採用しました(細かいこと言えば React はフレームワークではなくライブラリですが、ここではフレームワークとして扱います)。モダンな技術採用を謳う以上、Rails + jQuery/bootstrap の構成では心許ないと考えました。

JSフレームワークとしては、国内企業での採用状況から考えるに、React.jsVue.jsか、の二択になると思います。その中でもReact.jsを選んだ理由は、

  1. 自分が調べた範囲では、バックエンドに Rails を採用している企業群のうち、フロントに React を採用している企業の割合が高かった
  2. Vue.js よりも規約が厳格であり、初学者の自分であっても自然と可読性の高いコードを書くことができそう
  3. たまたま、React を効率的に学べる良い教材を見つけた

特に最後については、第3章で後述しています。

アプリ開発を通じて React.js が割と気に入ってきたので良い選択だったとは思いますが、この点に関してはどちらを選んでも間違いではなかったかな、とは思います。

主要ライブラリ等
  • create-react-app: Facebookが提供するオープンソースのReact開発パッケージ
  • Redux: Stateの一元管理するフレームワーク。Redux関連ファイルは、reducksパターン則って管理
  • Redux-thunk: Redux state の非同期処理を制御
  • react-helmet: 動的なmetaタグの挿入によるOGP情報の保持(Twitter card用)
  • react-share: Twitter含めたSNSシェア用ボタンを簡単に配置
  • Material-UI : Google が提供する UI コンポーネントライブラリ。簡単におしゃれな UI コンポーネントをアプリ内に配置できる
  • eslint & prettier: javascriptに対する静的コード解析。eslint は create-react-appに標準搭載されているものをベースに少しプラグインを追加 & prettier はイチから導入

今回はユーザーの利用シーンを考えると、Web上でもネイティブアプリのようにサクサク動く、JSリッチなアプリケーションにしたいと考えました。この点からも、jQuery+bootstrap ではなく React.js を採用してよかったと思います。

React の実装には、特にMaterial-UIが強力で、開閉モーダルや通知バー表示などのアニメーション演出や、ページ全体のレスポンシブ対応などがかなり簡単に実装できました。このライブラリを使えたというだけでも、React を採用した価値があったと思えるほどでした。

Infra

Docker/docker-compose

開発環境は、全てDockerコンテナ内で完結させています。docker-compose.ymlのサービス構成としては、

  • db: MySQL
  • api: Rails
  • web: Nginx
  • front: Node.js (React)

としています。

後述しますが、AWS ECS(Fargate)へのコンテナデプロイを利用することで、開発環境と本番環境の差異を小さくすることができています。

AWS(Amazon Web Service)

バックエンド( Rails + Nginx )のデプロイに使用。Railsチュートリアルなどではアプリの本番環境へのデプロイにはHerokuを用いることが一般的ですが「モダンな技術を採用したい」という観点から、AWSに挑戦しました。

稼働させるまでめちゃくちゃ苦労しました。ここを完全独学で完結させるのは相当しんどいと思います。第3章で触れていますが、AWSの学習については、メンターさんをかなり頼らせてもらいました。

※ 利用サービス

  • ECS (Fargate): コンテナ向けサーバーレスコンピューティングエンジン。この中に Rails と Nginx の Docker イメージを入れて稼働させる
  • ECR: Rails と Nginx の Docker イメージを保存しておくリポジトリ
  • RDS (MySQL): AWS が用意しているスケーラブルなデータベースエンジン
  • ALB: 負荷分散を担うロードバランシングサービス
  • Route53: サイトの独自ドメイン化に使用
  • ACM: サイトの https 化に使用
  • S3: 静的ホスティングサービス。書籍画像も保存・管理に使用
Netlify

フロントエンド(create-react-app)のホスティングで利用。

最初はバックエンドに合わせてフロントも AWS ( Amplify Console ) でホスティングしていたのですが、create-react-appはSPAとしてのアプリ開発となることから、metaタグ無いにOGP情報を保持できない = Twitterでページをシェアした時のカード表示を動的に制御できない、という問題が出てきました。

おそらくは AWS でも解決する手段はあると思うのですが、今回は Netlify の Pre-rendering機能を使うことで解決することにしました。この機能を使うことで、 create-react-app であっても、サーバー側で javascript をレンダリングしてからブラウザが解釈できるようになります ( あと単純に、無料で利用できるのもメリット )

この問題にあたってから、最近ホットな React フレームワークの Next.js の有り難みが自然と分かるようになってきた( Next.js は SPA/SSG/SSRを選択可能 )のですが、今回はすでに開発を始めていたこともあり、create-react-app + Netlify の構成で最後まで開発しました。

CircleCI

国内ではデファクトスタンダードとされている、Saas型のCI/CDサービスです。今回CircleCIで自動化した処理は、

  • Rspec
  • rubocop
  • eslint&prettier
  • AWS ECR への Image push
  • AWS ECS のタスク&サービスの更新

Netlifyにはもともと自動デプロイ機能がついていることから、CircleCIを導入することで、Github上の master ブランチに merge しただけで、本番環境への再デプロイが完了する、という状態に持っていくことができました。

一度ありがたみが分かると、もう手放せないですね( ´ ▽ ` )

3. 学習ロードマップ

さて、いよいよ本題です。ここまでで開発したアプリについて解説をしてきましたが、ここからは、このアプリ開発に至るまでの学習過程をたどっていきます。

第1章でもお伝えした通り、筆者はプログラミング経験自体はあっても、いわゆる「Web系」の知識はゼロからのスタートでした。繰り返しますが、HTMLすら知らなかった状態から、独学ベースで上記技術スタックをアプリに盛り込めるレベルまで到達することができました

独学ベースでの学習になると、「どの学習教材を選ぶべきか」というところから自分で考える必要があります。いろいろと紆余曲折ありましたが「これは役に立った!」と思うものを厳選し、時系列に沿ってお伝えします。

※下記サービスのWebリンクや、ロゴ画像、ホームページのスクリーンショットについては、事前に各運営者様に使用許可をいただいております。改めまして運営者皆様、利用快諾していただきありがとうございました。

3.1 Progate

image.png

皆大好きProgate。今からエンジニアを目指す方は、全員ここから入門して間違い無いでしょう。

URL: https://prog-8.com/

自分は手当たり次第に色々な講座をやってみていましたが、

  • HTML&CSS
  • Javascript(ES6)
  • jQuery
  • Ruby
  • Ruby on Rails

次いで

  • Command line
  • Git
  • SQL

辺りを押さえておけば十分だったかと思います。

各講座の序盤のレッスンは無料会員でも受けることができますが、本気でエンジニアを目指すのであれば、有料会員限定のコースも含めて取り組んでいきましょう。

これだけでもそれなりにボリュームはありますが、挫折しにくいよう学習ステップがかなり細かく設定されているので、初心者にとっても易しいつくりになっています。

ただ、Rails講座だけはさすがに難易度が高かったです。。。これは、Progateさんの講座の作り云々ではなく、Webフレームワークという概念が初学者にとって「始めまして」になるので、多少仕方がない部分ではあると思います。

1周目で全体像の把握、2週目以降で詳細理解に努める、というスタンスでよいかと思います。

3.2 Ruby on Railsチュートリアル

image.png

皆大好き(?) Rails チュートリアル。

URL: https://railstutorial.jp/

色々賛否ある教材ですが、無料かつ、ここまで体系的に「RailsのWebアプリ開発」を学べる教材は他にないと思います。

本教材の謳っているところでもありますが、「単にRuby, Railsの学習に終始せず、Webアプリ開発の全体像を俯瞰する」ものですので、本教材での知識は、他言語・他フレームワークで開発をする場合でも大いに活きると思います。

確かに、当教材に対する否定的な意見はいくつか見受けられ、

  • テストフレームワークとして、国内企業でデファクトスタンダードになっているRspecではなく、Railsに標準搭載されているminitestを使用している
  • 採用技術が古くなってきてしまっている

という点がよく指摘されています。

ただ、前者については、株式会社YassLab代表のコチラのYoutube動画の動画でも説明がある通り、「かつて(第2版まで)は Rspec を Rails チュートリアルでも採用していたが、Rspec 自体の学習コストが高いこともあり、それによる脱落者を多く出していた」という背景を受けてのものになります。

また後者については、例えば現在の Rails企業の多くは、Rails単体のアプリケーション(フロントはjQery/bootstrap)ではなく、APIとしてRailsを利用し、フロントはJSフレームワーク(Vue.jsやReact.js)を使うのが一般的になってきています。しかし、Rails初学者が、いきなりAPI開発から始めるのは、理解の階段を飛ばしすぎている、というのも事実でしょう(これについては、同者のコチラのYoutube動画も参考になるかもしれません)

つまり、Rails チュートリアルは「Railsを初めて触る人がなるべく挫折しにくい難易度設定を目指す」ということを念頭に置いた教材であり、最前線の現場で使用されているような本格的なRailsの習得の橋渡しをするようは役割である、と考えることができるかと思います。逆に、これ以上現場に近づけた本格的な内容にしてしまうと、それこそ多くの初学者が挫折してしまうと思います。

したがって、Rails初学者は、今この時代であっても、自信を持って当教材取り組んでよいと思います。少なくとも自分は、この後にも続くRails学習において、ベースとなるような知識をつけることができた、と感じています。

ただ、いくら難易度を落としているとはいってもRails初学者に取ってはかなり難しく、かつ量も膨大であるのは事実です。そのため、Railsチュートリアル完走を一つのマイルストーンとして設定し、内容につまづいたら「ひたすらググる」あるいは「適宜Progateに戻る」という進め方が効率的かと思います。

私も、一度はあまりの量と難易度に挫折してしまったのですが、社会人としてすでに働いており、ある程度お金に余裕があったので、動画版を購入して最後までやり切りました。個人的には、人が解説してくれている形式の方が理解がスムーズで、モチベーションの維持もしやすかったで、お金に余裕のある方にはオススメです。

Railsチュートリアルで身につく知識を整理すると、

  • バックエンド: Rails(シングルアプリケーション)
  • フロントエンド: jQuey+bootstrap (Railsの一部として内包)
  • テスト: minitest
  • 開発環境: AWS cloud9
  • デプロイ: Heroku

全くの初心者からWebアプリとして求められる一通りの機能を実装し、本番環境へデプロイするところまでできるのは、やはり教材として素晴らしいと思います。

しかし先ほども述べた通り、当教材はあくまでも橋渡しの位置付けです。未経験からの転職という自分自身の立場を鑑みると、Web系企業への転職用PF作成の準備としては、技術面でまだ心許ない、と考えました。

改めて複数企業の採用ページから実際に企業で使用されている技術を確認し、上記の学習知識と比較して整理をすると、

  • フロントは、Vue.jsReact.jsといったJavaScriptフレームワークを使用するのが一般的。それに伴い、Railsは単独アプリとしてではなく、APIモードで開発する
  • テストフレームワークは、minitestではなく、Rspecがデファクトスタンダード
  • 開発環境はPCローカルに構築する(Vagrantで仮想マシンを構築するか、Dockerでコンテナ化するか)
  • アプリケーションのデプロイは、小規模であれば Heroku を使うこともあるが、企業が提供するような中・大規模なアプリケーションであれば、AWSGCPなどをよく用いられる

次に習得すべきは、ここの技術領域であることが分かりました(個人的な話ですが、この辺りの時期からエンジニア採用ページに書かれている各技術がスタックが、どういった内容であるかが理解できるようになってきており、成長を実感していました)。

Railsチュートリアルを完走した方は、完走者向けのロードマップ紹介ページもありますので、まずはここを見てみるものよいかと思います。しかし私は、洗い出した上記項目をより体系的に学ぶことができるものがないかと考え、自分なりに色々と教材を探してみた結果、以下のサービスに辿りつきました。

3.3 Take off Rails

image.png

URL: https://freelance.cat-algorithm.com/lp/take-off-rails

『あなたを「初心者エンジニア」から「現場で活躍できるエンジニア」まで引き上げます。』というスローガンを掲げた教材とメンターがセットになったサービスで、Rails チュートリアルと実際の企業の間の穴埋めを狙った内容になっています。

基本的にはすでに作成された教材に則っとりながら自分のペースでアプリを開発していくのですが、都度 Slack でメンターさんに質問を投げることができるというのが大きな特徴です。。

最終的な教材のゴールとしてはQiitaのクローンサイトを開発することになります。これの技術スタックは下記の通りです。

  • バックエンド: Rails APIモード
  • フロントエンド: Vue.js (ソースコードは作成済みのものを使用。あくまで Rails との繋ぎ込みまでを扱う)
  • テスト: Rspec(+Factory_bot)
  • 開発環境: ローカル + DB(MySQL) は Docker コンテナを利用
  • アプリのデプロイ: Heroku
  • その他: CircleCI による rspec, rubocop の自動化

※ 私の当サイト用のリポジトリ: https://github.com/ddpmntcpbr/qiita_clone

Railsチュートリアル時点での技術スタックと比べると、かなり実務に近い技術が盛り込まれていることが分かるかと思います。

こちら、決して安い金額ではないサービスだと思います(スクール等に比べれば全然安いですが)。ただ、当時欲しいと思っていた知識が一気に身に着けられると考えて、購入を決意しました。

結果、良い買い物だったと思います。自分が学びたい内容がきれいに体系化されていたこともそうですが、何より、教材内容についてメンターさんへ質問ができるのも有意義だと感じました。遅くとも24時間以内にはレスポンスが返ってくるのもありがたく、「料金分を回収してやるぞ!」という気持ちで、たくさん質問させてもらいました笑

本教材では、上記技術の学習だけでなく、

  • Git commit の適切な粒度や、コミットメッセージの書き方
  • Github での PR の出し方や、コードレビューの流れ
  • Slack でのやり取り(意図が伝わりやすい質問の仕方など)

といった、「独学ではなかなか身につかない」けど「チーム開発では必須になる」ような周辺知識について学べた点も、非常に有用だったと思います。Railsチュートリアルの内容がおおよそ理解できていれば前提知識としては十分な難易度で、大きく挫折をすることがなかった点もプラスです。

さて、ここまでで、Railsに関しては、比較的モダンな開発手法に触れることができました。

しかし、当教材ではあくまでも Rails の開発に的を絞ったものであり、フロントエンドは既存のソースコードを流用する形での学習でした。この教材の内容を自身の転職用PFに組み込むためには、フロントエンド側についても自身で開発する知識が必要と考えました。

フロントエンドフレームワークの選定について、第2章でもお伝えしたとおり、「Reactの方がなんとなくよさそうかなー」と考えていたところ、次の教材を見つけたことをきっかけに、正式に React の学習を始めることにしました。

3.4 【とらゼミ】トラハックのエンジニア学習講座

image.png

現役の React エンジニアである トラハックさん ( @torahack_ )が、Youtube上で公開している講座で、Reactについて基礎の基礎から学ぶことができます。

動画チャンネルURL: https://www.youtube.com/user/1492tiger

こちらの教材の特長は、

  • Reactの基礎の基礎から体系的に学べる( Progate の JavaScript 講座完了が受講目安)
  • 動画によるハンズオン形式
  • 教材範囲にモダンなフロントエンドの開発手法を含む( Redux 等)
  • 動画のほぼ全てがYoutube上でなぜか無料公開されている

特に最後については完全にバグとしか思えない点で、Udemyなどで有料販売されていても動画講座と比べても遜色ないクオリティだと思います。

動画教材にありがちな「準備した原稿丸読み」のような堅い口調ではなく、フランクな若手予備校教師の授業(?)のような語り口のため、硬い喋りが苦手な人にもお勧めできます。私は復習のために、一度見た動画を耳だけで聞き返したりして、記憶の定着を図りました。

講座はいくつかのシリーズに分かれており、私が視聴をしたのは、

1.『日本一わかりやすいReact入門』シリーズ
2. 『日本一わかりやすいReact入門【実践編】』シリーズ
3. 『日本一わかりやすいReact-Redux入門』シリーズ

の3シリーズです。

最終的な成果物の技術スタックは、

  • create-react-app
  • Redux & redux-thunk
  • Material-UI
  • Firebase: Google が提供する mBaaS。バックエンド+インフラを手軽にセットアップできる

です。

※ 私のGithubリポジトリ: https://github.com/ddpmntcpbr/react-ec-app

こちらの動画については、学習備忘録記事をQiitaに投稿しております。よろしければこちらもご参考下さい。

参考ページ: 『日本一わかりやすいReact入門【実践編】#1~5 学習備忘録

本学習講座を全て受講するためには、有料コミュニティ『とらゼミ』への加入が必要になりますが、筆者は無料公開範囲の動画で必要な知識は十分に身に付いたと感じたため、加入はしておりません。(代わりの記事として書くことで、宣伝として少しでもお役に立てればと思っています笑)

さて、ここまででフロント側も自力で開発ができる基礎が身に付きました。これくらいの時期に並行してアプリのコンセプトが決定していましたので、いよいよポートフォリオ作成に取り掛かり始めました。

しかし、開発を始めるといくつも壁が出てきます。基本的にはググりながらの解決をしていきましたが、どうしても解決できないエラーにもぶち当たりました。特に、

  • ReactとRailsの繋ぎ合わせ
  • AWSでのアプリの公開方法
  • その他インフラ知識全般

あたりが、個人的な難所でした。その過程で頼らせてもらったのが、次のメンターサービスです。

3.5 TechTrain

image.png

有名企業のエンジニアから実務を学べるオンラインコミュニティ です。

URL: https://techbowl.co.jp/techtrain

特長を列挙すると、

  • 現役エンジニアであるメンターさんと、1 on 1でのオンライン面談が可能
  • メンターの方々の技術領域は多種多様
  • 全てのメンターと面談が可能で、技術トピックに応じて切り替えることが可能
  • 面談はこちらからのタイミングで入れることができる
  • なぜか全て無料で利用できる

はっきり言います。これだけのことができて完全無料なのは完全にバグです。これからエンジニア就職を目指しているU30の学生・社会人は、全員登録した方がいいレベルです

TechTrain の中ではいくつかの Mission が設けられており、それをメンターと一緒に取り組んでいくことで知識を習得していく、ということが可能です。 Mission は実際のIT企業とのコラボで作成されており、中には「Missionをクリアできた人は一次面接をスキップできる」のような特典もついていたりします。

ただ私は Misson には取り組まず、あくまでの個人開発のサポートとして利用させてもらっていました。基本的には自身の既存知識とググり力でPF作成を進めつつ、どうしても解決できない課題が出てきたときにピンポイントで面談予約を入れる、というイメージで、個々人の利用したい形式/ペースで利用できる点も大変ありがたかったです。

異なる技術領域を持ったエンジニアの方々全員と面談をするが可能なため、Rails, React, AWSそれぞれで、別のメンターの方に質問をさせてもらっていました。

特に自力での解決が難しかったのがAWS周りの本番環境構築で、本サービス無しでは乗り越えられなかったと思います。

AWSのことをAWS現役社員に無料で聞けるサービスと表現すれば、このサービスのやばさが伝わるかと思います。

最終的には、自身のググり力 + TechTrain で都度メンターを利用、を繰り返すことで、無事アプリを完成させることができました!

3.6 番外編

上記以外で役に立ったものについて、ざっくばらんに紹介します。

Udemy 『Git:はじめてのGitとGitHub』

無料で Git の基礎を学べる講座です。「Gitよう分からん!」って人は、まずこれから触れてみましょう

『キタミ式イラストIT塾 基本情報技術者』

基本情報処理の定番本です。コンピューターサイエンス領域の基礎知識が体系的に学べます。イラストが豊富であり、文章表現も柔らかいので初心者にも優しいです。資格自体の取る/取らないに関わらず一読をオススメします。

『米国AI開発者がゼロから教えるDocker講座』

Dockerについて一から学べる動画教材です。作者様はデータサイエンス領域の方ですが、Webアプリ開発を目的とした人であっても問題ありません(実際に、講座後半では、docker-composeを利用したRailsコンテナの構築まで扱っています)

非常にボリューミーな内容にも関わらず、Udemy講座の中ではかなり良心的な価格設定です。

『【AWS 入門】EC2とDockerでHello Worldしよう』

AWSについて何にも分からない状態から、「nginxだけのシンプルなコンテナアプリを動かす」ところまで、ハンズオン形式で学習ができます。

AWSのとっつきにくさは、「①インフラの概念が分からない」「②専門用語が分からない」に集約されると思います。まずは手を動かしながら、AWSでアプリをデプロイ流れを全体像で掴むことができます。

3.7 アプリの改善点

一通りアプリを完成させてみて、初めて見えてくる改善点が多くありましたので、合わせて列挙します。

AWSサーバー代高すぎ!!!

スケーラビリティの高い中・大規模向けインフラ構成になっているため、サーバー代がめっちゃ高い笑

長く公開するためには、どこかのタイミングで無料サーバーへ移管する必要があるかなと思います。Heroku の無料枠で上手にやりくりできれば、解決できるかもしれないです。

フロントエンド は Next.js + Typescriptで実装したい

Next.js はレンダリングのタイミングを制御できるので、OGP情報の保持が簡単に実現できます。

また、それ以外でも、

  • ルーティング設定が簡単
  • パフォーマンスをよくするような機能も豊富
  • Typescriptの導入が用意

というメリットもあり、とても気になっているフレームワークです。次、全く同じアプリを開発するとするのであれば、絶対に採用したい技術です。

デザインがあやしい気がする・・・?

アプリを開発して気づいたのは、アプリにおけるデザインの重要です。

これを思った理由は単純で、開発途中で「なんか自分のアプリ、イケてなくない?」と感じたからです笑

Webアプリにおけるデザインは、単なるお洒落さに関するものだけでは決してありません。デザインは、ユーザーにとって必要な情報を適切に配置することであり、ユーザーの価値提供のための最前線領域です

ユーザー側から価値提供の流れ(バリューチェーンと表現するのでしょうか)をざっっっくり並べると、

ユーザー -> UI/UXデザイン -> フロントエンド -> バックエンド -> インフラ

のようになっていると思っています。「エンジニアになろう!」と意気込んで後ろ3つについてはそこそこ勉強してきましたが、デザイン領域については開発初期は完全素人の状態で、途中までは勘でやっていました。。。

一応、付け焼き刃程度ではありますが、デザインの名著である『ノンデザイナーズ・デザインブック』に目を通し、途中からは意識できる範囲ではデザインのことを意識して、フロントを実装しました。

うまく取り込めているかは分かりませんが、少なくとも「デザインはセンスではなく論理」であることが学べただけでも、よい勉強になりました。こちらの書籍も、転職用PF作成者にオススメしておきます。

4. さいごに

以上、大変長い記事でしたが、最後まで読んでいただきありがとうございました。タイトルでは「学習ロードマップ」と銘打っておきながら、私自身の思考プロセスや価値観についても多く書かせてもらいました。

「エンジニアになりたい!」と思い立ってから、学習自体はほぼ一人で淡々と進めてきました。しかし、ほぼ独学でここまで学習を進めることができたのは、多くの先輩エンジニアの方々が様々な情報をインターネットに投稿し、それをオープンに取得できる環境にあったからだと思っております。

それであれば、次は自分自身も。他の駆け出しエンジニアの方々の助けになるような情報を発信できれば、と思い、この記事を書くこととしました。

もし、参考になったという方がいましたら、せひLGTM、ストック、twitterでのシェアお願いします!

また、私自身もtwitterをやっておりますので、気軽にフォローしてもらえるとうれしいです(^^)

よろしくお願いします!

Twitter: https://twitter.com/ddpmntcpbr
Github: https://github.com/ddpmntcpbr/rails_react_docker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験PF / Rails / React / AWS / Docker / CircleCI】独学+メンターでここまで出来た!Web知識ゼロからモダンな技術アプリ開発までに利用した5つのサービス

0. はじめに

こんにちは!辻野(@ddpmtcpbr)と申します。

当記事は、「Webエンジニアへのキャリアチェンジを目指している開発未経験者が、モダンな技術を備えたアプリを開発するまでの学習過程」についてまとめたものです。

現在筆者は非IT系企業の社員として働いており、Web開発エンジニアとしての実務経験はありません

そんな筆者がWebエンジニアとしてのキャリアチェンジをするためのポートフォリオとして、本アプリを開発しました。

学習開始から現時点までにおいて、プログラミングスクール等には通っておらず、学習はほぼ全て独学&一部メンターサービス利用の布陣で進めてきました。

独学中心でアプリ開発に挑戦したい、ポートフォリオを作成してWebエンジニアへのキャリアチェンジを進めていきたい、と考えている方々にとって、参考になればと思います。

最初に、今回私が開発したアプリの概要を紹介します。

アプリ名: 積読解消アプリ 「Yomukatsu!」

image.png

あなたの積読解消をサポートします」をスローガンに掲げたSPA風Webアプリです(”風”の詳細は後述)。

読書メンタルマップという手法を用いて、ユーザーの書籍完読に向けたモチベーション維持をサポートします。

Web URL: https://yomukatsu.com/

【3分動画】Yomukatsu 字幕解説

アプリの使い方を3分でまとめています。

使用技術

  • Backend: Rails ( API mode / Rspec / rubocop) + Nginx ( upstream puma-socket )
  • Frontend: React ( create-react-app / Redux / Material-UI / eslint&prettier)
  • Infra: AWS ( ECS Fargate/ ECR / RDS / ALB / Route53 ), Netlify, Docker&docker-compose, CircleCI

各項目の詳細は後述しています。

インフラ構成

yomukatsu_cloud_architecture.png

モダンな技術を採用したWeb系企業が提供している、中・大規模なアプリケーションを想定したインフラ構成にしています(そのため、個人開発アプリとしてみるとちょっと仰々しいかもです(;’∀’))

詳細は後述しています。

機能一覧

ユーザー利用機能

  • Twitterアカウントを利用したユーザー登録(OAuthによるSNS認証)
  • ゲストログイン機能
  • Google Books APIを用いた書籍検索機能
  • Google Books、Amazon、楽天ブックスへのリンクボタン配置
  • Twitterシェア機能
    • ハッシュタグ「#yomukatsu」付きTweet
    • Twitter card 表示
  • Twitter card用にリサイズした書籍画像をAWS S3へ保存・管理
  • Redux による state 管理を活用したローディング画面
  • Slack Incomming Webhookを利用したお問い合わせ機能
  • Route53 による独自ドメイン + SSL化

非ユーザー利用機能

  • Netlify の Pre-reidering 機能活用による動的なOGP情報の保持( Twitter card 表示用)
  • Docker による開発環境の完全コンテナ化
  • CircleCI による自動 CI/CD パイプライン構築
    • CI: Rspec, rubocop, eslint&prettier
    • CD: AWS ECR
  • その他セキュリティ対策(XXS, CSPF等)

まず触ってみてもらうのが一番良いかと思います!今回はフロントエンドに React を採用しているため、メニューモーダルの開閉や通知バー表示など、アニメーション的な表現も実装できているのが伝わるかと思います。

ゲストユーザー機能もありますので、気軽に利用してみてほしいです!レスポンシブにも対応しています。(推奨ブラウザはChrome、Safariになります)

アプリURL: https://yomukatsu.com/

※現在α版としてのリリースのため、配信内容が予告無く変更される可能性がございます

あわせて、 Githubも公開していますので、よかったら参考にしてください。

Github URL: https://github.com/ddpmntcpbr/rails_react_docker

この記事について

当記事は3章構成になっております。

まず、「1.自己紹介」で、簡単に自己紹介をさせていただきます。

次に、「2.開発アプリ解説」で、今回の開発アプリ開発について、コンセプト決定の流れから、実装機能/技術スタックについて詳しく紹介します。「転職用PF作りたいけど、どんなアプリを作ればいいか分からない」という方にとって、参考になることがあれば幸いです。

最後に、「3. 学習ロードマップ」で、Web知識ゼロだった筆者が、当アプリの開発にまでに利用した5つの教材およびサービスについて、時系列に沿って紹介したいと思います。特に独学では、「まず何を学べば良いのか」「どんな教材を選べば良いのか」というところから自身で考える必要があります。そういった方々にとって参考になり得る情報と思います。

1. 自己紹介

1.1 筆者スペック

  • 20代後半 男
  • 工学部機械系 修士卒 → 非IT系 日系製造業 技術開発職(現職)
  • 大学から現職において、データ分析ツールとしてプログラミングを経験(matlab / python)
  • Webエンジニアへのキャリアチェンジを目指し、社会人になってから独学を開始

大学入学以降、授業や研究データの分析ツールとしてプログラミングに触れる機会はありました。もともと自動化や効率化に興味があったことからプログラミングにだんだんとのめり込み、研究室ではプログラミングに多く触れられそうな研究テーマ(データ分析系)を選びました。

したがって、「プログラミング自体が完全に未経験」というわけではありませんでした。しかし、いわゆる”Web系”の知識は社会人以降の独学を開始するまではゼロ、という状態で、最初は HTML すら知らないところからのスタートでした( ̄▽ ̄;)。そのため当記事では「Web知識ゼロ」という表現をしています。

キャリアチェンジ志望の理由は、ざっくりと言えば、

1. もっとプログラミングがしたい
2. モノづくりで誰かの役に立ちたい
3. 非効率・非生産的な仕事を無くしたい

ここについては当記事の趣旨ではないので、詳細については省略します。

2. 開発アプリ解説

2.1 コンセプト方針

転職用PFアプリのコンセプトを決めるにあたり、満たすべき用件としては、下記3点を考えました。

(a) 実際の企業が採用しているようなモダンな技術を盛り込む
(b) 具体的な解決課題を明確にする
(c) サービスの利用が個人で完結する

(a) 実際の企業が採用しているようなモダンな技術を採用する

未経験からエンジニア転職において、高品質なポートフォリオは必須と考えました。

「モダンな技術をポートフォリオに組み込むことで、技術力の高さをアピールする」というのが基本的な考え方になるとは思いますが、個人的な解釈としては、ポートフォリオで証明すべきは「技術力」ではなく「自走力」だと考えています。

ぶっちゃけた話、実際の現場を経験したエンジニアと比べれば、未経験者間での能力差というものはどんぐりの背比べのようなものだと思います。多くの企業が「実務経験1年以上」をエンジニアの採用項目にあげていることからも、実務経験というのはそれだけ重い価値があり、未経験者とは大きな隔たりがあるのだと思います。

したがって、ポートフォリオの技術レベルの高さそのものはあまり重要ではなく、そこに到達するまでの過程の方が大事であり、「自走力=必要な情報は自らキャッチアップして吸収する能力」があることを示す方が、企業人事側としては採用しやすいのではないか?と考えました。

「スクールの制作アプリをそのまま提出する未経験者が足切りされてしまう」という話は多く聞きますが、これはそのアプリの技術力が低いからではなく、そのアプリから当人の「自走力」が主張できないから、だと私は考えています。私が独学ベースにこだわったのは、単純にお金の問題だけではなく、独学ベースでアプリを開発することができれば、自然とそれが「自走力の証明」につながると考えたからです。

今回のアプリにおいては、「独学:メンター=8:2」 くらいの割合で、上記技術を採用できるラインまで行くことができています。「メンター利用は独学からは外れるのでは?」という指摘もあるかもしれませんが、

  • プログラミングでは、個人ではどうにもならないようなエラーに遭遇してしまうことが多々ある
  • 実際に企業に入ってからは、先輩エンジニアの方々に分からないことを質問しながら業務を進めることになるため、「質問力」も重要な能力である
  • 完全独学オンリーだと、誤った癖が身についてしまっているリスクが高くなる

といった観点から、独学者が適宜メンターサービスを利用することは、かえって採用人事側にとって安心感を与える材料になるのでは、と考えたので、自信を持って「メンターサービスを使いました」と主張しています。

※ 具体的なモダンな技術リスト

  • Rails API + JSフレームワーク(React.js)の構成
  • Dockerで実行環境を完全コンテナ化
  • Herokuではなく、AWS でアプリをデプロイ(ECS Fargate & ECR)
  • Circle CIによるCI/CDパイプラインの構築

(b) 具体的な解決課題を明確にする

さて、前章とは一見真逆のことを言いますが、アプリ開発において、モダンな技術を採用することそのものには本来何の価値もない、と考えています。

なぜなら、アプリの目的はあくまでも「ユーザーにとって価値を提供できるか」であり、技術はそれを実現するための手段でしかないはずだからです。(保守・運用面でのメリットも考えられますが、保守・運用の最終目的もユーザーへの価値提供であることから、この点も包含した解釈になります)

これは、技術というものを下に見ているというわけでは決してありません。むしろ「新しい技術をどんどん使ってみたい!」という技術に対する好奇心、探究心は人一倍強い自負があります。現職はIT系ではありませんが、技術開発職という立場で業務に取り組んでおり、知的好奇心を満たせるという意味では、今の仕事に面白みを感じています。

しかしながら、かつては行き過ぎた技術先行思想によって「手段の目的化」が発生し、「最新技術を駆使した誰にとっても役に立たない技術」を開発してしまったという苦い過去の経験があったりもします。結果として「やっぱり技術は人の役に立ってなんぼ」というのが、約3年間技術職として働いてきて培った、技術者としての小さな矜持だったりします(この辺の話は直接お会いした方にはお話できるかと思います)

転職用PFであれば、技術ありきな考え方になることはある程度は仕方がないことでしょう。しかし「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」くらいのことは転職用PF作成においても考えていいんじゃないかな?と思いました。あるいは、もう少し目線を下げて「自分が欲しいものを作ろう」という程度でも十分でしょう。大事なのは、まず課題があり、それを解決する手段として技術があるという順番だと思いました。

もちろん今回の開発アプリは転職用PFが趣旨である以上、中には「技術力を証明したいから」という理由で選定した技術もあり、全てに対して課題が明確だったわけではありません。また、初心者の個人開発アプリがいきなりバズることは現実的には厳しいとは思います。しかし、そこを目指す姿勢があるかというのは、エンジニアとして本格的にキャリアを進めていく上では、長期的には大きな差異になると考えています。

また、

  • 課題が明確な方が採用した技術や実装した機能の根拠も明確にできるため、開発の方針を立てやすい
  • 自身がユーザー目線に立てることで、改善点を見つけやすい
  • 純粋にモチベーションを維持しやすい

といった点でもメリットもあると感じましたので、この方針は間違っていなかったと思います。

(c) サービスの利用が個人で完結する

「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」を、もう一歩深く考えた方針です。

例えば Rails を対象として考えたとき、一般的な転職用PFとしては、TwitterライクなSNS系アプリや、メルカリライクなEC系アプリが多いかと思います。

理由は、ユーザー認証、CRUD操作、DB間のリレーションなど、基本的なサーバーサイド技術を一通り抑えられるものであるから、だと思います。「Railsの一般的な知識を持っていることを証明する」手段と考えれば、妥当な方針でしょう。

しかしながら、未経験者が転職用に開発した上記アプリが実際にユーザーに継続して使われるということは、まず無いでしょう。SNS系アプリは「ユーザー数が多ければ多いほどサービスとして質が高まる」性質があるため、アプリとして軌道に載せること自体が非常に難しいです。EC系アプリは BtoC であれば出品企業がいないとサービスが成り立たない、CtoCであればより SNS として要素が強まる & いよいよメルカリで十分、という壁があります。

これらアプリの難しさは、ユーザーどうしがつながることを前提としている点にあります。裏を返せば「個人で完結するアプリであれば、活路はある」とも言えると考え、この方針でコンセプトを詰めていくことにしました。

2.2 コンセプト内容

上記3点を念頭に置きながら、自分自身の生活の中で"課題"を探し、最終的にたどり着いたものが「読書メンタルマップ術の電子化」というコンセプトでした。

そもそも皆様は、読書メンタルマップ術というものをご存知でしょうか?

読書メンタルマップ術とは、ハーバード大学の先生が提唱している積読解消術です。読破したい書籍に対して、

1. 完読したい本について、それを読む“理由”や“目的”を3つ、紙に書きだす
2. 飽きてきたら、それを見返す

を繰り返すことで、完読までモチベーションを維持するというシンプルな読書手法です。

積読というものは、だいたい「最初は読む気があったけど、次第に読む気がなくなってしまった」書籍です。この「最初の読む気」を事前に明文化・保存しておくことで、いつでも最初の頃に新鮮な気持ちを取り戻せるようにしておこう、というイメージになります。

自分自身、実際に活用している技術ではあるのですが、少し困ったことがあります。それは、電子書籍との相性が悪いことです。

通常であれば紙とペンを用いるものですが、例えば出先でスマホやタブレットで電子書籍を読んでいるような状況では、必ずしもこれらの道具があるとは限りません。特に私は、外に出る時はあまりものを多く持ちたく無い性分なので、外ではスマホと財布くらいしか持っていないことが多いです。

仮に持っていたとしても、例えば電車の中で紙とペンを出して色々と書き始めるのは、少し億劫だったりします。

これを全部ペーパーレスで完結できるようなアプリがあったら便利だな」と思ったのが、このコンセプトを思いついたきっかけになっています。

もちろん、これだけであればスマホのメモ帳だけでもできてしまうものですが、このアプリには、

  • Google Books APIを活用した書籍検索・保存機能
  • メンタルマップ作成のヒント機能
  • Google Books, Amazon, 楽天ブックスへのリンクボタン配置(特に書籍レビューはメンタルマップ作成の大きなヒントになる)
  • Twitterでの読書仲間への気軽なシェア機能

といった機能が備わっており、より読書メンタルマップ術を使用しやすい環境を整えています。

先ほどの「2. 具体的な解決課題を明確にする」に照らし合わせて考えると、このアプリの解決課題は、ユーザーの積読を解消すること、もっと言えば、読書メンタルマップ術をペーパーレスで実行できるすることで電子書籍で読書するユーザーにとっても扱いやすくすること、になります。

また、「3. サービスの利用が個人で完結する」も満たしています。当アプリには、ユーザー同士がつながる機能は一切実装されていません

代わりとして、Twitterとの連携にはかなり重きを置いて実装機能を決めました。具体的には、

  • Twitterアカウントを利用したユーザー登録
  • ワンタップでハッシュタグ付きツイート
  • 充実した Twitter カード表示

を機能として実装しています。原則的には個人でサービスが完結しつつも、ユーザーどうしの繋がりはTwitter内の既存のネットワークに乗っかる、ことを狙ったコンセプトになっています。

2.3 技術スタック

再度、インフラ構成を載せます。

yomukatsu_cloud_architecture.png

この内容について、ひとつひとつ解説します。

Back-end

Rails API + Nginx の組み合わせにしています。

サーバーサイドフレームワークとしては他にも Laravel, Django, Node.js などもあります。恐らく大体のことは、どれを選んでも実装・実現できる、と思うのですが、その中で今回 Rails を選んだ理由は、

  1. 採用している企業数が多い
  2. 日本語の教材が豊富なため学習のハードルが低い
  3. 国内コミュニティが発達しているため、インターネット上での日本語ドキュメントが豊富

最初の学習言語として Rails を選択する初学者の方は多くいるかと思います。その一方で、「Railsはオワコン」という説が各所で言われていることについて、不安に感じる初学者の方もいると思います。

この点に関して、あくまで個人的な見解を述べますと、初学者であった自分が、少なくとも最初に学ぶ言語/FW として Rails は間違いではなかった、と考えています。

正直、初学者である私には、「なぜ Rails がオワコンであるのか」について技術ベースで語ることはできません。しかし、

  • 現時点で Rails を採用している企業の絶対数は多く存在すること
  • 日本国内において、Rails に替わるサーバーサイドのデファクトスタンダードな技術が、まだ定まっていないこと

は事実と言ってよいかと思います。

もしかしたら、長期的に見れば日本国内でも Rails を採用する企業が減っていく流れにはあるのかもしれません。しかし、微分値と絶対値はセットで捉えないと判断を誤ることになります。初心者が目指すべきは「今すぐに仕事を得られる技術を身に着けること」であり「将来必要になってくる技術」ではない、自分が今学ぶべきはやはり Rails である、と判断をしました。

また、オワコンというのは裏を返せば、技術的に枯れていて、初心者にとっては学びやすい言語/FWである、とも言えます。

特に日本国内での(過去含めた)使用者が多いため、日本語のドキュメントが多く存在します。事実、Rails開発で遭遇するエラーは、Google検索すれば何かしらの日本語記事がヒットします。

最先端の技術は過去の技術の欠点を補う要素を持って生まれてきているのは事実ですが、検索しても欲しい情報が見つからなかったり、あったとしても英語ドキュメントだけだったりします。私自身は、このアプリの開発を通じてWebフレームワークに基本的な概念が身についてきているため英語でも大丈夫になってきましたが、ベースラインすら乏しい状態の初学者がいきなり英語のドキュメントを読み解いていくのは、二重にしんどいです。

以上が、私が最初の言語/FWとして Rails を選んだ根拠になります。

主要gem
  • devise_token_auth: APIモードでの devise。トークン認証を簡単に実装
  • twitter_omniauth:Twitter認証を簡単に実装
  • active_model_serializer: Rails APIからのレスポンスJSONを制御
  • imageMagic: 画像のリサイズを実行。特に、Twitter card用に書籍画像をリサイズする際に使用
  • aws-fog/carrierwave: リサイズした書籍画像を AWS S3 に保存
  • rspec: デファクトスタンダードになっているRubyテスト用フレームワーク
  • rubocop: Rubyの静的コード解析

TwitterアカウントでのOAuth認証は、過去の実装例が少なく、非常に苦労したところでもありました。しかし、「Twitterとの連携を重視」という今回のコンセプト上では絶対に欲しい機能と考え、頑張って実装しました。

AWS S3については元々採用予定はなかった(Google Books APIの画像リンクをそのまま引っ張ってくる予定だった)のですが、Twitter card で書籍画像を表示させる時にどうしても画像サイズを適切に制御する必要が出てきたので、imageMagic と合わせて Rails で画像リサイズ & S3保存、を実装することにしました。

Front-end

今回フロントエンドとしては、JSフレームワークであるReact.jsを採用しました(細かいこと言えば React はフレームワークではなくライブラリですが、ここではフレームワークとして扱います)。モダンな技術採用を謳う以上、Rails + jQuery/bootstrap の構成では心許ないと考えました。

JSフレームワークとしては、国内企業での採用状況から考えるに、React.jsVue.jsか、の二択になると思います。その中でもReact.jsを選んだ理由は、

  1. 自分が調べた範囲では、バックエンドに Rails を採用している企業群のうち、フロントに React を採用している企業の割合が高かった
  2. Vue.js よりも規約が厳格であり、初学者の自分であっても自然と可読性の高いコードを書くことができそう
  3. たまたま、React を効率的に学べる良い教材を見つけた

特に最後については、第3章で後述しています。

アプリ開発を通じて React.js が割と気に入ってきたので良い選択だったとは思いますが、この点に関してはどちらを選んでも間違いではなかったかな、とは思います。

主要ライブラリ等
  • create-react-app: Facebookが提供するオープンソースのReact開発パッケージ
  • Redux: Stateの一元管理するフレームワーク。Redux関連ファイルは、reducksパターン則って管理
  • Redux-thunk: Redux state の非同期処理を制御
  • react-helmet: 動的なmetaタグの挿入によるOGP情報の保持(Twitter card用)
  • react-share: Twitter含めたSNSシェア用ボタンを簡単に配置
  • Material-UI : Google が提供する UI コンポーネントライブラリ。簡単におしゃれな UI コンポーネントをアプリ内に配置できる
  • eslint & prettier: javascriptに対する静的コード解析。eslint は create-react-appに標準搭載されているものをベースに少しプラグインを追加 & prettier はイチから導入

今回はユーザーの利用シーンを考えると、Web上でもネイティブアプリのようにサクサク動く、JSリッチなアプリケーションにしたいと考えました。この点からも、jQuery+bootstrap ではなく React.js を採用してよかったと思います。

React の実装には、特にMaterial-UIが強力で、開閉モーダルや通知バー表示などのアニメーション演出や、ページ全体のレスポンシブ対応などがかなり簡単に実装できました。このライブラリを使えたというだけでも、React を採用した価値があったと思えるほどでした。

Infra

Docker/docker-compose

開発環境は、全てDockerコンテナ内で完結させています。docker-compose.ymlのサービス構成としては、

  • db: MySQL
  • api: Rails
  • web: Nginx
  • front: Node.js (React)

としています。

後述しますが、AWS ECS(Fargate)へのコンテナデプロイを利用することで、開発環境と本番環境の差異を小さくすることができています。

AWS(Amazon Web Service)

バックエンド( Rails + Nginx )のデプロイに使用。Railsチュートリアルなどではアプリの本番環境へのデプロイにはHerokuを用いることが一般的ですが「モダンな技術を採用したい」という観点から、AWSに挑戦しました。

稼働させるまでめちゃくちゃ苦労しました。ここを完全独学で完結させるのは相当しんどいと思います。第3章で触れていますが、AWSの学習については、メンターさんをかなり頼らせてもらいました。

※ 利用サービス

  • ECS (Fargate): コンテナ向けサーバーレスコンピューティングエンジン。この中に Rails と Nginx の Docker イメージを入れて稼働させる
  • ECR: Rails と Nginx の Docker イメージを保存しておくリポジトリ
  • RDS (MySQL): AWS が用意しているスケーラブルなデータベースエンジン
  • ALB: 負荷分散を担うロードバランシングサービス
  • Route53: サイトの独自ドメイン化に使用
  • ACM: サイトの https 化に使用
  • S3: 静的ホスティングサービス。書籍画像も保存・管理に使用
Netlify

フロントエンド(create-react-app)のホスティングで利用。

最初はバックエンドに合わせてフロントも AWS ( Amplify Console ) でホスティングしていたのですが、create-react-appはSPAとしてのアプリ開発となることから、metaタグ無いにOGP情報を保持できない = Twitterでページをシェアした時のカード表示を動的に制御できない、という問題が出てきました。

おそらくは AWS でも解決する手段はあると思うのですが、今回は Netlify の Pre-rendering機能を使うことで解決することにしました。この機能を使うことで、 create-react-app であっても、サーバー側で javascript をレンダリングしてからブラウザが解釈できるようになります ( あと単純に、無料で利用できるのもメリット )

この問題にあたってから、最近ホットな React フレームワークの Next.js の有り難みが自然と分かるようになってきた( Next.js は SPA/SSG/SSRを選択可能 )のですが、今回はすでに開発を始めていたこともあり、create-react-app + Netlify の構成で最後まで開発しました。

CircleCI

国内ではデファクトスタンダードとされている、Saas型のCI/CDサービスです。今回CircleCIで自動化した処理は、

  • Rspec
  • rubocop
  • eslint&prettier
  • AWS ECR への Image push
  • AWS ECS のタスク&サービスの更新

Netlifyにはもともと自動デプロイ機能がついていることから、CircleCIを導入することで、Github上の master ブランチに merge しただけで、本番環境への再デプロイが完了する、という状態に持っていくことができました。

一度ありがたみが分かると、もう手放せないですね( ´ ▽ ` )

3. 学習ロードマップ

さて、いよいよ本題です。ここまでで開発したアプリについて解説をしてきましたが、ここからは、このアプリ開発に至るまでの学習過程をたどっていきます。

第1章でもお伝えした通り、筆者はプログラミング経験自体はあっても、いわゆる「Web系」の知識はゼロからのスタートでした。繰り返しますが、HTMLすら知らなかった状態から、独学ベースで上記技術スタックをアプリに盛り込めるレベルまで到達することができました

独学ベースでの学習になると、「どの学習教材を選ぶべきか」というところから自分で考える必要があります。いろいろと紆余曲折ありましたが「これは役に立った!」と思うものを厳選し、時系列に沿ってお伝えします。

※下記サービスのWebリンクや、ロゴ画像、ホームページのスクリーンショットについては、事前に各運営者様に使用許可をいただいております。改めまして運営者皆様、利用快諾していただきありがとうございました。

3.1 Progate

image.png

皆大好きProgate。今からエンジニアを目指す方は、全員ここから入門して間違い無いでしょう。

URL: https://prog-8.com/

自分は手当たり次第に色々な講座をやってみていましたが、

  • HTML&CSS
  • Javascript(ES6)
  • jQuery
  • Ruby
  • Ruby on Rails

次いで

  • Command line
  • Git
  • SQL

辺りを押さえておけば十分だったかと思います。

各講座の序盤のレッスンは無料会員でも受けることができますが、本気でエンジニアを目指すのであれば、有料会員限定のコースも含めて取り組んでいきましょう。

これだけでもそれなりにボリュームはありますが、挫折しにくいよう学習ステップがかなり細かく設定されているので、初心者にとっても易しいつくりになっています。

ただ、Rails講座だけはさすがに難易度が高かったです。。。これは、Progateさんの講座の作り云々ではなく、Webフレームワークという概念が初学者にとって「始めまして」になるので、多少仕方がない部分ではあると思います。

1周目で全体像の把握、2週目以降で詳細理解に努める、というスタンスでよいかと思います。

3.2 Ruby on Railsチュートリアル

image.png

皆大好き(?) Rails チュートリアル。

URL: https://railstutorial.jp/

色々賛否ある教材ですが、無料かつ、ここまで体系的に「RailsのWebアプリ開発」を学べる教材は他にないと思います。

本教材の謳っているところでもありますが、「単にRuby, Railsの学習に終始せず、Webアプリ開発の全体像を俯瞰する」ものですので、本教材での知識は、他言語・他フレームワークで開発をする場合でも大いに活きると思います。

確かに、当教材に対する否定的な意見はいくつか見受けられ、

  • テストフレームワークとして、国内企業でデファクトスタンダードになっているRspecではなく、Railsに標準搭載されているminitestを使用している
  • 採用技術が古くなってきてしまっている

という点がよく指摘されています。

ただ、前者については、株式会社YassLab代表のコチラのYoutube動画の動画でも説明がある通り、「かつて(第2版まで)は Rspec を Rails チュートリアルでも採用していたが、Rspec 自体の学習コストが高いこともあり、それによる脱落者を多く出していた」という背景を受けてのものになります。

また後者については、例えば現在の Rails企業の多くは、Rails単体のアプリケーション(フロントはjQery/bootstrap)ではなく、APIとしてRailsを利用し、フロントはJSフレームワーク(Vue.jsやReact.js)を使うのが一般的になってきています。しかし、Rails初学者が、いきなりAPI開発から始めるのは、理解の階段を飛ばしすぎている、というのも事実でしょう(これについては、同者のコチラのYoutube動画も参考になるかもしれません)

つまり、Rails チュートリアルは「Railsを初めて触る人がなるべく挫折しにくい難易度設定を目指す」ということを念頭に置いた教材であり、最前線の現場で使用されているような本格的なRailsの習得の橋渡しをするようは役割である、と考えることができるかと思います。逆に、これ以上現場に近づけた本格的な内容にしてしまうと、それこそ多くの初学者が挫折してしまうと思います。

したがって、Rails初学者は、今この時代であっても、自信を持って当教材取り組んでよいと思います。少なくとも自分は、この後にも続くRails学習において、ベースとなるような知識をつけることができた、と感じています。

ただ、いくら難易度を落としているとはいってもRails初学者に取ってはかなり難しく、かつ量も膨大であるのは事実です。そのため、Railsチュートリアル完走を一つのマイルストーンとして設定し、内容につまづいたら「ひたすらググる」あるいは「適宜Progateに戻る」という進め方が効率的かと思います。

私も、一度はあまりの量と難易度に挫折してしまったのですが、社会人としてすでに働いており、ある程度お金に余裕があったので、動画版を購入して最後までやり切りました。個人的には、人が解説してくれている形式の方が理解がスムーズで、モチベーションの維持もしやすかったで、お金に余裕のある方にはオススメです。

Railsチュートリアルで身につく知識を整理すると、

  • バックエンド: Rails(シングルアプリケーション)
  • フロントエンド: jQuey+bootstrap (Railsの一部として内包)
  • テスト: minitest
  • 開発環境: AWS cloud9
  • デプロイ: Heroku

全くの初心者からWebアプリとして求められる一通りの機能を実装し、本番環境へデプロイするところまでできるのは、やはり教材として素晴らしいと思います。

しかし先ほども述べた通り、当教材はあくまでも橋渡しの位置付けです。未経験からの転職という自分自身の立場を鑑みると、Web系企業への転職用PF作成の準備としては、技術面でまだ心許ない、と考えました。

改めて複数企業の採用ページから実際に企業で使用されている技術を確認し、上記の学習知識と比較して整理をすると、

  • フロントは、Vue.jsReact.jsといったJavaScriptフレームワークを使用するのが一般的。それに伴い、Railsは単独アプリとしてではなく、APIモードで開発する
  • テストフレームワークは、minitestではなく、Rspecがデファクトスタンダード
  • 開発環境はPCローカルに構築する(Vagrantで仮想マシンを構築するか、Dockerでコンテナ化するか)
  • アプリケーションのデプロイは、小規模であれば Heroku を使うこともあるが、企業が提供するような中・大規模なアプリケーションであれば、AWSGCPなどをよく用いられる

次に習得すべきは、ここの技術領域であることが分かりました(個人的な話ですが、この辺りの時期からエンジニア採用ページに書かれている各技術がスタックが、どういった内容であるかが理解できるようになってきており、成長を実感していました)。

Railsチュートリアルを完走した方は、完走者向けのロードマップ紹介ページもありますので、まずはここを見てみるものよいかと思います。しかし私は、洗い出した上記項目をより体系的に学ぶことができるものがないかと考え、自分なりに色々と教材を探してみた結果、以下のサービスに辿りつきました。

3.3 Take off Rails

image.png

URL: https://freelance.cat-algorithm.com/lp/take-off-rails

『あなたを「初心者エンジニア」から「現場で活躍できるエンジニア」まで引き上げます。』というスローガンを掲げた教材とメンターがセットになったサービスで、Rails チュートリアルと実際の企業の間の穴埋めを狙った内容になっています。

基本的にはすでに作成された教材に則っとりながら自分のペースでアプリを開発していくのですが、都度 Slack でメンターさんに質問を投げることができるというのが大きな特徴です。。

最終的な教材のゴールとしてはQiitaのクローンサイトを開発することになります。これの技術スタックは下記の通りです。

  • バックエンド: Rails APIモード
  • フロントエンド: Vue.js (ソースコードは作成済みのものを使用。あくまで Rails との繋ぎ込みまでを扱う)
  • テスト: Rspec(+Factory_bot)
  • 開発環境: ローカル + DB(MySQL) は Docker コンテナを利用
  • アプリのデプロイ: Heroku
  • その他: CircleCI による rspec, rubocop の自動化

※ 私の当サイト用のリポジトリ: https://github.com/ddpmntcpbr/qiita_clone

Railsチュートリアル時点での技術スタックと比べると、かなり実務に近い技術が盛り込まれていることが分かるかと思います。

こちら、決して安い金額ではないサービスだと思います(スクール等に比べれば全然安いですが)。ただ、当時欲しいと思っていた知識が一気に身に着けられると考えて、購入を決意しました。

結果、良い買い物だったと思います。自分が学びたい内容がきれいに体系化されていたこともそうですが、何より、教材内容についてメンターさんへ質問ができるのも有意義だと感じました。遅くとも24時間以内にはレスポンスが返ってくるのもありがたく、「料金分を回収してやるぞ!」という気持ちで、たくさん質問させてもらいました笑

本教材では、上記技術の学習だけでなく、

  • Git commit の適切な粒度や、コミットメッセージの書き方
  • Github での PR の出し方や、コードレビューの流れ
  • Slack でのやり取り(意図が伝わりやすい質問の仕方など)

といった、「独学ではなかなか身につかない」けど「チーム開発では必須になる」ような周辺知識について学べた点も、非常に有用だったと思います。Railsチュートリアルの内容がおおよそ理解できていれば前提知識としては十分な難易度で、大きく挫折をすることがなかった点もプラスです。

さて、ここまでで、Railsに関しては、比較的モダンな開発手法に触れることができました。

しかし、当教材ではあくまでも Rails の開発に的を絞ったものであり、フロントエンドは既存のソースコードを流用する形での学習でした。この教材の内容を自身の転職用PFに組み込むためには、フロントエンド側についても自身で開発する知識が必要と考えました。

フロントエンドフレームワークの選定について、第2章でもお伝えしたとおり、「Reactの方がなんとなくよさそうかなー」と考えていたところ、次の教材を見つけたことをきっかけに、正式に React の学習を始めることにしました。

3.4 【とらゼミ】トラハックのエンジニア学習講座

image.png

現役の React エンジニアである トラハックさん ( @torahack_ )が、Youtube上で公開している講座で、Reactについて基礎の基礎から学ぶことができます。

動画チャンネルURL: https://www.youtube.com/user/1492tiger

こちらの教材の特長は、

  • Reactの基礎の基礎から体系的に学べる( Progate の JavaScript 講座完了が受講目安)
  • 動画によるハンズオン形式
  • 教材範囲にモダンなフロントエンドの開発手法を含む( Redux 等)
  • 動画のほぼ全てがYoutube上でなぜか無料公開されている

特に最後については完全にバグとしか思えない点で、Udemyなどで有料販売されていても動画講座と比べても遜色ないクオリティだと思います。

動画教材にありがちな「準備した原稿丸読み」のような堅い口調ではなく、フランクな若手予備校教師の授業(?)のような語り口のため、硬い喋りが苦手な人にもお勧めできます。私は復習のために、一度見た動画を耳だけで聞き返したりして、記憶の定着を図りました。

講座はいくつかのシリーズに分かれており、私が視聴をしたのは、

1.『日本一わかりやすいReact入門』シリーズ
2. 『日本一わかりやすいReact入門【実践編】』シリーズ
3. 『日本一わかりやすいReact-Redux入門』シリーズ

の3シリーズです。

最終的な成果物の技術スタックは、

  • create-react-app
  • Redux & redux-thunk
  • Material-UI
  • Firebase: Google が提供する mBaaS。バックエンド+インフラを手軽にセットアップできる

です。

※ 私のGithubリポジトリ: https://github.com/ddpmntcpbr/react-ec-app

こちらの動画については、学習備忘録記事をQiitaに投稿しております。よろしければこちらもご参考下さい。

参考ページ: 『日本一わかりやすいReact入門【実践編】#1~5 学習備忘録

本学習講座を全て受講するためには、有料コミュニティ『とらゼミ』への加入が必要になりますが、筆者は無料公開範囲の動画で必要な知識は十分に身に付いたと感じたため、加入はしておりません。(代わりの記事として書くことで、宣伝として少しでもお役に立てればと思っています笑)

さて、ここまででフロント側も自力で開発ができる基礎が身に付きました。これくらいの時期に並行してアプリのコンセプトが決定していましたので、いよいよポートフォリオ作成に取り掛かり始めました。

しかし、開発を始めるといくつも壁が出てきます。基本的にはググりながらの解決をしていきましたが、どうしても解決できないエラーにもぶち当たりました。特に、

  • ReactとRailsの繋ぎ合わせ
  • AWSでのアプリの公開方法
  • その他インフラ知識全般

あたりが、個人的な難所でした。その過程で頼らせてもらったのが、次のメンターサービスです。

3.5 TechTrain

image.png

有名企業のエンジニアから実務を学べるオンラインコミュニティ です。

URL: https://techbowl.co.jp/techtrain

特長を列挙すると、

  • 現役エンジニアであるメンターさんと、1 on 1でのオンライン面談が可能
  • メンターの方々の技術領域は多種多様
  • 全てのメンターと面談が可能で、技術トピックに応じて切り替えることが可能
  • 面談はこちらからのタイミングで入れることができる
  • なぜか全て無料で利用できる

はっきり言います。これだけのことができて完全無料なのは完全にバグです。これからエンジニア就職を目指しているU30の学生・社会人は、全員登録した方がいいレベルです

TechTrain の中ではいくつかの Mission が設けられており、それをメンターと一緒に取り組んでいくことで知識を習得していく、ということが可能です。 Mission は実際のIT企業とのコラボで作成されており、中には「Missionをクリアできた人は一次面接をスキップできる」のような特典もついていたりします。

ただ私は Misson には取り組まず、あくまでの個人開発のサポートとして利用させてもらっていました。基本的には自身の既存知識とググり力でPF作成を進めつつ、どうしても解決できない課題が出てきたときにピンポイントで面談予約を入れる、というイメージで、個々人の利用したい形式/ペースで利用できる点も大変ありがたかったです。

異なる技術領域を持ったエンジニアの方々全員と面談をするが可能なため、Rails, React, AWSそれぞれで、別のメンターの方に質問をさせてもらっていました。

特に自力での解決が難しかったのがAWS周りの本番環境構築で、本サービス無しでは乗り越えられなかったと思います。

AWSのことをAWS現役社員に無料で聞けるサービスと表現すれば、このサービスのやばさが伝わるかと思います。

最終的には、自身のググり力 + TechTrain で都度メンターを利用、を繰り返すことで、無事アプリを完成させることができました!

3.6 番外編

上記以外で役に立ったものについて、ざっくばらんに紹介します。

Udemy 『Git:はじめてのGitとGitHub』

無料で Git の基礎を学べる講座です。「Gitよう分からん!」って人は、まずこれから触れてみましょう

『キタミ式イラストIT塾 基本情報技術者』

基本情報処理の定番本です。コンピューターサイエンス領域の基礎知識が体系的に学べます。イラストが豊富であり、文章表現も柔らかいので初心者にも優しいです。資格自体の取る/取らないに関わらず一読をオススメします。

『米国AI開発者がゼロから教えるDocker講座』

Dockerについて一から学べる動画教材です。作者様はデータサイエンス領域の方ですが、Webアプリ開発を目的とした人であっても問題ありません(実際に、講座後半では、docker-composeを利用したRailsコンテナの構築まで扱っています)

非常にボリューミーな内容にも関わらず、Udemy講座の中ではかなり良心的な価格設定です。

『【AWS 入門】EC2とDockerでHello Worldしよう』

AWSについて何にも分からない状態から、「nginxだけのシンプルなコンテナアプリを動かす」ところまで、ハンズオン形式で学習ができます。

AWSのとっつきにくさは、「①インフラの概念が分からない」「②専門用語が分からない」に集約されると思います。まずは手を動かしながら、AWSでアプリをデプロイ流れを全体像で掴むことができます。

3.7 アプリの改善点

一通りアプリを完成させてみて、初めて見えてくる改善点が多くありましたので、合わせて列挙します。

AWSサーバー代高すぎ!!!

スケーラビリティの高い中・大規模向けインフラ構成になっているため、サーバー代がめっちゃ高い笑

長く公開するためには、どこかのタイミングで無料サーバーへ移管する必要があるかなと思います。Heroku の無料枠で上手にやりくりできれば、解決できるかもしれないです。

フロントエンド は Next.js + Typescriptで実装したい

Next.js はレンダリングのタイミングを制御できるので、OGP情報の保持が簡単に実現できます。

また、それ以外でも、

  • ルーティング設定が簡単
  • パフォーマンスをよくするような機能も豊富
  • Typescriptの導入が用意

というメリットもあり、とても気になっているフレームワークです。次、全く同じアプリを開発するとするのであれば、絶対に採用したい技術です。

デザインがあやしい気がする・・・?

アプリを開発して気づいたのは、アプリにおけるデザインの重要性です。

これを思った理由は単純で、開発途中で「なんか自分のアプリ、イケてなくない?」と感じたからです笑

Webアプリにおけるデザインは、単なるお洒落さに関するものだけでは決してありません。デザインは、ユーザーにとって必要な情報を適切に配置することであり、ユーザーの価値提供のための最前線領域です

ユーザー側から価値提供の流れ(バリューチェーンと表現するのでしょうか)をざっっっくり並べると、

ユーザー -> UI/UXデザイン -> フロントエンド -> バックエンド -> インフラ

のようになっていると思っています。

「エンジニアになろう!」と意気込んでから、後ろ3つについてはそこそこ勉強してきました。しかしデザイン領域については、開発初期は完全素人の状態で、途中までは勘でやっていました。。。

一応、付け焼き刃程度ではありますが、デザインの名著である『ノンデザイナーズ・デザインブック』に目を通し、途中からは意識できる範囲ではデザインのことを意識して、フロントを実装しました。

うまく取り込めているかは分かりませんが、少なくとも「デザインはセンスではなく論理」であることが学べただけでも、よい勉強になりました。こちらの書籍も、転職用PF作成者にオススメしておきます。

4. さいごに

以上、大変長い記事でしたが、最後まで読んでいただきありがとうございました。タイトルでは「学習ロードマップ」と銘打っておきながら、私自身の思考プロセスや価値観についても多く書かせてもらいました。

「エンジニアになりたい!」と思い立ってから、学習自体はほぼ一人で淡々と進めてきました。しかし、ほぼ独学でここまで学習を進めることができたのは、多くの先輩エンジニアの方々が様々な情報をインターネットに投稿し、それをオープンに取得できる環境にあったからだと思っております。

それであれば、次は自分自信が、他の駆け出しエンジニアの方々の助けになるような情報を発信できれば、と思い、この記事を書くこととしました。参考になったという方がいらっしゃったら幸いです。

是非LGTM、ストック、twitterでのシェアお願いします!また、私自身もtwitterをやっておりますので、気軽にフォローしてもらえるとうれしいです(^^)

よろしくお願いします!

Twitter: https://twitter.com/ddpmntcpbr
Github: https://github.com/ddpmntcpbr/rails_react_docker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験開発 Rails/React/AWS/Docker/CircleCI】独学+メンターでここまで出来た!Web知識ゼロからモダンな技術アプリ開発までに利用した5つのサービス

0. はじめに

こんにちは!辻野(@ddpmtcpbr)と申します。

当記事は、「Webエンジニアへのキャリアチェンジを目指している開発未経験者が、モダンな技術を備えたアプリを開発するまでの学習過程」についてまとめたものです。

現在筆者は非IT系企業の社員として働いており、Web開発エンジニアとしての実務経験はありません

そんな筆者がWebエンジニアとしてのキャリアチェンジをするためのポートフォリオとして、本アプリを開発しました。

学習開始から現時点までにおいて、プログラミングスクール等には通っておらず、学習はほぼ全て独学&一部メンターサービス利用の布陣で進めてきました。

独学中心でアプリ開発に挑戦したい、ポートフォリオを作成してWebエンジニアへのキャリアチェンジを進めていきたい、と考えている方々にとって、参考になればと思います。

最初に、今回私が開発したアプリの概要を紹介します。

アプリ名: 積読解消アプリ 「Yomukatsu!」

image.png

あなたの積読解消をサポートします」をスローガンに掲げたSPA風Webアプリです(”風”の詳細は後述)。

読書メンタルマップという手法を用いて、ユーザーの書籍完読に向けたモチベーション維持をサポートします。

Web URL: https://yomukatsu.com/

【3分動画】Yomukatsu 字幕解説

アプリの使い方を3分でまとめています。

使用技術

  • Backend: Rails ( API mode / Rspec / rubocop) + Nginx ( upstream puma-socket )
  • Frontend: React ( create-react-app / Redux / Material-UI / eslint&prettier)
  • Infra: AWS ( ECS Fargate/ ECR / RDS / ALB / Route53 ), Netlify, Docker&docker-compose, CircleCI

各項目の詳細は後述しています。

インフラ構成

yomukatsu_cloud_architecture.png

モダンな技術を採用したWeb系企業が提供している、中・大規模なアプリケーションを想定したインフラ構成にしています(そのため、個人開発アプリとしてみるとちょっと仰々しいかもです(;’∀’))

詳細は後述しています。

機能一覧

ユーザー利用機能

  • Twitterアカウントを利用したユーザー登録(OAuthによるSNS認証)
  • ゲストログイン機能
  • Google Books APIを用いた書籍検索機能
  • Google Books、Amazon、楽天ブックスへのリンクボタン配置
  • Twitterシェア機能
    • ハッシュタグ「#yomukatsu」付きTweet
    • Twitter card 表示
  • Twitter card用にリサイズした書籍画像をAWS S3へ保存・管理
  • Redux による state 管理を活用したローディング画面
  • Slack Incomming Webhookを利用したお問い合わせ機能
  • Route53 による独自ドメイン + SSL化

非ユーザー利用機能

  • Netlify の Pre-reidering 機能活用による動的なOGP情報の保持( Twitter card 表示用)
  • Docker による開発環境の完全コンテナ化
  • CircleCI による自動 CI/CD パイプライン構築
    • CI: Rspec, rubocop, eslint&prettier
    • CD: AWS ECR
  • その他セキュリティ対策(XXS, CSPF等)

まず触ってみてもらうのが一番良いかと思います!今回はフロントエンドに React を採用しているため、メニューモーダルの開閉や通知バー表示など、アニメーション的な表現も実装できているのが伝わるかと思います。

ゲストユーザー機能もありますので、気軽に利用してみてほしいです!レスポンシブにも対応しています。(推奨ブラウザはChrome、Safariになります)

アプリURL: https://yomukatsu.com/

※現在α版としてのリリースのため、配信内容が予告無く変更される可能性がございます

あわせて、 Githubも公開していますので、よかったら参考にしてください。

Github URL: https://github.com/ddpmntcpbr/rails_react_docker

この記事について

当記事は3章構成になっております。

まず、「1.自己紹介」で、簡単に自己紹介をさせていただきます。

次に、「2.開発アプリ解説」で、今回の開発アプリ開発について、コンセプト決定の流れから、実装機能/技術スタックについて詳しく紹介します。「転職用PF作りたいけど、どんなアプリを作ればいいか分からない」という方にとって、参考になることがあれば幸いです。

最後に、「3. 学習ロードマップ」で、Web知識ゼロだった筆者が、当アプリの開発にまでに利用した5つの教材およびサービスについて、時系列に沿って紹介したいと思います。特に独学では、「まず何を学べば良いのか」「どんな教材を選べば良いのか」というところから自身で考える必要があります。そういった方々にとって参考になり得る情報と思います。

1. 自己紹介

1.1 筆者スペック

  • 20代後半 男
  • 工学部機械系 修士卒 → 非IT系 日系製造業 技術開発職(現職)
  • 大学から現職において、データ分析ツールとしてプログラミングを経験(matlab / python)
  • Webエンジニアへのキャリアチェンジを目指し、社会人になってから独学を開始

大学入学以降、授業や研究データの分析ツールとしてプログラミングに触れる機会はありました。もともと自動化や効率化に興味があったことからプログラミングにだんだんとのめり込み、研究室ではプログラミングに多く触れられそうな研究テーマ(データ分析系)を選びました。

したがって、「プログラミング自体が完全に未経験」というわけではありませんでした。しかし、いわゆる”Web系”の知識は社会人以降の独学を開始するまではゼロ、という状態で、最初は HTML すら知らないところからのスタートでした( ̄▽ ̄;)。そのため当記事では「Web知識ゼロ」という表現をしています。

キャリアチェンジ志望の理由は、ざっくりと言えば、

1. もっとプログラミングがしたい
2. モノづくりで誰かの役に立ちたい
3. 非効率・非生産的な仕事を無くしたい

ここについては当記事の趣旨ではないので、詳細については省略します。

2. 開発アプリ解説

2.1 コンセプト方針

転職用PFアプリのコンセプトを決めるにあたり、満たすべき用件としては、下記3点を考えました。

(a) 実際の企業が採用しているようなモダンな技術を盛り込む
(b) 具体的な解決課題を明確にする
(c) サービスの利用が個人で完結する

(a) 実際の企業が採用しているようなモダンな技術を採用する

未経験からエンジニア転職において、高品質なポートフォリオは必須と考えました。

「モダンな技術をポートフォリオに組み込むことで、技術力の高さをアピールする」というのが基本的な考え方になるとは思いますが、個人的な解釈としては、ポートフォリオで証明すべきは「技術力」ではなく「自走力」だと考えています。

ぶっちゃけた話、実際の現場を経験したエンジニアと比べれば、未経験者間での能力差というものはどんぐりの背比べのようなものだと思います。多くの企業が「実務経験1年以上」をエンジニアの採用項目にあげていることからも、実務経験というのはそれだけ重い価値があり、未経験者とは大きな隔たりがあるのだと思います。

したがって、ポートフォリオの技術レベルの高さそのものはあまり重要ではなく、そこに到達するまでの過程の方が大事であり、「自走力=必要な情報は自らキャッチアップして吸収する能力」があることを示す方が、企業人事側としては採用しやすいのではないか?と考えました。

「スクールの制作アプリをそのまま提出する未経験者が足切りされてしまう」という話は多く聞きますが、これはそのアプリの技術力が低いからではなく、そのアプリから当人の「自走力」が主張できないから、だと私は考えています。私が独学ベースにこだわったのは、単純にお金の問題だけではなく、独学ベースでアプリを開発することができれば、自然とそれが「自走力の証明」につながると考えたからです。

今回のアプリにおいては、「独学:メンター=8:2」 くらいの割合で、上記技術を採用できるラインまで行くことができています。「メンター利用は独学からは外れるのでは?」という指摘もあるかもしれませんが、

  • プログラミングでは、個人ではどうにもならないようなエラーに遭遇してしまうことが多々ある
  • 実際に企業に入ってからは、先輩エンジニアの方々に分からないことを質問しながら業務を進めることになるため、「質問力」も重要な能力である
  • 完全独学オンリーだと、誤った癖が身についてしまっているリスクが高くなる

といった観点から、独学者が適宜メンターサービスを利用することは、かえって採用人事側にとって安心感を与える材料になるのでは、と考えたので、自信を持って「メンターサービスを使いました」と主張しています。

※ 具体的なモダンな技術リスト

  • Rails API + JSフレームワーク(React.js)の構成
  • Dockerで実行環境を完全コンテナ化
  • Herokuではなく、AWS でアプリをデプロイ(ECS Fargate & ECR)
  • Circle CIによるCI/CDパイプラインの構築

(b) 具体的な解決課題を明確にする

さて、前章とは一見真逆のことを言いますが、アプリ開発において、モダンな技術を採用することそのものには本来何の価値もない、と考えています。

なぜなら、アプリの目的はあくまでも「ユーザーにとって価値を提供できるか」であり、技術はそれを実現するための手段でしかないはずだからです。(保守・運用面でのメリットも考えられますが、保守・運用の最終目的もユーザーへの価値提供であることから、この点も包含した解釈になります)

これは、技術というものを下に見ているというわけでは決してありません。むしろ「新しい技術をどんどん使ってみたい!」という技術に対する好奇心、探究心は人一倍強い自負があります。現職はIT系ではありませんが、技術開発職という立場で業務に取り組んでおり、知的好奇心を満たせるという意味では、今の仕事に面白みを感じています。

しかしながら、かつては行き過ぎた技術先行思想によって「手段の目的化」が発生し、「最新技術を駆使した誰にとっても役に立たない技術」を開発してしまったという苦い過去の経験があったりもします。結果として「やっぱり技術は人の役に立ってなんぼ」というのが、約3年間技術職として働いてきて培った、技術者としての小さな矜持だったりします(この辺の話は直接お会いした方にはお話できるかと思います)

転職用PFであれば、技術ありきな考え方になることはある程度は仕方がないことでしょう。しかし「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」くらいのことは転職用PF作成においても考えていいんじゃないかな?と思いました。あるいは、もう少し目線を下げて「自分が欲しいものを作ろう」という程度でも十分でしょう。大事なのは、まず課題があり、それを解決する手段として技術があるという順番だと思いました。

もちろん今回の開発アプリは転職用PFが趣旨である以上、中には「技術力を証明したいから」という理由で選定した技術もあり、全てに対して課題が明確だったわけではありません。また、初心者の個人開発アプリがいきなりバズることは現実的には厳しいとは思います。しかし、そこを目指す姿勢があるかというのは、エンジニアとして本格的にキャリアを進めていく上では、長期的には大きな差異になると考えています。

また、

  • 課題が明確な方が採用した技術や実装した機能の根拠も明確にできるため、開発の方針を立てやすい
  • 自身がユーザー目線に立てることで、改善点を見つけやすい
  • 純粋にモチベーションを維持しやすい

といった点でもメリットもあると感じましたので、この方針は間違っていなかったと思います。

(c) サービスの利用が個人で完結する

「せっかく作るのであれば、誰かにとって役に立ち得るものを目指そう」を、もう一歩深く考えた方針です。

例えば Rails を対象として考えたとき、一般的な転職用PFとしては、TwitterライクなSNS系アプリや、メルカリライクなEC系アプリが多いかと思います。

理由は、ユーザー認証、CRUD操作、DB間のリレーションなど、基本的なサーバーサイド技術を一通り抑えられるものであるから、だと思います。「Railsの一般的な知識を持っていることを証明する」手段と考えれば、妥当な方針でしょう。

しかしながら、未経験者が転職用に開発した上記アプリが実際にユーザーに継続して使われるということは、まず無いでしょう。SNS系アプリは「ユーザー数が多ければ多いほどサービスとして質が高まる」性質があるため、アプリとして軌道に載せること自体が非常に難しいです。EC系アプリは BtoC であれば出品企業がいないとサービスが成り立たない、CtoCであればより SNS として要素が強まる & いよいよメルカリで十分、という壁があります。

これらアプリの難しさは、ユーザーどうしがつながることを前提としている点にあります。裏を返せば「個人で完結するアプリであれば、活路はある」とも言えると考え、この方針でコンセプトを詰めていくことにしました。

2.2 コンセプト内容

上記3点を念頭に置きながら、自分自身の生活の中で"課題"を探し、最終的にたどり着いたものが「読書メンタルマップ術の電子化」というコンセプトでした。

そもそも皆様は、読書メンタルマップ術というものをご存知でしょうか?

読書メンタルマップ術とは、ハーバード大学の先生が提唱している積読解消術です。読破したい書籍に対して、

1. 完読したい本について、それを読む“理由”や“目的”を3つ、紙に書きだす
2. 飽きてきたら、それを見返す

を繰り返すことで、完読までモチベーションを維持するというシンプルな読書手法です。

積読というものは、だいたい「最初は読む気があったけど、次第に読む気がなくなってしまった」書籍です。この「最初の読む気」を事前に明文化・保存しておくことで、いつでも最初の頃に新鮮な気持ちを取り戻せるようにしておこう、というイメージになります。

自分自身、実際に活用している技術ではあるのですが、少し困ったことがあります。それは、電子書籍との相性が悪いことです。

通常であれば紙とペンを用いるものですが、例えば出先でスマホやタブレットで電子書籍を読んでいるような状況では、必ずしもこれらの道具があるとは限りません。特に私は、外に出る時はあまりものを多く持ちたく無い性分なので、外ではスマホと財布くらいしか持っていないことが多いです。

仮に持っていたとしても、例えば電車の中で紙とペンを出して色々と書き始めるのは、少し億劫だったりします。

これを全部ペーパーレスで完結できるようなアプリがあったら便利だな」と思ったのが、このコンセプトを思いついたきっかけになっています。

もちろん、これだけであればスマホのメモ帳だけでもできてしまうものですが、このアプリには、

  • Google Books APIを活用した書籍検索・保存機能
  • メンタルマップ作成のヒント機能
  • Google Books, Amazon, 楽天ブックスへのリンクボタン配置(特に書籍レビューはメンタルマップ作成の大きなヒントになる)
  • Twitterでの読書仲間への気軽なシェア機能

といった機能が備わっており、より読書メンタルマップ術を使用しやすい環境を整えています。

先ほどの「2. 具体的な解決課題を明確にする」に照らし合わせて考えると、このアプリの解決課題は、ユーザーの積読を解消すること、もっと言えば、読書メンタルマップ術をペーパーレスで実行できるすることで電子書籍で読書するユーザーにとっても扱いやすくすること、になります。

また、「3. サービスの利用が個人で完結する」も満たしています。当アプリには、ユーザー同士がつながる機能は一切実装されていません

代わりとして、Twitterとの連携にはかなり重きを置いて実装機能を決めました。具体的には、

  • Twitterアカウントを利用したユーザー登録
  • ワンタップでハッシュタグ付きツイート
  • 充実した Twitter カード表示

を機能として実装しています。原則的には個人でサービスが完結しつつも、ユーザーどうしの繋がりはTwitter内の既存のネットワークに乗っかる、ことを狙ったコンセプトになっています。

2.3 技術スタック

再度、インフラ構成を載せます。

yomukatsu_cloud_architecture.png

この内容について、ひとつひとつ解説します。

Back-end

Rails API + Nginx の組み合わせにしています。

サーバーサイドフレームワークとしては他にも Laravel, Django, Node.js などもあります。恐らく大体のことは、どれを選んでも実装・実現できる、と思うのですが、その中で今回 Rails を選んだ理由は、

  1. 採用している企業数が多い
  2. 日本語の教材が豊富なため学習のハードルが低い
  3. 国内コミュニティが発達しているため、インターネット上での日本語ドキュメントが豊富

最初の学習言語として Rails を選択する初学者の方は多くいるかと思います。その一方で、「Railsはオワコン」という説が各所で言われていることについて、不安に感じる初学者の方もいると思います。

この点に関して、あくまで個人的な見解を述べますと初学者であった自分が、少なくとも最初に学ぶ言語/FW として Rails は間違いではなかった、と考えています。

正直、初学者である私には、「なぜ Rails がオワコンであるのか」について技術ベースで語ることはできません。しかし、

  • 現時点で Rails を採用している企業の絶対数は多く存在すること
  • 日本国内において、Rails に替わるサーバーサイドのデファクトスタンダードな技術が、まだ定まっていないこと

は事実と言ってよいかと思います。

もしかしたら、長期的に見れば日本国内でも Rails を採用する企業が減っていく流れにはあるのかもしれません。しかし、微分値と絶対値はセットで捉えないと判断を誤ることになります。初心者が目指すべきは「今すぐに仕事を得られる技術を身に着けること」であり「将来必要になってくる技術」ではないはずです。

また、オワコンというのは裏を返せば、技術的に枯れていて、初心者にとっては学びやすい言語/FWである、とも言えます。

Railsは日本国内での(過去含めた)使用者が多いため、関連する日本語のドキュメントがインターネット上に多く存在します。事実、Rails開発で遭遇するエラーは、Google検索すれば何かしらの日本語のサイトがヒットします。

最先端の技術は過去の技術の欠点を補う要素を持って生まれてきているのは事実ですが、検索しても欲しい情報が見つからなかったり、あったとしても英語ドキュメントだけだったりします。私自身は、このアプリの開発を通じてWebフレームワークに基本的な概念が身についてきているため英語ドキュメントでも読めるようになってきましたが、ベース知識すら乏しい状態からいきなり英語のドキュメントを読み解かないといけない状況になっていたら、途中で挫折してしまったかもしれないです。

以上が、私が最初の言語/FWとして Rails を選んでよかった、と考えている理由になります。

主要gem
  • devise_token_auth: APIモードでの devise。トークン認証を簡単に実装
  • twitter_omniauth:Twitter認証を簡単に実装
  • active_model_serializer: Rails APIからのレスポンスJSONを制御
  • imageMagic: 画像のリサイズを実行。特に、Twitter card用に書籍画像をリサイズする際に使用
  • aws-fog/carrierwave: リサイズした書籍画像を AWS S3 に保存
  • rspec: デファクトスタンダードになっているRubyテスト用フレームワーク
  • rubocop: Rubyの静的コード解析

TwitterアカウントでのOAuth認証は、過去の実装例が少なく、非常に苦労したところでもありました。しかし、「Twitterとの連携を重視」という今回のコンセプト上では絶対に欲しい機能と考え、頑張って実装しました。

AWS S3については元々採用予定はなかった(Google Books APIの画像リンクをそのまま引っ張ってくる予定だった)のですが、Twitter card で書籍画像を表示させる時にどうしても画像サイズを適切に制御する必要が出てきたので、imageMagic と合わせて Rails で画像リサイズ & S3保存、を実装することにしました。

Front-end

今回フロントエンドとしては、JSフレームワークであるReact.jsを採用しました(細かいこと言えば React はフレームワークではなくライブラリですが、ここではフレームワークとして扱います)。モダンな技術採用を謳う以上、Rails + jQuery/bootstrap の構成では心許ないと考えました。

JSフレームワークとしては、国内企業での採用状況から考えるに、React.jsVue.jsか、の二択になると思います。その中でもReact.jsを選んだ理由は、

  1. 自分が調べた範囲では、バックエンドに Rails を採用している企業群のうち、フロントに React を採用している企業の割合が高かった
  2. Vue.js よりも規約が厳格であり、初学者の自分であっても自然と可読性の高いコードを書くことができそう
  3. たまたま、React を効率的に学べる良い教材を見つけた

特に最後については、第3章で後述しています。

アプリ開発を通じて React.js が割と気に入ってきたので良い選択だったとは思いますが、この点に関してはどちらを選んでも間違いではなかったかな、とは思います。

主要ライブラリ等
  • create-react-app: Facebookが提供するオープンソースのReact開発パッケージ
  • Redux: Stateの一元管理するフレームワーク。Redux関連ファイルは、reducksパターン則って管理
  • Redux-thunk: Redux state の非同期処理を制御
  • react-helmet: 動的なmetaタグの挿入によるOGP情報の保持(Twitter card用)
  • react-share: Twitter含めたSNSシェア用ボタンを簡単に配置
  • Material-UI : Google が提供する UI コンポーネントライブラリ。簡単におしゃれな UI コンポーネントをアプリ内に配置できる
  • eslint & prettier: javascriptに対する静的コード解析。eslint は create-react-appに標準搭載されているものをベースに少しプラグインを追加 & prettier はイチから導入

今回はユーザーの利用シーンを考えると、Web上でもネイティブアプリのようにサクサク動く、JSリッチなアプリケーションにしたいと考えました。この点からも、jQuery+bootstrap ではなく React.js を採用してよかったと思います。

React の実装には、特にMaterial-UIが強力で、開閉モーダルや通知バー表示などのアニメーション演出や、ページ全体のレスポンシブ対応などがかなり簡単に実装できました。このライブラリを使えたというだけでも、React を採用した価値があったと思えるほどでした。

Infra

Docker/docker-compose

開発環境は、全てDockerコンテナ内で完結させています。docker-compose.ymlのサービス構成としては、

  • db: MySQL
  • api: Rails
  • web: Nginx
  • front: Node.js (React)

としています。

後述しますが、AWS ECS(Fargate)へのコンテナデプロイを利用することで、開発環境と本番環境の差異を小さくすることができています。

AWS(Amazon Web Service)

バックエンド( Rails + Nginx )のデプロイに使用。Railsチュートリアルなどではアプリの本番環境へのデプロイにはHerokuを用いることが一般的ですが「モダンな技術を採用したい」という観点から、AWSに挑戦しました。

稼働させるまでめちゃくちゃ苦労しました。ここを完全独学で完結させるのは相当しんどいと思います。第3章で触れていますが、AWSの学習については、メンターさんをかなり頼らせてもらいました。

※ 利用サービス

  • ECS (Fargate): コンテナ向けサーバーレスコンピューティングエンジン。この中に Rails と Nginx の Docker イメージを入れて稼働させる
  • ECR: Rails と Nginx の Docker イメージを保存しておくリポジトリ
  • RDS (MySQL): AWS が用意しているスケーラブルなデータベースエンジン
  • ALB: 負荷分散を担うロードバランシングサービス
  • Route53: サイトの独自ドメイン化に使用
  • ACM: サイトの https 化に使用
  • S3: 静的ホスティングサービス。書籍画像の保存・管理に使用
Netlify

フロントエンド(create-react-app)のホスティングで利用。

最初はバックエンドに合わせてフロントも AWS ( Amplify Console ) でホスティングしていたのですが、create-react-appはSPAとしてのアプリ開発となることから、metaタグ無いにOGP情報を保持できない = Twitterでページをシェアした時のカード表示を動的に制御できない、という問題が出てきました。

おそらくは AWS でも解決する手段はあると思うのですが、今回は Netlify の Pre-rendering機能を使うことで解決することにしました。この機能を使うことで、 create-react-app であっても、サーバー側で javascript をレンダリングしてからブラウザが解釈できるようになります ( あと単純に、無料で利用できるのもメリット )

この問題にあたってから、最近ホットな React フレームワークの Next.js の有り難みが自然と分かるようになってきた( Next.js は SPA/SSG/SSRを選択可能 )のですが、今回はすでに開発を始めていたこともあり、create-react-app + Netlify の構成で最後まで開発しました。

CircleCI

国内ではデファクトスタンダードとされている、Saas型のCI/CDサービスです。今回CircleCIで自動化した処理は、

  • Rspec
  • rubocop
  • eslint&prettier
  • AWS ECR への Image push
  • AWS ECS のタスク&サービスの更新

Netlifyにはもともと自動デプロイ機能がついていることから、CircleCIを導入することで、Github上の master ブランチに merge しただけで、本番環境への再デプロイが完了する、という状態に持っていくことができました。

一度ありがたみが分かると、もう手放せないですね( ´ ▽ ` )

3. 学習ロードマップ

さて、いよいよ本題です。ここまでで開発したアプリについて解説をしてきましたが、ここからは、このアプリ開発に至るまでの学習過程をたどっていきます。

第1章でもお伝えした通り、筆者はプログラミング経験自体はあっても、いわゆる「Web系」の知識はゼロからのスタートでした。繰り返しますが、HTMLすら知らなかった状態から、独学ベースで上記技術スタックをアプリに盛り込めるレベルまで到達することができました

独学ベースでの学習になると、「どの学習教材を選ぶべきか」というところから自分で考える必要があります。いろいろと紆余曲折ありましたが「これは役に立った!」と思うものを厳選し、時系列に沿ってお伝えします。

※下記サービスのWebリンクや、ロゴ画像、ホームページのスクリーンショットについては、事前に各運営者様に使用許可をいただいております。改めまして運営者皆様、利用快諾していただきありがとうございました。

3.1 Progate

image.png

皆大好きProgate。今からエンジニアを目指す方は、全員ここから入門して間違い無いでしょう。

URL: https://prog-8.com/

自分は手当たり次第に色々な講座をやってみていましたが、

  • HTML&CSS
  • Javascript(ES6)
  • jQuery
  • Ruby
  • Ruby on Rails

次いで

  • Command line
  • Git
  • SQL

辺りを押さえておけば十分だったかと思います。

各講座の序盤のレッスンは無料会員でも受けることができますが、本気でエンジニアを目指すのであれば、有料会員限定のコースも含めて取り組んでいきましょう。

これだけでもそれなりにボリュームはありますが、挫折しにくいよう学習ステップがかなり細かく設定されているので、初心者にとっても易しいつくりになっています。

ただ、Rails講座だけはさすがに難易度が高かったです。。。これは、Progateさんの講座の作り云々ではなく、Webフレームワークという概念が初学者にとって「始めまして」になるので、多少仕方がない部分ではあると思います。

1周目で全体像の把握、2週目以降で詳細理解に努める、というスタンスでよいかと思います。

3.2 Ruby on Railsチュートリアル

image.png

皆大好き(?) Rails チュートリアル。

URL: https://railstutorial.jp/

色々賛否ある教材ですが、無料かつ、ここまで体系的に「RailsのWebアプリ開発」を学べる教材は他にないと思います。

本教材の謳っているところでもありますが、「単にRuby, Railsの学習に終始せず、Webアプリ開発の全体像を俯瞰する」ものですので、本教材での知識は、他言語・他フレームワークで開発をする場合でも大いに活きると思います。

確かに、当教材に対する否定的な意見はいくつか見受けられ、

  • テストフレームワークとして、国内企業でデファクトスタンダードになっているRspecではなく、Railsに標準搭載されているminitestを使用している
  • 採用技術が古くなってきてしまっている

という点がよく指摘されています。

ただ、前者については、株式会社YassLab代表のコチラのYoutube動画の動画でも説明がある通り、「かつて(第2版まで)は Rspec を Rails チュートリアルでも採用していたが、Rspec 自体の学習コストが高いこともあり、それによる脱落者を多く出していた」という背景を受けてのものになります。

また後者については、例えば現在の Rails企業の多くは、Rails単体のアプリケーション(フロントはjQery/bootstrap)ではなく、APIとしてRailsを利用し、フロントはJSフレームワーク(Vue.jsやReact.js)を使うのが一般的になってきています。しかし、Rails初学者が、いきなりAPI開発から始めるのは、理解の階段を飛ばしすぎている、というのも事実でしょう(これについては、同者のコチラのYoutube動画も参考になるかもしれません)

つまり、Rails チュートリアルは「Railsを初めて触る人がなるべく挫折しにくい難易度設定を目指す」ということを念頭に置いた教材であり、最前線の現場で使用されているような本格的なRailsの習得の橋渡しをするようは役割である、と考えることができるかと思います。逆に、これ以上現場に近づけた本格的な内容にしてしまうと、それこそ多くの初学者が挫折してしまうと思います。

したがって、Rails初学者は、今この時代であっても、自信を持って当教材取り組んでよいと思います。少なくとも自分は、この後にも続くRails学習において、ベースとなるような知識をつけることができた、と感じています。

ただ、いくら難易度を落としているとはいってもRails初学者に取ってはかなり難しく、かつ量も膨大であるのは事実です。そのため、Railsチュートリアル完走を一つのマイルストーンとして設定し、内容につまづいたら「ひたすらググる」あるいは「適宜Progateに戻る」という進め方が効率的かと思います。

私も、一度はあまりの量と難易度に挫折してしまったのですが、社会人としてすでに働いており、ある程度お金に余裕があったので、動画版を購入して最後までやり切りました。個人的には、人が解説してくれている形式の方が理解がスムーズで、モチベーションの維持もしやすかったで、お金に余裕のある方にはオススメです。

Railsチュートリアルで身につく知識を整理すると、

  • バックエンド: Rails(シングルアプリケーション)
  • フロントエンド: jQuey+bootstrap (Railsの一部として内包)
  • テスト: minitest
  • 開発環境: AWS cloud9
  • デプロイ: Heroku

全くの初心者からWebアプリとして求められる一通りの機能を実装し、本番環境へデプロイするところまでできるのは、やはり教材として素晴らしいと思います。

しかし先ほども述べた通り、当教材はあくまでも橋渡しの位置付けです。未経験からの転職という自分自身の立場を鑑みると、Web系企業への転職用PF作成の準備としては、技術面でまだ心許ない、と考えました。

改めて複数企業の採用ページから実際に企業で使用されている技術を確認し、上記の学習知識と比較して整理をすると、

  • フロントは、Vue.jsReact.jsといったJavaScriptフレームワークを使用するのが一般的。それに伴い、Railsは単独アプリとしてではなく、APIモードで開発する
  • テストフレームワークは、minitestではなく、Rspecがデファクトスタンダード
  • 開発環境はPCローカルに構築する(Vagrantで仮想マシンを構築するか、Dockerでコンテナ化するか)
  • アプリケーションのデプロイは、小規模であれば Heroku を使うこともあるが、企業が提供するような中・大規模なアプリケーションであれば、AWSGCPなどをよく用いられる

次に習得すべきは、ここの技術領域であることが分かりました(個人的な話ですが、この辺りの時期からエンジニア採用ページに書かれている各技術がスタックが、どういった内容であるかが理解できるようになってきており、成長を実感していました)。

Railsチュートリアルを完走した方は、完走者向けのロードマップ紹介ページもありますので、まずはここを見てみるものよいかと思います。しかし私は、洗い出した上記項目をより体系的に学ぶことができるものがないかと考え、自分なりに色々と教材を探してみた結果、以下のサービスに辿りつきました。

3.3 Take off Rails

image.png

URL: https://freelance.cat-algorithm.com/lp/take-off-rails

『あなたを「初心者エンジニア」から「現場で活躍できるエンジニア」まで引き上げます。』というスローガンを掲げた教材とメンターがセットになったサービスで、Rails チュートリアルと実際の企業の間の穴埋めを狙った内容になっています。

基本的にはすでに作成された教材に則っとりながら自分のペースでアプリを開発していくのですが、都度 Slack でメンターさんに質問を投げることができるというのが大きな特徴です。。

最終的な教材のゴールとしてはQiitaのクローンサイトを開発することになります。これの技術スタックは下記の通りです。

  • バックエンド: Rails APIモード
  • フロントエンド: Vue.js (ソースコードは作成済みのものを使用。あくまで Rails との繋ぎ込みまでを扱う)
  • テスト: Rspec(+Factory_bot)
  • 開発環境: ローカル + DB(MySQL) は Docker コンテナを利用
  • アプリのデプロイ: Heroku
  • その他: CircleCI による rspec, rubocop の自動化

※ 私の当サイト用のリポジトリ: https://github.com/ddpmntcpbr/qiita_clone

Railsチュートリアル時点での技術スタックと比べると、かなり実務に近い技術が盛り込まれていることが分かるかと思います。

こちら、決して安い金額ではないサービスだと思います(スクール等に比べれば全然安いですが)。ただ、当時欲しいと思っていた知識が一気に身に着けられると考えて、購入を決意しました。

結果、良い買い物だったと思います。自分が学びたい内容がきれいに体系化されていたこともそうですが、何より、教材内容についてメンターさんへ質問ができるのも有意義だと感じました。遅くとも24時間以内にはレスポンスが返ってくるのもありがたく、「料金分を回収してやるぞ!」という気持ちで、たくさん質問させてもらいました笑

本教材では、上記技術の学習だけでなく、

  • Git commit の適切な粒度や、コミットメッセージの書き方
  • Github での PR の出し方や、コードレビューの流れ
  • Slack でのやり取り(意図が伝わりやすい質問の仕方など)

といった、「独学ではなかなか身につかない」けど「チーム開発では必須になる」ような周辺知識について学べた点も、非常に有用だったと思います。Railsチュートリアルの内容がおおよそ理解できていれば前提知識としては十分な難易度で、大きく挫折をすることがなかった点もプラスです。

さて、ここまでで、Railsに関しては、比較的モダンな開発手法に触れることができました。

しかし、当教材ではあくまでも Rails の開発に的を絞ったものであり、フロントエンドは既存のソースコードを流用する形での学習でした。この教材の内容を自身の転職用PFに組み込むためには、フロントエンド側についても自身で開発する知識が必要と考えました。

フロントエンドフレームワークの選定について、第2章でもお伝えしたとおり、「Reactの方がなんとなくよさそうかなー」と考えていたところ、次の教材を見つけたことをきっかけに、正式に React の学習を始めることにしました。

3.4 【とらゼミ】トラハックのエンジニア学習講座

image.png

現役の React エンジニアである トラハックさん ( @torahack_ )が、Youtube上で公開している講座で、Reactについて基礎の基礎から学ぶことができます。

動画チャンネルURL: https://www.youtube.com/user/1492tiger

こちらの教材の特長は、

  • Reactの基礎の基礎から体系的に学べる( Progate の JavaScript 講座完了が受講目安)
  • 動画によるハンズオン形式
  • 教材範囲にモダンなフロントエンドの開発手法を含む( Redux 等)
  • 動画のほぼ全てがYoutube上でなぜか無料公開されている

特に最後については完全にバグとしか思えない点で、Udemyなどで有料販売されていても動画講座と比べても遜色ないクオリティだと思います。

動画教材にありがちな「準備した原稿丸読み」のような堅い口調ではなく、フランクな若手予備校教師の授業(?)のような語り口のため、硬い喋りが苦手な人にもお勧めできます。私は復習のために、一度見た動画を耳だけで聞き返したりして、記憶の定着を図りました。

講座はいくつかのシリーズに分かれており、私が視聴をしたのは、

1.『日本一わかりやすいReact入門』シリーズ
2. 『日本一わかりやすいReact入門【実践編】』シリーズ
3. 『日本一わかりやすいReact-Redux入門』シリーズ

の3シリーズです。

最終的な成果物の技術スタックは、

  • create-react-app
  • Redux & redux-thunk
  • Material-UI
  • Firebase: Google が提供する mBaaS。バックエンド+インフラを手軽にセットアップできる

です。

※ 私のGithubリポジトリ: https://github.com/ddpmntcpbr/react-ec-app

こちらの動画については、学習備忘録記事をQiitaに投稿しております。よろしければこちらもご参考下さい。

参考ページ: 『日本一わかりやすいReact入門【実践編】#1~5 学習備忘録

本学習講座を全て受講するためには、有料コミュニティ『とらゼミ』への加入が必要になりますが、筆者は無料公開範囲の動画で必要な知識は十分に身に付いたと感じたため、加入はしておりません。(代わりの記事として書くことで、宣伝として少しでもお役に立てればと思っています笑)

さて、ここまででフロント側も自力で開発ができる基礎が身に付きました。これくらいの時期に並行してアプリのコンセプトが決定していましたので、いよいよポートフォリオ作成に取り掛かり始めました。

しかし、開発を始めるといくつも壁が出てきます。基本的にはググりながらの解決をしていきましたが、どうしても解決できないエラーにもぶち当たりました。特に、

  • ReactとRailsの繋ぎ合わせ
  • AWSでのアプリの公開方法
  • その他インフラ知識全般

あたりが、個人的な難所でした。その過程で頼らせてもらったのが、次のメンターサービスです。

3.5 TechTrain

image.png

有名企業のエンジニアから実務を学べるオンラインコミュニティ です。

URL: https://techbowl.co.jp/techtrain

特長を列挙すると、

  • 現役エンジニアであるメンターさんと、1 on 1でのオンライン面談が可能
  • メンターの方々の技術領域は多種多様
  • 全てのメンターと面談が可能で、技術トピックに応じて切り替えることが可能
  • 面談はこちらからのタイミングで入れることができる
  • なぜか全て無料で利用できる

はっきり言います。これだけのことができて完全無料なのはどう考えてもバグです。これからエンジニア就職を目指しているU30の学生・社会人は、全員登録した方がいいレベルです

TechTrain の中ではいくつかの Mission が設けられており、それをメンターと一緒に取り組んでいくことで知識を習得していく、ということが可能です。 Mission は実際のIT企業とのコラボで作成されており、中には「Missionをクリアできた人は一次面接をスキップできる」のような特典もついていたりします。

ただ私は Misson には取り組まず、あくまでの個人開発のサポートとして利用させてもらっていました。基本的には自身の既存知識とググり力でPF作成を進めつつ、どうしても解決できない課題が出てきたときにピンポイントで面談予約を入れる、というイメージで、個々人の利用したい形式/ペースで利用できる点も大変ありがたかったです。

異なる技術領域を持ったエンジニアの方々全員と面談をするが可能なため、Rails, React, AWSそれぞれで、別のメンターの方に質問をさせてもらっていました。

特に自力での解決が難しかったのがAWS周りの本番環境構築で、本サービス無しでは乗り越えられなかったと思います。

最終的には、自身のググり力 + TechTrain で都度メンターを利用、を繰り返すことで、無事アプリを完成させることができました!

3.6 番外編

上記以外で役に立ったものについて、ざっくばらんに紹介します。

Udemy 『Git:はじめてのGitとGitHub』

無料で Git の基礎を学べる講座です。「Gitよう分からん!」って人は、まずこれから触れてみましょう

『キタミ式イラストIT塾 基本情報技術者』

基本情報処理の定番本です。コンピューターサイエンス領域の基礎知識が体系的に学べます。イラストが豊富であり、文章表現も柔らかいので初心者にも優しいです。資格自体の取る/取らないに関わらず一読をオススメします。

『米国AI開発者がゼロから教えるDocker講座』

Dockerについて一から学べる動画教材です。作者様はデータサイエンス領域の方ですが、Webアプリ開発を目的とした人であっても問題ありません(実際に、講座後半では、docker-composeを利用したRailsコンテナの構築まで扱っています)

非常にボリューミーな内容にも関わらず、Udemy講座の中ではかなり良心的な価格設定です。

『【AWS 入門】EC2とDockerでHello Worldしよう』

AWSについて何にも分からない状態から、「nginxだけのシンプルなコンテナアプリを動かす」ところまで、ハンズオン形式で学習ができます。

AWSのとっつきにくさは、「①インフラの概念が分からない」「②専門用語が分からない」に集約されると思います。まずは手を動かしながら、AWSでアプリをデプロイ流れを全体像で掴むことができます。

3.7 アプリの改善点

一通りアプリを完成させてみて、初めて見えてくる改善点が多くありましたので、合わせて列挙します。

AWSサーバー代高すぎ!!!

スケーラビリティの高い中・大規模向けインフラ構成になっているため、サーバー代がめっちゃ高い笑

長く公開するためには、どこかのタイミングで無料サーバーへ移管する必要があるかなと思います。Heroku の無料枠で上手にやりくりできれば、解決できるかもしれないです。

フロントエンド は Next.js + Typescriptで実装したい

Next.js はレンダリングのタイミングを制御できるので、OGP情報の保持が簡単に実現できます。

また、それ以外でも、

  • ルーティング設定が簡単
  • パフォーマンスをよくするような機能も豊富
  • Typescriptの導入が用意

というメリットもあり、とても気になっているフレームワークです。次、全く同じアプリを開発するとするのであれば、絶対に採用したい技術です。

デザインがあやしい気がする・・・?

アプリを開発して気づいたのは、アプリにおけるデザインの重要性です。

これを思った理由は単純で、開発途中で「なんか自分のアプリ、イケてなくない?」と感じたからです笑

Webアプリにおけるデザインは、単なるお洒落さに関するものだけでは決してありません。デザインは、ユーザーにとって必要な情報を適切に配置することであり、ユーザーの価値提供のための最前線領域です

ユーザー側から価値提供の流れ(バリューチェーンと表現するのでしょうか)をざっっっくり並べると、

ユーザー -> UI/UXデザイン -> フロントエンド -> バックエンド -> インフラ

のようになっていると思っています。

「エンジニアになろう!」と意気込んでから、後ろ3つについてはそこそこ勉強してきました。しかしデザイン領域については、開発初期は完全素人の状態で、途中までは勘でやっていました。。。

一応、付け焼き刃程度ではありますが、デザインの名著である『ノンデザイナーズ・デザインブック』に目を通し、途中からは意識できる範囲ではデザインのことを意識して、フロントを実装しました。

うまく取り込めているかは分かりませんが、少なくとも「デザインはセンスではなく論理」であることが学べただけでも、よい勉強になりました。こちらの書籍も、転職用PF作成者にオススメしておきます。

4. さいごに

以上、大変長い記事でしたが、最後まで読んでいただきありがとうございました。タイトルでは「学習ロードマップ」と銘打っておきながら、私自身の思考プロセスや価値観についても多く書かせてもらいました。

「エンジニアになりたい!」と思い立ってから、学習自体はほぼ一人で淡々と進めてきました。しかし、ほぼ独学でここまで学習を進めることができたのは、多くの先輩エンジニアの方々が様々な情報をインターネットに投稿し、それをオープンに取得できる環境にあったからだと思っております。

それであれば、次は自分自信が、他の駆け出しエンジニアの方々の助けになるような情報を発信できれば、と思い、この記事を書くこととしました。参考になったという方がいらっしゃったら幸いです。

是非LGTM、ストック、twitterでのシェアお願いします!また、私自身もtwitterをやっておりますので、気軽にフォローしてもらえるとうれしいです(^^)

よろしくお願いします!

Twitter: https://twitter.com/ddpmntcpbr
Github: https://github.com/ddpmntcpbr/rails_react_docker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mysql2::Error::ConnectionErrorについて

現在プログラミングスクールにてフリマアプリを作成しております。
モデル単体テストコードを実施した際に遭遇した下記エラー。苦戦したので備忘録として。

「Mysql2::Error::ConnectionError: Lost connection to MySQL server during query」

63e0767931af109eefa7956d9696b158.png

上図のように、テストの途中でエラーが発生してるんですね.
でもこれ、コード的には特に問題ないと思うのです。それゆえに悩みました。
また、何度かテストを実施すると、図よりテスト成功ログが増えたりもしまして、これはおかしいと思いました。

エラー内容的には「Mysqlへ繋がらなかったよ。繋ぐまで時間がかかりすぎてタイムアウトしちゃったよ」
という意味合いのようですね。他の方の記事をみてみたところ、「Mysqlの処理量がテスト途中でいっぱいいっぱいになってしまって、時間までに処理できませんでした」ということのようですね

今回行っているモデルの単体テストコードですが、単体ではあるのですが、そのテストには複数モデルが必要だったのです。下記のように、item_orderだけではなく、それに付随するitem_id、user_idも必要なため、それぞれFactoryBotを追加しました。(これを追加するまではテスト通ってたのです)

qiita.rb
require 'rails_helper'

describe ItemOrder do
  before do
    user = FactoryBot.create(:user)
    item = FactoryBot.create(:item)
    @item_order = FactoryBot.build(:item_order, item_id: item.id, user_id: user.id)
  end

  describe '商品購入手続き' do
    context '商品購入が成功する時' do
      it '全ての項目が入力されていれば登録できる' do
        expect(@item_order).to be_valid
      end

つまり、明らかにこれが原因です。itemとuserから引っ張ってくるデータ量が多すぎたと思われます。
テスト途中にbinding.pryを差し込みまくればテストが通った、という記事も拝見しましたが、私はそれでもテスト成功しませんでした。

そこで登場するのが、sleepメソッドです。
sleepメソッドは、処理速度を指定時間に合わせて行ってくれます。
今回は元々の指定時間に合わせて行ったところ、それに間に合わず、途中で失敗してしまっている、と考えられます。
そこで下図のようにsleepメソッドを挿入してみました。「一つの処理に1秒かける」、という設定ですね。

sleep.rb
describe ItemOrder do
  before do
    user = FactoryBot.create(:user)
    item = FactoryBot.create(:item)
    @item_order = FactoryBot.build(:item_order, item_id: item.id, user_id: user.id)
    sleep(1)
  end

すると・・・・
b269dbf0d4d12c209d6d4f8f5b5a328a.png

成功しました!!!
今回の厄介な所は、コード自体に誤りはなかった、という所ですね。
コードに何か誤りがあるせいでエラーが発生してしまう、と考えていた私にとっては衝撃な点でした。
そもそもテスト中に処理落ちしないテストコードを書ければいい話なのかもしれませんが・・・・それは今後精進していきます!
改善点やダメ出しあれば是非お願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6.1ハンズオン(2)~フロント寄りのこと編~

はじめに

Rails6.1ハンズオン(1)の続きです。

環境は前回を参考にしてください。

コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。

やること

  • erbからhamlにする
  • Bootstrap 5.0.0.beta2(2020/2/XX時点)を入れる
  • Font Awesomeを入れる
  • i18nを使って日本語化
  • 見た目をいい感じに整える

実装

3-1. erbからhamlにする

Gemfileに

gem 'haml-rails'

を追加。bundle install。

rails haml:erb2haml

で、プロジェクト内のerbをhamlに一斉変換できる。(最後に元のerbを消していいか聞かれるのでyを入力)

3-2. Bootstrap 5.0.0.beta2を入れる

参考:

Use Bootstrap 5 with Ruby on Rails 6 and webpack step by step

ターミナルで以下のコマンドを入力

yarn add bootstrap@next
yarn add @popperjs/core

app/views/layouts/application.html.hamlのstylesheet_link_tagの行を以下に置換

= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'

app/javascript/stylesheets/application.scssを作成

@import "bootstrap";

を追加。

app/javascript/packs/application.js

import "bootstrap"
import "../stylesheets/application"

を追加。

3-3. Font Awesomeを入れる

yarn add @fortawesome/fontawesome-free

app/javascript/stylesheets/application.scss

@import '~@fortawesome/fontawesome-free/scss/fontawesome';

を追加。

app/javascript/packs/application.js

import '@fortawesome/fontawesome-free/js/all'

を追加

3-4. 日本語化

参考:

[初学者]Railsのi18nによる日本語化対応 - Qiita

config/application.rbに以下を追加

config.time_zone = 'Tokyo'
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

config/locales/model.ja.ymlを作成

ja:
  activerecord:
    models:
      community: コミュニティ 
      comment: コメント
    attributes:
        community:
          id: ID
          title: タイトル
          owner_name: 作成者
        comment:
          id: ID
          author_name: 投稿者
          content: 内容
  attributes:
    created_at: 作成日
    updated_at: 更新日

svenfuchs/rails-i18n

↑ここからconfig/locales/ja.ymlを作成

locales/views/以下にもyamlファイルを作りますが、詳しくはgithubにて...

3-5. 見た目を色々整える

githubを見てください

こんな感じになりました↓
localhost_3000_communities.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6.1ハンズオン(2)~見た目をそれっぽくする

はじめに

Rails6.1ハンズオン(1)の続きです。

環境はこれを参考にしてください。

コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。

やること

  • erbからhamlにする
  • Bootstrap 5.0.0.beta2(2020/2/XX時点)を入れる
  • Font Awesomeを入れる
  • i18nを使って日本語化
  • 見た目をいい感じに整える

実装

3-1. erbからhamlにする

Gemfileに

gem 'haml-rails'

を追加。bundle install。

rails haml:erb2haml

で、プロジェクト内のerbをhamlに一斉変換できる。(最後に元のerbを消していいか聞かれるのでyを入力)

3-2. Bootstrap 5.0.0.beta2を入れる

参考:

Use Bootstrap 5 with Ruby on Rails 6 and webpack step by step

ターミナルで以下のコマンドを入力

yarn add bootstrap@next
yarn add @popperjs/core

app/views/layouts/application.html.hamlのstylesheet_link_tagの行を以下に置換

= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'

app/javascript/stylesheets/application.scssを作成

@import "bootstrap";

を追加。

app/javascript/packs/application.js

import "bootstrap"
import "../stylesheets/application"

を追加。

3-3. Font Awesomeを入れる

yarn add @fortawesome/fontawesome-free

app/javascript/stylesheets/application.scss

@import '~@fortawesome/fontawesome-free/scss/fontawesome';

を追加。

app/javascript/packs/application.js

import '@fortawesome/fontawesome-free/js/all'

を追加

3-4. 日本語化

参考:

[初学者]Railsのi18nによる日本語化対応 - Qiita

config/application.rbに以下を追加

config.time_zone = 'Tokyo'
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

config/locales/model.ja.ymlを作成

ja:
  activerecord:
    models:
      community: コミュニティ 
      comment: コメント
    attributes:
        community:
          id: ID
          title: タイトル
          owner_name: 作成者
        comment:
          id: ID
          author_name: 投稿者
          content: 内容
  attributes:
    created_at: 作成日
    updated_at: 更新日

svenfuchs/rails-i18n

↑ここからconfig/locales/ja.ymlを作成

locales/views/以下にもyamlファイルを作りますが、詳しくはgithubにて...

3-5. 見た目を色々整える

githubを見てください

こんな感じになりました↓
localhost_3000_communities.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】業務未経験者がRailsを使用して情報資産共有アプリを開発したので共有します

facebook_cover_photo_1.png

はじめに

こんにちは!2020年10月よりRubyやRailsを中心に学習しているyutaro(@yutaro48)と申します。
このたび、Railsを使用して情報資産共有アプリ「KnowHow」を開発しましたので紹介させていただきます。

アプリ概要

情報資産共有アプリ「KnowHow」を作成しました。
top-page.gif

このアプリでできることは以下の3点です!


① ノウハウをアウトプットする

誰でも簡単にアウトプットできます。Markdownにも対応しています。下書きとして一時保存することもできます。
first.gif

② ノウハウを検索する

自分が求めるノウハウを検索することができます。ブックマークをして他の人のノウハウを自分のものにできます。
閲覧履歴表示やカテゴリー表示も実装しています。
third.gif

③ 気になる人をフォローする

気になる人をフォローすることができます。フォローした人のノウハウはタイムラインに表示されます。
second.gif

作成背景

「新卒社員 × 在宅勤務」によって顕在化した課題を解決するために作成

僕は2020年4月に某総合電機メーカーに新卒入社しました。
しかし、コロナウイルスの大流行により入社直後から在宅勤務になり、緊急事態宣言明けも在宅勤務が基本となりました。(緊急事態宣言後も用事がある時以外は基本在宅勤務という形態)
このように「新卒入社 × 在宅勤務」という特殊な状況下で私や同期は以下の3つの課題に直面しました。

課題1 会社ノウハウを学ぶ(社員の経験から学ぶ)機会の減少

会社独自のノウハウは属人的であり、先輩社員や同期との雑談などから学ぶことが多いが、リモート環境では雑談などから学ぶ機会がほとんどなくなってしまう。
そのため、学べることが限定的になってしまうという課題がある。

課題2 自身の学びをアウトプットする機会やレビューしていただく機会の減少

リモート環境では、自身の学んだことや経験したことを言語化したり、
他の社員の方からレビューしていただく機会が少なくなってしまう。
そのため、PDCAを回しづらい状況になってしまうという課題がある。

課題3 同期社員と情報共有する機会の減少

新卒社員の中では、同期が現在どのような仕事をしているか気になっている人が多い。通常であれば雑談や飲み会などの機会に情報共有をすることが多いと思う。
しかし、コロナ禍という状況下ではそのような機会が持てなくなってしまっているという課題がある。


これらの課題を解決したいと思い、情報資産共有アプリ「KnowHow」の作成に至りました。

機能一覧

上記の課題を解決するために実装した機能は以下の通りです。

基本機能
機能 Gem
ログイン機能 devise
ゲストログイン機能
プロフィール機能
課題1(会社ノウハウを学ぶ機会の減少)を解決するための機能
機能 Gem
検索機能
ブックマーク機能(Ajax)
カテゴリー別表示機能
閲覧履歴表示機能
ページネーション機能 kaminari

検索機能を実装し、自分が知りたいノウハウを検索できるようにしました。
カテゴリー別表示機能、閲覧履歴表示機能やページネーション機能を実装し、検索しやすいようにしました。
またブックマークをすることで他者のノウハウを自分のものにできます。
これらにより、様々な社員の経験から多くを学ぶことができるようになります。

課題2(自身の学びをアウトプットする機会やレビューしていただく機会の減少)を解決するための機能
機能 Gem
ノウハウのアウトプット機能(CRUD)
コメント機能(CRUD)
下書き保存機能
マークダウン投稿機能

ノウハウのアウトプット機能を実装し、自身の学びや経験をアウトプットできるようにしました。また、MarkDown投稿機能や下書き機能を実装し、アウトプットの体験価値向上を狙っています。
また、コメントで他の社員からレビューしていただくことも可能です。
これらにより、自身の経験についてPDCAを回すことができるようになります。

課題3(同期社員がどのようなことをしているのか情報共有する機会の減少)を解決するための機能
機能 Gem
フォロー機能
タイムライン表示機能

同期などが現在どのような仕事をしているかを確認するためにフォロー機能を実装しました。フォローした人のノウハウはタイムラインに表示されます。
これらにより、同期や気になる社員の動向を確認することができるようになります。

使用技術と選定理由

使用技術
  • 開発環境
    • macOS Catalina(10.15.6)
    • Visual Studio Code
  • フロントエンド
    • Haml/Sass/JavaScript(jQuery)
    • Semantic UI
    • Bootstrap
  • バックエンド
    • ruby 2.6.5p114
    • Rails 6.0.3.4
  • インフラ
    • PostgreSQL(13.0)
    • Heroku
選定理由

バックエンドにRailsを採用した理由は2つあります。

1つ目は、Qiita、note、GitHubなどプログラミング学習の際の知識のインプットやアウトプットに使用したサービスにRailsが使用されていたからです。
今回作成した「KnowHow」は社内ノウハウのインプットやアウトプットをテーマにしたアプリであるため、開発前段階からこれらのアプリの特徴を取り入れようと考えていました。
そのため、バックエンドはRailsで作成することに決めました。

2つ目は、様々な情報にアクセスしやすいからです。
PHP,Pythonなど他のプログラミング言語も検討しましたが、日本国内のコミュニティや参考資料が多く学習しやすいRubyとフレームワークRailsを選択しました。
Rubyの学習によって得た知見を活かし、他の言語も学んでいきたいと思っております。

データベース設計

draw.ioを使用して作成
KnowHow.png

インフラ構成図

(AWSにデプロイでき次第掲載いたします)

工夫した点

(設計面)

  • 実際に同期にヒアリングを行い課題設定を行なったこと
  • ユーザーのストレスがないようにUI/UXをとにかくシンプルにしたこと
  • 「誠実さ」を表現するためにベースカラーを青に設定したこと

(可読性向上)

  • 部分テンプレート化
  • デコレータを使用し、Modelの肥大化を回避
  • 基本7アクションしか使わない(チームメンバーが理解しやすいように)

(保守性向上)

  • 部分テンプレート化
  • 「namespace」や「scope」を使用したルーティングの整備

(その他)

  • 「チーム開発」を意識して、毎回ブランチを作りプルリクベースで開発
  • commitを細かく行ったこと

苦労した点

検索機能の実装(ActionTextを使用した場合)

検索機能はransack(gem)を使用すれば簡単に実装できますが、Rails6で導入されたActionTextを使用しているとransackによる検索が機能しなかったため、SQLを使用して自作で検索機能を作成しました。
とても苦労したと同時に「gemに頼らない機能実装」はとても良い経験だったので、別途記事にまとめました。
また、以前会社で学んだSQLの復習にもなりました!

今後やりたいこと

  • AWSにデプロイ
  • セキュリティの強化
  • テストのサンプル数を増やす
  • レスポンシブ対応
  • Vue.jsの導入
  • Docker / CircleCIの導入

感想

私は、前職在職時に自分自身や同期が本気で悩んでいた課題をどうにか解決できないかと思い、本アプリの開発を決めました。
しかし、自分で考えたWebアプリを形にするということは想像以上に大変でした。
「この処理はどこに書けばいいのか?」「この機能は本当に必要なのか?」「このUI/UXで使いやすいのか?」と常に自問自答を繰り返しながら開発していました。
ですが、開発を振り返ってみると大変だった記憶ではなく、楽しかった記憶や充実感ばかりが思い出されます。
なにより、エラーに直面しながらも自分自身のやりたいことを形にできた時の感動は一入でした。

このアプリ自体はこれで完成ではなく、インフラ面や機能面など課題がてんこ盛りなので、これからも改善を重ねていきたいと思っております!

最後までご覧いただきありがとうございました?‍♂️
これからも自身の学習したことや気づいたことなどあったらアウトプットしていきますので、もしよろしければLGTMフォローよろしくお願いいたします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] Rails6 × PostgresSQL × heroku × CircleCI の環境を0から作る方法の徹底解説!!

概要

ruby on railsのアプリケーションをdockerを使ってherokuにデプロイしてcircleciするメモとして書きます。

1. docker環境の構築

1のステップでは以下のような構成にフォルダをすることを目標とします。

./
|- docker-compose.yml
|- web/
   |- Dockerfile
|- Gemfile
|- Gemfile.lock
|- entrypoint.sh

docker-compose.ymlの作成は以下のようにします。

docker-compose.yml
version: '3.5'
services:
  web:
    build:
      context: .
      dockerfile: web/Dockerfile
    container_name: rails-web
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
      - /var/tmp
    ports:
      - 3200:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
    networks:
      - sample

  db:
    image: postgres:9.6.2-alpine
    container_name: rails-db
    environment:
      POSTGRES_PASSWORD: 12345678
      TZ: "Asia/Tokyo"
    volumes:
      - pg-data:/var/lib/postgresql/data
    restart: always
    ports:
      - 5432:5432
    networks:
      - sample

  pgadmin:
    image: dpage/pgadmin4:4.18
    restart: always
    environment:
      PGADMIN_DEFAULT_EMAIL: postgres@linuxhint.com
      PGADMIN_DEFAULT_PASSWORD: 12345678
      PGADMIN_LISTEN_PORT: 80
    ports:
    - 8080:80
    depends_on:
      - db
    volumes:
    - pgadmin-data:/var/lib/pgadmin
    links:
    - "db:pgsql-server"
    networks:
      - sample

networks:
  sample:

volumes:
  pg-data:
  pgadmin-data:

Dockerfileは以下のようにします。

Dockerfile
FROM ruby:2.6.5

RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get update && \
    apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

RUN apt-get update -qq && \
    apt-get install -y build-essential \
    libpq-dev \
    sudo

RUN yarn add node-sass

WORKDIR /app
RUN mkdir -p tmp/sockets
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

# COPY entrypoint.sh /usr/bin/
# RUN chmod +x /usr/bin/entrypoint.sh
# ENTRYPOINT ["entrypoint.sh"]

CMD bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"

entrypoint.shは以下の様に書きます。 (docker-composeのコマンドで叩くので不要です)

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

次に以下のコマンドを叩いてGemfileとGemfile.lockを作成して下さい。

terminal
bundle init
touch Gemfile.lock
Gemfile
gem 'rails', '~> 6'

そして以下のコマンドを叩くことでファイルが展開されます。

terminal
docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundle

そして次にbundle installをすることでgemをインストールしていきます。

terminal
docker-compose build

dockerを起動させます。

docker-compose up

追記(2021/2/21)

.gitignoreが自動生成されると思いますので以下の記述をコメントアウトしておきます。

# /public/packs
# /public/packs-test

2.Webpackのインストール

このままだと以下のエラーが発生してしまいます。

Webpacker configuration file not found /app/config/webpacker.yml.

以下のコマンドを叩いてyarn.lockをインストールしていきます。

docker-compose run web yarn install --check-files

以下のコマンドを叩くことでwebpackの設定ファイルをインストールしていきます。

docker-compose run web bin/rails webpacker:install

DBの設定

このままだとDBに接続できないのでdatabase.ymlの設定を変えていきます。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: postgres

development:
  <<: *default
  database: app_development
  # hostをdbに変更
  host: db
  password: 12345678

・
・
・

test:
  <<: *default
  database: app_test
  host: <%= ENV['DB_HOST'] || 'db' %>
  password: <%= ENV['DB_PASSWORD'] || '12345678' %>

そして以下のコマンドを叩くことでdbを展開していきます。

terminal
docker-compose run --rm web rails db:create
docker-compose run --rm web rails db:migrate

3.railsの起動

railsの起動を行っていきます。
以下のコマンドでdockerを起動していきます。

terminal
docker-compose up

以下のように表示されればokです。

image.png

4.pdadminの起動

pdadminはpostgresqlをguiで操作できるツールです。
これがあることでテーブルの確認等を行うことができます。
docker-compose.ymlでpgadminを同時に起動させているのでログインしてきます。

http://localhost:8080 にアクセスすると以下のようなログイン画面が出るので、

id: postgres@linuxhint.com
pass: 1235678

でログインしていきます。

image.png

add new server より新しいコネクションを作ります。

image.png

以下の情報を入力します。

Name: 好きなもの
Host: db (127.0.0.1出ないので注意)
Port: 5432
database: app_development
Username: postgres
password: 12345678

image.png

image.png

以下のようにdbを登録できたら成功です。

image.png

5.webpackerを使用するデモ

webpackを読み取れるようにapplication.html.erbを変更します。

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

   - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   - <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
   + <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   + <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

以下のようにしてサンプルページを作成していきます。

terminal
docker-compose run --rm web rails generate controller Hello index

次にroutes.rbの設定をいじります。

routes.rb
Rails.application.routes.draw do
  get 'hello/index', to: 'hello#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

http://localhost:3200/hello/index に接続して以下のような画面になることを確認して下さい。

image.png

webpackerではpacks配下のapplication.jsのファイルが変換され、表示できる形となります。
そのため、scssやjs, imageのファイルを最終的にapplication.jsにインポートすることでまとめてバンドルして、追加されます。

webpackのフォルダの最終形は以下のようになるようにします。

./app/javascript
|- packs/
   |- application.js   # 全体を読み込むためのファイル
   |- hello.js

|- scss/
   |- application.scss
   |- style.scss
   |- hello/
      |- hello.scss

|- js/
   |- application.js 
   |- hello_world.js

|- images/
   |- test.png  # 好きな写真を追加しておく

では実際に、やってみましょう!

まず、scssとjs,imageを追加していきましょう。

まず、app/javascriptの配下にscss, js, imageのフォルダを追加します。

terminal
cd app/javascript
mkdir js scss images

まずはscssを追加していきます。

terminal
# scssの配下で
touch application.scss style.scss
mkdir hello
touch hello/hello.scss
application.scss
@import 'style';
style.scss
@import 'hello/hello';
hello.scss
p {
    color: red;
}

次にjsを設定していきます。

terminal
# jsの配下で
touch application.js hello_world.js
js/application.js
import hello_world from './hello_world'
js/hello_world.js
window.onload = () => {
    document.getElementsByTagName("H1")[0].innerText = 'Hello World';
}

そしてpacksの配下にhello.jsを作成して下さい。

terminal
touch hello.js
hello.js
console.log("hello")

最後に全体を読み込むためにpacksのは以下のapplication.jsに以下の追記をします。

packs/application.js
・
・
import '../js/application'
import '../scss/application'
const images = require.context('../images', true)

次に以下のようにviwes/hello/index.html.erbを変更し、ブラウザをリロードすることで以下のように文字の色が変更されているのが確認できると思います。

index.html.erb
# このように個々のファイルの呼び出しもできる
<%= javascript_pack_tag 'hello', 'data-turbolinks-track': 'reload' %>
<h1>Hello#index</h1>
<p>Find me in app/views/hello/index.html.erb</p>
<%= image_pack_tag 'test.png' %>

image.png

左上の表示はなにか?

どうやら左上の数字の表示は調べてみるとrailsのパフォーマンスが計測できるrack-mini-profilerというgemのせいだそうです。
今回あると邪魔なので、Gemfileのrack-mini-profilerの部分を以下のように書き換えます。

Gemfile
gem 'rack-mini-profiler', '~> 2.0', require: false

そしてbundle installをすると表示が消えるはずです。

6.データベースの作成

次にデータベースの内容が表示されるか確認するために実際に一つテーブルを作ってみましょう。

terminal(コンテナ内)
rails g model Hello hello:text content:text

rails db:migrate

次にrails c で実際にカラムを追加していきます。

terminal(コンテナ内)
post = Hello.new(hello:"ohayou", content:"konnnitiha")
post.save()

そうすると実際にテーブルにカラムが追加されているのが確認できますので実際にindex.html.erb内で表示してみましょう。

image.png

controller/hello_controller.rbにまずは変更を加えます。

controller/hello_controller.rb
class HelloController < ApplicationController
  def index
    @hello = Hello.all
  end

  def create
    @get = Hello.new(content:params[:content])
    @get.save

    redirect_to("/hello/index")
  end
end

次にindex.html.erbを変更して実際に表示してみます。

index.html.erb
<%= javascript_pack_tag 'hello', 'data-turbolinks-track': 'reload' %>
<h1>Hello#index</h1>
<p>Find me in app/views/hello/index.html.erb</p>
<%= image_pack_tag 'test.png' %>

<% @hello.each do |h| %>
    <%= h.content %>
<% end %>

<%= form_tag("/hello/create") do %>
<div class="form">
    <div class="form-body">
        <textarea  name="content"></textarea>
        <input type="submit" value="挨拶を追加">
    </div>
</div>
<% end %>

routes.rbも新規登録できるように以下のように書き換えます。

Rails.application.routes.draw do
  get 'hello/index', to: 'hello#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  post 'hello/create', to: 'hello#create'
end

結果、以下のように表示されれば成功です。

image.png

rspecの導入

次にrspecの導入をします。

Gemfileに以下の記述を追記します。

Gem
・
・
gem 'rspec-rails'file
・
・

bundle installします。

terminal
docker-comopse run --rm web bundle install

コンテナにはいり以下を実行します。

terminal
rails generate rspec:install
rails db:migrate RAILS_ENV=test
rails g rspec:model hello

そしてspec/models/hello_spec.rbに以下を記述します。

hello_spec,rb
require 'rails_helper'

RSpec.describe Hello, type: :model do
    it "is valid with title, text" do
      hello = Hello.new(
        hello: "こんばんわに",
        content:"ちゃす"
        )
      expect(hello).to be_valid
    end
end

7.CircleCIの設定

rspecが通るかどうかのCircleCIの設定をします。
まず、ルートディレクトリに戻って以下のコマンドを叩いてcircleciの設定ファイルを作成します。

terminal
mkdir .circleci
touch .circleci/config.yml
.circleci/config.yml
version: 2.1

jobs:
  test_backend:
    docker:
    # dockerイメージで使っているrubyバージョンと一致させる
      - image: circleci/ruby:2.6.5-stretch-node
        environment:
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
          DB_PASSWORD: ''
      - image: circleci/postgres:9.6.2-alpine
        environment:
          POSTGRES_USER: root
          POSTGRES_DB: circleci_testdb
          POSTGRES_HOST_AUTH_METHOD: trust # パスワードなし

    working_directory: ~/repo

    steps:
      - checkout

      # restore gem from cache
      - restore_cache:
          keys:
            - gem-cache-v1-{{ checksum "~/repo/Gemfile.lock" }}
            - gem-cache-v1-
          working_directory: ~/repo

      # gem install
      - run:
          command: |
            gem install bundler
            bundle config set path 'vendor/bundle'
            bundle install --jobs=4 --retry=3
          working_directory: ~/repo

      - save_cache:
          key: gem-cache-v1-{{ checksum "~/repo/Gemfile.lock" }}
          paths:
            - ~/repo/backend/vendor/bundle
          working_directory: ~/repo

      # Database setup
      - run:
          command: bundle exec rails db:create
          working_directory: ~/repo
      - run:
          command: bundle exec rails db:migrate
          working_directory: ~/repo

      - run:
          name: create directory to store test results
          command: mkdir /tmp/test-results
          working_directory: ~/repo

      # run tests
      - run:
          name: RSpec
          command: |
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
          working_directory: ~/repo

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
      - store_artifacts:
          path: /home/circleci/repo/tmp/screenshots


workflows:
  version: 2
  test:
    jobs:
      - test_backend:
          filters:
            branches:
              ignore: develop

githubにリポジトリを作成すると同時にcircleciのサイトで対称のリポジトリをsetupを押して初期化して自動テストが実行できるように設定します。
master branchにpushした結果以下のようにsuccessとなれば成功です。

image.png

8.本番環境への動作確認

webpackerが効かない問題が発生して大幅に時間を使ってしまいました。
以下の記事のおかげで解決することができました。

Webpacker の基本的な仕組み

結果としてはRAILS_SERVE_STATIC_FILES=1などを予め環境変数に設定してあげないといけないそうです。
本番環境用に新しくdocker-compose.prod.ymlを作成します。

docker-compose.prod.yml
version: '3.5'
services:
  web:
    build: .
    container_name: rails-web
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
      - /var/tmp
    environment:
      RAILS_ENV: production
      RAILS_SERVE_STATIC_FILES: 1
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
    networks:
      - sample

  db:
    image: postgres:9.6.2-alpine
    container_name: rails-db
    environment:
      POSTGRES_PASSWORD: 12345678
      TZ: "Asia/Tokyo"
    volumes:
      - pg-data:/var/lib/postgresql/data
    restart: always
    ports:
      - 5432:5432
    networks:
      - sample

networks:
  sample:

volumes:
  pg-data:

以下のコマンドでコンパイルしていきます。

terminal
docker-compose -f docker-compose.prod.yml up -d

# コンテナに入る
docker-compose exec web bash

bin/rails assets:precompile RAILS_ENV=production

rails db:create
rails db:migrate

ちなみにデータベースの設定は以下のように変えています。

database.yml
production:
  <<: *default
  database: app_production
  username: <%= ENV['DB_USERNAME'] || 'postgres' %>
  host: <%= ENV['DB_HOST'] || 'db' %>
  password: <%= ENV['DB_PASSWORD'] || '12345678' %>

また、routes.rbにrootを設定しないとアクセスされた際のはじめに表示するページが迷子になってしまします。
以下のように適当にrootを設定してあげましょう。

routes
Rails.application.routes.draw do
  root  'hello#index'  #この1行を追加
  get 'hello/index', to: 'hello#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  post 'hello/create', to: 'hello#create'
end

本番環境でも実際に動いているのを確認できました。

image.png

9.herokuへのデプロイ

最後にherokuにデプロイしていきます。
ここが一番つまりました。

まずは以下のコマンドを叩いてherokuアプリの基本設定をしていきます。

terminal
heroku login

heroku container:login

heroku create つけたいアプリ名

次に、precompileをしてwebpackでコンパイルします。

terminal
docker-compose run --rm web rails assets:precompile RAILS_ENV=production

次にDockerfileを本番用に書き換えます。

Dockerfile
FROM ruby:2.6.5

RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get update && \
    apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

RUN apt-get update -qq && \
    apt-get install -y build-essential \
    libpq-dev \
    sudo  \
    vim

RUN yarn add node-sass

WORKDIR /app
RUN mkdir -p tmp/sockets
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

# COPY entrypoint.sh /usr/bin/
# RUN chmod +x /usr/bin/entrypoint.sh
# ENTRYPOINT ["entrypoint.sh"]

# 以下の記述を追加
ENV RAILS_ENV=production

# この記述を追加
CMD bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"

次にデータベースの追加をします。
以下のコマンドを叩き、アプリにデータベースを設定します。その際に、productionの設定のためにdatabase.ymlも変更します。

terminal
heroku addons:create heroku-postgresql:hobby-dev
database.yml
production:
  <<: *default
  database: app_production
  username: <%= ENV['DB_USERNAME'] || 'postgres' %>
  host: <%= ENV['DB_HOST'] || 'db' %>
  password: <%= ENV['DB_PASSWORD'] || '12345678' %>
  url: <%= ENV['DATABASE_URL'] %>

または (いずれかで動作確認済み)

database.yml
production:
  <<: *default
  database: app_production
  url: <%= ENV['DATABASE_URL'] %>

そして、コンテナをアップします。

terminal
# データベースのmigrate (createは必要なし)
heroku run rails db:migrate
# webpackを反映させる
heroku run rails assets:precompile
# コンテナを起動する
heroku container:release web
# アプリを開く
heroku open

エラーハンドリング

herokuでのアプリの起動の際にいくつかのエラーに悩まされたのでその解決法を書きます。

Web process failed to bind to $PORT within 60 seconds of launch

flaskの記事で書いたのと同様、herokuに静的にportを割り当てていたのが問題
以下のようにすることで解決しました。

Dockerfile
- CMD rails server -b 0.0.0.0

+ CMD bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"

Failed binding port to Ruby application on Heroku
を参考

Blocked host

こちらのエラーはRails6から発生するエラーらしくアクセスできるホストが限定されるために起きるエラーらしいです。
config/environments/development.rbに以下の記述を追記することで解決できます。

development.rb
config.hosts.clear

Rails6の本番環境でBlocked hostエラーが発生したときの対処法

Webpackがproductionでコンパイルされない問題

これが一番つらかったです。
結論から言うとconfig/environments/production.rbノ設定が問題だそうです。
RAILS_SERVE_STATIC_FILESという環境変数が設定されていないとコンパイル後のファイルの読み込みができないそうです。そのため、Dockerfileに環境変数を設定するかそもそもconfig.public_file_server.enabledをtrueにしておくのが良いかと思われます。

Dockerfile
# RAILS_SERVE_STATIC_FILESを適当に環境変数として設定する
ENV RAILS_SERVE_STATIC_FILES=1
config/environments/production.rb
- config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.public_file_server.enabled = true

以上をすべてすれば、以下のようにheroku上でもアプリが展開できているのが確かめることができます。

10.CircleCIで自動でプロイする

masterブランチにpushされた際にherokuへのデプロイが完了するようにcircleciを変更する。

最終的に作成した.circleci/config.ymlは以下のようになる。

.circleci/config.yml
version: 2.1

orbs:
  heroku: circleci/heroku@1.2.5

jobs:
  test_backend:
    docker:
    # dockerイメージで使っているrubyバージョンと一致させる
      - image: circleci/ruby:2.6.5-stretch-node
        environment:
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
          DB_PASSWORD: ''
      - image: circleci/postgres:9.6.2-alpine
        environment:
          POSTGRES_USER: root
          POSTGRES_DB: circleci_testdb
          POSTGRES_HOST_AUTH_METHOD: trust # パスワードなし

    working_directory: ~/repo

    steps:
      - checkout

      # restore gem from cache
      - restore_cache:
          keys:
            - gem-cache-v1-{{ checksum "~/repo/Gemfile.lock" }}
            - gem-cache-v1-
          working_directory: ~/repo

      # gem install
      - run:
          command: |
            gem install bundler
            bundle config set path 'vendor/bundle'
            bundle install --jobs=4 --retry=3
          working_directory: ~/repo

      - save_cache:
          key: gem-cache-v1-{{ checksum "~/repo/Gemfile.lock" }}
          paths:
            - ~/repo/backend/vendor/bundle
          working_directory: ~/repo

      # Database setup
      - run:
          command: bundle exec rails db:create
          working_directory: ~/repo
      - run:
          command: bundle exec rails db:migrate
          working_directory: ~/repo

      - run:
          name: create directory to store test results
          command: mkdir /tmp/test-results
          working_directory: ~/repo

      # run tests
      - run:
          name: RSpec
          command: |
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
          working_directory: ~/repo

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
      - store_artifacts:
          path: /home/circleci/repo/tmp/screenshots

  deploy:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - run:
          name: Storing previous commit
          command: |
            git rev-parse HEAD > ./commit.txt
      - heroku/install
      - setup_remote_docker:
          version: 18.06.0-ce
      - run:
          name: Pushing to heroku registry
          command: |
            heroku container:login
            heroku container:push web -a $HEROKU_APP_NAME
            heroku container:release web -a $HEROKU_APP_NAME
      - run:
         name: db  migration
         command: |
            heroku run rails db:migrate --app ${HEROKU_APP_NAME}


workflows:
  version: 2
  test:
    jobs:
      - test_backend:
          filters:
            branches:
              ignore: master
  deploy:
    jobs:
      - deploy:
          # masterブランチへのpushでのみ反応
          filters:
              branches:
                  only:
                  - master

CircleCIの環境変数をいかに以下のものを設定します。

HEROKU_API_KEY : herokuのapiトークン (heroku authorizations:create で取得できる値)
HOROKU_APP_NAME : herokuのアプリにあなたがつけた名前
RAILS_MASTER_KEY : masterkeyの値

以下のようにherokuにもRAILS_MASTER_KEYの環境変数を設定しておきます。

terminal
heroku config:set RAILS_MASTER_KEY="$(< config/master.key)"

masterブランチへpushして滞り無く

エラーハンドリング

この流れをする際に以下のエラーにハマったので解決法を記します。

Rails: How to fix “Missing secret_key_base for 'production' environment”

このエラーが発生した際にはconfig/environments/production.rbで以下の記述を追記します。

config/environments/production.rb
config.require_master_key = true

ActiveSupport::MessageEncryptor::InvalidMessage

なにかの拍子にcredentials.yml.encの内容が置き換わったのが原因だと思われます。
以下のコマンドを叩き再度credentials.yml.encを作成することで解消できます。

terminal
# -eがないとエラーになる可能性あり
docker-compose run -e EDITOR=vim web bin/rails credentials:edit

Missing encryption key to decrypt file with. Ask your team for your master key and write it to /app/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].

gitにSecurity上の問題からmasterkeyが送られないことが起因するエラーです。
以下のようにherokuの環境変数にRAILS_MASTER_KEYを設定してあげれば大丈夫です。

terminal
heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

以下のように無事にcircleciが通っていることが確認できればokです。

image.png

heroku status=500 エラーについて

herokuのログを見るとstatus=500のエラーになっており特定のページが見れないErrorが出ていることを発見しました。
しかし、これだけだと何のErrorか分からない為困っていたのですが、以下の記事を参考にすることでくわしくError内容が見れるようになりました。

RailsアプリをHerokuにデプロイして、エラーがでたけど、1行しか出なくて困った時・・

やり方としてはGemfileに以下の記述を追加して、bundle updateするだけです。

Gemfile
gem 'rails_12factor', group: :production
terminal
bundle update

最後にherokuにpushするとError内容が具体的に見れるようになります。

ちなみに私の場合は以下のErrorが出ていました。

hecoku_logs_-t
ActionView::Template::Error (Webpacker can't find hello.js in /app/public/packs/manifest.json. Possible causes:

manifest.jsonが見つからないとのことなので、git似pushする際にmanifest.jsonが失われないように以下の記述を.gitignoreからコメントアウトしました。

# /public/packs
# /public/packs-test

おまけ bootstrapとjqueryの導入

以下の記事を参考にjqueryとbootstrapを導入してきます。

Rails 6にjQueryとBootstrapを入れる

まずは必要なパッケージをコンテナ内でインストールします。

terminal
yarn add jquery bootstrap popper.js

config/webpack/environment.jsを以下のように書き換えます。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')


// jQueryとBootstapのJSを使えるように
const webpack = require('webpack')
environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: 'popper.js'
  })
)

module.exports = environment

scss/applicaiton.scssとpacks/application.jsにイカの記述を書き加えます。

scss/application.scss
@import "~bootstrap/scss/bootstrap";
packs/applicationn.js
・
・
import "bootstrap/dist/css/bootstrap.min.css";
// BootstrapのJavaScript側の機能を読み込む
import "bootstrap";
・
・

参考文献

[Rails] Webpacker の使い方

Railsのパフォーマンス調査でrack-mini-profilerを使ってみました。

PostgreSQL pgAdmin 4の使い方(起動からデータ参照)

CircleCI config.yml ひな型 Rails 6 / PostgreSQL / Rspec

Webpacker の基本的な仕組み

Failed binding port to Ruby application on Heroku

Rails6の本番環境でBlocked hostエラーが発生したときの対処法

Rails 6にjQueryとBootstrapを入れる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vision API(LABEL_DETECTION)】seeds.rbにも反映させる

はじめに

今回は、先日初めて実装したGoogle Vision APIの機能について自身の備忘録として残しておきたいと思います。
本記事では、Google Cloud Platformの登録につきましては省略させて頂いておりますので、ご了承ください。

Google Vision APIとは

Google社が提供している、画像解析ツールのことを指します。
画像の中に写りこんでいる文字やロゴを認識したり、不適切な画像でないか判断してくれたりと、種類は様々あります。
その中で、今回私が取り組んだのは、LABEL_DETECTIONというもので、画像を認識し自動でラベル(タグ)を発行してくれるという機能になります。
こちらを活用し、写真付きの投稿記事に自動でタグが付くよう実装しております。

見本

「Food」「Cake decorating」「Cake」の茶色の部分が今回作成していく自動生成されるラベルになります。
※デフォルト検出が英語になっており、日本語にて検出する方法は現在模索中です。
※また、cssにてデザインは作成しておりますが、今回デザインにつきましてのご説明は記載しておりませんので、ご了承くださいますようお願い致します。

スクリーンショット 2021-02-21 0.26.54.png

今回使用するモデル

※必要最低限のカラムのみで構成しております。

モデル名 カラム名
User id
name
モデル名 カラム名
Tweet id
user_id
image_id
title
content
モデル名 カラム名
Tag tweet_id
name

※こちらのTagモデルが、LABEL_DETECTIONの機能で発行されるタグ用のモデル。
そして、nameのカラムの部分が発行されるタグ名に当たります。

config/initializers/refile.rb

上記のディレクトリの階層に、refile.rbというファイルを作成します。

config/initializers/refile.rb
Refile.backends['store'] = Refile::Backend::FileSystem.new('public/uploads/')


Gemfile

Google API keyの情報が必要になり、keyの値は個人情報になりますので、環境変数に置き換えて使用します。
そのため、以下のgemを追加しbundle installします。

Gemfile.
gem 'dotenv-rails'


.env

もし、.envファイルを今回初めて作成されるようであれば、ご自身のアプリケーション直下に作成してください。
また、GitHubにpushする際に公開されてしまいますと、個人情報の流出に繋がりますので、必ず.gitignoreファイルに「/.env」を追記しましょう。

.env
GOOGLE_API_KEY="ご自身のGOOGLE_API_KEYをコピペする"
.gitignore
/.env


lib/vision.rb

上記のディレクトリの階層に、vision.rbというファイルを作成します。
基本的には、こちらをコピペしていただければ動作するかと思います。

lib/vision.rb
require 'base64'
require 'json'
require 'net/https'

module Vision
  class << self
    def get_image_data(image_file)
      # APIのURL作成
      api_url = "https://vision.googleapis.com/v1/images:annotate?key=#{ENV['GOOGLE_API_KEY']}"

      # 画像をbase64にエンコード
      base64_image = Base64.encode64(open("#{Rails.root}/public/uploads/#{image_file.id}").read)

      # APIリクエスト用のJSONパラメータ
      params = {
        requests: [{
          image: {
            content: base64_image
          },
          features: [
            {
              type: 'LABEL_DETECTION'
            }
          ]
        }]
      }.to_json

      # Google Cloud Vision APIにリクエスト
      uri = URI.parse(api_url)
      https = Net::HTTP.new(uri.host, uri.port)
      https.use_ssl = true
      request = Net::HTTP::Post.new(uri.request_uri)
      request['Content-Type'] = 'application/json'
      response = https.request(request, params)
      response_body = JSON.parse(response.body)
      # APIレスポンス出力
      if (error = response_body['responses'][0]['error']).present?
        raise error['message']
      else
        # take(3)の部分が取り出すラベル数になります。
        # 必要に応じて変更してください。
        response_body['responses'][0]['labelAnnotations'].pluck('description').take(3)
      end
    end
  end
end


config/application.rb

先ほど作成したlib以下のファイルを読み込むために、以下の1行を追記してください。
config.paths.add 'lib', eager_load: true

config/application.rb
module (アプリケーション名)
  class Application < Rails::Application
    config.load_defaults 5.2
    # 追加
    config.paths.add 'lib', eager_load: true
  end
end


tweet_controller.rb

tweet_controller.rb
  def create
    tweet = Tweet.new(tweet_params)
    tweet.save
    # ラベルを作成するために追記
    tags = Vision.get_image_data(tweet.image)    
    tags.each do |tag|
      tweet.tags.create(name: tag)
    end
    redirect_to tweets_path


tweet/index.html.erb

最後にビューにラベルを取得するための記述を追加します。
※ 投稿した記事が保存される時にラベルは自動生成されるので、ビューで変更を加えるのはラベルを表示させたい画面のみになります。

tweet/index.html.erb
<% @tweets.each do |tweet| %>
 <% tweet.tags.each do |tag| %>
  # lib/vision.rbで指定した数分だけラベルを取得
  <%= tag.name %>
 <% end %>
 <%= tweet.title %>
 <%= tweet.user.name %>
<% end %>


今回一番書きたかったポイント

長々と書いてしまいましたが、一番記事として残しておきたかった部分はこちらからになります...
こちらまでお目通しいただけた方がおりましたら、長々とお付き合いいただきありがとうございます。

seeds.rbでデータを作成しても、このままでは反映されない!

実はデプロイ後を想定して、seeds.rbには以下のような形で、数十件分のtweetを記述していました。
しかし、seeds.rbでデータを作成する際は、コントローラーもアクションも呼び出されないため、ラベルは発行されません。

seeds.rb
# 一部抜粋
Tweet.create!(
 user_id: 1,
 title: "隣町のケーキ屋!",
 content:"おしゃれで可愛いケーキがいっぱい。",
 image: File.open('./app/assets/images/tiramisu.jpg')
)


解決方法

①まず、tweet.contoroller.rbに追記したラベル作成(4〜8行目)の部分を削除します。

②ラベル作成の記述をtweetモデルに記述します。
意味:
● after_create :create_tags
  tweetが生成された後にtagsを生成する

● def create_tags以降
  実際にラベルを生成する記述

(補足)
先ほどのコントローラーの記述では、vision_tagsの部分はtagsのみになっております。
変更している理由は以下の通りです。
開発環境では、全てtagsのみで問題なかったのですが、デプロイ後の本番環境でseeds.rbを読み込もうとすると、任意で命名してるtagsの部分と、テーブル名のtagsの部分(tags.create...)で判別がつかない事によりエラーの原因になってしまったようでした。
※現状、開発環境と本番環境での差異については詳しく分かっておりません。

models/tweet.rb
belongs_to :user
has_many :tags, dependent: :destroy
attachment :image

# コールバックを使用
after_create :create_tags

def create_tags
  # Vision APIのLABEL保存の記述
  vision_tags = Vision.get_image_data(self.image)
  vision_tags.each do |tag|
    tags.create(name: tag)
  end
end

● コールバック

何かのイベント発生時に毎回実行されるコードのことを指します。
「〜をする前に実行する」、「〜をした後に実行する」、これらを定義するものになります。

モデルとコントローラーどちらへの記述が良いのか

結論からいうと、モデルが良いそうです!
(メンターさんより教わった情報を参考にさせて頂いております。)
コントローラーはなるべくスマートに最低限にを意識し、モデルをうまく活用すると良いとご教授いただきました。

終わり

今回は以上になります。
最後の部分に繋げるべく、一から長々と執筆してしまいました。
大変長くなってしまい、読みづらい部分も多いかと思います。申し訳ございません。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む