20200331のRailsに関する記事は16件です。

dockerでGemfile(gem追加)更新したら後、データベースが消えた、gemが反映しない

サーバーを再起動してますか????

#dockerではない通常開発の場合

dockerなし
control c でサーバー終了
rails s

これでgem反映します。

ということはdockerもサーバーを再起動しなければいけない。

dockerあり
$ docker-compose restart

これで反映されます。

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

仮想環境でRails API × Nuxt.jsのアプリケーション開発をしたいが、まずブラウザに表示されない!

自己紹介

  • 現在プログラミングの学習中の者です
  • 言い回しや知識に関して、諸所間違い等あるかと思います
  • その際は、ご指摘いただけますと幸いです

やりたいこと

RailsをAPIとして使用し、Nuxt.jsをフロント側に使用する開発において、
それらを仮想環境で開発を開始すること。

具体的には、@saongtx7様が書いてくださった
こちらの記事を仮想環境で進めたいと思ったことがきっかけです。

[入門]Rails API × Nuxt SPA × Firebase Authで作る Todo Appチュートリアル
https://qiita.com/saongtx7/items/d97ef5aec393e704fd3f

本当に素晴らしいチュートリアルでした。
この場を借りて感謝申し上げます。

問題点

私の場合このような問題が起こりました。

  • Rails側でポート指定をしてもブラウザに表在されない(rails s -b 5000)
  • Rails側でポートを指定せずに起動した場合、Nuxt.jsとポート番号が競合する

結論

1.Vagrantfileへ追記をする

まず、仮想環境のあるディレクトリに移動してください。
Vagrantfileがあると思いますのでテキストエディターで起動し、以下の追記をお願いします。

Vagrantfile

//省略
  config.vm.network "forwarded_port", guest: 3000, host: 3000
  config.vm.network "forwarded_port", guest: 5000, host: 5000 #追記!
//省略

2.Railsで起動をする際に-b 0.0.0.0を指定する。

大前提としてrails new --apiができており、アプリケーションのディレクトリは完成しているものとします。所定の場所に移動したら、ターミナルで以下を入力し、起動してください。

rails s -b 0.0.0.0 -p 5000

3.Nuxt.jsのpackage.jsonを書き換える

こちらも大前提として、npx create-nuxt-appができているものとします。所定の場所に移動する前に、テキストエディターでディレクトリを開き、直下のpackage.jsonを開いてください。

package.json
//省略

  "scripts": {
    "dev": "HOST=0.0.0.0 PORT=3000 nuxt", //変更後
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },

//省略

これができたら、通常通り
npm run dev
(yarnでインストールしている方は違うかも…?)

そして、locallhost:3000にアクセス。
自分はこれで解決し、無事仮想環境でもRails/Nuxtの同時起動ができました!

参考

初学者ながら加筆させていただいた部分としましては、

  • Rails s時の書き方
  • Vagrantfileの書き方

以上になります。

超独学プログラマ様の以下の記事に救われました。
本当に何日も悩みましたので・・・

Nuxt.jsからRailsへ、初めてのapi通信でHelloを表示しよう
https://blog.cloud-acct.com/posts/spa-nuxt-firstapi/

最後に、初投稿となりますので至らない点もあるかと思います。
何かお気づきの点がございましたら、後学のためご指摘をお願いいたします。
この記事が誰かのためになれば幸いです。

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

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

はじめに

ポートフォリオで実装必須な、「テストアカウントでの簡単ログイン」
ポートフォリオくらいでしか使用しないため、記事が少なく、deviseのコントローラ作成方法、エラーの対処法についても、いろんな記事に散らばっていたので、まとめていきます。

どなたかの参考になれば幸いです!

また、私自身、参考記事を元に手探りで実装したので、
誤っている部分があればご指摘いただけますと幸いです:hatched_chick:

開発環境

  • Ruby 2.5.1
  • Rails 5.0.7.2

もくじ

1. devise用コントローラーの作成
  (1)コントローラ作成コマンド
  (2)作成したコントローラの確認
  (3)ルーティング の設定
2. SessionsControllerの編集
  (1)Userモデルにクラスメソッドguestメソッドを作成
  (2)コントローラにnew_guestアクションを定義
  (3)ハマりポイントを解説
3. 参考記事

1.devise用コントローラーの作成

ログインするためのメソッドを記載するコントローラが必要なので、作成します。

deviseの導入方法が不明な方は、こちらを参考にしてください。
(最初〜「4.viewの編集」まででOKです。)
[Rails] deviseの使い方(rails5版)

(1)コントーラーの作成

ターミナルで、簡単ログインを導入したいアプリのディレクトリに移動。
以下コマンドを実行。

ターミナル
$rails g devise:controllers users

(2)作成したコントローラを確認

上記コマンドで、「app/controllers/users」ディレクトリの中に、以下4つのコントローラができたことを確認します。

  • confirmations_controller.rb
  • passwords_controller.rb
  • registrations_controller.rb (ユーザー新規登録に関するコントローラ)
  • sessions_controller.rb (ログインに関するコントローラ)

今回は、sessions_controller.rbを使用します。

(3)ルーティングの設定

以下、コードを追記してください。

routes.rb
  devise_for :users, :controllers => {
    :registrations => 'users/registrations',
    :sessions => 'users/sessions',
   }

  devise_scope :user do
    get "sign_in", :to => "users/sessions#new"
    get "sign_out", :to => "users/sessions#destroy" 
    post 'users/guest_sign_in', to: 'users/sessions#new_guest'
  end

ポイント

  • 「resources :users, only: :show」の記述があるときは、上記コードより下に記述。 (上に書くとエラーになります。2(3)ハマりポイントで説明します。)
  • devese_for :users の記述がすでにある時は、削除

2.SessionsControllerの編集

さて、ここから、簡単ログインを実装します。

(1)Userモデルにクラスメソッドguestを作成。

user.rb
  def self.guest
    find_or_create_by(email: "test@com") do |user|
      user.password = Rails.application.secrets.test_account_pass
    end
  end
  • クラスメソッドなので、メソッド名にselfがつきます。

  • find_or_create_byメソッドを使用することで、テストアカウントが削除されてしまってもエラー発生を防ぐことができます。
    (テストアカウントが存在すれば、find_byメソッドとして()内の検索ワードで、アカウントを検索。
    存在しなければcreateメソッドとして機能し、()内を属性値として、アカウントを作成してくれます。)

  • パスワードはuser.passwordの記述で設定。
    パスワードは、環境変数に格納しました。
    (テストアカウントなので、パスワードが漏れても問題ないのですが、念の為。
    環境変数の設定の仕方は、別記事にまとめます。)

  • パスワードを直打ちする場合は、「user.password = 12345678」のように、記載します。

(2)sessions_controller.rbに、new_guestメソッドを定義

sessions_controller.rb
  def new_guest
    user = User.guest
    sign_in user
    redirect_to root_path, notice: "Thank you for your testing!"
  end
  • (1)で作成したguestメソッドを使用。テストアカウントのemail、password情報を持ったuserを作成。
  • deviseのメソッドであるsigh_inメソッドでログイン
  • redirect先を決定。ログインが成功したときの、メッセージをnotice: 以下の記載。(ここでは、「notice: "Thank you for your testing!"」)

(✴︎)ハマりポイントの解説

上記(1)(2)を設定して意気揚々とブラウザを開いたところ、以下のエラー!!

スクリーンショット 2020-03-31 12.45.38.png

ターミナルを見ると、無限ループが発生しているようです。
スクリーンショット 2020-03-31 12.49.00.png

これは、ルーティングを書く順番が誤っているためのエラーです。

sing_inアクションを実行すると、users#showのルーティングが上にあるため、/users/の後に続く文字列を、idだと認識。
そのため、usersのshowアクション(users/:id)が実行されてしまいます。
でも、showアクションはログインしていないと、閲覧できないので、またログイン画面→users#showの無限ループになるようです。

「resources :users, only: :show」 の位置を以下の変更してあげたら、
無事に解消しました。

「localhost でリダイレクトが繰り返し行われました。」はルーティングのせいだった、Ruby on Rails エラーの分析

routes.rb
  devise_for :users, :controllers => {
    :registrations => 'users/registrations',
    :sessions => 'users/sessions',
   }

  devise_scope :user do
    get "sign_in", :to => "users/sessions#new"
    get "sign_out", :to => "users/sessions#destroy" 
    post 'users/guest_sign_in', to: 'users/sessions#new_guest'
  end

  resources :users, only: :show 
 ##devise_for :usersや、deveise_scope :userより下に書くのがポイント

(3)簡単ログインのためのリンクをはる。

最後に、ヘッダー or トップページ or ログインページに以下のリンクを貼り付けてあげると完成です!
(私は、ログインページに貼りました)

sessions/new.html.haml
  -#hamlの場合
  = link_to 'Log in for TEST', users_guest_sign_in_path, method: :post

  -#htmlの場合
  <%= link_to 'Log in for TEST', users_guest_sign_in_path, method: :post %>

 login画面.png

以上で簡単ログインの実装は完了です!!

その他、テストユーザーの削除や、属性の変更をできないようにする方法もありますので、
知りたい方は、ぜひ以下の記事を参考にしてください!

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

最後まで、ご覧いただき、ありがとうございました。

参考記事

[Rails] deviseの使い方(rails5版)
Deviseの設定手順をまとめてみた。 その2 ViewとControllerのカスタマイズ編
ゲストログイン・簡単ログイン機能の実装方法(ポートフォリオ用)
「localhost でリダイレクトが繰り返し行われました。」はルーティングのせいだった、Ruby on Rails エラーの分析

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

railsのルーティングからOpenAPI(V3)ドキュメントを自動生成・管理するツールを作成し、10ヶ月間会社で運用した話(下期編)

前回、railsのルーティングからOpenAPI(V3)ドキュメントを自動生成・管理するツールを作成し、4ヶ月間会社で運用した話(開発秘話もあるよ)
という記事で下期の進捗も報告すると書いたのでその記事になります。

使用したツール

自分で作ったr2-oasです。

4ヶ月の実績

※会社の許可をとりデータを載せております。

全体の実績

赤線が目標ライン。目標は、5個/週 です。

スクリーンショット 2020-03-30 11.04.36.png

各週の実績

10月の中旬に進捗がないのは、他のプロジェクトを引き継ぐために必死だったからです。
着手する余力がなかったです。12/30の週に進捗がないのは正月休み。その他進捗がなかったのは

スクリーンショット 2020-03-30 11.04.54.png

個人の実績

チームとの話し合いでAPIドキュメント書いていくのはシンドイとなったので個人でコツコツやってました。

私の9ヶ月の進捗実績は、7.56個/週でした。
下期だけの進捗だと 3.8個/週でした。

最初、週10個/週を目標にやっていたおかげもあってそんなにひどい結果にならずにすんだとはいえ、目標未達成です。

残念です。

スクリーンショット 2020-03-30 10.57.51.png

進捗管理表

これまで乗せてきたグラフや表はこの進捗管理表から自動生成されてます。
pathsファイル というのがrailsで言うところのコントローラーに対応してます。
後で紹介しますが、r2-oas は「コントローラー毎にAPIドキュメントが書ける」特徴があります。

スクリーンショット 2020-03-30 11.02.38.png

まとめ

下期は個人でコツコツやってました。途中でなげだすのはよくないと思うので、上司と話あって継続で5個/週でやっていきます。

項目 実績/目標
個人合計(個) 189個
個人平均(個/週) 7.58個/週
下期個人平均(個/週) 3.8個/週

r2-oasのチュートリアル

r2-oas ってなんだよ。って人向けのチュートリアルです。r2-oasが得意とすることは既に完成されたAPIのAPIドキュメントを自動で作成することです。そしてコードベースで書いていかないので、途中からでも始められるし、途中でやめやすいというのが特徴です。

rails6でAPIドキュメントを作ってみましょう。

SwaggerUIやSwaggerEditorで開いたりする場合は以下の準備が必要です。

$ brew cask install chromedriver
$ docker pull swaggerapi/swagger-ui:latest
$ docker pull swaggerapi/swagger-editor:latest

rails newしたら、Gemfileのdevelopmentに r2-oas を追加してください。

group :development do
 gem 'r2-oas'
end

準備

$ rails _6.0.0_ new example-600 -d mysql --skip-bundle
$ cd example-600
$ bundle install --path vendor/bundle
$ # mysql2のエラーが出るときは以下を実行して、bundle installをやり直す。
$ # bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"

scaffoldで適当にルーティングを作成する。

$ bundle exec rails g scaffold user name:string age:integer
$ bundle exec rails g scaffold task status:string content:string
$ bundle exec rails g scaffold Api/V1/Account status:string content:string
$ bundle exec rails g scaffold Api/V2/CustomPost status:string content:string

一旦コミットする

$ git init
$ git add . && git commit -nm "initial commit "
$ # OpenAPI(V3)形式のドキュメントの雛形生成
$ bundle exec rake routes:oas:docs
# ドキュメントはコロコロ変わるのでgitignoreに追加する。
$ echo 'oas_docs/oas_doc.yml' >> .gitignore
$ # 一旦コミットする
$ git add . && git commit -nm "generate docs"

SwaggerEditor(UI)で開く。 PATHS_FILE という環境変数を指定したほうが最小限のドキュメントだけビルドされるので動作は軽いです。

$ bundle exec rake routes:oas:editor
$ # もしくは
$ # こんな風にpathsファイル毎に開くこともできます。環境変数のPATHS_FILEを指定します。
$ PATHS_FILE=oas_docs/src/paths/task.yml bundle exec rake routes:oas:editor

ここでは PATHS_FILE を指定して開いてみましょう。

image.png

  • GET /taskssummary を「タスク一覧取得」に修正
  • GET /tasks422 に関しての情報をすべて削除

以上ができたら、ターミナルに戻り Ctrl+C を押してみましょう。

wait for signal trap ...
^CI, [2020-03-30T11:52:20.903111 #21328]  INFO -- : [Analyze OAS file] start
I, [2020-03-30T11:52:20.908564 #21328]  INFO -- : [Analyze OAS file (tags)] start
I, [2020-03-30T11:52:20.910406 #21328]  INFO -- :   Write schema file:  /Users/yukihirop/RubyProjects/example-600/oas_docs/src/tags.yml
I, [2020-03-30T11:52:20.910440 #21328]  INFO -- : [Analyze OAS file (tags)] end
I, [2020-03-30T11:52:20.910450 #21328]  INFO -- : [Analyze OAS file (paths)] start
I, [2020-03-30T11:52:20.915803 #21328]  INFO -- :   Write schema file:  /Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/task.yml
I, [2020-03-30T11:52:20.915851 #21328]  INFO -- : [Analyze OAS file (paths)] end
I, [2020-03-30T11:52:20.915865 #21328]  INFO -- : [Analyze OAS file (components)] start
I, [2020-03-30T11:52:20.915883 #21328]  INFO -- : [Analyze OAS file (components/schemas)] start
I, [2020-03-30T11:52:20.918129 #21328]  INFO -- :   Write schema file:  /Users/yukihirop/RubyProjects/example-600/oas_docs/src/components/schemas/task.yml
I, [2020-03-30T11:52:20.918168 #21328]  INFO -- : [Analyze OAS file (components/schemas)] end
I, [2020-03-30T11:52:20.918182 #21328]  INFO -- : [Analyze OAS file (components/requestBodies)] start
I, [2020-03-30T11:52:20.920313 #21328]  INFO -- :   Write schema file:  /Users/yukihirop/RubyProjects/example-600/oas_docs/src/components/requestBodies/task.yml
I, [2020-03-30T11:52:20.920364 #21328]  INFO -- : [Analyze OAS file (components/requestBodies)] end
I, [2020-03-30T11:52:20.920381 #21328]  INFO -- : [Analyze OAS file (components/securitySchemes)] start
I, [2020-03-30T11:52:20.920889 #21328]  INFO -- : [Analyze OAS file (components/securitySchemes)] end
I, [2020-03-30T11:52:20.920908 #21328]  INFO -- : [Analyze OAS file (components/parameters)] start
I, [2020-03-30T11:52:20.921348 #21328]  INFO -- : [Analyze OAS file (components/parameters)] end
I, [2020-03-30T11:52:20.921365 #21328]  INFO -- : [Analyze OAS file (components/responses)] start
I, [2020-03-30T11:52:20.921804 #21328]  INFO -- : [Analyze OAS file (components/responses)] end
I, [2020-03-30T11:52:20.921820 #21328]  INFO -- : [Analyze OAS file (components/examples)] start
I, [2020-03-30T11:52:20.922285 #21328]  INFO -- : [Analyze OAS file (components/examples)] end
I, [2020-03-30T11:52:20.934988 #21328]  INFO -- : [Analyze OAS file (components/headers)] start
I, [2020-03-30T11:52:20.940521 #21328]  INFO -- : [Analyze OAS file (components/headers)] end
I, [2020-03-30T11:52:20.940773 #21328]  INFO -- : [Analyze OAS file (components/links)] start
I, [2020-03-30T11:52:20.944804 #21328]  INFO -- : [Analyze OAS file (components/links)] end
I, [2020-03-30T11:52:20.944943 #21328]  INFO -- : [Analyze OAS file (components/callbacks)] start
I, [2020-03-30T11:52:20.945420 #21328]  INFO -- : [Analyze OAS file (components/callbacks)] end
I, [2020-03-30T11:52:20.945540 #21328]  INFO -- : [Analyze OAS file (components)] end
I, [2020-03-30T11:52:20.945569 #21328]  INFO -- : [Analyze OAS file] end
I, [2020-03-30T11:52:21.162340 #21328]  INFO -- : container id: fddeedcd579c4e53ec549778ae5dc3888796b011543e504f77b93706d4dac232 removed
I, [2020-03-30T11:52:21.162463 #21328]  INFO -- : [R2-OAS] end

こんな感じにログが流れて差分を確認すると以下の様になっているはずです。

image

これをcommitしましょう。

git add . && git commit -nm "Write GET /tasks"

このようにAPIドキュメントを書いていきます。

完成したAPIドキュメントだけ表示したい

そういう場合もあるでしょう。そのためにr2-oasには、.paths ファイルに完成したpathsのyamlファイルを書いていきます。

今、oas_docs/src/paths/task.yml が完成したとすると、

# pathsディレクトリ以下の相対パスを書きます。
echo 'task.yml' >> .paths

こうすることで環境変数のPATHS_FILEが指定される時以外は、oas_docs/src/paths/task.ymlに関してのAPIドキュメントに関してだけしか表示されなくなります。

$ bundle exec rake routes:oas:ui
I, [2020-03-30T12:00:39.428227 #22297]  INFO -- : [R2-OAS] start
I, [2020-03-30T12:00:39.543178 #22297]  INFO -- : [Generate OAS schema files] start
I, [2020-03-30T12:00:39.543224 #22297]  INFO -- : [Generate OAS schema files] end
I, [2020-03-30T12:00:39.543237 #22297]  INFO -- : [Generate OAS docs from schema files] start
I, [2020-03-30T12:00:39.543595 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/openapi.yml
I, [2020-03-30T12:00:39.543683 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/external_docs.yml
I, [2020-03-30T12:00:39.543797 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/tags.yml
I, [2020-03-30T12:00:39.543882 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/info.yml
I, [2020-03-30T12:00:39.544091 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/servers.yml
I, [2020-03-30T12:00:39.544328 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/task.yml
I, [2020-03-30T12:00:39.544437 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/components/schemas/task.yml
I, [2020-03-30T12:00:39.544537 #22297]  INFO -- :  Use schema file:     /Users/yukihirop/RubyProjects/example-600/oas_docs/src/components/requestBodies/task.yml
I, [2020-03-30T12:00:39.552871 #22297]  INFO -- : [Generate OAS docs from schema files] end

wait for single trap ...

image.png

pahtsファイルの一覧が知りたい

PATHS_FILEを指定して書くと軽くなるといってもpathsファイルのyamlファイルのpathを調べるのがだるい。
その問題のために一覧を出すコマンドがあります。(ただ動作が遅いのでshellスクリプトで解決したがいいかもしれません。)

$ bundle exec rake routes:oas:paths_ls
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/action_mailbox/ingresses/postmark/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/action_mailbox/ingresses/sendgrid/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/action_mailbox/ingresses/mandrill/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/action_mailbox/ingresses/mailgun/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/action_mailbox/ingresses/relay/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/active_storage/disk.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/active_storage/representation.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/active_storage/blob.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/active_storage/direct_upload.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/user.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/api/v1/account.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/api/v2/custom_post.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/task.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/rails/conductor/action_mailbox/inbound_email.yml
/Users/yukihirop/RubyProjects/example-600/oas_docs/src/paths/rails/conductor/action_mailbox/reroute.yml

cool-pecoを使った便利なスクリプト

pecoを便利につかうためのcool-pecoを使って便利スクリプトを書いておくといいです。

api-editorapi-ui で pathsファイルを選択してEnterでSwaggerを開くことができるようになります。

pecoでAPIドキュメント編集

#pecoでAPIドキュメント編集
function peco-api-editor(){
  local unit_paths_file_path=$(find . -type f -name "*.yml" | grep paths | peco)
  if [ -n "$unit_paths_file_path" ]; then
    res="PATHS_FILE=$unit_paths_file_path bundle exec rake routes:oas:editor"
  fi
  _cool-peco-insert-command-line $res
}
alias api-editor=peco-api-editor

pecoでAPIドキュメント閲覧

#pecoでAPIドキュメント閲覧
function peco-api-ui(){
  local unit_paths_file_path=$(find . -type f -name "*.yml" | grep paths | peco)
  if [ -n "$unit_paths_file_path" ]; then
    res="PATHS_FILE=$unit_paths_file_path bundle exec rake routes:oas:ui"
  fi
  _cool-peco-insert-command-line $res
}
alias api-ui=peco-api-ui

おわりに

次回は9月に進捗を報告します。
目標は 5個/週 です。

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

Rails ウィザード形式導入について 1

はじめに

Qiitaサボっておりましたが、学習は継続してしっかりやっております!
むしろチーム開発を通じて、アプリ開発が楽しくてたまらない状態です!
しかし、サボりっぱなしは気持ちよくないので、リスタートさせてください(泣)

上述の通り、チーム開発でフリマサイトを開発致しました。
その際、ユーザーの新規登録画面でウィザード形式を導入致しましたので、内容を整理します。
もうすでにご存知の方、省略の仕方等ご存知でしたら、ご教授願います。

ウィザード形式とは

  • 世に出ているアプリ等でも見かける画面が切り替わりながらユーザー等の登録を行うもの
  • ウィザード形式とは、対話するように順番に操作が進んでいくインターフェースのこと

なぜウィザード形式が必要なのか

  • 使いやすくなる
  • UXのクオリティが高まる

私自身も「使いやすい」、「何がどこにあるのか」が分かり易いアプリは、いい印象です。
え? そんなところをクリックできたのか!
マイページに飛んで、また入力か。

それらを回避できるイメージです!

前提条件

  • 今回はuser情報に併せて、destination(住所)情報をウィザード形式で入力できる
  • deviseは導入済み
  • deviseのデフォルト状態での新規登録・ログイン機能は実装済み

では早速!

destinationsモデルの設定

  • destinations(住所)情報の実装をするので、マイグレファイルを設定します!
db/migrate/20XXXXX.rb
class CreateDestinations < ActiveRecord::Migration[5.2]
  def change
    create_table :destinations do |t|
      t.integer :post_code
      t.text :destination
      t.references :user
      t.timestamps
    end
  end
end

バリデーション、アソシエーションの設定

  • まずはdestinationの方から!
app/models/destination.rb
class Destination < ApplicationRecord
  belongs_to :user, optional: true
  validates :post_code, :destination ,presence: true
end
  • 続いて、userの方も!
app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name, :age ,presence: true
  has_one :destination
end

deviseのカスタマイズ

deviseのコントローラの作成

  • 編集(ウィザード形式実装のため)できるようにする為に、必要となります。
% rails g devise:controllers users

しかし、このままでは、devise管理化のコントローラーが呼ばれてしまいます。

呼びたいのはDevise::RegistrationsControllerを継承したUsers::RegistrationsControllerです!

% rails routes 

で確認してくださーい!

例えばですが、controller#Actionのdevise/registrations#newです。

ルーティングの設定

  • どのコントローラを参照するのか設定
config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  root to: "top#index"
end

ここでもう一度

% rails routes 

で確認してくださーい!

rails routesをすると、参照するコントローラが変更されていることが確認できます。

例えばですが、
controller#Actionのdevise/registrations#new

controller#Actionのusers/registrations#new
になっているかと思います!

今行ったことは、Devise::RegistrationsControllerを継承したUsers::RegistrationsControllerを作成したイメージです。

続きは次回!

さいごに

日々勉強中ですので、随時更新します。
皆様の復習にご活用頂けますと幸いです。

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

everydayrailsで Selenium::WebDriver::Error::WebDriverError: がでた (geckodrive)

macosです。

追記: すいません。この記事ではfirefoxを使えるようにしたのですが、chromeを使うべきだそうです。

「ドライバのダウンロード」

Unable to find Mozilla geckodriver. 
というエラー文が出ていたので、(実際に出たエラー文cpし忘れたのでこれじゃないかもしれませんがほぼこれです) geckodriver をダウンロードする。

ダウンロードは
https://github.com/mozilla/geckodriver/releases
このURLからした。

macos,linux,winなどいくつか種類があるので自分のPCにあったのを選ぶ。

「ドライバをPATHに置く」

とりあえず初心者でPATHの設定が初めてだったのでディレクトリをDesktopに作成し、そこにブラウザでダウンロードしたgeckodriveを入れといた。

実際にPATHを通す

ターミナルで一応
$ echo $PATH
でPATHを確認(しなくていいけど)すると

/いつも通り出てくるデフォルトのPATH/
になってる

$ export PATH=$PATH:/Users/ユーザー名/Desktop/geckodriverを入れたディレクトリ名
でPATHを追加

もう一回
$ echo $PATH
で確認してみると

/いつも通り出てくるデフォルトのPATH/Users/ユーザー名/Desktop/geckodriveを入れたディレクトリ名
になってた。

これでPATHは通せたと思います。(違ったらすいません)

bin/rspec spec/features/tasks_spec.rb でテストしてみる

Could not find Firefox binary (os=macosx).

Firefoxが見つからないというエラー

$ brew cask install --appdir="/Applications" firefox

でHomebrewからダウンロード

これで
$ bin/rspec spec/features/tasks_spec.rb
が成功しました

/usr/local/bin に保存する

上記の環境変数PATHを通すやり方では、ターミナルを消したときこの設定も消えてしまうので

/usr/local/bin という場所に保存する

/usr/local/bin
はユーザが追加のパッケージをインストールした場合の実行ファイルを保存する場所らしい。

デフォルトでは存在しないので /usr/local/bin をsudoコマンドを使い作る

sudo mkdir /usr/local/bin

使えるようにしたいファイルを作成したPATHへ移動させる

sudo mv 移動させたいファイル名 /usr/local/bin/新しいファイル名

保存できたか確認する

which 確認したいファイル名

出力結果は

/usr/local/bin/geckodriver

なので保存できた

ターミナルを一回消して bin/rspec spec/features/tasks_spec.rb

通ったのでとりあえずオッケー。

環境変数PATHに関しては
https://reffect.co.jp/windows/full_understanding_mac
を参考にしました。ありがとうございました。

間違っている箇所など見つけた場合コメントしていただけたら幸いです。

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

【Rails6, heroku】初めてのデプロイ(MySQL使用)

せっかく開発したならデプロイして世間に公開したくなるのが世の常ですね(?)
AWSは個人開発で使うには複雑で、ランニングコストも馬鹿にならん。けど、管理もデプロイも簡単にしたいなーってなったらherokuでデプロイしてみましょう。

対象読者

・初めてherokuでデプロイする人
・herokuでMySQLを使いたい人
・楽にデプロイしたい人

参考記事

railsアプリをgithubとherokuにpush
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】
Herokuへのデプロイ方法【Heroku+Rails+MySQL】
Railsアプリでherokuを使うときのDBをMySQLに変更する

開発環境

Ruby 2.6.5
Rails 6.0.0
MySQL 14.14

herokuでデプロイ

デプロイのためのツールをインストール

$ brew install heroku-toolbelt
とやったものの、エラーが。

Error: No available formula with the name "heroku-toolbelt" 
==> Searching for a previously deleted formula (in the last month)...
Warning: homebrew/core is shallow clone. To get complete history run:
  git -C "$(brew --repo homebrew/core)" fetch --unshallow

Error: No previously deleted formula found.
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
==> Searching taps...
==> Searching taps on GitHub...
Error: No formulae found in taps.
MasutomoMacBook-Pro:medipra user$ cd ~
MasutomoMacBook-Pro:~ user$ brew install heroku-toolbelt
Error: No available formula with the name "heroku-toolbelt" 
==> Searching for a previously deleted formula (in the last month)...
Warning: homebrew/core is shallow clone. To get complete history run:
  git -C "$(brew --repo homebrew/core)" fetch --unshallow

Error: No previously deleted formula found.
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
==> Searching taps...
==> Searching taps on GitHub...
Error: No formulae found in taps.


で、色々調べてみると、今(2020/03/27現在)、toolbeltというのは使われていないらしい。。。
今はCLI(Command Line Interface)だそうです。
なので、herokuのページにあるSet Upから以下を実行。

herokuのセットアップに従って下記を実行(macの場合)
$ brew install heroku/brew/heroku

そしたら次のエラー

Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /usr/local
Could not symlink bin/heroku
Target /usr/local/bin/heroku
already exists. You may want to remove it:
  rm '/usr/local/bin/heroku'  <=これを実行する

・・・略

To use the Heroku CLI's autocomplete --
  Via homebrew's shell completion:
    1) Follow homebrew's install instructions https://docs.brew.sh/Shell-Completion
        NOTE: For zsh, as the instructions mention, be sure compinit is autoloaded
              and called, either explicitly or via a framework like oh-my-zsh.
    2) Then run
      $ heroku autocomplete --refresh-cache <=次にこれを実行

  OR

  Use our standalone setup:
    1) Run and follow the install steps:
      $ heroku autocomplete

解決方法らしきこと書いてあったので以下を順に実行。

$ rm '/usr/local/bin/heroku'
$$ heroku autocomplete --refresh-cache

そしたらherokuのログインが求められ
無事にログインできました(^O^)/

これで、herokuのコマンドが使えるようになります。

herokuでMysqlを使えるようにする

クレジットカード情報が必要なので、まだの人は予め登録しておきましょう(無料で使えますので)

Image from Gyazo

Image from Gyazo

Ignite-freeを選べば無料で使えるようです。
Image from Gyazo

MySQLのURLを追加する

Settingsの二段目を確認しましょう。Config Varsです。
ちゃんとClearDBが追加できていれば、上のだんCLEARDB_DATABASE_URLはすでに入っています。
Image from Gyazo

ターミナル から以下を実行してください。
hogehogeの部分は、CLEARDB_DATABASE_URLのmysql://以下の部分をコピーして入れ替えます。

$ heroku config:add DATABASE_URL='mysql2:{hogehoge}'

heroku masterにアップ

masterにプッシュするときは以下のコマンド

$ git push heroku master

エラー①puma

こんなエラーが出ました

Your bundle is locked to puma (3.12.3), but that version could not be found in
remote:        any of the sources listed in your Gemfile. If you haven't changed sources, that
remote:        means the author of puma (3.12.3) has removed it. You'll need to update your
remote:        bundle to a version other than puma (3.12.3) that hasn't been removed in order
remote:        to install.

どうやらpumaがダメっぽいです。
そういえばgithubのsecurityで指摘されていたので、gemfileで以下のように追記してbundle install

gem 'puma', '~> 3.12.4'

エラー②URIの登録ミス

次はこちらでした。真っ赤な字で書かれています。

remote:  !     Could not detect rake tasks
remote:  !     ensure you can run `$ bundle exec rake -P` against your app
remote:  !     and using the production group of your Gemfile.
remote:  !     rake aborted!
remote:  !     URI::InvalidURIError: bad URI(is not URI?): mysql2:{//b4...略...}

指示に従って bundle exec rake -P を実行するも、なんも変わらず。
で、よく見たらURIのエラーっぽい
よーくみると、mysql2:の後に余計な{}が入っている!
なので、一旦削除して、再度登録し直し。

で、めでたくデプロイできました。

Githubとの連携も可能

ご存知githubでバージョン管理しているなら、デプロイはgithubのmasterにマージするだけで完了させることもできます。楽すぎてやばいですね。
各自のダッシュボードから画像のページにいけると思うので、Githubと連携させたら完了です。なお、Githubと連携後も $ git push heroku master は使えます。

Image from Gyazo

herokuでmigrate

masterにプッシュできたら、おなじみのマイグレーションを忘れずに実行しましょう。

$ heroku run rake db:migrate

僕の場合はseedファイルを用意しているのでそれもやっておきます

$ heroku run rake db:seed

うまくいけば、ザーーーーーッとデータが入る様子がみられます。

migrate時のエラー

rails aborted!
NoMethodError: undefined method `standard_name_cont_all

「どのファイルのstandard_name_cont_allだよぉ」って探したら、
modelのproduct.rbのコードが原因でした。
コメントアウトで通しておきました。

てか、このmodel、あまり意味なく残しておいたコードでした。余計なコードはエラーの元になるので消しておくのがよろしいですね。

その他メモ

clearDBは無料だと3,600クエリ/時間

seedファイルでデータを流し込んだ時のエラー

rails aborted!
ActiveRecord::StatementInvalid: Mysql2::Error: User 'hogehogehoge' has exceeded the 'max_questions' resource (current value: 3600)

公式Q&Aに回答がありました。
Image from Gyazo

無料だと1時間当たり3,600クエリで、有料にすると18,000クエリまで増えるんだとか。必要ならアップグレード必須ですね。

また、容量は以下のようになってます。
Ignite : 5MB (free)
Punch : 1GB (9.99/month)
Drift : 5GB ($49.99/month)

ちなみに、csvファイルでデータを入れるとき、1行のデータを入れるのに3回クエリが飛ぶようです。(画像の赤枠で1行分(3クエリ)です)
大量のデータをseedで入れるときはこのへんも気を配る必要があります。
Image from Gyazo

独自ドメイン、SSLが必要なら有料化が必要

無料の範囲だとドメインはhogehoge.herokuapp.comとなっていて、ちょっとダサめ。
また、今はSSL化が必須なので、サービスとして稼働させるならこちらもやっておきましょう。
$7/monthからできます。

ということで、以上です。
役に立ったらコメント残してもらえるとモチベーションになります。

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

エラーが出たAn error occurred while installing pg (1.2.3), and Bundler cannot continue. Make sure that `gem install pg -v '1.2.3' --source 'https://rubygems.org/'` succeeds before bundling.

An error occurred while installing pg (1.2.3), and Bundler cannot continue.
Make sure that `gem install pg -v '1.2.3' --source 'https://rubygems.org/'` succeeds before bundling.

解決

brew install postgresql

参考

https://qiita.com/masanarih0ri/items/5ad2f7e11c8c94170d6b

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

Vagrant環境で、rails db:dropでエラーになるトラブルシューティング

環境

Vagrant + Ubuntu 16.04.5 LTS
Rails 5.2.4.2
ruby 2.5.1p57

はじめに

rails db:createした後に、rails db:dropをすると、下記のエラーが出た時のトラブルシューティング

$ rails db:drop
Text file busy @ apply2files - /vagrant/test03/db/development.sqlite3
Couldn't drop database 'db/development.sqlite3'
rails aborted!
Errno::ETXTBSY: Text file busy @ apply2files - /vagrant/test03/db/development.sqlite3
/vagrant/test03/bin/rails:9:in `<top (required)>'
/vagrant/test03/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => db:drop:_unsafe
(See full trace by running task with --trace)

内容

どうも、Railsのバグらしく、Vagrant環境で、データベースがsqlite3の場合に発生するようです。
同じVagrant環境でも、postgresqlの場合は発生しませんでした。(確認済)
sqlite3のデータベースファイルを直接削除して対応するしかないようです。

rm db/development.sqlite3
rm db/test.sqlite3

なお、下記コマンドでも同じエラーが出ます。

rails db:migrate:reset

rails db:createrails db:migrateを連続して、1コマンドつづ打っていくしかないようです。

参考URL
https://teratail.com/questions/67393
https://qiita.com/totechite/items/d4b7f97372881d1d7a25

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

Rails 404エラー画面を設定した時にrefileで設定した画像が表示されなくなる

概要

備忘録代わりに書かせていただきます

Railsでroutesの最下行に

routes.rb
get '*path', to: 'application#render_404',

でどのパスにもいかなかった場合に404エラーを独自で出力するように設定した場合に、
Refile等で指定されたattachment_image_tagの画像が表示されない。

原因

ログを見たらすぐに分かるんですけど、attachment_image_tagでURLを生成して、GETを送ってRoutesを通る時にrefileではroutesの設定が無いので、当てはまるパスがなく404のgetを返してしまうという話。

解決法

もっと綺麗なやり方があるのかもしれないですけど、とりあえずRoutesのconstraintsを使ってrequestオブジェクトのurlに"attachments/"が含まれている場合は404の行を通らないようにして回避しました。

もっといい方法100%あるような気がするので、よかったらコメントで教えていただけると嬉しいです。

routes.rb
class ErrorAvoid
  def initialize
    @url = "attachments/"
  end

  def matches?(request)
    @url.include?(request.url)
  end
end

Rails.application.routes.draw do
  get '*path', to: 'application#render_404',
    constraints: ErrorAvoid.new
end

とりあえず当面はこれで回避できます。

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

モデル使わず指定位置を簡単にGoogleMapで表示(複数地点可)

はじめに

初投稿です、プログラミング歴は半年弱ほどなので、誤りがあるかもしれません。
ご指摘いただけると幸いです。
今後は、gmaps4railsを用いた現在地取得機能や、ジオコーディング機能(位置情報を入力すると緯度と経度に変換する)についても書いていこうと思っています。

記事制作の励みになりますので、いいと思ったらLGTMしてもらえると嬉しいです。

対象者

  • 時間がない人
  • ポートフォリオに地図を組み込んでオリジナリティを出したい人
  • 決まった位置を地図で表示させたい人
  • 指定する位置が決まっており、データベースを使う必要がない人

環境

Rails 5.1.6

この記事を読んで何ができるか

  1. こんな感じの地図が作れます。
    完成した地図

  2. このように複数地点表示もできます。拠点が複数ある場合などに使えます。
    複数地点表示

それでは、具体的な手順を解説していきます。

GoogleMap表示手順

⒈ GoogleMapsAPIの取得

下記サイトに沿って、GoogleMapsAPIを取得してください。
Google Maps の APIキー を取得する

取得したGoogleMapsAPIは環境変数に設定してください。

.env
GOOGLE_MAP_API_KEY="ここに取得したキーを貼る"

下記記事を参考にすると簡単です。
Railsで使える環境変数を管理できるgem(dotenv-rails)や.envの導入方法

2. gmaps4railsの導入

Gemfile
gem 'gmaps4rails'

を追記してから

$ bundle install

3. Javascriptの読み込み

下記を追記する。
出来るだけhtmlの上のほうの行に貼ったほうがエラーが出にくいです。(Javascriptの読み込み順に関するエラー)

application.html.erb
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API_KEY'] %>"></script>
<script src="//cdn.rawgit.com/mahnunchik/markerclustererplus/master/dist/markerclusterer.min.js"></script>
<script src='//cdn.rawgit.com/printercu/google-maps-utility-library-v3-read-only/master/infobox/src/infobox_packed.js' type='text/javascript'></script>

下記のリンク先のコードを全てコピーする。
http://underscorejs.org/underscore-min.js

app/assets/javascripts/underscore.js を作成し、さっきコピーしたコードを貼る。

app/assets/javascripts/underscore.js
// ここにコピーしたコードを貼る

次に、app/assets/javascripts/application.jsに下記を追記する。

//= require underscore
//= require gmaps/google

下記のようにします。

app/assets/javascripts/application.js
.
.
.
省略
//= require underscore
//= require gmaps/google
//= require_tree .

4.位置情報を取得する

GoogleMapで表示したい位置を検索して、検索結果のURLから位置情報(緯度と経度)を取得します。

例えば、「東京駅」で検索すると、URLがこのようになります。

https://www.google.co.jp/maps/place/東京駅/@35.6812362,139.7649361,17z/data=...以下省略

このうち、以下の部分に緯度と経度が含まれています。

...@35.6812362,139.7649361,17z...

緯度(latitude)=35.6812362
経度(longitude)=139.7649361

となります。

5. GoogleMapの表示

地図を表示させたい場所に、以下のコードを貼ってください。

foobar.html.erb
<div id="map" style='width: 100%; height: 500px;'></div>
  <script type="text/javascript">
    handler = Gmaps.build('Google');
      handler.buildMap({ provider: { scrollwheel: false }, internal: {id: 'map'}}, function(){
        markers = handler.addMarkers([
          {
            "lat": 35.6812362, //さっき取得した緯度
            "lng": 139.7649361, //さっき取得した経度
            "infowindow":'東京駅' //検索した位置の説明
          }
          ]);

      handler.bounds.extendWith(markers);
      handler.fitMapToBounds();
      handler.getMap().setZoom(15);
    });
  </script>

こんな感じになります。↓
完成した地図

緯度経度の数値を変えればどの場所でも表示できると思います。

また、縮尺を変えたい場合は、setZoom(15)の数値を変えてください。

これでおわりです。

複数地点をGoogleMap上に表示したい場合のみ、以下を読んでください。

6. GoogleMapに複数位置を表示

foobar.html.erb
markers = handler.addMarkers([
  {
    "lat": 35.6812362, //さっき取得した緯度
    "lng": 139.7649361, //さっき取得した経度
     "infowindow":'東京駅' //検索した位置の説明
  }
]);

一つ前のコードの中の、この部分をいじると表示する場所を増やせます。
例えば、以下のコードでは「東京駅」に加えて、横浜駅を表示させるようにしています。

foobar.html.erb
<div id="map" style='width: 100%; height: 500px;'></div>
  <script type="text/javascript">
    handler = Gmaps.build('Google');
      handler.buildMap({ provider: { scrollwheel: false }, internal: {id: 'map'}}, function(){
        markers = handler.addMarkers([
          {
            "lat": 35.6812362, //さっき取得した緯度
            "lng": 139.7649361, //さっき取得した経度
            "infowindow":'東京駅' //検索した位置の説明
          },{
            "lat": 35.4657901,
            "lng": 139.6201245,
            "infowindow":'横浜駅'
          }
          ]);

      handler.bounds.extendWith(markers);
      handler.fitMapToBounds();
      handler.getMap().setZoom(15);
    });
  </script>

このようになります。
地図の中心が2地点の真ん中になるので、setZoomの値を変えて、縮尺を変えています。
複数地点表示

おわりに

以上です、何か質問要望等あればぜひコメントしてもらえればと思います。
あと、LGTMもしてもらえると嬉しいです。

参考

Githubの公式(英語ですが、動画のチュートリアルがあります)↓
https://github.com/apneadiving/Google-Maps-for-Rails
gmaps4railsの公式のサンプルコード集
https://apneadiving.github.io/
RailsでGoogleMapを表示させる(gem 'gmaps4rails'の使い方)

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

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン、Rails on Dockerハンズオン vol.13 - TDDでPost機能をコーディング part2 -

はじめに

こんにちは!
今回は前回のPost機能のコーディングの続きです。ユーザー詳細ページでそのユーザーが投稿したポストに絞って表示させる機能をコーディングします。

前回のソースコード

前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。

前回の残り

まずは前回やり残したテストシナリオをもう一度確認しておきましょう。

  1. ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
  2. ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること
  3. ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
  4. サインイン済のユーザーは、ポストページで全ユーザーのポストを投稿日時降順で閲覧できること
  5. サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
  6. 未サインインのユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  7. 未サインインのユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  8. サインイン済のユーザーは、ユーザー詳細ページでそのユーザーのポストを投稿日時降順で閲覧できること
  9. サインイン済のユーザーが、ユーザー詳細ページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと
  10. サインイン済のユーザーは、プロフィールページで自身のポストを投稿日時降順で閲覧できること
  11. サインイン済のユーザーが、プロフィールページでそのユーザーのポストのユーザー名をクリックしたとき、何も起こらないこと

残り11シナリオ。今回は5シナリオをやっていきます!それでポストページが完了、あとはユーザー詳細ページにポストを表示するストーリーだけになります。

今回もコンテナを立ち上げてコンテナの中でコマンドを実行していきたいと思います。

$ docker-compose up -d
$ docker-compose exec web ash

ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること

今回もテストコードからです。「ポストする」ボタンにはpost_buttonidを付与するとしましょう。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること" do
      # テスト用のユーザーを作成する
+     user = create_user(1)
      # このテストシナリオで使うポスト内容として空文字を定義する
+     content = ""
      # このテストシナリオで期待するエラーメッセージを定義する
+     error_message = "ポストを入力してください"
      # テスト開始前のDB内のPostの数を記憶しておく
+     post_count = Post.count
      # userでサインインする
+     sign_in(user)
+ 
      # ポストページにアクセスする
+     visit posts_path
      # ポスト入力欄にcontentを入力する
+     fill_in :post_content, with: content
      # 投稿するボタン(#post_button)をクリックする
+     click_on :post_button
+ 
      # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
      # ページ内に期待するエラーメッセージが表示されていることを検証する
+     expect(page).to have_text error_message
      # DB内のPostの数が変わらない(=Postの登録が失敗している)ことを検証する
+     expect(Post.count).to eq post_count
+   end
  end

検証内容としては特に「期待するエラーメッセージが表示されていること」と「DB内のPostの数に変化がないこと」を検証することでバリデーションが効いていることを確認しています。

では、テストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
     Failure/Error: click_on :post_button

     Capybara::ElementNotFound:
       Unable to find link or button :post_button

Finished in 20.3 seconds (files took 7.34 seconds to load)
11 examples, 1 failure

#post_buttonがないので怒られました。ので、ボタンを追加しましょう。

app/views/posts/index.html.erb
  <div class="container my-5">
    <%= form_with model: @post, url: nil, local: true do |form| %>
      <div class="form-group">
        <%= form.text_area :content, class: "form-control", placeholder: "いまどうしてる?", autofocus: true %>
      </div>
+     <div class="text-right">
+       <%= form.submit "ポストする", class: "btn btn-primary", id: :post_button %>
+     </div>
    <% end %>
  </div>

post_buttonを追加してみました。UI的には以下のような感じになっているはず!
image.png

OK。ボタンは探せるようになったはずなのでまたテスト。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
     Failure/Error: expect(page).to have_text error_message
       expected to find text "ポストを入力してください" in "The page you were looking for doesn't exist.\nYou may have mistyped the address or the page may have moved.\nIf you are the application owner check the logs for more information."

Finished in 11.97 seconds (files took 8.31 seconds to load)
11 examples, 1 failure

次はエラーメッセージがみつからないみたいです。確かにエラーメッセージを表示する機能は作っていない!

まずエラーメッセージを表示するためには、ポストを送信してバリデーションに引っかかり、そのエラーメッセージをポストページ、つまりposts/index.html.erbで表示できるようにする必要があります。

まずは、Postsコントローラーにcreateアクションを作成して、そのアクションにポストを送信できるようにルーティングとform_withを更新しましょう。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    def index
      redirect_to root_path unless signed_in?
      @post = Post.new
    end
+ 
+   def create
+     # 未サインインの場合、トップページにリダイレクトする
+     redirect_to root_path unless signed_in?
+     # Strong parameterからリクエスト内容の通りにPostモデルオブジェクトを作成する
+     @post = Post.new(post_params)
+     # Postモデルオブジェクトのuser_idにサインイン中のuserのidを定義する
+     @post.user = current_user
+ 
+     if @post.save
+       # Todo: DB保存が成功した場合の動作
+     else
+       # DB保存が失敗した場合、ポストページをレンダリングする
+       render :index
+     end
+   end
+ 
+   private
+     # PostのStrong parameter
+     def post_params
+       params.require(:post).permit(:content)
+     end
  end

@post.userはあえてフォームからの送信ではなく、サーバーサイドでcurrent_user、つまりサインイン中のユーザーを設定しています。フォームから送信されたユーザーのIDを設定する方法も考えられますが、フォームの内容はユーザー側で簡単に改変できてしまうので違うユーザーのポストとして登録されてしまう危険性があります。
そのため、今回はサーバー側で処理をするようにしてみました。

config/routes.rb
  Rails.application.routes.draw do
    ...
    get   '/posts', to: 'posts#index',  as: :posts
+   post  '/posts', to: 'posts#create', as: :create_post
  end
app/views/posts/index.html.erb
...
- <%= form_with model: @post, url: nil, local: true do |form| %>
+ <%= form_with model: @post, url: create_post_path, local: true do |form| %>
...

これでcreateアクションで@post.saveが失敗した場合、@postにエラーメッセージが格納されてindex.html.erbがレンダリングされるようになるはずです。

次に、@post.savecontentが未入力の場合にエラーになるようにPostモデルのcontent属性にpresenceを定義しましょう。

app/models/post.rb
  class Post < ApplicationRecord
    belongs_to :user
+
+   validates :content,
+     presence: true
  end

Postモデルにはpresenceのバリデーションを定義します。

最後にエラーメッセージを表示できるようにViewを更新します。

app/views/posts/index.html.erb
  <div class="container my-5">
+
+   <% if @post.errors.any? %>
+     <div class="alert alert-danger">
+       <ul class="mb-0">
+         <% @post.errors.full_messages.each do |msg| %>
+           <li><%= msg %></li>
+         <% end %>
+       </ul>
+     </div>
+   <% end %>
+
    <%= form_with model: @post, url: create_post_path, local: true do |form| %>
      <div class="form-group">
        <%= form.text_area :content, class: "form-control", placeholder: "いまどうしてる?", autofocus: true %>
      </div>
      <div class="text-right">
        <%= form.submit "ポストする", class: "btn btn-primary", id: :post_button %>
      </div>
    <% end %>
  </div>

これでポストが未入力の場合にエラーメッセージが表示されるようになっているはずです。再びテストを実行してみます。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
     Failure/Error: expect(page).to have_text error_message
       expected to find text "ポストを入力してください" in "sample app\nPosts\nProfile\nSign out\nContentを入力してください\n(c) Hoge Inc. All Rights Reserved."

Finished in 19.22 seconds (files took 7.93 seconds to load)
11 examples, 1 failure

またテスト失敗理由が変わりました。
今度は「ポストを入力してください」というエラーメッセージを期待していたが、「Contentを入力してください」となってしまっていたようです。

属性の日本語表記はconfig/locales/ja.ymlで定義していますので、更新していきます。

config/locales/ja.yml
  ja:
    activerecord:
      attributes:
        user:
          name: "お名前"
          email: "メールアドレス"
          password: "パスワード"
          password_confirmation: "確認用パスワード"
+       post:
+         content: "ポスト"
      errors:
  ...

ここまででモデルのバリデーションとエラーメッセージの表示までの実装を終えました。ではテストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 19.99 seconds (files took 7.33 seconds to load)
11 examples, 0 failures

これでテストがGreenの状態になりました。

ここで少しリファクタリングしておきます。今、Postsコントローラーのindexアクションとcreateアクションの最初に未サインインであればトップページにリダイレクトする、全く同じ処理を書いてしまっています。
これではDRYではないので、メソッド化してbefore_actionで実行するようにリファクタします。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
+   before_action :redirect_to_root_unless_signed_in

    def index
-     redirect_to root_path unless signed_in?
      @post = Post.new
    end

    def create
-     redirect_to root_path unless signed_in?
      @post = Post.new(post_params)
      @post.user = current_user
      if @post.save
      else
        render :index
      end
    end

    private
      def post_params
        params.require(:post).permit(:content)
      end
+ 
+     # 未サインインの場合、トップページにリダイレクトするメソッド
+     def redirect_to_root_unless_signed_in
+       redirect_to root_path unless signed_in?
+     end
end

はい。before_actionにひとまとめにしてみました。

ではこれでもちゃんとテストがパスするか確認をしておきましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 18.5 seconds (files took 5.6 seconds to load)
11 examples, 0 failures

Greenな状態をキープしたままリファクタリングができましたね!

ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること

まずはテストです!

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること" do
+     # テスト用のユーザーを作成する
+     user = create_user(1)
+     # このテストシナリオで使うポスト内容として141文字を定義する    
+     content = "a" * 141
+     # このテストシナリオで期待するエラーメッセージを定義する
+     error_message = "ポストは140文字以内で入力してください"
+     # テスト開始前のDB内のPostの数を記憶しておく
+     post_count = Post.count
+     # userでサインインする
+     sign_in(user)
+  
+     # ポストページにアクセスする
+     visit posts_path
+     # ポスト入力欄にcontentを入力する
+     fill_in :post_content, with: content
+     # 投稿するボタン(#post_button)をクリックする
+     click_on :post_button
+ 
+     # 現在のページがポストページであることを検証する
+     expect(current_path).to eq posts_path
+     # ページ内に期待するエラーメッセージが表示されていることを検証する
+     expect(page).to have_text error_message
+     # ポスト入力欄に入力していたポスト内容がそのまま残っていることを検証する
+     expect(find("#post_content").value).to eq content
+     # DB内のPostの数が変わらない(=Postの登録が失敗している)ことを検証する
+     expect(Post.count).to eq post_count
+   end
  end

テスト実行です。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること
     Failure/Error: expect(page).to have_text error_message
       expected to find text "ポストは140文字以内で入力してください" in "sample app\nPosts\nProfile\nSign out\n(c) Hoge Inc. All Rights Reserved."

Finished in 27.16 seconds (files took 5.71 seconds to load)
12 examples, 1 failure

エラーメッセージを見つけられないため失敗しているようです。今、140文字の制限をPostモデルのcontent属性に付与していないのでPost.savetrueだったのでしょう。
バリデーションを追加してみます。

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user

  validates :content,
-   presence: true
+   presence: true,
+   length: { maximum: 140 }
end

では、またテストを実行してみます。

# rspec spec/system/07_posts_spec.rb

Finished in 22.78 seconds (files took 6.21 seconds to load)
12 examples, 0 failures

Greenになりました!

ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること

テストから。

spec/system/07_posts_spec.rb
  ...
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること" do
+     # テスト用のユーザーを作成する
+     user = create_user(1)
+     # このテストシナリオで使うポスト内容を4つ用意する
+     # "Hello, world.":  通常のポスト内容
+     # "a":              0文字がNGなので境界値として1文字のポスト内容を用意
+     # "a" * 140:        141文字がNGなので境界値として140文字のポスト内容を用意
+     # "Hello.\nWorld.": 特殊なケースとして改行が入っているポスト内容を用意
+     contents = ["Hello, world.", "a", "a" * 140, "Hello.\nWorld."]
+     # userでサインインする
+     sign_in(user)
+
+     # contentsの中から1つずつをテストする
+     contents.each do |content|
+       # テスト開始前のDB内のPostの数を記憶しておく
+       post_count = Post.count
+      
+       # ポストページにアクセスする
+       visit posts_path
+       # ポスト入力欄にcontentを入力する
+       fill_in :post_content, with: content
+       # 投稿するボタン(#post_button)をクリックする
+       click_on :post_button
+
+       # 現在のページがポストページであることを検証する
+       expect(current_path).to eq posts_path
+       # ポスト内容がクリアされていることを検証する
+       expect(find("#post_content").value).to eq ""
+       # DB内のPostが1つ増えている(=投稿したポストが保存された)ことを検証する
+       expect(Post.count).to eq post_count + 1
+       # ポストページのポスト一覧の一番上に投稿したポストが表示されていることを検証する
+       expect(find("#posts_list").all(".post-item").first).to have_text content
+       expect(find("#posts_list").all(".post-item").first).to have_text user.name
+     end
+   end
  end

あまりeachとかを使うとどのケースでテスト失敗したのか行数からはわかりにくくなってしまう危険性もあるのですが、長く書くのも面倒なので今回はいくつかの文字列をポストするテストをeachで繰り返してみました。
"a""a" * 140は境界値のテストをしています。
"Hello.\nWorld."は改行が入った時に正しく登録・表示されるかをテストするためのパターンです。

posts_listはポストページで過去のポストが一覧表示されるエリアのidとしてます。その中で各ポストにはpost-itemclassを割り当ててall(".post-item")でそのコレクションを取得できるようにしようと思います。.firstでその中でも一番最初に表示されるもの、つまり一番上に表示される要素を検証対象としています。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
     Failure/Error: expect(find("#post_content").value).to eq ""

       expected: ""
            got: "Hello, world."

       (compared using ==)

Finished in 22.11 seconds (files took 6.73 seconds to load)
13 examples, 1 failure

テストは失敗しています。
ポストが成功した場合はポストの入力エリアが空白に戻るようにしたいのですがそれができていないようです。

ポスト成功時の動作をposts#createでまだコーディングできていないので記述していきます。
単にposts#indexアクションにリダイレクトしてあげるだけで、indexアクションの中で@post = Post.newが実行されるのでcontentを初期化できそうです。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    ...
    def create
      @post = Post.new(post_params)
      @post.user = current_user
      if @post.save
+       redirect_to posts_path
      else
        render :index
      end
    end
    a...
  end

これで完成。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
     Failure/Error: expect(find("#posts_list").all(".post-item").first).to have_text content

     Capybara::ElementNotFound:
       Unable to find css "#posts_list"

Finished in 26.85 seconds (files took 7.87 seconds to load)
13 examples, 1 failure

失敗理由が変わりました。#posts_listなんて要素ないよ、とのことなのでViewを作っていきましょう。

まずは#posts_listで表示するデータをコントローラー側で取得しておく必要がありますので、posts#indexアクションで全てのPostを作成日降順で取得するようにしましょう。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    ...
    def index
      @post = Post.new
+     # 更新日時降順で全てのポストを@postsに代入する
+     @posts = Post.order(created_at: :desc)
    end
    ...
  end

全てのポストを作成日時降順で@postsに入れてます。
続いてはこのインスタンス変数を使ってViewを作っていきます。

app/views/posts/index.html.erb
  <div class="container my-5">

    <% if @post.errors.any? %>
      <div class="alert alert-danger">
        <ul class="mb-0">
          <% @post.errors.full_messages.each do |msg| %>
            <li><%= msg %></li>
          <% end %>
        </ul>
      </div>
    <% end %>

    <%= form_with model: @post, url: create_post_path, local: true do |form| %>
      <div class="form-group">
        <%= form.text_area :content, class: "form-control", placeholder: "いまどうしてる?", autofocus: true %>
      </div>
      <div class="text-right">
        <%= form.submit "ポストする", class: "btn btn-primary", id: :post_button %>
      </div>
    <% end %>
+
+   <div id="posts_list" class="my-5">
+     <% @posts.each do |post| %>
+       <div class="card post-item my-1">
+         <div class="card-body">
+           <h5 class="card-title"><%= post.user.name %></h5>
+           <p class="card-text"><%= post.content %></p>
+         </div>
+       </div>
+     <% end %>
+   </div>
  </div>

form_withよりも下にid=posts_listのフィールドを作って@postsをひとつずつ表示してます。
post.user.namepostの外部キーuser_idのUserモデルオブジェクトのnameを取得して表示してます。PostモデルとUserモデルを関連付けしているのでこういった使い方ができるんですね。

では、テストしてみましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポスト未入力のユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト未入力のエラーメッセージを確認できること
     Failure/Error: expect(page).to have_text error_message
       expected to find text "ポストを入力してください" in "We're sorry, but something went wrong.\nIf you are the application owner check the logs for more information."

  2) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを141文字以上入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿は失敗しポスト文字数超過のエラーメッセージを確認できること
     Failure/Error: expect(page).to have_text error_message
       expected to find text "ポストは140文字以内で入力してください" in "We're sorry, but something went wrong.\nIf you are the application owner check the logs for more information."

  3) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
     Failure/Error: expect(find("#posts_list").all(".post-item").first).to have_text content
       expected to find text "Hello.\nWorld." in "John Smith\nHello. World."

Finished in 34 seconds (files took 5.62 seconds to load)
13 examples, 3 failures

うぉ、3つもテストが失敗している...
1)2)はなにやら例外が発生してしまっているようです。試しにdevelopment環境でポストを未入力で「ポストする」ボタンをクリックしてみましょう。
image.png
ふむふむ。<% @posts.each do |post| %>のところでundefined method 'each'が起きてますね。

この2つのテストはposts#createアクションで@post.savefalseの時にrender :indexでレンダリングさせているケースです。
もう一度コードをよくみると、このレンダリングまでにこのアクションでは@postsというインスタンス変数を定義していません。
今回の例外は@postsというモデルオブジェクトのインスタンス変数がないにもかかわらず@posts.eachを使おうとしていることから起きた例外と推測できます。

posts#createアクションを見直してみましょう。

app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    ...
    def create
      @post = Post.new(post_params)
      @post.user = current_user
      if @post.save
        redirect_to posts_path
      else
+       # 更新日時降順で全てのポストを@postsに代入する
+       @posts = Post.order(created_at: :desc)
        render :index
      end
    end
    ...
  end

@post.savefalseの場合、renderの前に@postsを定義するようにしてみました。
例外が解消されたかもう一度テストしてみましょう。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in ポストページでポストを正しく入力したユーザーが、「ポストする」ボタンをクリックしたとき、ポスト投稿が成功しポスト入力フィールドがクリアされ、ポスト一覧の最上部に投稿したポストを確認できること
     Failure/Error: expect(find("#posts_list").all(".post-item").first).to have_text content
       expected to find text "Hello.\nWorld." in "John Smith\nHello. World."

Finished in 32.49 seconds (files took 7.4 seconds to load)
13 examples, 1 failure

ふー。今取り組んでいるテストシナリオだけがテスト失敗している状態に戻せましたね。
内容をみてみると"Hello.\nWorld."のポストが成功しているはずなのにページに表示されていないようです。
image.png
スクリーンショットをみると、確かに一番上のポストの文字列が改行されていないHello. World.という文字列になっていますね。

実はpost.contentのような書き方では文字列を表示することはできるのですが、改行がうまく表現できません。
これを解決するために、今回はsafe_joinメソッドを使って改行を正しく表示できるようにします。(参考: 「simple_format」や「safe_join」を使って、正常に改行表示させる方法(Rails) - りょうたくの技術ブログ

app/views/posts/index.html.erb
- <p class="card-text"><%= post.content %></p>
+ <p class="card-text"><%= safe_join(post.content.split("\n"), tag(:br)) %></p>

ちょっと書き方が複雑ですが、これでテキストエリアでつけた改行の通りに改行を表現することができるようになります。
こういったやり方はいろいろあるので、どう表現したいかによって使い分けが必要です。

では再度テストを実行してみましょう。

# rspec spec/system/07_posts_spec.rb

Finished in 30.57 seconds (files took 7.13 seconds to load)
13 examples, 0 failures

無事テストをパスすることができました!!
このように、テストで用いるデータの種類をいろいろなケース用意することで本当に期待通りの動作をしているかを正しく把握し実装することができるのです。

サインイン済のユーザーは、ポストページで全ユーザーのポストを投稿日時降順で閲覧できること

ここからはポストページの表示系ですね。
まずは投稿日時降順で表示ができているかという点です。複数ユーザーが投稿していることにします。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーは、ポストページで全ユーザーのポストを投稿日時降順で閲覧できること" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # ポストを用意する
+     posts = []
+     posts.unshift Post.create(content: "first post", user: user1)
+     posts.unshift Post.create(content: "初めてのポスト", user: user2)
+     posts.unshift Post.create(content: "second post!!", user: user1)
+     # userでサインインする
+     sign_in(user1)
+
+     # ポストページにアクセスする
+     visit posts_path
+
+     # 投稿日時降順でポストが表示されていることを検証する
+     posts.each_with_index do |post, i|
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.user.name
+       expect(find("#posts_list").all(".post-item")[i]).to have_text post.content
+     end
+   end
  end

今回は適当に3つのポストを生成しておき、作成日時降順でポストが表示されているかをチェックしています。
postsという空のArrayを最初に作成し、unshiftメソッドを使って配列の先頭にPost.createを挿入していきます。最終的には作成日時降順で配列にPostのモデルオブジェクトが格納されるかたちになります。(参考: Rubyで配列に要素を追加・挿入する:push, insert, unshift | UX MILK

これをeach_with_indexでモデルオブジェクトとindexを取り出して、posts_listi番目にある要素が新しい方からi番目に作成されたポストであるかどうかを検証しているというわけです。(参考: Rubyのeachでindexを取得する:each_with_index | UX MILK

では、テストを実行してみましょう。

# rpsec spec/system/07_posts_spec.rb

Finished in 30.64 seconds (files took 6.38 seconds to load)
14 examples, 0 failures

この辺りはすでに作り込みが終わっているのでテストがパスしていますね。

サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること

まずはテストです。ポストしたユーザーの名前を表示している要素にはpost-user-nameというclassをつけることにします。

spec/system/07_posts_spec.rb
  feature "ユーザーとして、ポストを投稿したい", type: :system do
    ...
+   scenario "サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること" do
+     # テスト用のユーザーを作成する
+     user1 = create_user(1)
+     user2 = create_user(2)
+     # ポストを用意する
+     posts = []
+     posts.unshift Post.create(content: "First Post!!", user: user1)
+     posts.unshift Post.create(content: "初めてのポスト", user: user2)
+     # user1でサインインする
+     sign_in(user1)
+
+     posts.each_with_index do |post, i|
+       # ポストページにアクセスする
+       visit posts_path
+       # 上からi番目のポストのユーザー名をクリックする
+       find("#posts_list").all(".post-item")[i].find(".post-user-name").click
+
+       # 現在のページがクリックしたポストのユーザーのユーザー詳細ページであることを検証する
+       expect(current_path).to eq user_path(post.user)
+     end
+   end
  end

いままではclick_onを使ってクリック操作を実装していましたが、要素.clickでも同じようにクリック操作ができます。click_onの場合はボタンやリンクに限定されていたのですが、clickの場合はどんな要素であれクリック操作ができるので必要になるケースもあります。覚えておいてくださいね。

# rspec spec/system/07_posts_spec.rb

Failures:

  1) ユーザーとして、ポストを投稿したい After sign in Created posts サインイン済のユーザーが、ポストページでポストのユーザー名をクリックしたとき、そのユーザーのユーザー詳細ページに遷移すること
     Failure/Error: find("#posts_list").all(".post-item")[i].find(".post-user-name").click

     Capybara::ElementNotFound:
       Unable to find css ".post-user-name" within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[1]/DIV[1]/DIV[1]">

Finished in 35.66 seconds (files took 6.04 seconds to load)
15 examples, 1 failure

post-user-nameclassの要素が見つからないようですね。現在ポストしたユーザーの名前を表示している要素にpost-user-nameclassのリンクを定義しましょう。

app/views/posts/index.html.erb
- <h5 class="card-title post-user-name"><%= post.user.name %></h5>
+ <h5 class="card-title"><%= link_to post.user.name, post.user, class: "post-user-name" %></h5>

link_toメソッドを使ってリンクを生成してみました。

# rspec spec/system/07_posts_spec.rb

Finished in 34.14 seconds (files took 5.9 seconds to load)
15 examples, 0 failures

テストパス!

まとめ

はい。今日はここまでです!
今回まででポストページの機能を実装することができましたね。
あとはユーザー詳細ページ側でそのユーザーのポストを表示するテストと機能を実装していきましょう!

ではまた次週!

後片付け

# exit
$ docker-compose down

本日のソースコード

Other Hands-on Links

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

あなたの日本語をRails語に翻訳する辞書 データベース編

はじめに

TECH CAMPというプログラミング教室に40日ほど通った添野です。
本日はRailsで重要な概念「データベース」という概念を説明していきます。

皆さんはRailsにおける「データベース」が何なのか説明できるでしょうか?
この記事を読めば、かなり具体的に説明できるようになります。

※MySQLの使用を前提に記事を書いています

結論

データベースとは「配列に入った配列に入ったハッシュ」です。
わかりやすく説明するため、徐々に進化させて説明していきます。


雑魚敵。ドラクエでいう「スライム」です。
最終的にビューで表示したいのはこいつです。

りんご



配列
雑魚敵の集合体です。ドラクエでいう「キングスライム」です。

[りんご , ゴリラ , ラッパ]

簡略化すると:
[A,B,C]



配列に入った配列
キングスライムの集合体です。キングスライムって進化すんの・・・?
Rubyというゲームの場合は無限に進化します。

[ [りんご , みかん , バナナ] , [ゴリラ , サル] , [ラッパ , ピアノ, トランペット , ギター] ]

簡略化すると:
[ [A,B,C] , [D,E,F] , [G,H,I] ]



ハッシュ
ラスボスです。ドラクエでいうゾーマです。
Railsではこいつを「レコード」と呼びます。

{名前: りんご, 値段: 100 , 色: 赤}

簡略化すると:
{ A:a, B:b , C:c }



配列に入ったハッシュ
歴代ドラクエのラスボスが合体した恐るべき存在です。
レコードがいっぱい並びます。つまり、Railsではこいつを「テーブル」と呼びます。

[ {名前: りんご, 値段: 100 , 色: 赤} , {名前: みかん, 値段: 200 , 色: 橙} , {名前: バナナ, 値段: 60 , 色: 黄} ]

簡略化すると:
[ {A:a, B:b, C:c} , {D:d, E:e, F:f} , {G:g, H:h, I:i} ]



配列に入った配列に入ったハッシュ
様々なゲームの歴代ラスボスを集めてそれぞれ合体させた存在を、さらに合体させた存在です。恐るべき最終形態。テーブルがいっぱい並びます。つまり、こいつがRailsにおける「データベース」の正体です。

[ [ {名前: りんご, 値段: 100 , 色: 赤} , {名前: みかん, 値段: 200 , 色: 橙} , {名前: バナナ, 値段: 60 , 色: 黄} ] 
, [ {名前: ゴリラ, 年齢: 30 , 性別:オス , 性格: 乱暴} , {名前: サル, 年齢: 10 , 性別:メス , 性格: おとなしい} ] 
, [ {名前: ラッパ, 種別: 金管楽器} , {名前: ピアノ, 種別: 弦楽器} , {名前: トランペット, 種別: 金管楽器 } , {名前:ギター, 種別: 弦楽器} ] ]

簡略化すると:
[ [ {A:a, B:b, C:c} , {D:d, E:e, F:f} , {G:g, H:h, I:i} ]
, [ {J:j, K:k, L:l} , {M:m, N:n, O:o} , {P:p, Q:q, R:r} ]
, [ {S:s, T:t, U:u} , {V:v, W:w, X:x} , {Y:y, Z:z, Ω:ω} ] ]



さて、「データベース」のことが分かってきたのではないでしょうか。
上記の「配列に入った配列に入ったハッシュ」を少し分解してみると、以下のように分けられます。


「果物」が入ってる配列

[ {名前: りんご, 値段: 100 , 色: 赤} , {名前: みかん, 値段: 200 , 色: 橙} , {名前: バナナ, 値段: 60 , 色: 黄} ] 

「動物」が入ってる配列

[ {名前: ゴリラ, 年齢: 30 , 性別:オス , 性格: 乱暴} , {名前: サル, 年齢: 10 , 性別:メス , 性格: おとなしい} ] 

「楽器」が入っている配列

[ {名前: ラッパ, 種別: 金管楽器} , {名前: ピアノ, 種別: 弦楽器} , {名前: トランペット, 種別: 金管楽器 } , {名前:ギター, 種別: 弦楽器} ]

これらがRailsにおいて、上から順に「果物テーブル」「動物テーブル」「楽器テーブル」と呼ばれるのです。

つまり、Railsのデータベース用語をまとめて説明すると、

  • レコードは「ハッシュ
  • テーブルは「配列に入ったハッシュ
  • データベースは「配列に入った配列に入ったハッシュ

です。

日本語をRails語に翻訳する

それでは、実際にデータベースから値を取り出します。
様々なゲームのラスボスが集結合体した恐るべき存在を、勇者の剣で「スライム」になるまで切り刻みます。

今回のゴールは「果物テーブル」から「りんご」「みかん」「バナナ」を取り出すことにします。
まずはデータベースから「果物テーブル」を取り出す必要がありますね。

日本語:データベースから「果物テーブル」を取り出す→ Rails語:「果物.all」

(テーブル名).allとすると、そのテーブルだけ取り出せます。
まずは「いろんなゲームのラスボス集団」を「ドラクエのラスボス集団」まで刻みます。

果物.all

結果:
[ {名前: りんご, 値段: 100 , : } , {名前: みかん, 値段: 200 , : } , {名前: バナナ, 値段: 60 , : } ]

次は「果物テーブル」から「レコード」を取り出す必要があります。

日本語:「果物テーブル」から「レコード」を取り出す→ Rails語:「果物.all.each do |f| f end」

(テーブル名).all.each do |変数| 変数 endとすると、全てのレコードを取り出すことができます。この.each doというのは重要な呪文なので覚えましょう。読み方は「ドットイーチドゥー」です。.each do|変数|変数は適当に決めちゃってオッケーです。今回はfにしました。これを使って「ドラクエのラスボス集団」を「ドラクエのラスボス1体」まで刻みます。

果物.all.each do|f|
  puts f
end

結果:
{名前: りんご, 値段: 100 , : }
{名前: みかん, 値段: 200 , : }
{名前: バナナ, 値段: 60 , : }

あとは出てきたラスボスがスライムになるまで切り刻むのみです。

日本語:「レコード」から「名前」を取り出す→ Rails語:「果物.all.each do |f| f.名前 end」

(テーブル名).all.each do |変数| 変数.(カラム名) endと書くと指定したカラムのレコードを取り出せます。ここでいう「カラム」はハッシュにおける「キー」のことです。つまり今回は「名前」です。

果物.all.each do|f|
  puts f.名前
end

結果:
りんご
みかん
バナナ

おめでとうございます。ついに「果物テーブル」から「りんご」「みかん」「バナナ」を取り出すことに成功しました。実際に使う上では、ビューファイル上では以下のように記載します。

ERBの場合

index.html.erb
<% 果物.all.each do|f| %>
  <%= f.名前 %>
<% end %>

hamlの場合

index.html.maml
- 果物.all.each do|f|
  = f.名前

他にも、pictweetで「Tweetsテーブルのnameカラムの情報を一覧表示したい」なら、コードは以下になります。

ERBの場合

index.html.erb
<% Tweet.all.each do|t| %>
  <%= t.name %>
<% end %>

hamlの場合

index.html.maml
- Tweet.all.each do|t|
  = t.name

まだ少しだけしっくりこないかもしれません。
私のpictweetだとTweet.allじゃなくて@tweetsみたいな書き方だよ!という方。
コントローラーのファイルを開くと、こんな感じのモノが書かれてないでしょうか。

def index
  @tweets = Tweet.all
end

これはデータベースからTweetsテーブルを取り出して@tweetsという変数(配列)にぶち込め!という意味です。MVCの流れって「コントローラ」の後に「ビュー」なので、先にこれが読み込まれているのです。だからビューで@tweetsというものを、Tweet.allの代わりに使えます。

そしてビューにはこんな事を書きますね。

<% @tweets.each do |tweet| %>
  <div class="content_post" style="background-image: url(<%= tweet.image %>);">
    <%= simple_format(tweet.text) %>
    <span class="name">
      <%= tweet.name %>
    </span>
  </div>
<% end %>

ゴチャゴチャしてますが、見るべきところはちゃんと決まっています。
まずは1行目の@tweets.each do |tweet|。これはラスボス集団をラスボス一体まで刻む処理でした。

そしてよく見てください、3行目と5行目です。tweet.textとか、tweet.nameって書いてあります。
これはレコードからカラムを取り出す、つまりラスボスを雑魚敵になるまで刻む処理です。

tweet.textの起源を辿って書くとTweet.all.each do|tweet| tweet.text endです。
これを日本語→Rails語訳を使って逆翻訳すると

「データベースのTweetsテーブルから、textカラムを取り出せ」

になる訳です。これにて、データベースにおけるRails語を日本語訳することも可能になりました。

まとめ

データベースとは「レコードというハッシュテーブルという配列に入ったものをデータベースという配列に入れたもの」です。

取り出し方

「データベースから〇〇テーブルを取り出す」
〇〇.all 

「データベースから取り出した〇〇テーブルから、レコードを取り出す」
〇〇.all.each do |変数| 変数 end

「データベースから取り出した〇〇テーブルのレコードから、××カラムを取り出す」
〇〇.all.each do |変数| 変数.×× end

あとがき

いかがでしたでしょうか。他にもwhereメソッドやparamsといった概念がデータベースの理解には必要ですが、全てはここに書いてある知識がベースとなった概念です。記事の内容を理解することができれば、自ずと他の概念も理解できるようになるはずです。

この記事が、皆様のご理解の一助となりましたら幸いです。

それでは。


添野文哉(@enjoy_omame)
https://twitter.com/enjoy_omame

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

VS codeの初期設定とRuby on Railsの環境設定

初期設定につまづくとかなり時間をロスするので、まとめることにしました

VSCodeの初期設定

VSCode(Visual Studio Code)をインストール

  1. VSCodeダウンロードページを開く スクリーンショット 2020-03-31 5.50.34.png
  2. Download for Macをクリックして、ダウンロードを完了します

VS Codeに拡張機能を追加

追加する拡張機能

  • Japanese Language Pack for Visual Studio Code →日本語表記にします。

スクリーンショット 2020-03-31 5.56.45.png

  • Ruby→Rubyの構文をチェックし、間違った箇所を指摘。

スクリーンショット 2020-03-31 5.56.59.png

  • HTML Snippets→HTMLタグ、CSSタグの入力を補完。

スクリーンショット 2020-03-31 5.56.37.png

  • Better Haml

スクリーンショット 2020-03-31 5.56.22.png

  • zenkaku→全角スペースを知らせてくれます。

スクリーンショット 2020-03-31 5.57.16.png

  • vscode-icons →言語ごとにファイルのアイコンを変えてくれます。
  • Docker

スクリーンショット 2020-03-31 5.56.29.png

実際にインストールする

  1. VS Codeを開く スクリーンショット 2020-03-31 5.53.49.png
  2. 拡張機能を追加するためのボタンを押す Image from Gyazo
  3. 追加したい拡張機能を検索する

Image from Gyazo
4. インストールする
Image from Gyazo
5. 反映させるためにvs codeを再起動「command + q」で終了し再度、起動する

自動保存する設定

記述したのに、保存できていないと泣くので、自動保存するように設定しましょう
1. vs codeの設定を開く

Image from Gyazo
2. ユーザー設定がひらけているか確認
スクリーンショット 2020-03-31 6.20.28.png
3. Files: Auto Save「onFocusCange」が選択されていれば完了

半角スペースの見分けがつくようにする

  1. VSCodeのサイドバーより、「管理」→ 「設定」の順に選択。
  2. 開いたページで「ユーザー設定」タブを開く。
  3. Editor: Tab Sizeを2に設定。 Editor: Render Whitespaceで「all」を選択。

Finderの設定

1.FInderを開く!
スクリーンショット 2020-03-31 6.29.45.png
2.Finderを選択の上、画面左上の「Finder」をクリック
スクリーンショット 2020-03-31 6.29.58.png
3.環境設定をクリック
4.サイドバーに「owner」を追加
スクリーンショット 2020-03-31 6.30.11.png
5.Finderにownerの表示が出る
スクリーンショット 2020-03-31 6.32.21.png

Ruby on Railsの開発環境を整える

ターミナル
#ホームディレクトリに戻す
$ cd
ターミナル
$ xcode-select --install

Homebrewをインストール

ターミナル
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

途中でエンターキーを押して、進める必要があるので、押し忘れないでください。

インストールされたか確認
:ターミナル
$ brew -v
Homebrew 1.8.0など表示される

Homebrewのアップデート

ターミナル
$ brew update

Homebrewの権限を変更

ターミナル
$ sudo chown -R `whoami`:admin /usr/local/bin

Rubyをインストール

ターミナル
$ brew install rbenv ruby-build

rbenvをどこからも使用できるようにしよう

ターミナル
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

bash_profileの変更を反映

ターミナル
$ source ~/.bash_profile

readlineをinstall
ターミナルのirb上で日本語入力を可能にする設定を行うために、以下のコマンドでインストール

ターミナル
$ brew install readline

readlineをどこからも使用できるようにしよう

ターミナル
$ brew link readline --force

rbenvを利用してRubyをインストール

ターミナル
$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
$ rbenv install 2.5.1

利用するRubyのバージョンを指定

ターミナル
$ rbenv global 2.5.1

rbenvを読み込んで変更を反映

ターミナル
$ rbenv rehash

Rubyのバージョンを確認

ターミナル
$ ruby -v

さきほど指定したバージョンになっていれば、OKです。

MySQLを用意

ターミナル
$ brew install mysql@5.6

MySQLの自動起動設定

MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、面倒なので自動化

ターミナル
$ mkdir ~/Library/LaunchAgents 
$ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist 

どこからもmysqlを利用できるように設定

ターミナル
# mysqlのコマンドを実行できるようにする
$ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile
# mysqlのコマンドが打てるか確認する
$ which mysql
# 以下のように表示されれば成功
/usr/local/opt/mysql@5.6/bin/mysql

Railsを用意

bundlerをインストール

Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストール

ターミナル
$ gem install bundler

Railsをインストール

ターミナル
# インストール
$ gem install rails --version='5.2.3'

# 再読み込みして反映
$ rbenv rehash

# 成功しているかバージョン確認
$ rails -v

Appを保存するためのフォルダ(ディレクトリ)を作成する

ターミナル
#ホームディレクトリに移動
$ cd ~

#projectsディレクトリの作成
$ mkdir projects

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

[devise]現在ログインしているユーザ名が表示されない場合の解決策

環境

バージョン
ruby 2.6.5
rails 5.2.4.2
devise 4.7.1

目標

現在ログインしているユーザの名前を表示させたい。

問題

@yasuno0327氏が公開していたdevise にusername カラムを追加し、usernameを登録できるようにする。を参考にして実装したところ以下のようなエラーを吐いた。

Error
undefined method `username' for nil:NilClass

(意訳)
NilClassのnilオブジェクトに`username`メソッドは定義されてないよ

解決策

_current_username.html.erb
<% if user_signed_in? %>
  <!-- 変更前-->
  <%= @user.username %>
    <!-- 変更後-->
    <%= current_user.username %>
<% end %>

解決に至るまでに引っかかったこと

1.エラー文はどういう意味を指しているのか?

どうやら、@userがnilになっていることを指しているらしい。

参考

https://qiita.com/gonzaemon111/items/a6d9f3ad0645037125c2

2.@userがnilになっているのは何故?

インスタンス変数のスコープ外だから値が参照されない?
(正直、理解があやふやです。自分なりにかみ砕けたら、追記なり修正なり行います。)

[復習]各変数のスコープについて

種別 説明 文法
ローカル変数 それぞれのメソッドの中だけで存在しており、メソッドの処理が終了すると失われてしまう変数です。 通常の変数通り
インスタンス変数 インスタンスに属する変数で、インスタンスの中からであれば、どのメソッドからでも参照・変更できます。インスタンスごとに作られる変数ですので、インスタンスごとに違う値を保持しています。インスタンス生成後、最初に代入された時点で生成されますが、メソッド内でしか生成できませんので、注意が必要です。 変数名の前に「@」
クラス変数 クラスで共通の変数として生成されますので、同じクラスから生成されたインスタンスすべてで共通の値を持つことになります 変数名の前に「@@」
クラスインスタンス変数 クラスメソッドからのみアクセスできる変数で、メソッド内ではなく、クラス定義内に定義しなければいけません。クラスが定義されると同時に生成され、クラス変数同様、1つのクラスで共通の値を持っています 変数名の前に「@」

引用

https://web-camp.io/magazine/archives/15956

3.current_userと@userは何が違うのか?

メソッドか変数かの違い(...だと認識してます。)

current_user @user
deviseのヘルパーメソッド userのインスタンス変数
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】連動しないwill_paginateの設定

static_pages_controller.rb
:
@myfeed_items = current_user.myfeed.paginate(page: params[:page], per_page: 3)
@followingfeed_items = current_user.followingfeed.paginate(page: params[:page], per_page: 3)
:

上記のように同様のファイルにwill_paginateの設定がされているときに、両方とも連動してページが動いてしまいます。

image.png

image.png

解決策

static_pages_controller.rb
:
@myfeed_items = current_user.myfeed.paginate(page: params[:myfeed_items_page], per_page: 3)
@followingfeed_items = current_user.followingfeed.paginate(page: params[:followingfeed_items_page], per_page: 3)
:

上記のようにpage: params[任意の値]に設定することで連動を防ぐことができます。

_myfeed.html.erb
:
  <%= will_paginate @myfeed_items, :param_name => 'myfeed_items_page', class: "center" %>
:
_followingfeed.html.erb
:
  <%= will_paginate @followingfeed_items, :param_name => 'followingfeed_items_page', class: "center" %>
:

それぞれのhtml.erbも修正すればwill_paginateで設定されているボタンが単独で動いてくれます。

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