Rails5にリッチテキストエディタを実装してみた！


![qiita用.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619712/99991c63-185c-3a8e-328f-e2b4d4727ca9.gif)

##環境
####・開発環境
Rails 5.2.3
Ruby 2.5.7
windows
vagrant
<font color="Red">bootstrap,jquery導入済み</font>
####・本番環境
EC2
RDS
Mysql



##summernote

・summernoteのgithub
https://github.com/summernote/summernote-rails

リッチテキストエディタが簡単に実装できる便利なgemです。
rails6からはaction textというデフォルトの機能で実装できますが、自分はrails5なのでsummernoteをつかいました。

rails5での実装方法はほかにもあります。

##他の実装方法
####①action textを導入する。
rails6標準の機能ですが、rails5にも導入できます。
[rails5.2での導入デモ（英語です）](https://www.driftingruby.com/episodes/using-action-text-in-a-rails-5-2-application)

デモ通りやってもyarnなどに関するエラーが出ますが、僕は、yarnやwebsocketの導入がわからず断念しました。
rails6の環境構築ができている人は、こちらがいいと思います。

####②gem froalaを使う
こちらは比較的簡単に導入できますが、画像の保存まで下の記事でできたかは覚えてないです。。。
課金しないと余計なテキストが表示されます。
[frola実装記事](https://ys3128.hatenadiary.jp/entry/2019/07/05/192300)

####③gem trixをつかう
rails 6のaction textと同じ記述のものがgemとして切り出されたものらしいです。
こちらはまだ試してません。

##実装!

summernoteのgit hub通り実装していきます。

####①gemの導入

```ruby:Gemfile
gem 'simple_form'
gem 'summernote-rails', '~> 0.8.10.0'
```

READMEでは、summernote-railsを '0.8.16.0'とありますがbundel installでそんなバージョンはないよと言われてしまったので、とりあえずこのバージョンで実装しました。

```
bundle install
```

####②application.scssを編集
以下を追加します。

```ruby:application.scss
@import "summernote-bs4";
```
####③application.jsを編集
以下を追加します。

```javascript:application.js

//= require summernote/summernote-bs4.min 
//= require summernote-init

```
####④summernote-init.cofeeファイルを作成する。
手動で作成しました。
READMEのコードをそのまま書きましょう。

```coffeescript:summernote-init.cofee
$(document).on 'turbolinks:load', ->
  $('[data-provider="summernote"]').each ->
    $(this).summernote
      height: 300
```
####⑤viewを編集

#####form_forの場合
フォームの中に５行目の１行を設置するだけでリッチテキストエディタになります。

```ruby:formのあるview
<%= form_for(post) do |form| %>
  ・・・
  <div class="field">
    <%= form.label :text %>
    <%= form.text_area :text, 'data-provider': :summernote %> #この行
  </div>
  ・・・
<% end %> 
```

```html:記事詳細ページ
# =を２つ付ける
<%== post.text %>
```


以上でローカルサーバーを起動すると、テキストエディタが使えるようになっています！(超簡単！)

<img width="1196" alt="スクリーンショット 2020-07-28 17.41.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619712/66208530-600f-9366-0fbd-bce4a5b271d8.png">



と、思いきや、、、、

deploy環境ではこのままだとうまくいきませんでした。。。

##本番環境でのエラー
deploy環境用に改善していきます。

###①summernoteが適用されない

原因 : rails_ujsとjquery_ujsが共存できない！（僕はもともとrails_ujsを記述してあったのにreadmeの内容をそのまま書いたのでうまくいきませんでした）

解決：summernoteのgit hubではjquery_ujsを入れてますが、rails_ujsとぶつかってしまうのでrequire jquery_ujsを消しました。

rails_ujsの記述があればjquery_ujsは消して大丈夫です。
ローカルではエラーにならなかったので気付きませんでした。

###②画像を貼り付けると投稿後エラーになる。
僕の場合、開発環境ではsqlite3、本番環境ではmysqlを使用していましたが、mysqlではbodyの文字列が長過ぎる！と怒られてしまいました。。。
本番環境で同じようなエラーが起きた場合は以下の方法で解決できると思います。
bodyにtextとして含んでいた画像部分を他modelのカラムとして切り離します！

[summernoteのgithub](https://github.com/summernote/summernote-rails/tree/master/example)

####carrierwaveを導入

```ruby:Gemfile
gem 'carrierwave'
```

####uploadモデルとuploaderを作成

```
$ rails g model Upload image
$ rails g uploader Image
```

```ruby:models/upload
class Upload < ApplicationRecord
  mount_uploader :image, ImageUploader
end
```

コントローラを作成

```
$ rails g controller uploads create destroy
```

```ruby:controllers/uploads/uploads_controller.rb
class UploadsController < ApplicationController

  def create
    @upload = Upload.new(upload_params)
    @upload.save

    respond_to do |format|
      format.json { render :json => { url: @upload.image.url, upload_id: @upload.id } }
    end
  end

  def destroy
    @upload = Upload.find(params[:id])
    @remember_id = @upload.id
    @upload.destroy
    FileUtils.remove_dir("#{Rails.root}/public/uploads/upload/image/#{@remember_id}")
    respond_to do |format|
      format.json { render :json => { status: :ok } }
    end
  end

  private

  def upload_params
    params.require(:upload).permit(:image)
  end
end
```

####summernote-init.cofeeを編集する。
元の記述を消して以下で置き換えます。

```ruby:summernote-init.cofee
sendFile = (file, toSummernote) ->
  data = new FormData
  data.append 'upload[image]', file
  $.ajax
    data: data
    type: 'POST'
    url: '/uploads'
    cache: false
    contentType: false
    processData: false
    success: (data) ->
      img = document.createElement('IMG')
      img.src = data.url
      console.log data
      img.setAttribute('id', "sn-image-#{data.upload_id}")
      toSummernote.summernote 'insertNode', img      

deleteFile = (file_id) ->
  $.ajax
    type: 'DELETE'
    url: "/uploads/#{file_id}"
    cache: false
    contentType: false
    processData: false

$(document).on 'turbolinks:load', ->
  $('[data-provider="summernote"]').each ->
    $(this).summernote
      lang: 'ko-KR'
      height: 400
      callbacks:
        onImageUpload: (files) ->
          sendFile files[0], $(this)
        onMediaDelete: (target, editor, editable) ->
          upload_id = target[0].id.split('-').slice(-1)[0]
          console.log upload_id
          if !!upload_id
            deleteFile upload_id
          target.remove()
```

####ルーティングを設定

```ruby:routes.rb
resources :uploads, only: [:create, :destroy]
```

これで、画像は別modelとして保存されます。

####③画像が表示されない。
本番環境で画像が表示されなかったので僕の場合config/environment/production.rbに追記しました。

```ruby
config.public_file_server.enabled = true
```
この行を追加します。

####④S3へのアップロード
記事の画像のS3へのアップロードを設定したところ投稿後しばらく経つと画像が表示されなくなるよになってしまいました。
summernoteの性質上、最初に与えられたトークンごと文字列として保存してしまうので表示のたびにトークンが更新されず10分で表示されなくなります。

config.fog_publicをtrueにすることでトークンをなくせるので10分たっても消えません。（今回作ったのはブログのようなもので、他人にみてもらうことが前提なのでセキュリティ的な問題はないと判断しました。）

```ruby:carrierwave.rb
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  if Rails.env.production? # 本番環境の場合
    :
    :
    config.fog_public = true
    :
    :
  end
end
```