railsにmarkdownを実装したよ

markdown 機能実装

記事を書くのにいちいちHTMLで書くのは面倒。

だから使い慣れているmarkdownを導入することにした。

今回欲しかった機能は以下の通り

  • markdown
  • リアルタイムプレビュー
  • toc(目次)機能

順番に説明していく。

実装工程

markdown

今回使うgemはこれ

gem 'redcarpet'
gem 'rouge'

そしておなじみのやつを打ち込んで

sudo bundle install

以下のファイルを作ってコードをぶち込む

app/helper/markdown_helper.rb

module MarkdownHelper
  def markdown(text)
    options = {
      filter_html:     true,
      hard_wrap:       true,
      space_after_headers: true,
      with_toc_data: true
    }

    extensions = {
      autolink:           true,
      no_intra_emphasis:  true,
      fenced_code_blocks: true,
      tables:             true
    }

    renderer = Redcarpet::Render::HTML.new(options)
    markdown = Redcarpet::Markdown.new(renderer, extensions)
    markdown.render(text).html_safe
  end

end

optionsとextentionsの中身(filter_htmlとか)は参考サイトを参照してほしい。もっと丁寧に書いてあって参考になるはず。

これができたらあとは、適当なviewにこれをかく。()の中身は各自対応させる。

app/views/articles/show.html.slim

  = markdown(@article.content)

これだけで@article.contentがmarkdownで表示される。

すげええ。

参考

redcarpetでrailsにシンプルにMarkdownを適用する

Railsでカスタムmarkdownを実装する - k0kubun's blog

Ruby On Railsでredcarpetを利用し、シンタックスハイライトに対応したブログ機能を実装する - Ruby_on_Rails | ゼロイチ | 独学者・初心者向けプログラミング・SEO入門サイト

リアルタイムプレビュー

vue.jsとmarked.jsをheadに追加する

/app/views/layout/application.html.slim

doctype html
html
  head
    title
      | hoge
    link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
    script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"
    script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
    /! Realtime preview 始まり
    script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.10/vue.js"
    script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.js"
   /! Realtime preview 終わり

    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  body
    /! 以下略

ほんで、フォームの部分に、投稿部分とプレビュー部分を並べる。

JSも書き忘れないように。

app/views/articles/_form.html.slim

= form_for @article do |f|
  /! 中略
  .field
    #editor
      textarea.form-control debounce="50" name="article[content]" rows="20" v-model="input"
      div v-html=("input | marked")
  .actions = f.submit "保存"

  javascript:
    window.onload = function() {
      new Vue({
        el: '#editor',
        data: {
          input: '#{j @article.content}',
        },
        filters: {
          marked: marked,
        },
      });
    };

f:id:kapiba-ra:20180609181025p:plain

参考

Railsにマークダウンのリアルタイムプレビューを実装する

html.erbファイルでjs直書きでその中にrubyのコードを埋め込んでいる状態のものをslimに置き換えるときの書き方

toc(目次)機能

先ほど作成したヘルパーに追記していく。

toc用のものを作成した。markdownを参考にしたら簡単にできた。

app/helper/markdown_helper.rb

module MarkdownHelper
  def markdown(text)
    options = {
      filter_html:     true,
      hard_wrap:       true,
      space_after_headers: true,
      with_toc_data: true
    }

    extensions = {
      autolink:           true,
      no_intra_emphasis:  true,
      fenced_code_blocks: true,
      tables:             true
    }

    renderer = Redcarpet::Render::HTML.new(options)
    markdown = Redcarpet::Markdown.new(renderer, extensions)
    markdown.render(text).html_safe
  end

  /!以下追加
  def toc(text)
    toc_option = {
      nesting_level: 2
    }

    toc_renderer = Redcarpet::Render::HTML_TOC.new
    toc = Redcarpet::Markdown.new(toc_renderer, toc_option)
    toc.render(text).html_safe
  end
  /! 以上追加

end

あとは、markdownを実装したとき同様こうやると完成。

app/views/articles/show.html.slim

  = markdown(@article.content)

f:id:kapiba-ra:20180609181044p:plain

参考

RedcarpetでTOC表示 | | Scimpr Blog

datetimepickerを取り入れた際につけたオプション

datetimepickerとは

時刻入力のフォームの際にカレンダーから選択できるようにするもの。

詳細は下の記事を参考にしてください。

今回は、datetimepickerを入れる際に利用したオプション的な機能を紹介します。

musicamusik.hatenablog.com

オプション的な機能

オートコンプリートをoff

テキストフィールドのオートコンプリート(文字を全部打たなくても補完してくれるやつ)が ちょうど邪魔になってしまったので削除した。

teratail.com

AMPM表記から24時間表記に変更

AMPM表記が調子悪かったので24時間表記を採用した。 時間表記のhh:mmの部分をHH:mmにすれば解決する。

RailsPro

日本語化

「日本語化」以降の部分を参照。 一行書くだけで日本語化されるので、ぜひ導入しておきたい。

arfyasu.hatenablog.com

こんなとこです

とまあ。この辺りを導入しました。先人の知恵を拝借してばかりで申し訳ないですね。

bootstrap3-datetimepickerをとりあえず入れてみた

bootstrap3-datetimepickerが必要になった

日付と日時を入れるのに使用したのがbootstrap3-datetimepicker。

今回はメモするのが面倒なので、ひたすらにコードを貼っていく。

# Gemfile

gem 'momentjs-rails'
gem 'bootstrap3-datetimepicker-rails'

で、sudo bundle installして。

 // application.js

//= require moment
//= require moment/ja
//= require bootstrap-datetimepicker

var data = {'data-date-format': 'YYYY-MM-DD hh:mm' };
$(function(){
  $('.datepicker').attr(data);
  $('.datepicker').datetimepicker({
    format: 'YYYY-MM-DD hh:mm',
    locale: 'ja',
    dayViewHeaderFormat: 'YYYY年 MM月'
  });
});

ここでカレンダーの見た目やクリックした際のテキスト出力を変更する。

//= require moment
//= require bootstrap-datetimepicker

ここは最低限必要。

//= require moment/ja

これは日本語化するのに必要。下のものと合わせて使う。

    locale: 'ja',
    dayViewHeaderFormat: 'YYYY年 MM月'

以下はひたすらコピペ。

/* application.css */

*= require bootstrap-datetimepicker
# articles_controller.rb

def create
    @article = Article.new(article_params)
    @article.user_id = current_user.id

    respond_to do |format|
      if @article.save
        format.html { redirect_to @article, notice: 'Article was successfully created.' }
        format.json { render :show, status: :created, location: @article }
      else
        format.html { render :new }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /articles/1
  # PATCH/PUT /articles/1.json
  def update
    respond_to do |format|
      if @article.update(article_params)
        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
        format.json { render :show, status: :ok, location: @article }
      else
        format.html { render :edit }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :meta_description, :content, :published_at)
    end

publised_atに追加したいからそれを許可する。

/ _form.html.slim

= form_for @article do |f|

  .input-group.date.datepicker
    = f.label :投稿時間
    = f.text_field :published_at, class: 'form-control'
    span.input-group-addon

でもうまくいかなくて

こうすると、ページ遷移したときにうまくいかないんですよね、、

よくわからない。

今回解消したのは{:method => :get }を追加する方法。

link_to '編集', edit_article_path(@article), {:method => :get }

下のを追加するという方法もあったのだが、なぜか機能しなかったので諦めた。

$(document).on 'ready page:load', ->

参考 ↓

Turbolinksをオフしないためにやった事

まとめ(個人的なやつ)

機能を実装するときは複数のサイトを見比べることで、

  • より良い構成案が浮かぶ
  • どのコードがどの役割をしているか予想がつく

などのメリットがあるんですねえ。

参考

[Rails]日付と時間の入力フォームにDateTimePickerを使う(bootstrap3-datetimepicker-rails) – hello-world.jp.net

RailsにDateTimePickerを導入する

rails で bootstrap3-datetimepicker を使ってみた - spring of life

Ruby on Rails - Railsで特定の画面遷移時にjQueryが効かない(52508)|teratail

Turbolinksをオフしないためにやった事

kaminariでページが切り替わらなくて焦った

問題

いきさつ

この下のエントリを参考にやってます。

kaminari徹底入門

Gemfileに

gem 'kaminari'

を入れて

% bundle install

を実行した。

そして、index.html.slimに

= page_entries_info @articles

/ 本文

= paginate @articles

として追加した。

また、

@articles = Article.page(params[:article]).order(created_at: :desc)

しかし、なぜかページが変わらない。

f:id:kapiba-ra:20180528162902p:plain

2とかnextを押してもリンクが変わるだけで、次のページに行く気配がない。なんだこれ。

問題の要約

つまり、「リンクが変わるのにページが変わらない」というのが問題である。

解決の道筋

下のエントリをみたのがきっかけ

KaminariをSlimで使用した際のメモ

@articles = Article.page(7).order(created_at: :desc)

で、やってみたらpageが7まで、進んだ。 ふむ。こいつがpageを指定しているのはわかった。 いやまてよ。こいつの数が影響しているのではないか。

試しに、articlesのようにsを追加してやってみたら、全く結果が変わらなかったのだ。

@articles = Article.page(params[:articles]).order(created_at: :desc)

つまり、こいつが機能していないのだと。

解決方法

  def index
    @articles = Article.page(params[:page]).order(created_at: :desc)
  end

:pageにすることで解決したのだった。私が勝手に変なarticleに変えてしまったのか、、

反省点

これからはこういう風に考えていこう、、

  • 本当にそのものをコピーしているのか
  • できれば、順番に原因になりそうなとこをコメントアウト
  • それでもわからなければ、しらみつぶしにいじくってみる

deviseで新規カラムを追加・保存できるようにした(slim)

deviseの追加で困ったお話

deviseでuserモデルを作ったわけですが、そこに新規のカラムを追加したいのです。

f:id:kapiba-ra:20180527122949p:plain

usernameは、Twitterのusernameを取得するようにomniauthとかで自動で生成したものなので、今回は使いたくないなと。 そこでnameカラムを新規で作成し、ユーザー名を表示したりできるようになりたいなという魂胆です。

deviseの自動生成に困惑

でもdeviseで自動生成したものなので、手のつけようがない(知識がマジでないので私)。 そもそもviewもcontrollerもないじゃないかと嘆いたときにこのエントリーを発見。

viewとcontrollerを生成できることに気づく

blog.piyo.tech

めっちゃ書いてくれてあるやん! これに基づいて編集することにしました。

controllerのsuperについて

あとcontrollerにsuperっていう文字をちょいちょい見かけたんですよ。でも何か全然わかんなくて。

下の記事を見たところ、

「super」は特殊なメソッドと考えて下さい。メソッドの中で「super」メソッドを実行すると、スーパークラスの中でその呼び出されたメソッドと同じメソッド名を持つメソッドを探して実行します。

とのこと。つまり、どこかしらで同じメソッド名が記述されていて、それを引き継いでいるみたいな感じかな。例えば、def editのなかにsuperが書いてあったら、親のクラスのeditも引き継ぐよーってとこですかね。勉強が足らなくて難しい。

www.rubylife.jp

実際にやったこと

columnを作成

マイグレーションファイルを作成した。

% rails g migration AddNameToUsers name:string

んで、このあとはおきまりの

rake db:migrate

で、無事カラムをカラムを作成できたと。

f:id:kapiba-ra:20180527130525p:plain

viewを作成、編集

デフォルトではviewとcontrollerは編集できない(見えない?)ので、追加していきやす。

% rails generate devise:views

で作られたファイルは、erbなんでslimに変換して

musicamusik.hatenablog.com

form内に書き込める場所を作ると。(viewの英語表示部分は適当に日本語に変更していますが、ひとまず無視してもらって大丈夫です)

app/views/devise/registrations/edit.html.slim

h2
  | ユーザー編集
  - resource_name.to_s.humanize
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
  = devise_error_messages!

  / 以下追加-------------------------------------------------------
  .field
      = f.label :name, "名前"
      = f.text_field :name, :autofocus => true, class:"form-control"
  / 以上追加-------------------------------------------------------

  .field
    = f.label :メールアドレス
    br
    = f.email_field :email, autofocus: true, autocomplete: "email"
  - if devise_mapping.confirmable? && resource.pending_reconfirmation?
    div
      | Currently waiting confirmation for: 
      = resource.unconfirmed_email
  .field
    = f.label :新規のパスワード
    i
      | 変更したくない場合は入力しないでください。
    br
    = f.password_field :password, autocomplete: "off"
    - if @minimum_password_length
      br
      em
        = @minimum_password_length
        |  文字以上で入力してください。
  .field
    = f.label :新規のパスワード(確認)
    br
    = f.password_field :password_confirmation, autocomplete: "off"
  .field
    = f.label :現在のパスワード
    i
      | 更新には現在のパスワードの入力が必要です。
    br
    = f.password_field :current_password, autocomplete: "off"
  .actions
    = f.submit "更新"
h3
  |  アカウント削除
  = button_to "アカウント削除", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete
= link_to "戻る", :back

んで、一人でなんとなくて作ったときにエラーが出たのはここ。nameカラムの変更が許可されていならしい。 先ほどの記事を見るとどうやらstrong parameterで引っかかっているらしい。strong parameterってセキュリティ対策で許可されていないカラムは権限がない限り変更できない仕様にするやつです。 ここで登場するのがcontrollerってわけ。

controllerの作成、編集

これでcontrollerを作成して、

% rails g devise:controllers users

早速その、カラムの変更を許可しにいきましょ。

app/controllers/users/registrations_controller.rb

# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # 以下追加-------------------------------------------------------
  before_action :configure_permitted_parameters, only: [:update]

  private
    def configure_permitted_parameters
    devise_parameter_sanitizer.for(:account_update) do |u|
      u.permit(:name,
        :email, :password, :password_confirmation, :current_password)
    end
  end
  # 以上追加-------------------------------------------------------

# 略

end

としたらなんかエラーがでた。

NoMethodError at /users 
undefined method `for' for #<Devise::ParameterSanitizer:0x007fc8748f96b8>
Did you mean?  fork

そういえばprivate中にあるsanitize入れてないわ。入れます。 gem 'sanitize'をGemfileに入れてbundle install、と思ったらなぜかできなかった。 できなかった詳細は下のエントリーを参考にしてほしい。 要は、sudo bundle installでやらなきゃいけなかったぽい。

musicamusik.hatenablog.com

そんなこんなでなんとかsanitizeを入れましたと。それでも同じエラーがでる

いよいよ表示できた

今度こそと思いこいつを参考にした今までforでトラブっていたところをpermitに変えてみるといけるかも?

Rails4からRails5にアップデートしたらDeviceでエラーが出た

で、変えてみたらとうとうできた!そもそもpermitにしておけばよかったのか。 (もしかしてgemのインストールいらなかった、、? あとあとsanitizeは使うからいっか、、)

headerでnameを表示

こちらも一部日本語化とかしてあるけど、追加の一行を書けばおけ。 nameは初期値ではnullになっているので、何も表示されないというのを防ぐため仮としてemaliを入れておいた。 email以外を入れるなりなんなりはお任せします。とりあえずこれでいけましたわ。

doctype html
html
  head
    title
      | MusicaMusik
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  body
    header
        nav
            - if user_signed_in?
                                 
                                 # 下の一行を追加 ----------------------------------------------------------
                = link_to current_user.name || current_user.email, users_show_path

                = link_to 'プロフィール変更', edit_user_registration_path
                = link_to 'ログアウト', destroy_user_session_path, method: :delete
            - else
                = link_to 'サインアップ', new_user_registration_path
                = link_to 'ログイン', new_user_session_path
    / p.notice = notice
    / p.alert = alert
    = yield

こうして、nameを入力する前はemailが表示され、

f:id:kapiba-ra:20180528012608p:plain

name入力後はnameが表示されるようになりましたとさ。めでたし。

f:id:kapiba-ra:20180528012613p:plain

存在しないファイルを指定されたから他のエラー(Errno::EACCES)を探して解決したった。

gemをインストールできないっ

gem 'nokogumbo' がなぜかインストールできないという自体になったのです。

(gem 'nokogumbo'をGemfileに書いた前提で話を進めていきます。)

ぱっと見簡単そうな問題なのですが、なんで今回私がこんなに悩んだかと言いますと、、

ディレクトリが存在しない

一部抜粋でエラー文を紹介します。

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /var/folders/h5/7qckhn4s59z93wzyj4ggqg_c0000gn/T/bundler20180527-63132-1bjn9r1nokogumbo-1.5.0/extensions/universal-darwin-17/2.3.0/nokogumbo-1.5.0/mkmf.log

extconf failed, exit code 1

と書いてあったので、mkmf.logの中身を見れば何でエラーになったのかわかるだろうと。

しかし、このファイルは存在しませんでした。

まあ要領悪くで探せなかっただけなのかもしれないですが、

bundler20180527-63132-1bjn9r1nokogumbo-1.5.0/extensions/universal-darwin-17/2.3.0/nokogumbo-1.5.0/mkmf.log

から下の階層がどうやって探してみても存在しないんですよ。

% find / nokogumbo-1.5.0

で検索してみるもヒット数は0。

ここでどん詰まりました。

他のエラー文をみて見ると

どこか他にヒントはないかと探すと

/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/fileutils.rb:253:in `mkdir': Permission denied @ dir_s_mkdir - /Library/Ruby/Gems/2.3.0/gems/nokogiri-1.8.2/ports (Errno::EACCES)

という一文を発見。

Errno::EACCES

ってなんかググればでできそうなエラー文やなと思い立ち、下のエントリーにたどり着く。

tech.librastudio.co.jp

これで解決

以下のコマンドを打てと書いてあったから脳死でとりあえず打ち込むもうまくいかず。

% sudo gem update

しかし、このエントリーをよく見ると、permission関連でエラーがあったとかいてあるではないか。

もしやと思いsudoで試す。

% sudo bundle install

できた! どうやら権限を欲していたらしい。

できたのはいいのだけどなんで今回はsudoを必要としたのだろうか。いつものbundle installとは何か違ったということだろうか。

とりあえず、permission関連のエラーがあったら権限を付与して実行してみると良さそうだ。

erb→slimに変換するときにお世話になるサイト2つ

結構めんどいから自分でまとめおく

erbからslimに変換することがちょいちょいあるのでここで簡単にまとめておく

ディレクトリごとまとめて変換するとき

erb2slim app/views/ app/views/ -d

app/views/部分がそれぞれ、変更前の場所、変更後の場所となっている。 基本的に同じ場所におきたいはずなので、ディレクトリを指定する。 /views/の状態でこれを指定すると、view全体をまとめてslimにできるので楽。

そのほかの記法は以下を参考。

marketing-web.hatenablog.com

ファイル単体で変換するとき

下のサイトで左側にerbを入れれば、右側にslimで変換してくれる。 超絶便利。

erb2slim.com