FactoryBotメモ

『Everyday Rails - RSpecによるRailsテスト入門』を読んでRSpecを使い始めました。
以下メモ

ファクトリ

↓spec/factories/users.rb

FactoryBot.define do
  factory :user do
    name "マイク"
    text "こんにちわです"
    sequence(:email) { |n| "tester#{n}0@example.com" }
    password "dottle-nouveau-pavilion-tights-furze"
    
  end
end


↓spec/factories/blogs.rb

FactoryBot.define do
  factory :blog do
    title "サンプルブログ"
    text  "日記帳です。"
    association :user
   
 end
end


↓spec/factories/articles.rb

FactoryBot.define do
  factory :article do
    sequence(:title) { |n| "海に行きました #{n}" }
    text  "キレイでした。"
    association :blog

  end
end


実行コード

require 'rails_helper'

RSpec.feature "Articles", type: :feature do

 scenario "ファクトリの確認" do
    article = FactoryBot.create(:article)
    puts article.inspect

    puts article.blog.inspect

    puts article.blog.user.inspect
    
    other_article = FactoryBot.create(:article)
    puts other_article.inspect

    puts other_article.blog.inspect

    puts other_article.blog.user.inspect
 
  end
end


結果

#<Article id: 1, title: "海に行きました 1", text: "キレイでした。", blog_id: 1, created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33">
#<Blog id: 1, title: "サンプルブログ", text: "日記帳です。", user_id: 4, created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33">
#<User id: 4, email: "tester20@example.com", created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33", name: "マイク", text: " こんにちわです", admin: false>
#<Article id: 2, title: "海に行きました 2", text: "キレイでした。", blog_id: 2, created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33">
#<Blog id: 2, title: "サンプルブログ", text: "日記帳です。", user_id: 5, created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33">
#<User id: 5, email: "tester30@example.com", created_at: "2018-10-16 03:22:33", updated_at: "2018-10-16 03:22:33", name: "マイク", text: " こんにちわです", admin: false>


  • ファクトリデータ同士で関連付けが出来ているので、FactoryBot.create(:article)だけで紐付いたblogとuserが作成される
  • 複数回作成すれば、紐付いたデータも作成した分だけ作られる
  • データの参照は例えばユーザーなら article.blog.user で可能


  • フィーチャースペックだけ実行したい場合は $ bin/rspec spec/features
  • スペックの作成方法は $ rails generate rspec:(スペックの種類) (スペック名)

RailsアプリにシンプルなWYSIWYGエディタを導入

Basecampが開発しているWYSIWYGエディタ、TrixをRailsに導入しました。

Railsプロジェクトは作成中のブログサービスアプリケーション。
ブログ記事の作成ページにエディタを導入します。

バージョン

Rails 5.2.1
Trix 0.10.1


インストール

まずgemを導入します。

gem 'trix'


bundle install



application.jsに追記

//= require trix


application.scss最上部に追記

@import "trix";


入力フォームを編集

ブログ記事の入力フォームのテキスト(:text)部分

  <div class="field">
    <%= form.label :text %>
    <%= form.text_area :text %>
   
  </div>  


これを以下のように変更します。

  <div class="field">
    <%= form.label :text %>
    <%= form.hidden_field :text, id: :article_text %>
    <trix-editor input="article_text"></trix-editor>
  </div>  


作成したテキストの表示方法

そして入力したテキストのshowページで表示するんですが、HTML要素がエスケープされないようにする必要があります

<%= sanitize @article.text %>

これでも良いのですが、

 <%= sanitize @article.text, tags: %w(h1 h2 h3 h4 h5 h6 ul ol li p a img table tr td em br strong div), attributes:  %w(id class href) %>

このように、許可する要素と属性を明示的に指定するとより安心です。

結果

これだけで、リッチなテキストエディタRailsアプリに導入することができました。
f:id:naito-coding0322:20181013204259p:plain



参考リンク

GitHub - maclover7/trix

Rails で raw HTML を sanitize する - FIVETEESIXONE

Trix WYSIWYG Editor And File Uploads - YouTube

AWS Cloud9に繋がらないときはインスタンスの停止→起動

現象

一昨日からAWS Cloud9でenvironmentに接続しようとしても「connecting...」のまま数分動かず、そのまま放置していると

This is taking longer than expected. If you think there might be an issue, contact AWS Support. It might be caused by VPC configuration issues. Please check documentation.

というメッセージが出る、という状態でした。
色々試したんですが解消せず弱っていたんですが、さきほどあっさり解決したので解決方法を記録しておきます。


解決法

AWSサービス → すべてのサービス → コンピューティングのEC2 をクリック f:id:naito-coding0322:20181001135609p:plain

問題のインスタンスにチェックを付ける
アクション → インスタンスの状態 → 停止 の順に選択 f:id:naito-coding0322:20181001140031p:plain

インスタンスの状態が stopped になったのを確認して、いつもどおりアクセスして開く

他に試したこと

これをやる前には

・ブラウザのキャッシュ、Cookieの削除 ・ブラウザの拡張機能Adblockを停止 ・ブラウザをchromeから別のものに変更

なども試しました。
とにかく、使えるようになって一安心です。

ranked-modelを使うときは :with_sameオプションを付けよう

前置き

自作中のTODOアプリではタスクの表示順を自由に入れ替えできるようになっています。

f:id:naito-coding0322:20180922192059p:plain

indexページには User の Task 一覧を表示

表示の順番はranked-model gemを使って :row_order の値で制御

それぞれの Task はjQueryUIのsortableを導入してドラッグ&ドロップで入れ替えが可能

という感じです。

class Task < ApplicationRecord
  include RankedModel
  belongs_to :user
  scope :donetask,  -> { where(:done => false ) }
  ranks :row_order, :scope => :donetask
end


問題点

並び替えが上手くいかないときがある。

並び替えをしてページを更新すると、ひとつ下にズレたりすることがたびたびありました。
ログを見ると一応 :row_order の値はきちんと更新されています。


原因

新規にテストユーザーを作成し、タスクを4つ作成しました。
DB Browserで確認します。
f:id:naito-coding0322:20180922193453p:plain

1番目のタスクを1つ下(2と3のあいだ)へ移動すると :row_order の値が更新されます。 f:id:naito-coding0322:20180922193637p:plain

本来であれば、1番目のタスクの:row_order の値は「1162961895」と「1655222771」の間で設定されなければいけません。

では「-2146624780」は何かというと、これはデータベースに登録されたすべての Task のなかで上から2番目に位置する数値です。
(実際には「すべての未完了なタスク」が正しいです。Taskモデルの画像参照。) データベース上の全てのタスクの中ではなく、そのユーザーが持っているタスクの中で順番を変更しないといけません。

対処法

ranks :row_orderに :with_same => :(親モデルの外部キー) というオプションを付け足します。

class Task < ApplicationRecord
  include RankedModel
  belongs_to :user
  scope :donetask,  -> { where(:done => false ) }
  ranks :row_order, :scope => :donetask, :with_same => :user_id ←これ
end

これでユーザーごと、つまり同じ user_id を持った複数の Task ごとにrow_orderの数値が割り振られるようになります。

長々と書きましたが、単純に「gemを使うときはREADMEを必ず読もうぜ」という話ですね。気をつけよう

テンプレート側で出力する値の処理を行うのは避ける

表示に必要なデータはプログラム側で用意し、
テンプレートではデータを埋め込む場所や表示方法などを記述する。

前置き

「そのタスクがいくつのサブタスクを保持しているか」をindex画面に表示しています。
f:id:naito-coding0322:20180919155248p:plain

この部分のコードが以下です。

<td><%= task.subtasks.count unless task.subtasks.empty? %><%= "-" unless task.subtasks.present? %></td>


問題点

これだとテンプレート側で出力に必要なデータの処理を行っているため、ビューとコントローラの役割分担が上手くできていません。
冒頭に述べたように、テンプレートではあらかじめ用意されたデータの「埋め込み場所や表示方法」を記述するのが正しい使い方です。


解決策

該当部分をメソッドに切り出します。テンプレートで使うメソッドなのでhelperに定義します。

module TasksHelper

  #タスクにサブタスクがあれば数を表示
  def subtask_present?(task)
    if task.subtasks.present?
      task.subtasks.count
    else  
      "-"
    end
  end

end

これでindexページでの記述が以下のようにシンプルになり、処理された結果を表示するだけになりました。

<td><%= subtask_present?(task) %></td>


今回はtasks#indexでしか使わないヘルパーメソッドなのでtasks_helperに記述しましたが、アプリケーション全体で使うようであればapplication_helperに記述しましょう。

繰り返されるインスタンス変数の初期化をメソッドに切り出す

前置き

作成中のTODOアプリでは、ユーザーがタスクを複数所有し、それぞれのタスクがサブタスクを複数所有しています。

アクションメソッド(数個だけ抜粋)

  def show
    @user = User.find(params[:user_id])
    @task = Task.find(params[:task_id])
    @subtask = Subtask.find(params[:id])
  end

  def update
    @user = User.find(params[:user_id])
    @task = Task.find(params[:task_id])    
    @subtask = Subtask.find(params[:id])
    
    if @subtask.update(subtask_params)
      redirect_to user_task_path(@user, @task, @subtask)
    end
  end
  
  def destroy
    @user = User.find(params[:user_id])
    @task = Task.find(params[:task_id])
    @subtask = Subtask.find(params[:id])
    
    @subtask.destroy
    redirect_to user_task_path(@user, @task)
  end

今回はこれらのコードをリファクタリングしていきます。


問題点

OnceAndOnlyOnce原則に反していることです。
インスタンス変数の初期化が繰り返し記述されています。


対処

private
  def set_user
   @user = User.find(params[:user_id])
  end

このようなprivateメソッドを作ってbefore_actionで適用させれば、記述はこの一度で済ませることができます。

対処後

class SubtasksController < ApplicationController
 before_action :set_user
 before_action :set_task
 before_action :set_subtask

  def show
  end

  def update
    if @subtask.update(subtask_params)
      redirect_to user_task_path(@user, @task, @subtask)
    end
  end
  
  def destroy
   @subtask.destroy
    redirect_to user_task_path(@user, @task)
  end


  private
      def set_user
        @user = User.find(params[:user_id])
      end
      
      def set_task
        @task = Task.find(params[:task_id])
      end
      
      def set_subtask
        @subtask = Subtask.find(params[:id])
      end
end

ただこのset_○○には色々な考え方があり、

  • 機能が増えていくにつれてbefore_actionが大量に増えてしまう

  • before_actionの並び順によってエラーが起きる可能性もある

  • アクションをパッと見た時にどのようなインスタンス変数を扱うのかがすぐ分からない

という問題もあるようです。今回のようにシンプルなアプリケーションであれば上記のやり方で良いと思います。
インスタンス変数の初期化の処理が複雑なのであれば、find_○○というメソッドを定義してそれぞれのアクションの中で使うと分かりやすくなります。

参考リンク

ソースコードの減らし方 - 基本的な考え方と10個の方法 - クラウドワークス エンジニアブログ

Rails の before_action :set_* って必要? - ネットの海の片隅で

入力したテキストの改行をviewに反映する方法

f:id:naito-coding0322:20180913132958p:plain このように、テキストの改行が反映されるようになります。

simple_formatメソッドを使う

このメソッドは与えられた文字列に対して

文字列を<p>で括る
改行は<br/>を付ける
連続した改行は</p><p>に変換

という処理を行います。

ただ、HTMLタグなどをサニタイズする働きもあるようです。

Railsソースコード

    def simple_format(text, html_options = {}, options = {})
      wrapper_tag = options.fetch(:wrapper_tag, :p)

      text = sanitize(text) if options.fetch(:sanitize, true)
      paragraphs = split_paragraphs(text)

      if paragraphs.empty?
        content_tag(wrapper_tag, nil, html_options)
      else
        paragraphs.map! { |paragraph|
          content_tag(wrapper_tag, raw(paragraph), html_options)
        }.join("\n\n").html_safe
      end
    end


ということは、前回の記事で行ったテキストへの処理が無効化されてしまいます。
(テキスト内のURLがaタグに変換されるようにする - naito-coding0322’s diary)

筆者の場合は、リンクをクリックしたときに別ページでリンク先を開いてほしいのに、 target="_blank"が無効化されてしまい困っていました。


解決方法

オプションでsanitizeをfalseにすることができます!

<%= simple_format(文字列, {}, sanitize: false) %>


前回の記事に反映させると

<%= simple_format(convert_url_into_a_tag(h(@task.description)).html_safe, {}, sanitize: false) %>

これで、文中のURLをaタグに変換したテキストの改行を反映してviewで表示することができました。

もちろんですがsimple_formatのサニタイズをfalseにするのは、その前段階でテキストにhtml_escapeメソッドを使ってユーザーからの入力に対処していることが前提です。