Help us understand the problem. What is going on with this article?

Railsにmedium-editorでMedium.comライクな投稿機能を導入する

More than 1 year has passed since last update.

はじめに

Ruby on RailsでMediumEditorを導入するまで。
Medium.comのかっこいいエディタをRailsプロジェクトに簡単に導入出来るので、オススメのWysiwygエディタです。

環境

  • Ruby 2.4.0
  • Rails 5.2.0

1. Railsプロジェクトの作成

Railsプロジェクトの作成からやっていきます。

$ rails new medium-editor

以上でmedium-editorというプロジェクトを作成します。

2. gem install

使用するgemをインストールします。
Gemfileを開き以下を入力します。

# medium editor
gem 'medium-editor-rails'
gem 'medium-editor-insert-plugin-rails'

以上を記載したら、bundle installを行います。
gemをインストールしたら、medium-editorで使用するjsとcssを読み込ませます。

app/assets/javascripts/application.jsに以下を追記

//= require medium-editor
//= require medium-editor-insert-plugin

app/assets/stylesheets/application.cssに以下を追記

*= require medium-editor/medium-editor
*= require medium-editor/themes/beagle
*= require medium-editor-insert-plugin

3. scaffoldで投稿機能の作成

scaffoldでCRUDの投稿機能をささっと作ってしまいます。

$ rails g scaffold Article title:string body:text

$ rails db:migrate

ここまで実行したら、http://localhost:3000/articlesを確認して見ましょう。

4. routes.rbを変更する

http://localhost:3000/articlesでもいいのですが、articlesと入力しなくてもアクセスが出来るようにしておきます。
config/routes.rbに以下のようにします。

Rails.application.routes.draw do
  resources :articles

  root 'articles#index'
end

5. フォームにmedium-editorを適用させる

app/views/articles/_form.html.erbを開く

テキストエリアにeditableというクラスを付与させておく。
該当ファイルの最終行に以下を追記することで、medium-editorを適用することが出来る。

<script>
  var editor = new MediumEditor('.editable', {
    // placeholder settings
    placeholder: {
      text: 'Type your story',
      hideOnClick: true
    }
  });

  $('.editable').mediumInsert({
    editor: editor
  });
</script>

6. 見た目を整える

scaffoldをしただけでは、寂しいので、Siimpleを使って見た目を最低限整えてあげましょう。

layouts/application.html.erb

app/views/layouts/application.rbを以下のようにします。
今回はCDN経由でSiimpleを使えるようにしておきます。

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

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/siimple@3.0.0/dist/siimple.min.css">
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

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

articles/index.html.erb

app/views/articles/index.html.erb

<div class="siimple-content">
  <p id="notice"><%= notice %></p>

  <h1 class="siimple-h2">Articles</h1>

  <table class="siimple-box">
    <thead>
      <tr>
        <th>Title</th>
        <th>Body</th>
        <th colspan="3"></th>
      </tr>
    </thead>

    <tbody>
      <% @articles.each do |article| %>
        <tr>
          <td><%= article.title %></td>
          <td><%= link_to 'Show', article, class: 'siimple-link' %></td>
          <td><%= link_to 'Edit', edit_article_path(article), class: "siimple-link" %></td>
          <td><%= link_to 'Destroy', article, class: 'siimple-link', method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>

  <br>

  <%= link_to 'New Article', new_article_path, class: 'siimple-link' %>
</div>

articles/show.html.erb

app/views/articles/show.html.erb

<div class="siimple-size siimple-size--large">
  <div class="siimple-box">
    <p id="notice"><%= notice %></p>

    <p class="siimple-box-title">
      <strong>Title:</strong>
      <%= @article.title %>
    </p>

    <p class="siimple-box-detail">
      <strong>Body:</strong>
      <%= @article.body.html_safe %>
    </p>
  </div>
  <%= link_to 'Edit', edit_article_path(@article), class: 'siimple-link' %> |
  <%= link_to 'Back', articles_path, class: 'siimple-link' %>
</div>

articles/_form.html.erb

app/views/articles/_form.html.erb

<%= form_with(model: article, local: true, class: 'siimple-form') do |form| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>

      <ul>
      <% article.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="siimple-form-field">
    <%= form.label :title, class: 'siimple-label' %>
    <%= form.text_field :title, id: :article_title, class: 'siimple-input' %>
  </div>

  <div class="siimple-form-field">
    <%= form.label :body, class: 'siimple-label' %>
    <%= form.text_area :body, id: :article_body, class: 'editable siimple-textarea siimple-textarea--fluid' %>
  </div>

  <div class="siimple-form-field">
    <%= form.submit class: 'siimple-btn siimple-btn--blue' %>
  </div>
<% end %>

articles/new.html.erb

app/views/articles/new.html.erb

<h1 class="siimple-h2">New Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Back', articles_path, class: 'siimple-link' %>

articles/edit.html.erb

app/views/articles/edit.html.erb

<h1 class="siimple-h2">Editing Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Show', @article, class: 'siimple-link' %> |
<%= link_to 'Back', articles_path, class: 'siimple-link' %>

7. carrierwaveを使って画像を保存出来るようにする

以上までで、投稿が出来るようになったのですが、文中に挿入した画像を保存出来るようにします。

Gemfile

Gemfileを開き、以下を追加

gem 'carrierwave'

追加したら、bundle install

uploaderを生成

rails g uploader Image

画像保存用のリソースの作成

rails g scaffold Image file:string

imageモデルの修正

生成されたapp/models/image.rbを以下のように修正

class Image < ApplicationRecord
  mount_uploader :file, ImageUploader
end

routes.rbにpostメソッドを加える

config/routes.rbを開き、以下を加える

post 'images/upload', to: 'images#upload'

images_controller.rb

app/controllers/images_controller.rbを以下のように編集する。

scaffoldで生成したアクションは全て消してしまって大丈夫です。

class ImagesController < ApplicationController
  def upload
    files = params.require(:files)

    @image = Image.new
    @image.file = files[0]
    respond_to do |format|
      if @image.save
        format.html { redirect_to @image, notice: 'Image was successfully created.' }
        format.json do
          render json: {
            files:
              [
                {
                  url: @image.file.metadata['url']+"?id=#{@image.file.model.id}",
                  thumbnail_url: @image.file.metadata['url']+"?id=#{@image.file.model.id}",
                  size: 0,
                  delete_url: "/images/delete",
                  delete_type: "DELETE"
                }
              ]
          }
        end
      else
        format.html { render :new }
        format.json { render json: @image.errors, status: :unprocessable_entity }
      end
    end
  end
end

articles/_form.html.erbを修正する

app/views/articles/_form.html.erbの最終行に追加したscriptタグの中身を変更する。

<script>
  $(document).ready(function(){
    var editor = new MediumEditor('.editable', {
      // placeholder settings
      placeholder: {
        text: 'Type your story',
        hideOnClick: true
      }
    });

    $('.editable').mediumInsert({
      editor: editor,
      addons: {
        images: {
          fileUploadOptions: {
            url: '/images/upload',
            type: 'post',
            acceptFileTypes: /(.|\/)(gif|jpe?g|png)$/i
          }
        }
      }
    });
  });
</script>

8. 完成形のスクリーンショット

スクリーンショット 2018-05-05 23.50.53.png

スクリーンショット 2018-05-05 23.51.06.png

スクリーンショット 2018-05-06 0.24.40.png

スクリーンショット 2018-05-06 0.25.40.png

上記で一通り使えるまでには出来たかと思います。
そのうち、画像のストレージをCloudinaryに置き換えて画像を保存する方法について書こうと思います。

何かあればコメントください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした