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 で可能
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アプリに導入することができました。
参考リンク
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 をクリック
問題のインスタンスにチェックを付ける
アクション → インスタンスの状態 → 停止 の順に選択
インスタンスの状態が stopped になったのを確認して、いつもどおりアクセスして開く
他に試したこと
これをやる前には
・ブラウザのキャッシュ、Cookieの削除 ・ブラウザの拡張機能Adblockを停止 ・ブラウザをchromeから別のものに変更
なども試しました。
とにかく、使えるようになって一安心です。
ranked-modelを使うときは :with_sameオプションを付けよう
前置き
自作中のTODOアプリではタスクの表示順を自由に入れ替えできるようになっています。
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で確認します。
1番目のタスクを1つ下(2と3のあいだ)へ移動すると :row_order の値が更新されます。
本来であれば、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画面に表示しています。
この部分のコードが以下です。
<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_○○というメソッドを定義してそれぞれのアクションの中で使うと分かりやすくなります。
参考リンク
入力したテキストの改行をviewに反映する方法
このように、テキストの改行が反映されるようになります。
simple_formatメソッドを使う
このメソッドは与えられた文字列に対して
文字列を<p>で括る
改行は<br/>を付ける
連続した改行は</p><p>に変換
という処理を行います。
ただ、HTMLタグなどをサニタイズする働きもあるようです。
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メソッドを使ってユーザーからの入力に対処していることが前提です。