SinatraアプリをRailsにリプレイスするときに詰まったところ

#なぜRailsにリプレイスしようと思ったのか？
僕が通っていたプログラミングスクール「CODE BASE」ではRubyのマイクロフレームワークである「Sinatra」を使って、簡単なwebアプリを制作します。

今後、Railsを使った企業にwebエンジニア転職を目指すに当たり、「勉強したRails技術で何か作ってみたい」と思い、Sinatraで作ったwebアプリをRailsでリプレイスすることにしました。

※「リプレイス」 ＝ 英語：replace
置き換えることを意味する英語。既存のものに取って代わること、交換すること、置換、後任といった意味でも用いられる。
引用：https://www.weblio.jp/content/%E3%83%AA%E3%83%97%E3%83%AC%E3%82%A4%E3%82%B9

#リプレイスに挑戦する時点での僕のスキル

- ProgateのRailsコースは1週済み
- Railsチュートリアル9章まで終わらせている
- 人生逆転サロンというプログラミングのオンラインサロンの学習教材でRailsを使ったCRUDアプリが作れる

#Sinatra => Rails へのリプレイスで詰まったところ

##deviseのpath記述が分からない
RailsはdeviseというGemを使えば、ログイン機能を簡単に実装することができます。

が、しかし！！ deviseで自動生成されるルーティングが分からず、詰みそうになりました。

###具体的に分からなかったところ
deviseでusersテーブルを作成し、GETのHTTPリクエストで「ユーザー編集画面」に飛ぶパス記述が分からない状態でした。
URL指定でアクセスするなら「 /posts/:id(.:format)」に該当するルーティングですね。

###解決方法
ターミナルで`rails routes`を叩けば即解決。

```terminal:
    Prefix Verb   URI Pattern                                                                              Controller#Action
         new_user_session GET    /users/sign_in(.:format)                                                                 devise/sessions#new
             user_session POST   /users/sign_in(.:format)                                                                 devise/sessions#create
     destroy_user_session DELETE /users/sign_out(.:format)                                                                devise/sessions#destroy
        new_user_password GET    /users/password/new(.:format)                                                            devise/passwords#new
       edit_user_password GET    /users/password/edit(.:format)                                                           devise/passwords#edit
            user_password PATCH  /users/password(.:format)                                                                devise/passwords#update
                          PUT    /users/password(.:format)                                                                devise/passwords#update
                          POST   /users/password(.:format)                                                                devise/passwords#create
 cancel_user_registration GET    /users/cancel(.:format)                                                                  devise/registrations#cancel
    new_user_registration GET    /users/sign_up(.:format)                                                                 devise/registrations#new
   edit_user_registration GET    /users/edit(.:format)                                                                    devise/registrations#edit
        user_registration PATCH  /users(.:format)                                                                         devise/registrations#update
                          PUT    /users(.:format)                                                                         devise/registrations#update
                          DELETE /users(.:format)                                                                         devise/registrations#destroy
                          POST   /users(.:format)                                                                         devise/registrations#create
                     root GET    /                                                                                        posts#index
                    posts GET    /posts(.:format)                                                                         posts#index
                          POST   /posts(.:format)                                                                         posts#create
                 new_post GET    /posts/new(.:format)                                                                     posts#new
                edit_post GET    /posts/:id/edit(.:format)                                                                posts#edit
                     post GET    /posts/:id(.:format)                                                                     posts#show
                          PATCH  /posts/:id(.:format)                                                                     posts#update
                          PUT    /posts/:id(.:format)                                                                     posts#update
                          DELETE /posts/:id(.:format)                                                                     posts#destroy
                 comments GET    /comments(.:format)                                                                      comments#index
                          POST   /comments(.:format)                                                                      comments#create
              new_comment GET    /comments/new(.:format)                                                                  comments#new
             edit_comment GET    /comments/:id/edit(.:format)                                                             comments#edit
                  comment GET    /comments/:id(.:format)                                                                  comments#show
                          PATCH  /comments/:id(.:format)                                                                  comments#update
                          PUT    /comments/:id(.:format)                                                                  comments#update
                          DELETE /comments/:id(.:format)                                                                  comments#destroy
                          GET    /likes/:id(.:format)                                                                     likes#touch
            follow_action GET    /follow/action(.:format)                                                                 follows#touch
                    users GET    /users(.:format)                                                                         users#index
                     user GET    /users/:id(.:format)                                                                     users#show
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
```

Railsは`rails routes` を叩くことで定義されているルーティング情報を一発で全件表示することができます。
今回、僕が見つけられなかった`/users/edit(.:format)`もこれで即調べることが可能です。
`edit_user_registration` ←の末尾に「_path」を付けるだけ。

```erb
<%= link_to "プロフィールを編集する", edit_user_registration_path %>
```

↑こういう書き方をすれば、プロフィール編集ページへのリンクが簡単に作れるわけですね。

## RailsからDBへのテーブル接続がうまくいかない
`carrierwave` という画像アップロードのGemを導入したが画像投稿時に「画像のパス」ではなく「クラス名」が登録されてしまう事態が発生。

```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController

  # 新しいコメントオブジェクト作成
  def new
    @comment = Comment.new
    # binding.pry
  end

  # 新しいコメントをテーブルに保存
  def create
    binding.pry
    Comment.create(
        score: comment_params[:score],
        comment_text: comment_params[:comment_text],
        comment_image: comment_params[:comment_image],
        post_id: params[:id],
        user_id: current_user.id)
    redirect_to root_path
  end


    private

    def comment_params
      params.require(:comment).permit(:score, :comment_text, :comment_image)
    end

end
```

```erb:app/views/comments/new.html.erb
<div class="text-center mt-5 mb-5">
  <h2>口コミ投稿する</h2>
</div>

<%= form_for @comment, method: :post do |f| %>
  <!-- 評価 -->
  <div class="form-group row">
    <label class="col-md-3 col-form-label" for="score">評価</label>
    <div class="col-md-9">
      <%= select :comment, :score, [["5", "5"], ["4", "4"], ["3", "3"], ["2", "2"], ["1", "1"]] %>
    </div>
  </div>
  <!-- 口コミ内容 -->
  <div class="form-group row">
    <label class="col-md-3 col-form-label" for="comment_text">口コミ内容</label>
    <div class="col-md-9">
      <%= text_area :comment, :comment_text, :size=>"40x5" %>
    </div>
  </div>
  <!-- 口コミ画像 -->
  <div class="form-group row">
    <label class="col-md-3 col-form-label" for="comment_image">口コミ画像</label>
    <div class="col-md-9">
      <%= f.file_field :comment_image %>
    </div>
  </div>
  <input type="submit" value="内容を送信">
<% end %>
```

```pry:binding.pry 
[3] pry(#<CommentsController>)> params[:comment][:comment_image]
=> #<ActionDispatch::Http::UploadedFile:0x00007f9062250e78
 @content_type="image/jpeg",
 @headers="Content-Disposition: form-data; name=\"comment[comment_image]\"; filename=\"1-2.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="1-2.jpg",
 @tempfile=#<File:/var/folders/y8/0wn59wsd67d090zgbpv_8r6w0000gn/T/RackMultipart20190717-48041-rjs60c.jpg>>
```

下記の画像のように commentsテーブルの `comment_image`カラムに対して
`#<ActionDispatch::Http::UploadedFile:0x00007f9062250e78` のようなクラス名が
登録されてしまう状態でした。
<img width="1155" alt="スクリーンショット 2019-07-17 21.27.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/f21eb297-879d-e221-98d3-5aef89c789b7.png">

同じ`carrierwave`のGemを使っているpostsテーブルは。`product_image`というカラムにファイル名が正しく書き込めている状態。
<img width="1155" alt="スクリーンショット 2019-07-17 21.30.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/d183901a-4cc1-5fd1-1af2-4fe4b93a29ff.png">

###原因
`app/models/comment.rb` ファイル内のタイポ（タイピングミス）
<img width="522" alt="スクリーンショット 2019-07-17 22.12.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/42f3118d-7f36-17fe-4e10-8345e02265c8.png">

###対策
エディタ側でタイポを防ぐ。RubyMineならタイポ部分をハイライトしてくれる。

##ActiveRecordを使って、2つのテーブルを結合し、特定の条件でレコードを取得したい

下記画像のように
following_user_id「3」を持つ人がフォローしている➔followed_user_id「1」「4」「6」のユーザー情報をusersテーブルから取得したい、という場合、
`app/controllers/users_controller.rb` に必要な記述が分からず、詰まっていました。
![followテーブル図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/7a838c1d-469e-eae2-d847-97087948d612.jpeg)

###解決方法
テーブルを結合するのではなく、下記のようにSQLを2回発行する方法で欲しいレコードを取得しました。

```ruby:app/controllers/users_controller.rb
# フォローしてる人、フォロワー情報、お気に入りしている投稿情報を取得
    followed_user_ids = Follow.where(following_user_id: params[:id]).pluck(:followed_user_id)
    @follow_list = User.find(followed_user_ids)
```
まず最初に`followed_user_ids = Follow.where(following_user_id: params[:id]).pluck(:followed_user_id)`で[4, 6, 1]のユーザー情報を取得。

```pry
[1] pry(#<UsersController>)> followed_user_ids 
=> [4, 6, 1]
```

後はfindメソッドを使い、usersテーブルの主キーであるIDを`[4, 6, 1]`で検索し欲しいレコードを`@follow_list`のインスタンス変数に代入するだけ。
`@follow_list = User.find(followed_user_ids)`
このインスタンス変数をerbに渡せば、フォローしている人、フォロワー情報などを簡単に出力することができる。
![スクリーンショット 2019-07-22 19.33.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/00522e44-97df-69ea-c646-a996c752c501.png)

## find と where 使い方の理解不足で1時間半、沼にハマる

###沼にハマった内容
フォローしている人の`user_id`を`follows`テーブルから取得するところまでは良かったのですが・・・

```ruby:app/controllers/users_controller.rb
    # タイムライン（フォローしている人の投稿一覧）を取得
    timeline_post_ids = Follow.where(following_user_id: params[:id]).pluck(:followed_user_id)
    @post_list = Post.find(user_id: timeline_post_ids )
```
上記のように`@post_list = Post.find(user_id: timeline_post_ids )`で
主キーで検索する findメソッドを使ってしまい。postsテーブルからレコードが取れない・・・と悩みました・

本来なら下記のように書くのが正しい記述です。

```ruby:app/controllers/users_controller.rb
    # タイムライン（フォローしている人の投稿一覧）を取得
    timeline_post_ids = Follow.where(following_user_id: params[:id]).pluck(:followed_user_id)
    @post_list = Post.where(user_id: timeline_post_ids )
```

###対策
ActiveRecordのメソッドをしっかりと理解する。
また、Rails・Rubyで使うメソッドの理解を深めること！

**find**
idなどの主キーが一致するレコードを取得します。
`user = User.find(1)`

**where**
条件が一致するすべてのレコードを取得します。
名前がsuzukiのユーザーを検索する
`User.where(name: 'suzuki')`

#その他、分からない部分などを参考にした記事

##updated_atを日本時間で表示する
https://qiita.com/yurina0402/items/46508aa303dfab97b471

##口コミや、タイムライン部分を日本語曜日入り日付フォーマットを1行で書く
https://qiita.com/hisa0507/items/8209448f1e3e20b96b52

##女装アイテム（商品）が削除された際、紐付ている「お気に入り」「口コミ」も同時に削除する
https://qiita.com/kamelo151515/items/0fa7fb15a1d2c1e44db2

##口コミ内で「改行」が起きた場合、<br>タグへ変換させてブラウザ上でも改行してる状態で見えるようにする
https://qiita.com/defunty/items/8c64a2939bcd31a62ffb
https://qiita.com/t_oginogin/items/0164ad76b753fdff9902

##口コミに写真が投稿されていない場合の判定（nil?, empty?, blank?）の使い分け

```erb:app/views/posts/show.html.erb
<% unless comment.comment_image.blank? %>
```
https://qiita.com/aeroastro/items/b58228decb7094af7594

##link_toメソッドの使い方
https://qiita.com/Hal_mai/items/94b7d5c7e00c72324013

##RailsのDeviseで新たに追加したカラムをStrong Parametersに追加してSignup/Updateする方法
https://qiita.com/tana117/items/60c3e56b6d7b8b9bad1a

##formのバリデーション
https://rails-study.net/validation/

##【Rails】Flashメッセージの使い方とメッセージの拡張・応用
https://pg-happy.jp/rails-flash-message.html

##お気に入り数のカウントをする際のActiveRecord参考
https://techracho.bpsinc.jp/hachi8833/2018_02_15/52296

##devise日本語化
https://kitsune-programming.com/rails-devise

## gem 'bootstrap-sass', '3.3.7' をGemfileに記述すると下記のエラーがでる「rails s」でアプリは起動できる

```terminal
/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootstrap-sass-3.3.7/lib/bootstrap-sass/version.rb:2: warning: already initialized constant Bootstrap::VERSION
/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootstrap-4.1.3/lib/bootstrap/version.rb:2: warning: previous definition of VERSION was here
/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootstrap-sass-3.3.7/lib/bootstrap-sass/version.rb:3: warning: already initialized constant Bootstrap::BOOTSTRAP_SHA
/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/bootstrap-4.1.3/lib/bootstrap/version.rb:3: warning: previous definition of BOOTSTRAP_SHA was here
```
https://qiita.com/SonoT/items/a45ca6e91e32191f5449
↑で解決


#リプレイスを通して得られたこと
Railsチュートリアルや、オンラインサロンの学習教材でも、簡単にwebアプリは一通り作っていましたが、やはりオリジナルのアプリをRailsで作成する。となると難易度は結構上がります、

今回、僕が制作した「女装アイテムの口コミ投稿アプリ」は

- usersテーブル
- postsテーブル
- commentsテーブル
- likesテーブル
- followsテーブル

合計「5つ」のテーブル数でしたが、Sinatra => Railsへリプレイスするだけでも「３０時間」かかりました。
![スクリーンショット 2019-07-22 20.08.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407853/219ce7bf-c32e-9401-01f3-30b28fad1ec2.png)

１週間詰め込みだったので、結構たいへんでしたが「初めて自分でオリジナルのRailsアプリを完成させた」という達成感は半端ないです！

作っていく中で、色んな人に質問してアドバイスを頂けたのも完成まで挫折しなかった、大きな理由かと思います。

最後に、完成した「女装アイテムの口コミ投稿アプリ」のGit-hubのリポジトリを共有↓
https://github.com/yuikotonoha/sinatra_replace_app

今後はSinatraでは実装できなかった機能をGemで増やしたり、テーブルなどを増やして新機能を拡張していったりして
webエンジニアの転職活動に持っていけるポートフォリオにレベルアップさせていきます！！

ではでは！この辺で失礼いたします。