- 投稿日:2020-02-21T22:28:08+09:00
【Rails】多対多の関連付け
1体多関連の復習
まずは1体多の関連を復習し、それからどのような場合に多対多の関連を付けるべきなのかを見ていきましょう。
関連ですので、二つモデルを用意します。一つはAuthorもう一つはBookにしましょう。
それぞれ著者と書籍を表すクラスです。書籍は一人の著者に書かれており、一人の著者は複数の書籍を出版しているとします。
この時、1体多の関連を作成するには、Bookクラスにauthor_idというカラムを定義し、次のようにクラスを記述するのでした。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author endこのように定義することで、次のようなメソッドを使うことができます。
author = Author.find(id) # ある著者が書いた書籍の配列 # author_idがauthorのidと同じ値であるbookを取得 author.books book = Book.find(id) # ある書籍の著者 # bookが持つauthor_idと同じ値をidに持つauthorを取得 book.authorしかし、実際には一つの書籍に複数の著者がいるという状況の方が一般的です。
ですので、
book.authors
とauthor.books
の双方向で複数の値を取得できるのが理想的です。現状、bookには一つのauthor_idを持つことしかできないので、何かしらの改修が必要になります。多対多の実装
そこで多対多の関連が必要となってきます。
多対多の関連にはAuthor,Bookの他に中間クラスと言われるAuthorBookクラスを追加します。
このAuthorBookクラスはauthor_idとbook_idを持っており、著者と書籍の関連1つに付き1レコード生成されます。ではモデルを作っていきましょう。
rails g model author_book author:references book:references rake db:migrateauthor:references,book:referencesと書くことでそれぞれのクラスと紐付けがされます。
次に各クラスを修正していきます。
class Author < ApplicationRecord has_many :author_books has_many :books, through: :author_books end class Book < ApplicationRecord has_many :author_books has_many :authors, through: :author_books end class AuthorBook < ApplicationRecord belongs_to :book belongs_to :author endこれで準備は整いました。では実験してみましょう
book = Book.create author1 = Author.create author2 = Author.create book.authorsこの時点ではまだ中間クラスによる関連付けが存在しないので、書籍に関連する著者は表示されないはずです。
AuthorBook.create(author_id: author1.id, book_id: Book.id) AuthorBook.create(author_id: author2.id, book_id: Book.id) book.authorsでは次はどうでしょうか?
設定がちゃんと出来れいれば2つのauthorインスタンスが表示されているはずです。簡単ですが多対多の関連付けの説明は以上になります。
多対多の関連は例えば、投稿に対するイイね機能や俳優と出演作品の関連などに用いられます。
複数のモデルが出てきて複雑になってきましたが、使いこなして複雑なデータ構造も扱えるようにしましょう。
- 投稿日:2020-02-21T21:17:08+09:00
rails s できない場合の原因事例
※初心者向け
アウトプットの練習の為記述しております。rails sできなかったケースの1つ
かなりイージミスですので初心者の方のみ参考にしてください。
ターミナルでrails sを記述し実行したのですがエラーが発生しました。
下記のメッセージ内容です。=> Booting Puma => Rails 5.2.4.1 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.2 (ruby 2.5.1-p57), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development Exiting Traceback (most recent call last): 44: from bin/rails:3:in `<main>' 43: from bin/rails:3:in `load' .....
これは結果から言いますと、別のローカルサーバーが既に立ち上がっていた為、エラーとなりました。
解決策して、使用していないローカルサーバーをcontrolボタン+Cボタンで切り再びrails s
これで解決しました。もしかしたらと思ったら試してください。
- 投稿日:2020-02-21T20:28:01+09:00
Rails の helper でブロックを受け取って、そのブロックの中で render をつかいたい
こんなことがしたい(けどうまくいかない)
ブロックを受け取って
h1
を出力するヘルパーと、それの使用例hoge_helper.rbdef render_h1_with_block(&block) content_tag(:h1, yield, class: 'title') endhoge/index.html.erb<%= render_h1_with_block do %> <%= render 'fuga' %> <% end %>hoge/_fuga.html.erb<span>I am fuga.</span>これは失敗する。
h1
の外側に_fuga.html.erb
がレンダリングされてしまっている。render_h1_with_block
でyield
したタイミングでレスポンスボディにrender 'fuga'
の結果が追加されてしまうようだ。意図しない結果になったindex.html<span>I am fuga.</span> <h1 class="title"></h1>解決策:
capture
を使う
CaptureHelper#capture
をつかって render の結果を保留すればうまくいく。The capture method extracts part of a template as a
String
object. You can then use this object anywhere in your templates, layout, or helpers.(適当な翻訳)
capture
メソッドはテンプレートの一部をString
オブジェクトとして切り出します。そのオブジェクトはテンプレート、レイアウト、ヘルパーで利用できます。ActionView::Helpers::CaptureHelper#capture - Ruby on Rails API Document より引用
hoge_helper.rbdef render_h1_with_block(&block) content_tag(:h1, capture(&block), class: 'title') end公式ドキュメントに従って
yield
をcapture(&block)
に書き換えてみた。意図通りの結果になったindex.html<h1 class="title"><span>I am fuga.</span></h1>うまくいった!
ドキュメント
- 投稿日:2020-02-21T18:44:12+09:00
【初心者向け】Railsで投稿される日時を日本時間に変更する(タイムゾーンの修正)
タイムゾーンを日本に変更するにはconfig/application.rbを修正する
参考
伊藤さんいつも参考にしています、ありがとうございます。
https://qiita.com/jnchito/items/831654253fb8a958ec25どのように記述するのか
module hogehoge class Application < Rails::Application config.time_zone = 'Tokyo' config.active_record.default_timezone = :local end end上記2文を追加するだけ。
追加した記述の2文を解説
config.time_zone = 'Tokyo'↑実際の表示を修正する記述
config.active_record.default_timezone = :local↑DBに保存する際にどの時間帯で保存するかの記述
伊藤さんが推奨されているlメソッドが便利でした!!しかも設定簡単
module hogehoge class Application < Rails::Application config.time_zone = 'Tokyo' config.active_record.default_timezone = :local end endこの設定を下記のように修正します。
module hogehoge class Application < Rails::Application config.i18n.default_locale = :ja config.active_record.default_timezone = :local end endconfig.time_zone = 'Tokyo'を書き換えただけですね。
config/localesにja.ymlファイルを作成
ja: time: formats: default: "%Y/%m/%d %H:%M:%S"と記述しました。
このあと、必ずサーバーを再起動(再起動しないと反映されないっぽい)
あとは、時間を表示させたいviewに
<%= l post.created_at %>っといった形式にするだけ。
ここは違う、こうした方が良い等々ございましたらご指摘いただけますと幸いです。
最後までみていただき、ありがとうございます。
- 投稿日:2020-02-21T18:29:16+09:00
rails でのドラクエ再現挑戦(モンスターのデータ)
はじめに
ドラゴンクエストシリーズをRuby,Ruby on railsで作る場合はどうするのか?という事を色々試してみたいと思います。
実行
ドラゴンクエストシリーズ(以下ドラクエ)には色んな要素がありますが
まずはモンスターのデータです。
サンプルでスライムのデータを記述しています。monsters.rbmonsters = [] monster = {name: "スライム", species_id: 1, HP: 5, MP: 0, attack: 4, defence: 4, speed: 5, pattern: [1,1,1,1,1,1,1,1], exp: 1, gold, 2} monsters << monster (後略)種族は別テーブルで保存しておいて、idで呼び出すことにします。
種族テーブルは次にような感じです。species.rbspecies = ["スライム","ドラキー","ゴースト",(後略)]行動パターンには配列を置いて、別テーブルに保存しておいた値をidで呼び出します。
patterns.rbpatterns = [] pattern = {name: "攻撃", target: 0, value: attack / 2, message: "#{monster.name}のこうげき"} patterns << pattern
target
は効果対象で、0が相手・1が味方、となります。value
はダメージ計算式です。message
は行動時にどんなメッセージが表示されるかを表します。そのモンスターの名前が表示されるように、#{monster.name}
を記述しています。モンスター関連のデータはこんな感じで良いかと思います。
- 投稿日:2020-02-21T18:23:38+09:00
Rails Whereで取得した値にdestoryが使えない理由
詰まったこと
Whereを使い取得した値をdestroyを使って削除しようとしたが、以下のエラーが出てできなかった。
ArgumentError (wrong number of arguments (given 0, expected 1)):解決方法
whereで取得した値は配列になっているため、destroyを使えない。
find_byを使うと削除できるようになる。以下の記事を参考
Railsのwhereメソッドと仲良くなろうproduct = Product.where(product_id: 3) product.destroy ⬇︎ product = Product.find_by(product_id: 3) product.destroy
- 投稿日:2020-02-21T18:02:37+09:00
[Rails6][sprockets4.0.0]Sprocketsを無効にするためにapp/assetsフォルダ以下を削除すると、Sprockets::Railtie::ManifestNeededErrorが出た
はじめに
Railsが用意しているjavascript, css, 画像などのアセットを管理するGemに、
Sprockets
とWebpacker
があります。
この2つを軽く説明すると、
名称 説明 Sprockets Rails3.1から導入されたアセットパイプライン。 app/assets
ディレクトリでアセットを管理するWebpacker Rails6から導入された、WebpackというバンドラをRails用にラップしたgem WebpackerはSprocketsとの共存を考えて設計されており、Sprockets, Webpackerどちらも使うことを想定しています。
ただまあどちらもアセットを管理するgemなわけで、ややこしくなるしどちらか1つだけ使えばいいのでは?と僕は考えています。
以前作成したAsobiというWebアプリでも、Webpackerでアセットを管理したので、最終的にSprocketsが管理するapp/assets
ディレクトリを丸ごと削除しました。
次のWebアプリでもWebpackerを使う予定だったので、app/assets
を削除したのですが…環境
- Ruby 2.7.0
- Rails 6.0.2
起きたこと
Sprockets
を使わないようにするための手順はいくつかありますが、最終的にapp/assets
というディレクトリは完全にいらなくなります。
なので最初にapp/assets
を削除しました。$ rm -rf app/assets試しにここで
rails s
でRailsを起動しました。
ここでは正常にRailsサーバが起動すると思ったのですが…WARNING: Nokogiri was built against LibXML version 2.9.10, but has dynamically loaded 2.9.4 => Booting Puma => Rails 6.0.2.1 application starting in development => Run `rails server --help` for more startup options Exiting Traceback (most recent call last): # 中略... /Users/user/rails_test/vendor/bundle/ruby/2.7.0/gems/sprockets-rails-3.2.1/lib/sprockets/railtie.rb:105:in `block in <class:Railtie>': Expected to find a manifest file in `app/assets/config/manifest.js` (Sprockets::Railtie::ManifestNeededError) But did not, please create this file and use it to link any assets that need to be rendered by your app: Example: //= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .css and restart your serverどうやら
app/assets/config/manifest.js
が無いためSprockets::Railtie::ManifestNeededError
というエラー出てrails s
が終了したようです。
だが待ってほしい。これまでこんなエラー出てこなかったぞ?原因
こちらのページによると、どうやらSprocketsのバージョンが
4.0.0
になってから起きるようになったエラーのようです。Redmine doesn't start with Sprockets::Railtie::ManifestNeededError if sprockets 4.0.0 is installed.
$ bin/rails c
Traceback (most recent call last):
28: from bin/rails:4:in<main>'
require'
27: from bin/rails:4:in
.
.
.
/Users/maeda/redmines/gems/ruby/2.6.0/gems/sprockets-rails-3.2.1/lib/sprockets/railtie.rb:105:inblock in <class:Railtie>': Expected to find a manifest file in
app/assets/config/manifest.js` (Sprockets::Railtie::ManifestNeededError)
But did not, please create this file and use it to link any assets that need
to be rendered by your app:Example:
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
and restart your server
Redmine doesn't start with Sprockets::Railtie::ManifestNeededError if sprockets 4.0.0 is installed.
と書いてありますね。Sprocketsのissueにも同様のエラー報告がありました。
Actual behavior
An error is thrown since 4.0, this didn't occur on 3.7.2:rake aborted!
Sprockets::Railtie::ManifestNeededError: Expected to find a manifest file inapp/assets/config/manifest.js
But did not, please create this file and use it to link any assets that need
to be rendered by your app:Example:
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
and restart your server3.7.2では起きなかったエラーとのことで、やはりSprockets 4.0.0から起きているエラーのようです。
対処法
Sprockets
のバージョンを3.7.2に下げるバージョン4.0.0から起きているのであれば、バージョンを3.7.2に下げれはエラーが解消されるはず!
まずはGemfile
に以下のコードを追記します。# Sprockets4.0だと、app/assetsディレクトリを削除するとSprockets::Railtie::ManifestNeededErrorが発生する gem 'sprockets', '~> 3.7.2'そして
bundle update
を実行します。(bundle installじゃないよ)
改めてrails s
を実行すると、無事エラーが解消されました。$ rails s WARNING: Nokogiri was built against LibXML version 2.9.10, but has dynamically loaded 2.9.4 => Booting Puma => Rails 6.0.2.1 application starting in development => Run `rails server --help` for more startup options /Users/user/rails_test/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call /Users/user/rails_test/vendor/bundle/ruby/2.7.0/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/static.rb:110: warning: The called method `initialize' is defined here Puma starting in single mode... * Version 4.3.1 (ruby 2.7.0-p0), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:3000 * Listening on tcp://[::1]:3000 Use Ctrl-C to stop
rails new
のオプションの--skip-sprockets
を指定する
rails new
のオプションに--skip-sprockets
というものがあります。
これを指定してRailsアプリを作成すると、config/application.rb
のrequireが一部変化します。application.rb# --skip-sprocketsを指定した場合 require "rails" # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" require "active_record/railtie" require "active_storage/engine" require "action_controller/railtie" require "action_mailer/railtie" require "action_mailbox/engine" require "action_text/engine" require "action_view/railtie" require "action_cable/engine" # require "sprockets/railtie" require "rails/test_unit/railtie" # 指定しない場合、この1文のみ記述される require 'rails/all'
require "sprockets/railtie"
がコメントアウトされています。
この状態であれば、app/assets
ディレクトリを削除してもSprockets::Railtie::ManifestNeededError
は発生しません。余談 :
--skip-sprockets
を指定してもSprocketsはインストールされるRailsガイドに書いてありますが、この
--skip-sprockets
はapplication.rb
の変更と一部のgemを除外してくれるだけです。# Railsガイドで説明されている、除外されるgem gem 'sass-rails' gem 'uglifier' gem 'coffee-rails'上記の除外されるgemのリストにSprocketsはありません。
そう、--skip-sprockets
オプションでSprocketsを使わないRailsアプリを作成しても、Sprocketsはインストールされます。更にapp/assets
も生成されます。これに関しては、GitHubのRailsにも以下のissueが投げられています。
The
app/assets
folder, and its underlying structure, is created, despite the fact that dropping stylesheets intoapp/assets/stylesheets
no longer causes them to be loaded. This is misleading, at best.つまり、「
--skip-sprockets
を指定したらapp/assets
を作らないようにした方が誤解がないのでは?」というissueが3年前に投げられています。
でも3年経っても変化はないので、恐らくこの状況を変えるつもりはないのでしょう。まとめ
Sprockets
を使わないのであれば、Sprockets
のバージョンを3.7.2
に落としましょう。
もしくはrails new
のオプションで--skip-sprockets
を指定しましょう。参考文献
https://www.redmine.org/issues/32223
https://github.com/rails/sprockets/issues/643
https://github.com/rails/rails/issues/29749
https://railsguides.jp/asset_pipeline.html
- 投稿日:2020-02-21T16:05:28+09:00
Rails"モデル"は不倫とガチ恋をする
はじめに
添野です。プログラミングを勉強中です。
今日はRails苦戦中の方向けの記事で、モデルについてです。
RailsのMVCの"M"、"モデル"がよく分かりません、という声をよく耳にします。モデルとは
結論をいいます、モデルとは「メンヘラPretender」です。
Pretender→https://www.youtube.com/watch?v=TQ8WlA2GXbk
※Pretenderをdisっている訳ではないです。筆者はかなり髭男ファンです。Pretenderを歌いたいがために月に4回ボイトレに行っています。モデルの役割
2つです。
・他のモデルとの人間関係を暴露する(アソシエーション)
・データベースとイチャイチャする下記はモデルファイルの一例です。
tweet.rbclass Tweet < ApplicationRecord belongs_to :user has_many :comments def self.search(search) return Tweet.all unless search Tweet.where('text LIKE(?)', "%#{search}%") end end一行ずつ、順番に解説していきます。
他のモデルとの人間関係を暴露する 「アソシエーション」
belongs_to :user has_many :comments
日本語訳「私はuserモデルにガチ恋しています」「私はcommentsモデルたちと不倫しています」
まず、belongs_to = ガチ恋 has_many = 不倫を覚えてください。下記の図は、tweetモデルさんの人間関係を表しています。
tweetさんはuserさんに対し秘めたる思いを抱えており、つまりガチ恋をしています。
しかしこれは叶わぬ恋であり、想いは一方通行なのです。美しいですね。君の運命の人は僕じゃないんですね。辛いけど否めないんです。つまりPretenderです。ところが、tweetさんはOfficial髭男dismのように綺麗な心を持っていません。
tweetさんはメンヘラであり、commentsさんたちにN股不倫をかけています。
そしてmodelファイルのなかでtweetさんはこれを堂々と宣言しています。tweetさんはかなりヤバイです。データベースとイチャイチャする
tweetさんはメンヘラであり他のモデル達とイチャイチャしたがると上に書きましたが、データベースともイチャイチャしたがります。最高ですね。
def self.search(search)
日本語訳「メソッドを定義しろ Tweetクラスのsearchメソッドで 引数はsearch」
カレーの話をします。searchメソッドはルーティングとコントローラに定義したカレールーの隠し味です。index,new,show...みたいなのが7種類あったと思いますが、これらはカレールーです。この7種類の他にも隠し味としてリンゴ(searchメソッド)をカレールーにブチ込んだ、みたいな感じです。引数searchは、検索ワードです。return Tweet.all unless search
日本語訳「返り値をよこせ Tweetモデルの全てを 検索ワードが空っぽなら」
入力欄に何も入れずに検索したら、ツイートを全部表示しろという意味です。Tweet.where('text LIKE(?)', "%#{search}%")
日本語訳「Tweetモデルの中身を取得しろ(含んでいれば,"検索ワード"に)」
記号だらけでゴチャゴチャしてますが、中身は簡単です。".where"は値を取得できます。取得する時の条件も設定できる優れものです。
'text LIKE(?)',"%#{search}%"は見た目の通り呪文であり、「引数searchを含んでいれば」という意味です。
引数searchは検索ワードです。この行は、一行前のunlessの影響を受けており、検索ワードが空じゃない時だけ仕事をします。
end
日本語訳「グッバイ」
ここからサビか!と思いきや、終わります。書いていて思いましたが、サビの初っ端が「グッバイ」ってセンスありすぎですよね。
一番盛り上がるところで「グッバイ」て。そんなことあります?逆に、終わりの歌詞は
「たったひとつ確かなことがあるとするのならば 君は綺麗だ」
エェェッッッッッモ...
そんな悲しいことある...?さとっちゃん、センスありすぎアリス議員です。
まとめ
モデルの役割は2つありました。
・他のモデルとの人間関係を暴露する(アソシエーション)
・データベースとイチャイチャする他にも空っぽのツイートを禁止するバリデーション機能などもありますが、とりあえず今回の2つを覚えればOKです。モデルがやっていることは、モデルのコードに書いてあることが全てです。「○○がよく分からん」と感じたら○○に書かれているコードを読みましょう。コードは、そのコードに書かれていること以外のことはしません。
以上がRails"モデル"の説明になります。
他にもRailsの疑問を解消するための記事がありますので、ご活用ください。共にTECH::EXPERTを駆け抜けましょう。
おすすめ記事
Rails消化のコツ
Railsは"5つの属性"を意識しろ
Rails用語集 基礎
- 投稿日:2020-02-21T15:05:20+09:00
DockerでRailsアプリケーション立ち上げ〜Githubプッシュまで
学んだこと、細かい設定、エラーをメモ
環境
Ruby 2.6.5
Rails 5.2.4
Docker 19.03.5
bundler 2.1.4
実装:Docker環境
各ファイル用意
適当にディレクトリ作成してその中に下記のファイルを入れる
ターミナル$ mkdir hoge
Dockerfile
docker-compose.yml
Gemfile
Gemfile.lock
Dockerfile
Dockerの新しいimageを作成する際に使用する設定ファイル。
コンテナはこの設定(image)を元に作成される。
Dockerfile → クラス
コンテナ → インスタンス
みたいなイメージ?DockerfileFROM ruby:2.6.5 RUN apt-get update -qq && apt-get install -y build-essential nodejs RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN bundle install COPY . /appdocker-compose.yml
Dockerで複数のコンテナを設定に従ってまとめて起動するために使用する。
今回はRailsを実行するコンテナ、Mysqlを実行するコンテナの2つを起動する設定を記述。docker-compose.ymlversion: '3' services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/app ports: - 3000:3000 depends_on: - db tty: true stdin_open: true db: image: mysql:5.7 volumes: - db-volume:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: password volumes: db-volume:Gemfilesource 'https://rubygems.org' gem 'rails', '5.2.4'Gemfile.lock空でOKRailsプロジェクト作成
ターミナル$ docker-compose run web rails new . --force --database=mysql
docker-compose run web
docker-compose.ymlで設定したWebコンテナ(Railsコンテナ)の中で後に続くコマンドが実行される。イメージをビルド
ターミナル$ docker-compose build
Dockerfile
が実行database.yml編集
passwordとhostの指定
database.ymlpassword: docker-compose.ymlの MYSQL_ROOT_PASSWORD で設定した文字列(今回はpassword) host: MySQLサーバーのコンテナ名(今回はdb)コンテナ起動
・起動
ターミナル$ docker-compose up -d・起動確認
ターミナル$ docker-compose psstateがUpになっていれば起動確認
・コンテナ、ネットワークの削除
--rmi all
でイメージも削除ターミナル$ docker-compose down --rmi allデータベース作成
ターミナル$ docker-compose run web bundle exec rails db:createbundlerのバージョンを変更したらエラー
1系がインストールされていることに気がついたので2系にしたらエラーがでた。
こちらの記事を参考に。DockerfileFROM ruby:2.6.5 RUN apt-get update -qq && apt-get install -y build-essential nodejs RUN mkdir /app WORKDIR /app COPY Gemfile /app/Gemfile COPY Gemfile.lock /app/Gemfile.lock RUN gem install bundler # 追加 RUN bundle install COPY . /app
bundle install
の前に最新のbundlerをインストールすることで解決。Docker内のMySQL接続方法
ターミナルdockerで立ち上げた MySQLへの接続 $ docker-compose up -d →コンテナ起動 $ docker-compose ps →現在立ち上がってるコンテナ確認 ↪︎ NAMEのdb名を確認(my_youtube_space_db_1) $ docker exec -it my_youtube_space_db_1 bash → コンテナへ接続 # mysql -u root -p →シャープが表示されたら入力する Enter password: → docker-compose.ymlで設定したパスワード入力 後はローカルのMySQLいじる時の操作と同じ実装:Githubへプッシュ
Git Flow
ターミナル$ git flow init enter * 7回リモート作成
github → Your profile → Repositories → NEW → リモート名記述 → Create repository
リモートとローカルの紐付け
ターミナル$ git remote add origin url $ git push -u origin master $ git push --all.gitignore編集
.gitignore# 追記 /config/database.yml docker-compose.ymladd commit push
割愛
- 投稿日:2020-02-21T14:38:55+09:00
【Rails】SwitchPoint利用時にSchemaCacheを設定しSHOW FULL FIELDSを防ぐ
SwitchPointとは?
https://github.com/eagletmt/switch_point
DBの書き込み接続と読み込み接続を切り替えることができるgemです。SchemaCacheについて
Railsでは
rake db:schema:cache:dump
を使うことでdb/schema_cache.yml
にテーブルやカラムの情報が書き出されます。
このキャッシュを使うことでActiveRecordが型情報などを特定する手助けになします。SwitchPointによりSchemaCacheが使えなくなる
SwitchPointを使うと
ActiveRecord::Base
とは別のConnectionPoolを保持してしまうため、SchemaCacheが自動で読み込まれません。
これによる、SQL実行時にSHOW FULL FIELDS FROM some_table
が実行され不要な遅延を発生させてしまいます。対策
config/initializers/switch_point.rbActiveSupport.on_load(:after_initialize) do ApplicationRecord.switch_point_proxy.connection.pool.schema_cache = ActiveRecord::Base.connection.schema_cache ApplicationRecord.switch_point_proxy.connection.schema_cache = ActiveRecord::Base.connection.schema_cache endinitializer内で
switch_point_proxy
のもつConnectionPoolにActiveRecord::Base
と同様のschema_cacheを設定してあげることで対策できます。※ただしこのコールバックより適当な実行タイミングがあるかは未確認
- 投稿日:2020-02-21T14:35:10+09:00
[Rails] 2つのテーブルをjoinsで結合したときのwhereのRailsライクな書き方
きっかけ
2つのテーブルをjoinsで結合した際に、結合した側のテーブルのカラムを使用して
whereで絞り込みをする際に、どうしてもwhereの中でSQLライクな書き方をしていました。それをRailsライクな書き方で書く方法を見つけたので残しておきます。
前提
以下のような関連があったとします。
Author has_many Book(s) Book belongs_to AuthorあるAuthorが複数のBookを持っている、という状況です。
これらのテーブルは、主に以下のようなカラムを持ち合わせています。Author - id - name Books - id - name - author_ididとnameという同じ名前のカラムがあります。
本題
Author.joins(:books)とすれば、SQLのINNER JOINが実行されるので
author.id
とbook.author_id
が等しい、つまり
author.id == book.author_id
となるようなレコードが抽出されます。
ということは、book.author_id == nil
のレコードはこの時点で弾かれます。ではここで、Bookのnameに対してwhereを使って絞り込みを行いたいと思います。
SQLライクな書き方
その際、自分はいつもこのようなSQLライクな書き方をしていました。
Author.joins(:books).where("books.name": "XXXXX")これをRailsライクな書き方で書こう、ということです。
Railsライクな書き方
以下のようにネストするだけです。
Author.joins(:books).where(books: { name: "XXXXX" } )これで上記SQLライクの場合と同じように調べられます。
ぱっと見、わかりやすくなったなと個人的には思っています。
おわり
- 投稿日:2020-02-21T13:16:50+09:00
RailsでHamlを使う方法⭐︎
RailsでHamlを使えるようにする方法
復習がてらにhamlを使ってコーディングしていたのでこの際アウトプットしておきます。
手順↓
- hamlのgemをインストールする
- 拡張子をerbからhamlにする
以上。超簡単
Hamlのgemをインストールする
Gemfilegem 'haml-rails'Gemfileに以上の記載をして
ターミナルで必ずbundle install
を実行しましょう。$ bundle installこの時点でhamlは使えるようになりますが拡張子も一気にerbからhamlに変更しておくと便利なので実行します。
拡張子の変更をターミナルで実行する
$ rails haml:erb2haml実行するとターミナル上で何か聞かれますが
y
コマンドを押しておくとオッケーです。以上。簡単
- 投稿日:2020-02-21T11:52:23+09:00
if文を用いて出力メソッドを一呼び出し
※初心者向け
※アウトプット練習の為開発環境
rails 5.2.4.1
ruby 2.5.1問題内容
20時から翌朝7時までにオウムに喋られると問題があるのでその場合は「NG」、
それ以外は「OK」と出力するメソッドを作成します。
オウムが喋る時をture、喋らない時をfalseと入力することにし、時刻も同時に入力します。
呼び出し方:
parrot_trouble(talking, hour)def parrot_trouble(talking, hour) if (talking && (hour < 7 || hour > 20)) puts "NG" else puts "OK" end end
- 投稿日:2020-02-21T11:46:18+09:00
erb拡張子をhaml拡張子に変更する
※初心者向け
※アウトプット練習の為、記述しております。今回は拡張子の変換について記述しております。
開発環境
rails 5.2.4.1
ruby 2.5.1やりたい事下記のファイルの拡張子(.erb)をファイルの内容も含め全てhamlへ変換したい。
/view/_form.html.erb /item.html.erb /new.html.erbまずはGemファイルに記述
gem 'erb2haml'ターミナル
bundle install rails haml:erb2haml
実行結果拡張子
/view/_form.html.haml /item.html.haml /new.html.haml以上で変換完了です
- 投稿日:2020-02-21T11:21:14+09:00
simple_format で textデータに改行を反映させる
text_areaでデータを入力してそのまま表示させると改行が反映されずに読みづらい文章になってしまいます。
<%= form_with model: @post, local: true do |f| %> <%= f.label :text %> <%= f.text_area :description, rows: 5, class: 'form-control', id: 'post_description' %> <% end %>みたいなformに
hello.
hello.
hello.と入力して
<%= @post.description %>みたいな感じで普通に表示させると
hello.hello.hello.改行が反映されずにくっついています。
調べると
simple_format
を使うのがいいようです。<%= simple_format(@post.description) %>hello. hello. hello.になるはずです。長い文章の入力でも大丈夫そうです。
- 投稿日:2020-02-21T11:02:13+09:00
bundle install した際にnokogiriのインストールでエラー
環境
- Windows 10 Pro
- Ubuntu 18.04.3 LTS on WSL(VSCode Remote Development)
C:>ver Microsoft Windows [Version 10.0.18363.657] $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionicエラー内容
$ bundle install --path vendor/bundle (skip) Fetching nokogiri 1.10.8 Installing nokogiri 1.10.8 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /mnt/f/Dropbox/ToDoApp_rooter_ex/rails-todo-sample/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.8/ext/nokogiri /home/takao/.rbenv/versions/2.6.5/bin/ruby -I /home/takao/.rbenv/versions/2.6.5/lib/ruby/2.6.0 -r ./siteconf20200221-3705-ydqzxx.rb extconf.rb --use-system-libraries checking if the C compiler accepts ... yes Building nokogiri using system libraries. pkg-config could not be used to find libxslt Please install either `pkg-config` or the pkg-config gem per gem install pkg-config -v "~> 1.1" pkg-config could not be used to find libexslt Please install either `pkg-config` or the pkg-config gem per gem install pkg-config -v "~> 1.1" checking for xmlParseDoc() in libxml/parser.h... yes checking for xsltParseStylesheetDoc() in libxslt/xslt.h... no checking for xsltParseStylesheetDoc() in -lxslt... yes checking for exsltFuncRegister() in libexslt/exslt.h... no checking for exsltFuncRegister() in -lexslt... yes checking for xmlHasFeature()... yes checking for xmlFirstElementChild()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetValidStructuredErrors()... yes checking for xmlSchemaSetValidStructuredErrors()... yes checking for xmlSchemaSetParserStructuredErrors()... yes creating Makefile : (skip) : An error occurred while installing nokogiri (1.10.8), and Bundler cannot continue. Make sure that `gem install nokogiri -v '1.10.8' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: rails was resolved to 6.0.2.1, which depends on actioncable was resolved to 6.0.2.1, which depends on actionpack was resolved to 6.0.2.1, which depends on actionview was resolved to 6.0.2.1, which depends on rails-dom-testing was resolved to 2.0.3, which depends on nokogiri「pkg-configをインストールしてください」と書いてあるが,既にpkg-configは入っているので,問題は別にありそう.
解決策
checking for xmlParseDoc() in libxml/parser.h... yes checking for xsltParseStylesheetDoc() in libxslt/xslt.h... no checking for xsltParseStylesheetDoc() in -lxslt... yes checking for exsltFuncRegister() in libexslt/exslt.h... no checking for exsltFuncRegister() in -lexslt... yes checking for xmlHasFeature()... yes checking for xmlFirstElementChild()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetParserStructuredErrors()... yes checking for xmlRelaxNGSetValidStructuredErrors()... yes checking for xmlSchemaSetValidStructuredErrors()... yes checking for xmlSchemaSetParserStructuredErrors()... yes creating Makefile
この部分に注目すると,
libxslt
内のファイルが見つかっていないようなので,libxslt
をインストール.$ sudo apt-get install libxslt-dev再び
bundle install
を行う.$ bundle install --path vendor/bundle : (skip) : Bundle complete! 17 Gemfile dependencies, 75 gems now installed. Bundled gems are installed into `./vendor/bundle`無事
bundle install
できた.
- 投稿日:2020-02-21T09:36:30+09:00
bundle install時に起きたmysql2のgemエラー
注意
自分用のメモ書き&同じ状況のエラーに遭遇した初学者のための投稿になっていますので記事内容が読みづらく雑であったり間違えている箇所があるかと思いますが大目に見てくださると助かります。
間違えている箇所についてはコメント欄にて指摘していただけると助かります。内容
既存のrailsプロジェクトを久しぶりに修正しようとした際にbundle installをしたら下記のエラーが出ました。
An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.???
何もいじっていなかったので戸惑いながらエラー文を読み三行目のgem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
を実行。
解決できずで何も変わらず、、、解決
まずエラー時は赤くなっている箇所のみではなくとりあえず遡ってちゃんと読むのが大事と改めて学びました。
この時はこんなことが書いてある箇所が少し遡ったところに書いてありました。。。mysql client is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.MySQLが見当たらない、、これをしてくれと書いてあるではないか!!と思い、
brew install mysql
をまず実行、、、そしたら無事解決しbundle installができました!!
もう一方のport in stall mysql
はMacPortsという、macOSおよびDarwin OS上のソフトウェアの導入を単純化するパッケージ管理システムのひとつでmysqlを導入できるものらしいです。
- 投稿日:2020-02-21T09:18:00+09:00
Rails Unicorn起動時のエラー
本記事投稿のいきさつ
最近Railsの勉強を始めたが、その中でエラー対応に苦戦したためメモ代わりにここに残したいと思います。
また、不慣れのため表現や書き方など、分かりづらい部分があるかと思います。
優しい目で見ていただければ幸いです。エラー
Capistranoでデプロイ後にunicornを再起動をしたかったのですが、EC2でunicorn接続をした際上手く接続ができずlessコマンドでエラーログを確認。
ArgumentError: Already running on PID:~~との表示がされていました。
仮説
エラー文から以前のunicorn接続のプロセスが残っていると思い
ps aux | grep unicorn入力し確認したところどうやら余分なプロセスは確認できませんでした。
もしプロセスが表示されていればkill プロセスidで解決できます。しかし他に原因があるようです。
ネットで調べたところ unicorn.rb に問題がある場合にもこのエラーが発生するとのことでした。原因と対策
unicorn.rbを確認したところ
unicorn.rbpid "#{app_path}tmp/pids/unicorn.pid"の設定を発見しました。
Capistranoの導入でディレクトリの構造が変わるのですが、それに伴う設定の変更を一箇所出来ていませんでした。
そのため記述を以下に変更unicorn.rbpid "#{app_path}/shared/tmp/pids/unicorn.pid"これで無事動くようになりました。
エラー文だけで判断せず調べることも重要ということを改めて再認識した事象でした。
- 投稿日:2020-02-21T09:06:05+09:00
Elastic Beanstalk で Railsアプリをデプロイする時にハマったとこ
Elastic Beanstalk で Railsアプリをデプロイする時にハマったとこ
まとめ
Elastic Beanstalk へデプロイした経験から、ハマりやすいポイントをまとめました。
詳細については、以下の1回目と2回目のところを参照してみてください。
- セキュリティグループ
- MySQL エンコード
- .ebextension 設定
- 各gemの対応
1回目
- 文字コード(日本語の場合、utf8 へ変更必要)
- セキュリティグループ (Mysql2::Error: Can't connect to MySQL server on '**********.*****.ap-northeast-1.rds.amazonaws.com' (4))
- EC2 と RDS を同じVPC/サブネット上に置く方法
- RDS のセキュリティグループに EC2 からのアクセスを許可する。
rails db:createをやってくれない?
(Mysql2::Error: Unknown Databese'**********')
- 下記コマンドにて、自分でMySQLに接続して、DB作成。
- MySQL への接続(EC2上で(eb ssh))
- mysql -h **************.***.ap-northeast-1.rds.amazonaws.com -P 3306 -u ** -p
- DB作成
- create database ***********;
- utf8 へ変更されているかコマンドを実行して確認
initializers/carrierwave.rb 用に beanstalk へS3設定を追記
2回目
- eb init
- eb create
- RDS 作成
- ebに環境変数を格納
.ebextensionを記述
eb deploy
EC2 cd /var/app/ondeck/ 内で
- bundle update --bundler
- gem install bundler:2.0.2
local上の Ruby のversionを上げることでglobal環境のversionと合わせる
# install可能なversionを表示 $ rbenv install -l # versionを指定してinstall $ rbenv install 2.6.5 # インストールしたversionを使用可能な状態にする⇒shimsへの反映 $ rbenv rehash $ rbenv local 2.6.5 $ rbenv global 2.6.5
- bundler のバージョンがあっていない?
(参照:Gem::GemNotFoundExceptionと出てきたときの対処法 - Qiita)
$ gem install bundler -v '1.17.3'$ bundle install
- MySQLのインストールでエラーが出る。 mysql2 gemインストール時のトラブルシュート - Qiita
$ gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl/include --with-ldflags=-L/usr/local/opt/openssl/lib
nodeのversionが古いとかいうエラーがでた、mini-racer っていうgemを追加すれば直るって記述があった
(ruby on rails - ERROR: ServiceError - Failed to deploy application. on ElasticBeanstalk - Stack Overflow)mini-racer入れたら直った??
- .ebextension に rails db:create を追加
RDS のセキュリティグループを変更
Sequel Pro から MySQLに接続できるかどうかを確認。→ できなかった
EC2 に入って、コマンドで接続できるか
$ mysql -h *****RDSインスタンス*****.ap-northeast-1.rds.amazonaws.com -P 3306 -u **DB名** -p→ 接続できた。
つまり、他の問題?
EC2内から直接、DB(-)を作成するコマンドを打ったところ、ハイフンは使えないということを言われる。
→ 環境設定の ***-*** を _** へ変更。再度、eb deployしたところ、同じエラーが出る。
→ わからないので、MySQLの中から _** を作成してしまって、再度 eb deployエラーコードが変わって、MySQLのエンコードが問題っぽい。
[AWS][RDS][MySQL] 文字コードをutf8mb4にする - Qiita【MySQL】Mysql2::Error: Incorrect string value 【エラー】 - Qiita
対象のRDSのパラメーターグループをさっき変更したものに指定する。
作ったDBを一旦削除して、作成し直す。
これも意味がなく、過去のEBではまったところが確認し直したところ、以下の記事を見つける。
RDSに作成したMySqlのDatabaseに日本語が登録出来ない問題 - QiitaALTER DATABASE データベース名 default character set utf8;
- 下記エラーコードが出たので、crontabのコードを削除して、再度デプロイしてみる。
container_command 06-crontab in .ebextensions/02_setup_app.configようやくデプロイ完了!!
- 投稿日:2020-02-21T08:42:34+09:00
Railsは"5つの属性"を意識しろ
はじめに
TECH::EXPERT 72期生の添野です。短期集中Aチームです。
今日も、Rails苦戦中の方におすすめの記事です。○○メソッドを単体で覚えてもキリがない
急ですが、皆さんは下記3つの呪文を詳細に説明できるでしょうか。
・ paramsメソッド
・ AcctiveRecordクラス
・ newメソッド私は無理です。しかし、次のように一言添えることはできます。
「paramsは、ビューやコントローラで使うよね」
「AcctiveRecordは主にコントローラで使うよね」
「newメソッドはAcctiveRecordクラス自体が持ってる属性みたいな感じ」いかがでしょうか?字面ではピンとこないかもですが、同期生との対面でコレを唱えると、「うお、コイツめっちゃ理解してやがる」と思われたりします。実際のところ、ふんわりとですが、内容を理解しています。
ポケモンはなぜ覚えやすいのか
突然ですが、皆さんはポケモンをやったことがあるでしょうか。
ピカチュウがどんなのか、って言われたら大半の人が「黄色いポケモン」もしくは「でんきタイプだよね」とか言うと思います。ポケモンって890種類いるらしいのですが、どうしてこんなに覚えやすいのでしょうか?「属性」があるからです。ピカチュウは、「でんき」「黄色い」「ねずみ」などの属性を持っています。
メソッドはポケモン
頭のいい方は、私が何を言いたいか分かってしまったかもです。
上記3つの呪文について、私は「属性」を使って覚えています。「paramsは、ビューやコントローラで使うよね」
「AcctiveRecordは主にコントローラで使うよね」
「newメソッドはAcctiveRecord クラス自体が持ってる属性みたいな感じ」Railsの属性は5つしかない
実は、Railsの属性はめちゃくちゃ少ないです。下記5つだけです。
1. D属性 "データベース"
2. M属性 "モデル"
3. R属性 "ルーティン"
4. C属性 "コントローラ"
5. V属性 "ビュー"基本はこれだけ覚えれば十分です。
これらの5属性は、それぞれファイルが分かれており、記述もクセがあります。属性分けできることを知っていれば覚えやすくなります。まとめ
私は、Railsレッスンで度々出現する呪文を5つの属性で覚えています。
皆さんも、明日から是非使ってみてください。他にも、皆さんの役に立ちたいと思い作成した記事がありますので、是非ご活用ください。
・Rails用語集 基礎
・TECH::EXPERTはカレーづくり教室だった話
・Rails消化のコツ使えるものは何でも使っていきましょう。
それでは。
- 投稿日:2020-02-21T08:33:47+09:00
【RubyonRails】メッセージごとに異なるflashを表示する【Bootstrap】
ポートフォリオを作る中で、bootstrapを利用したflashを実装したのでメモしておきます。
flashとは
そもそもflashとは何ぞやという人のために簡単に説明すると、Webサービスによくある「ログインに成功しました!」みたいなメッセージです。
railsではあれを簡単に実装できます。
さらにbootstrapを使えばデザインもいい感じに実装できます。前提
- deviseを導入している
- bootstrapをgemで導入している
ヘルパーの設定
app/helpers/devise_helper.rbを作成し、以下のように記述してください。
app/helpers/devise_helper.rbmodule DeviseHelper def bootstrap_alert(key) case key when "alert" "warning" when "notice" "success" when "error" "danger" end end enddeviseに使われてるkeyをbootstrapのアラートごとに割り当てます。
app/views/layouts/_flashes.html.erb
app/views/layouts/_flashes.html.erbを作成して以下を記述。
app/views/layouts/_flashes.html.erb<% flash.each do |key,value| %> <div class="alert alert-<%= bootstrap_alert(key) %>"> <strong> <%= value %> </strong> </div> <% end %>「 _ 」から始まるファイルは他ファイルでも共有できるようになっていますので、application.html.erbなどに
app/views/layouts/application.html.erb<%= render layauts/flash %>と記述してください。
これでflashが表示されたらOKです。
まとめ
webサイト作成時flashはなかなか切り離せないので、こんな風にbootstrapを利用して簡単に設定できるのは良いですね。
- 投稿日:2020-02-21T08:27:34+09:00
【GCP/Rails】RakeタスクでGCSにjsonを放り込む
やりたいこと
railsのrakeタスクを実行し、GCSに送信したいと思います。
今回は、主に下記のドキュメントを参考にしました。
google-cloud-ruby/google-cloud-storage at 120144431b3542b34f83f748632d933040ef3153 · googleapis/google-cloud-ruby · GitHubrakeタスク
rakeタスクに関しては、他の記事を参考にしてください。
RailsでRakeタスクの作成 - QiitaGCSへアップロード
最終的な実装は下記の通りです。
# 認証 storage = Google::Cloud::Storage.new( project_id: ENV["GOOGLE_CLOUD_PROJECT"], credentials: ENV["GOOGLE_CLOUD_KEYFILE_JSON"] ) # User情報をテーブルに書きだす。 json_file = Test.all.to_json File.open("./test.json", 'w') do |json| json.write(json_file) end # バケットを指定する bucket = storage.bucket ENV["GOOGLE_CLOUD_STORAGE_BUCKET"] file = bucket.create_file "./test.json", "test.json"サービスアカウントの作成
GCPにログインし、サービスアカウントを作成します。
権限としては、クラウドストレージの権限を付与しました。環境変数の設定
.env
ファイルに上記で指定している環境変数を指定します。
サービスアカウントを環境変数に入れる場合は、GOOGLE_CLOUD_KEYFILE_JSON
を使用すると、問題なく動作しました。
その他のプロジェクトID、バケットも環境変数で指定します。アウトプット
rakeタスクを実行し、下記のようにファイルが作成されていれば成功です。
- 投稿日:2020-02-21T06:34:41+09:00
Railsチュートリアル 第14章 ユーザーをフォローする - [Follow] のWebインターフェイス
フォローのサンプルデータ
別記事で解説します。
演習 - フォローのサンプルデータ
1.コンソールを開き、
User.first.followers.count
の結果がリスト 14.14で期待している結果と合致していることを確認してみましょう。「最初のユーザーをフォローしている人の数」ということですね。「4番目から41番めのユーザーの合計数」、すなわち
(3..40).count
の値と一致するはずです。>> User.first.followers.count => 38 >> (3..40).count => 38 >> User.first.followers.count == (3..40).count => true一致していますね。
2. 先ほどの演習と同様に、
User.first.following.count
の結果も合致していることを確認してみましょう。「最初のユーザーがフォローしている人の数」ということですね。「3番目から51番めのユーザーの合計数」、すなわち
(2..50).count
の値と一致するはずです。>> User.first.following.count => 49 >> (2..50).count => 49 >> User.first.following.count == (2..50).count => true一致していますね。
統計と [Follow] フォーム
別記事で解説します。
演習 - 統計と [Follow] フォーム
1.1. ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。
ログインユーザーのidが1で、DBの内容がフォローのサンプルデータの内容であるとすると、 /users/2 へのアクセス結果は以下の通りです。
対応するRailsサーバーのログ(抜粋)は以下のようになります。
Started GET "/users/2" for 172.17.0.1 at 2020-02-01 13:55:21 +0000 Rendering users/show.html.erb within layouts/application Rendered shared/_stats.html.erb (15.4ms) Rendered users/_follow.html.erb (3.2ms) Rendered users/_follow_form.html.erb (32.1ms) Rendered collection of microposts/_micropost.html.erb [30 times] (16.0ms) Rendered users/show.html.erb within layouts/application (148.5ms)
users/_follow.html.erb
、ならびにusers/_follow_form.html.erb
が描画されているのがわかります。また、/users/5 へのアクセス結果は以下の通りです。
対応するRailsサーバーのログ(抜粋)は以下のようになります。
Started GET "/users/5" for 172.17.0.1 at 2020-02-01 13:57:32 +0000 Rendering users/show.html.erb within layouts/application Rendered shared/_stats.html.erb (10.7ms) Rendered users/_unfollow.html.erb (5.2ms) Rendered users/_follow_form.html.erb (31.5ms) Rendered collection of microposts/_micropost.html.erb [30 times] (27.0ms) Rendered users/show.html.erb within layouts/application (141.3ms)
users/_unfollow.html.erb
、ならびにusers/_follow_form.html.erb
が描画されているのがわかります。1.2. /users/1 にアクセスすると、どのような結果が表示されるでしょうか?
ログインユーザーのidが1である場合、 /users/1 においては、[Follow]/[Unfollow]ボタンをレンダリングする場所そのものが確保されず、これらのボタンも表示されないはずです。どうなっているでしょうか。
確かに想定通りの動作になっています。
対応するRailsサーバーのログ(抜粋)は以下のようになります。
Started GET "/users/1" for 172.17.0.1 at 2020-02-01 14:05:02 +0000 Rendering users/show.html.erb within layouts/application Rendered shared/_stats.html.erb (12.5ms) Rendered users/_follow_form.html.erb (0.5ms) Rendered collection of microposts/_micropost.html.erb [30 times] (16.7ms) Rendered users/show.html.erb within layouts/application (132.9ms)
users/_follow.html.erb
およびusers/_unfollow.html.erb
はいずれもレンダリングされず、users/_follow_form.html.erb
のみがレンダリングされているのがわかります。2. ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認してみましょう。
現在ログインしているユーザーは、id=1のユーザーであることを前提とします。前述の演習「演習 - フォローのサンプルデータ」より、以下の表示内容となることが期待されます。
- Homeページ(/)とプロフィールページ(/users/1)の両方に統計情報パーシャルが表示される
- 「following」の前に表示される数は
User.first.following.count
と一致する- 「followers」の前に表示される数は
User.first.followers.count
と一致する>> User.first.following.count => 49 >> User.first.followers.count => 38まずはHomeページの表示結果です。「49 following / 38 followers」という表示内容に問題はありません。
このとき、Railsサーバーに記録されるログ(抜粋)は以下のようになります。
Started GET "/" for 172.17.0.1 at 2020-02-02 10:32:27 +0000 Rendering static_pages/home.html.erb within layouts/application Rendered shared/_user_info.html.erb (7.1ms) Rendered shared/_stats.html.erb (10.7ms) Rendered shared/_error_messages.html.erb (0.5ms) Rendered shared/_micropost_form.html.erb (20.3ms) Rendered collection of microposts/_micropost.html.erb [30 times] (118.9ms) Rendered shared/_feed.html.erb (151.2ms) Rendered shared/_home_logged_in.erb (289.7ms) Rendered static_pages/home.html.erb within layouts/application (317.0ms)統計情報パーシャルの実体である
shared/_stats.html.erb
が描画されているのがわかりますね。続いてプロフィールページの表示結果です。Homeページと同様、「49 following / 38 followers」という表示内容に問題はありません。
このとき、Railsサーバーに記録されるログ(抜粋)は以下のようになります。
Started GET "/users/1" for 172.17.0.1 at 2020-02-02 10:31:33 +0000 Rendering users/show.html.erb within layouts/application Rendered shared/_stats.html.erb (16.7ms) Rendered users/_follow_form.html.erb (0.5ms) Rendered collection of microposts/_micropost.html.erb [30 times] (13.5ms) Rendered users/show.html.erb within layouts/application (120.5ms)こちらも、統計情報パーシャルの実体である
shared/_stats.html.erb
が描画されている様子が記録されています。3.1. Homeページに表示されている統計情報に対してテストを書いてみましょう。
ヒント: リスト 13.28で示したテストに追加してみてください。
テストコードの実体
統計情報に対するテストコードの実体は以下のようになります。
assert_select 'strong', { id: 'following', text: /#{@user.following.count.to_s}/ } assert_select 'strong', { id: 'followers', text: /#{@user.followers.count.to_s}/ }上記コードは、以下の事柄についてテストを行っています。
- CSS idが
following
であり、テキストとしてログイン済みユーザーがフォローしているユーザーの数を含むstrong
要素が描画されていること- CSS idが
followers
であり、テキストとしてログイン済みユーザーのフォロワーの数を含むstrong
要素が描画されていることHomeページに対するテストをどこに書くか
Homeページに対するテストの実装は、
test/integration/site_layout_test.rb
に既に存在します。しかしながら、test/integration/site_layout_test.rb
上のテストというのは、header
要素内やfooter
要素内といった「サイト全体に適用されるレイアウトに関するテスト」と考えるべき趣旨のものです。コンテンツ内容に関するテストを含めるのは適当ではないと考えます。というわけで、新たに統合テストを生成してしまいましょう。「Homeページに対するテスト」ということで、統合テストの名前は
home
とします。# rails generate integration_test home invoke test_unit create test/integration/home_test.rb実際のテストの記述
実際にHomeページに対するテストを記述していきます。記述場所は、只今生成したばかりの
test/integration/home_test.rb
です。test/integration/home_test.rbrequire 'test_helper' class HomeTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) log_in_as(@user) end test "home should include following and followers with login" do get root_path assert_select 'strong', { id: 'following', text: /#{@user.following.count.to_s}/ } assert_select 'strong', { id: 'followers', text: /#{@user.followers.count.to_s}/ } end endHomeページ上の統計情報の描画に対するテストが成功することを確認する
現時点で、上記のテストは問題なく成功します。
# rails test test/integration/home_test.rb Running via Spring preloader in process 754 Started with run options --seed 26350 1/1: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.34328s 1 tests, 2 assertions, 0 failures, 0 errors, 0 skipsHomeページ上の統計情報の描画に対するテストが失敗する例
例えば、
app/views/shared/_home_logged_in.erb
に以下の欠落がある場合を考えてみます。app/views/shared/_home_logged_in.erb<div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section class="stats"> - <%= render 'shared/stats' %> </section> <section class="micropost_form"> <%= render 'shared/micropost_form' %> </section> </aside> <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div>
この状態で
test/integration/home_test.rb
に対するテストを行うと、以下のようにテストが失敗します。# rails test test/integration/home_test.rb Running via Spring preloader in process 767 Started with run options --seed 11814 FAIL["test_home_should_include_following_and_followers_with_login", HomeTest, 3.5389004000026034] test_home_should_include_following_and_followers_with_login#HomeTest (3.54s) Expected at least 1 element matching "strong", found 0.. Expected 0 to be >= 1. test/integration/home_test.rb:11:in `block in <class:HomeTest>' 1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.54956s 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips発展 - ログイン済みユーザーのHomeページに対するテストとして考えられる例
マイクロポスト投稿フォームが表示されていること
test "home should include new micropost form with login" do log_in_as @user get root_path assert_select 'form', id: 'new_micropost' endマイクロポスト表示フィードがレンダリングされること
test "home should render micropost feed placeholder with login" do log_in_as @user get root_path assert_select 'div' do assert_select 'h3', text: 'Micropost Feed' end end単純に「テキストが'Micropost Feed'である
h3
要素を含むdiv
要素が存在すること」についてテストを行っています。3.2. 同様にして、プロフィールページにもテストを追加してみましょう。
テストコードの内容そのものは、上記演習3.1.のものと同一です。
プロフィールページの表示内容に対するテストの実体は
test/integration/users_profile_test.rb
です。前述の内容を踏まえ、test/integration/users_profile_test.rb
全体の変更内容は以下のようになります。test/integration/users_profile_test.rbrequire 'test_helper' class UsersProfileTest < ActionDispatch::IntegrationTest include ApplicationHelper def setup @user = users(:rhakurei) end test "profile display" do get user_path(@user) assert_template 'users/show' assert_select 'title', full_title(@user.name) assert_select 'h1', text: @user.name assert_select 'h1>img.gravatar' assert_match @user.microposts.count.to_s, response.body + assert_select 'strong', { id: 'following', text: /#{@user.following.count.to_s}/ } + assert_select 'strong', { id: 'followers', text: /#{@user.followers.count.to_s}/ } assert_select 'div.pagination', count: 1 @user.microposts.paginate(page: 1).each do |micropost| assert_match micropost.content, response.body end end end
プロフィールページ上の統計情報の描画に対するテストが成功することを確認する
現状の実装では、上記テストは問題なく成功します。
# rails test test/integration/users_profile_test.rb Running via Spring preloader in process 584 Started with run options --seed 41315 1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.06740s 1 tests, 71 assertions, 0 failures, 0 errors, 0 skipsプロフィールページ上の統計情報の描画に対するテストが失敗する例
例えば、
app/views/users/show.html.erb
に以下の欠落がある場合を考えてみます。app/views/users/show.html.erb(バグあり)<% provide(:title, @user.name) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> ...略 </section> <section> - <%= render 'shared/stats' %> </section> </aside> <div class="col-md-8"> ...略 </div> </div>
この状態で
test/integration/users_profile_test.rb
に対するテストを行うと、以下のようにテストが失敗します。# rails test test/integration/users_profile_test.rb Running via Spring preloader in process 672 Started with run options --seed 11646 FAIL["test_profile_display", UsersProfileTest, 4.164021200005664] test_profile_display#UsersProfileTest (4.16s) Expected at least 1 element matching "strong", found 0.. Expected 0 to be >= 1. test/integration/users_profile_test.rb:17:in `block in <class:UsersProfileTest>' 1/1: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.17069s 1 tests, 7 assertions, 1 failures, 0 errors, 0 skips[Following] と [Followers] ページ
ページの基本的な仕様
「フォローしているユーザー一覧」「フォロワー一覧」いずれも、そのレイアウトは類似するものとなります。具体的には、以下の要素が含まれることになります。
- サイドバー
- ログインユーザーの基本情報
- ログインユーザーがフォローしているユーザーの数
- ログインユーザーのフォロワーの数
- フォローしているユーザー、またはフォロワーのアイコンを縮小表示して格子状に並べたもの
- 当該ユーザーのプロフィールページへのリンクが貼られている
- フォローしているユーザー、もしくはフォロワーのリスト
Railsチュートリアル本文では、フォローしているユーザーの一覧のモックアップを図 14.14で、フォロワーの一覧のモックアップを図 14.15で示しています。
フォロー/フォロワーページの認可のテスト
「フォローしているユーザーの一覧、フォロワーの一覧、いずれのページもログイン済みユーザーでなければアクセスできないこととする」「非ログインユーザーがこれら一覧ページにアクセスしようとした場合、 /login にリダイレクトする」という仕様を採用することをまず前提とします。これはTwitterにおける実装に倣ったものです。
となると、「これらのページへのアクセスにおいて、認可機構が正しく働いているか」のテストが必要となります。情報セキュリティに関する部分の仕様であるだけに、この部分の動作が正しいものであることは重要です。というわけで、実装より先にテストを書いていくこととします。
テストそのものの実体は以下のようになります。
test "should redirect following when not logged in" do get following_user_path(@user) assert_redirected_to login_url end test "should redirect followers when not logged in" do get followers_user_path(@user) assert_redirected_to login_url end追加するテストの実装先は、
test/controllers/users_controller_test.rb
です。test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other_user = users(:mkirisame) end ...略 + + test "should redirect following when not logged in" do + get following_user_path(@user) + assert_redirected_to login_url + end + + test "should redirect followers when not logged in" do + get followers_user_path(@user) + assert_redirected_to login_url + end end
現時点でテストが成功しないことの確認
新たに実装したテストは、当然ながら現時点では成功しません。
# rails test test/controllers/users_controller_test.rb Running via Spring preloader in process 840 Started with run options --seed 47643 ERROR["test_should_redirect_followers_when_not_logged_in", UsersControllerTest, 1.9549646000086796] test_should_redirect_followers_when_not_logged_in#UsersControllerTest (1.96s) AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action 'followers' could not be found for UsersController test/controllers/users_controller_test.rb:80:in `block in <class:UsersControllerTest>' ERROR["test_should_redirect_following_when_not_logged_in", UsersControllerTest, 2.14522200000647] test_should_redirect_following_when_not_logged_in#UsersControllerTest (2.15s) AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action 'following' could not be found for UsersController test/controllers/users_controller_test.rb:75:in `block in <class:UsersControllerTest>' 11/11: [=================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.21912s 11 tests, 18 assertions, 0 failures, 2 errors, 0 skipsそもそも
following
やfollowers
というアクションはまだ実装していないので、テストが通らないのみ当然といえば当然です。ただ、RoutingError
ではなくActionNotFound
なので、ルーティングの実装は正常に行えているようです。Usersコントローラーに、
following
アクションとfollowers
アクションを実装する先ほどテストで発生したエラーを解決するために、Usersコントローラーに
following
アクションとfollowers
アクションを実装していきます。app/controllers/users_controller.rbclass UsersController < ApplicationController ...略 + + def following + end + + def followers + end private ...略 end
Usersコントローラーに
following
アクションとfollowers
アクションがある状態でのテスト結果この時点で
test/controllers/users_controller_test.rb
を対象としてテストを行うと、その結果は以下のようになります。# rails test test/controllers/users_controller_test.rb Running via Spring preloader in process 861 Started with run options --seed 8813 ERROR["test_should_redirect_followers_when_not_logged_in", UsersControllerTest, 2.068188299992471] test_should_redirect_followers_when_not_logged_in#UsersControllerTest (2.07s) ActionController::UnknownFormat: ActionController::UnknownFormat: UsersController#followers is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. test/controllers/users_controller_test.rb:80:in `block in <class:UsersControllerTest>' ERROR["test_should_redirect_following_when_not_logged_in", UsersControllerTest, 4.581023499995354] test_should_redirect_following_when_not_logged_in#UsersControllerTest (4.58s) ActionController::UnknownFormat: ActionController::UnknownFormat: UsersController#following is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. test/controllers/users_controller_test.rb:75:in `block in <class:UsersControllerTest>' 11/11: [=================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.58386s 11 tests, 18 assertions, 0 failures, 2 errors, 0 skips上記エラーが発生する状態で、Webブラウザから /users/1/following というリソースにアクセスすると、Railsサーバーには以下のようなログが記録されます。
Started GET "/users/1/following" for 172.17.0.1 at 2020-02-03 22:46:07 +0000 Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by UsersController#following as HTML Parameters: {"id"=>"1"} Completed 406 Not Acceptable in 1125ms ...略HTTPリクエストが406というエラーコードを返して終了している、という状態ですね。「406」というエラーコードの意味はさておき、この処理で返ってくるHTTPのレスポンスコードは「3XX(リダイレクト)」でなければなりません。
Usersコントローラーの
following
アクションとfollowers
アクションに対し、「ログイン済みユーザーでなければログイン画面にリダイレクトする」という動作が行われるようにする表題記載の動作が行われるようにするためには、Usersコントローラーのbeforeフィルターに以下のコードを追加します。
before_action :logged_in_user, only: [:following, :followers]実際に
app/controllers/users_controller.rb
に適用する変更は以下のようになります。app/controllers/users_controller.rbclass UsersController < ApplicationController - before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy ...略 endこの時点で、
test/controllers/users_controller_test.rb
を対象としたテストが成功するようになるこの時点で、
test/controllers/users_controller_test.rb
を対象としたテストは成功するようになります。# rails test test/controllers/users_controller_test.rb Running via Spring preloader in process 958 Started with run options --seed 46648 11/11: [=================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.49225s 11 tests, 20 assertions, 0 failures, 0 errors, 0 skipsしかしながら、現時点で
following
アクションおよびfollowers
アクションの動作は何も実装していません。これらの動作の実装が必要となります。
following
アクションとfollowers
アクションの動作の実装
following
アクションとfollowers
アクションの動作に対するテストfollowing/followerをテストするためのfixture
test/fixtures/relationships.ymlone: follower: rhakurei followed: skomeiji two: follower: rhakurei followed: rusami three: follower: skomeiji followed: rhakurei four: follower: mkirisame followed: rhakureiこのfixtureは、以下のようなフォロー関係を定義しています。
- rhakureiがskomeijiとrusamiをフォローする
- skomeijiとmkirisameがrhakureiをフォローする
following/followerページに対する統合テストを生成する
「実際にWebブラウザに描画される内容をテストしたい」という場面なので、テストの種類は統合テストとなります。following/followerページのビューの実装が現状存在しないので、対応する統合テストも現状存在しません。まずは必要な統合テストを生成することが始まりですね。テストの名前は
following
とします。# rails generate integration_test following invoke test_unit create test/integration/following_test.rbfollowing/followerページのテストの実体
前項で生成された統合テストのファイル名は
test/integration/following_test.rb
となります。テストそのものの内容は以下のようになります。test/integration/following_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) log_in_as(@user) end test "following page" do get following_user_path(@user) assert_not @user.following.empty? assert_match @user.following.count.to_s, response.body @user.following.each do |user| assert_select "a[href=?]", user_path(user) end end test "followers page" do get followers_user_path(@user) assert_not @user.followers.empty? assert_match @user.followers.count.to_s, response.body @user.followers.each do |user| assert_select "a[href=?]", user_path(user) end end end
assert_not @user.following.empty?
やassert_not @user.followers.empty?
というテストの意味合いassert_not @user.following.empty?
@user.following.empty?
の戻り値がtrueである場合、このあとの@user.following.each
ブロック内にあるassert_select
というテストが実行されなくなってしまいます。そのような状況で「テストが成功した」と主張するのは不適当です。ゆえに、「@user.following.empty?
の戻り値がtrueである場合はテストを失敗させる」という処理を先に実行しています。@user.following.empty?
の戻り値がtrueとなる場合には、例えば「fixtureの内容が不適当な場合」があります。また、
@user.followers
に対する以下のテストも意味合いは同様です。assert_not @user.followers.empty?現状における、
test/integration/following_test.rb
を対象としたテストの結果「Usersコントローラーに、
following
/followers
両アクションのみが実装されており、following
/followers
アクションの処理内容が実装されていない」という状態で、test/integration/following_test.rb
を実行してみます。結果は以下のようになります。# rails test test/integration/following_test.rb Running via Spring preloader in process 1077 Started with run options --seed 36731 ERROR["test_followers_page", FollowingTest, 2.2801150000013877] test_followers_page#FollowingTest (2.28s) ActionController::UnknownFormat: ActionController::UnknownFormat: UsersController#followers is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. test/integration/following_test.rb:19:in `block in <class:FollowingTest>' ERROR["test_following_page", FollowingTest, 3.6601012000028277] test_following_page#FollowingTest (3.66s) ActionController::UnknownFormat: ActionController::UnknownFormat: UsersController#following is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. test/integration/following_test.rb:10:in `block in <class:FollowingTest>' 2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.66550s 2 tests, 0 assertions, 0 failures, 2 errors, 0 skipsHTTPリクエストが406というエラーコードを返して終了している、という状態ですね。「406」というエラーコードの意味はさておき、この処理で返ってくるHTTPのレスポンスコードは「200」でなければなりません。
Usersコントローラーにおける、
following
アクションとfollowers
アクションの動作の実装
- 「誰がフォローしているユーザーか」「誰のフォロワーか」の「誰」の部分については、
GET
リクエストに渡すパラメータのid
属性の値によって与える- ユーザー一覧の表示に対し、ページネーション処理を行う
上記箇条書きの内容を前提条件とすると、
following
アクションとfollowers
アクションの動作の実装内容は以下のようになります。def following @title = "Following" @user = User.find(params[:id]) @users = @user.following.paginate(page: params[:page]) render 'show_follow' end def followers @title = "Followers" @user = User.find(params[:id]) @users = @user.followers.paginate(page: params[:page]) render 'show_follow' end後述するように、フォローしているユーザーの一覧/フォロワーの一覧とも、一つのERbで両方の場合をカバーできる程度にページ構造は酷似しています。ゆえに、「コントローラーの2つのアクションが同一ビューを描画する」という実装になるわけです。
最終的に、
app/controllers/users_controller.rb
に対して加える変更の内容は以下のようになります。app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy ...略 def following + @title = "Following" + @user = User.find(params[:id]) + @users = @user.following.paginate(page: params[:page]) + render 'show_follow' end def followers + @title = "Followers" + @user = User.find(params[:id]) + @users = @user.following.paginate(page: params[:page]) + render 'show_follow' end private ...略 end
following
アクションとfollowers
アクションに必要なビューの実装当然ながら、
show_follow
というビューそのものの実装も必要となります。ファイル名はapp/views/users/show_follow.html.erb
とします。app/views/users/show_follow.html.erb<% provide(:title, @title) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= gravatar_for @user %> <h1><%= @user.name %></h1> <span><%= link_to "view my profile", @user %></span> <span><b>Microposts:</b> <%= @user.microposts.count %></span> </section> <section class="stats"> <% if @users.any? %> <div class="user_avatars"> <% @users.each do |user| %> <%= link_to gravatar_for(user, size: 30), user %> <% end %> </div> <% end %> </section> </aside> <div class="col-md-8"> <h3><%= @title %></h3> <% if @users.any? %> <ul class="users follow"> <%= render @users %> </ul> <%= will_paginate %> <% end %> </div> </div>再び
test/integration/following_test.rb
を対象としたテストを実行するUsersコントローラーの
following
アクションとfollowers
アクション、これらのアクションに必要なビュー、以上の実装が完了しました。この時点で、再びtest/integration/following_test.rb
を対象としたテストを実行してみましょう。# rails test test/integration/following_test.rb Running via Spring preloader in process 1129 Started with run options --seed 47849 2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.18609s 2 tests, 10 assertions, 0 failures, 0 errors, 0 skipsここまで実装したコードの内容に間違いがなければ、
test/integration/following_test.rb
を対象としたテストは成功するはずです。# rails test Running via Spring preloader in process 1142 Started with run options --seed 56786 72/72: [=================================] 100% Time: 00:00:10, Time: 00:00:10 Finished in 10.74545s 72 tests, 357 assertions, 0 failures, 0 errors, 0 skipsテストスイート全体に対するテストも成功しましたね。
following/followerページの表示結果
現在のユーザーにフォローされているユーザーの一覧表示のスクリーンショットを以下に示します。アドレスバー部分を見てのとおり、
following
アクションを経由してshow_follow
ビューが呼び出された結果となります。続いて、現在のユーザをフォローしているユーザーの一覧表示のスクリーンショットを以下に示します。アドレスバー部分を見てのとおり、
followers
アクションを経由してshow_follow
ビューが呼び出された結果となります。ログイン済みであれば、ログインユーザー以外のユーザーに対しても、当該ユーザーをフォローしているユーザーを一覧表示することも可能です。以下のスクリーンショットがその例です。
演習 - [Following] と [Followers] ページ
1.1. ブラウザから /users/1/followers と /users/1/following を開き、それぞれが適切に表示されていることを確認してみましょう。
以下のスクリーンショットの通りです。
1.2. /users/1/followers や /users/1/following において、サイドバーにある画像は、リンクとしてうまく機能しているでしょうか?
/users/1/followers における、サイドバーにある単一の画像に対応するHTMLコードは、例えば以下のようになります。
サイドバーにある単一の画像に対応するHTMLコード<a href="/users/3"> <img alt="Berry Cremin" class="gravatar" src="https://secure.gravatar.com/avatar/2065436fdfe2d27dc7f06b6787a4a1af?s=30"> </a>リンク先が /users/3 であることを踏まえて、実際に当該画像をクリックしてみます。すると、Railsサーバーは以下のようなログを出力します。
Started GET "/users/3" for 172.17.0.1 at 2020-02-05 22:50:12 +0000 ...略 Completed 200 OK in 1276ms (Views: 1154.3ms | ActiveRecord: 69.4ms)/users/3 への
GET
リクエストが発行され、「200 OK」でリクエストが完了しています。「サイドバーにある画像は、リンクとしてうまく機能していることが確認できた」といえそうです。2. リスト 14.29の
assert_select
に関連するコードをコメントアウトしてみて、テストが正しくred
に変わることを確認してみましょう。
app/views/users/show_follow.html.erb
に以下の欠落がある場合、当該テストは、assert_select
のところで失敗するはずです。app/views/users/show_follow.html.erb<% provide(:title, @title) %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= gravatar_for @user %> <h1><%= @user.name %></h1> <span><%= link_to "view my profile", @user %></span> <span><b>Microposts:</b> <%= @user.microposts.count %></span> </section> <section class="stats"> <%= render'shared/stats' %> <% if @users.any? %> <div class="user_avatars"> <% @users.each do |user|%> - <%= link_to gravatar_for(user, size: 30), user %> <% end %> </div> <% end %> </section> </aside> <div class="col-md-8"> <h3><%= @title %></h3> <% if @users.any? %> <ul class="users follow"> - <%= render @users %> </ul> <%= will_paginate %> <% end %> </div> </div>実際にテストを実行してみましょう。
# rails test test/integration/following_test.rb Running via Spring preloader in process 1196 Started with run options --seed 9425 FAIL["test_followers_page", FollowingTest, 2.336555000001681] test_followers_page#FollowingTest (2.34s) Expected at least 1 element matching "a[href="/users/919532091"]", found 0.. Expected 0 to be >= 1. test/integration/following_test.rb:23:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:22:in `block in <class:FollowingTest>' FAIL["test_following_page", FollowingTest, 2.4256373999960488] test_following_page#FollowingTest (2.43s) Expected at least 1 element matching "a[href="/users/314048677"]", found 0.. Expected 0 to be >= 1. test/integration/following_test.rb:14:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:13:in `block in <class:FollowingTest>' 2/2: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.42861s 2 tests, 8 assertions, 2 failures, 0 errors, 0 skips想定通りの形でテストが失敗しました。
following
アクションおよびfollowers
アクションの統合テストにおける、Railsチュートリアル本文記載のテストの不具合実は、Railsチュートリアル本文のリスト 14.29に記述されているテストには、1つの不具合があります。不具合の内容とその修正については、別記事に記載しています。
[Follow] ボタン (基本編)
Relationshipsコントローラーの作成
「フォロー」「フォロー解除」という動作は、それぞれリレーションシップの作成と削除に対応しています。RESTアーキテクチャを前提とした場合、少なくとも
create
アクションとdestroy
アクションが確実に必要となる場面ですね。というわけで、まずはRelationshipsコントローラーの作成から始めます。
# rails generate controller Relationships Running via Spring preloader in process 1262 create app/controllers/relationships_controller.rb invoke erb create app/views/relationships invoke test_unit create test/controllers/relationships_controller_test.rb invoke helper create app/helpers/relationships_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/relationships.coffee invoke scss create app/assets/stylesheets/relationships.scssRelationshipsコントローラーに対するテストの記述
認可に関係する動作なので、実装に万全を期すために、テストを先に書いてから実装に取り掛かっていくこととしましょう。
test/controllers/relationships_controller_test.rbrequire 'test_helper' class RelationshipsControllerTest < ActionDispatch::IntegrationTest test "create should require logged-in user" do assert_no_difference 'Relationships.count' do post relationships_path end assert_redirected_to login_url end test "destroy should require logged-in user" do assert_no_differende 'Relationships.count' do delete relationship_path(relationships(:one)) end assert_redirected_to login_url end endテストの記述内容に問題がないのであれば、現時点における
test/controllers/relationships_controller_test.rb
に対するテストの実行結果は以下のようになります。# rails test test/controllers/relationships_controller_test.rb Running via Spring preloader in process 1284 Started with run options --seed 280 ERROR["test_create_should_require_logged-in_user", RelationshipsControllerTest, 1.4866881000052672] test_create_should_require_logged-in_user#RelationshipsControllerTest (1.49s) AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action 'create' could not be found for RelationshipsController test/controllers/relationships_controller_test.rb:7:in `block (2 levels) in <class:RelationshipsControllerTest>' test/controllers/relationships_controller_test.rb:6:in `block in <class:RelationshipsControllerTest>' ERROR["test_destroy_should_require_logged-in_user", RelationshipsControllerTest, 1.673922499991022] test_destroy_should_require_logged-in_user#RelationshipsControllerTest (1.67s) AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action 'destroy' could not be found for RelationshipsController test/controllers/relationships_controller_test.rb:14:in `block (2 levels) in <class:RelationshipsControllerTest>' test/controllers/relationships_controller_test.rb:13:in `block in <class:RelationshipsControllerTest>' 2/2: [===================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.68059s 2 tests, 0 assertions, 0 failures, 2 errors, 0 skips
AbstractController::ActionNotFound
というエラーが発生しています。Relationshipsコントローラーに、create
アクションもdestroy
アクションも定義されていないためにエラーが発生しているのですね。Relationshipsコントローラーに、
create
アクション・destroy
アクション・logged_in_user
フィルターを追加する何はなくとも、まずRelationshipsコントローラーに
create
アクションおよびdestroy
アクションの実装が必要となります。logged_in_user
フィルターによるアクセス制御も同時に追加します。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController + before_action :logged_in_user + + def create + end + + def destroy + end end
create
アクション・destroy
アクション・logged_in_user
フィルターがあるRelationshipsコントローラーに対するテストの結果ここまでの実装が完了したところで、現時点の
test/controllers/relationships_controller_test.rb
を対象に、改めてテストを実行してみます。# rails test test/controllers/relationships_controller_test.rb Running via Spring preloader in process 1323 Started with run options --seed 62701 2/2: [===================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.55615s 2 tests, 4 assertions, 0 failures, 0 errors, 0 skipsテストが無事成功しました。
Relationshipsコントローラーの完全な実装
Relationshipsコントローラーの完全な実装、すなわち
app/controllers/relationships_controller.rb
の最終的な中身は、以下のようになります。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create user = User.find(params[:id]) current_user.follow(user) redirect_to user end def destroy user = Relationships.find(params[:id]).followed current_user.unfollow(user) redirect_to user end end非ログインユーザーが、Relationshipsリソースに直接
POST
やDELETE
を行った場合の動作実は、beforeフィルターがない状態でも、「非ログインユーザーがRelationshipsリソースに直接
POST
やDELETE
を実行した場合、RDBの内容に変化は生じない」という動作は実現されています。その流れは以下の通りです。
- 非ログインユーザーが(
curl
)Relationshipsリソースに直接POST
やDELETE
を実行するcreate
アクションにせよdestroy
アクションにせよ、current_user
はnil
になるfollow
やunfollow
が呼び出された時点で例外が発生するしかしながら、「アプリケーションロジックの正常な動作が、例外の発生に依存したものとなる」というのは避けたいパターンです。しかもそれが、「
nil
に対する参照」という例外であるならばなおさらです。ゆえに今回は、「beforeフィルターを追加する」という実装を行っています。演習 - [Follow] ボタン (基本編)
1. ブラウザ上から /users/2 を開き、[Follow] と [Unfollow] を実行してみましょう。うまく機能しているでしょうか?
下記が初期状態のスクリーンショットです。「ログインユーザーはid=2のユーザーをフォローしていない」という状態です。
[Unfollow]ボタンではなく[Follow]ボタンが表示されていますね。
では[Follow]ボタンを押してみましょう。結果は以下のようになります。
[Follow]ボタンではなく[Unfollow]ボタンが表示されていますね。
では[Unfollow]ボタンを押してみましょう。結果は以下のようになります。
[Unfollow]ボタンではなく[Follow]ボタンが表示されています。
2. 先ほどの演習を終えたら、Railsサーバーのログを見てみましょう。フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているでしょうか?
フォローが実行されたときの処理として、Railsサーバーには以下のログが出力されています。
Started POST "/relationships" ...略 Redirected to http://localhost:8080/users/2 Completed 302 Found in 65ms (ActiveRecord: 38.6ms) Started GET "/users/2" for 172.17.0.1 at 2020-02-06 22:52:42 +0000 Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by UsersController#show as HTML Parameters: {"id"=>"2"} ...略 Rendering users/show.html.erb within layouts/application ...略 Rendered shared/_stats.html.erb (16.1ms) ...略 Rendered users/_unfollow.html.erb (9.7ms) Rendered users/_follow_form.html.erb (41.2ms) ...略 Rendered users/show.html.erb within layouts/application (168.1ms) ...略 Completed 200 OK in 629ms (Views: 576.3ms | ActiveRecord: 28.2ms)「
_unfollow.html.erb
が描画されている」というのが重要です。「_unfollow.html.erb
は、_follow_form.html.erb
において、ログインユーザーが対象のユーザーをフォローしている場合に描画される」ように実装したのでしたよね。一方、フォロー解除が実行されたときの処理としては、Railsサーバーには以下のログが出力されています。
Started DELETE "/relationships/88" ...略 Redirected to http://localhost:8080/users/2 Completed 302 Found in 55ms (ActiveRecord: 36.7ms) Started GET "/users/2" for 172.17.0.1 at 2020-02-06 22:52:49 +0000 Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by UsersController#show as HTML Parameters: {"id"=>"2"} ...略 Rendering users/show.html.erb within layouts/application ...略 Rendered shared/_stats.html.erb (12.9ms) ...略 Rendered users/_follow.html.erb (2.0ms) Rendered users/_follow_form.html.erb (30.1ms) ...略 Rendered collection of microposts/_micropost.html.erb [30 times] (14.1ms) ...略 Rendered users/show.html.erb within layouts/application (160.8ms) ...略 Completed 200 OK in 881ms (Views: 828.7ms | ActiveRecord: 24.9ms)「
_follow.html.erb
が描画されている」というのが重要です。「_follow.html.erb
は、_follow_form.html.erb
において、ログインユーザーが対象のユーザーをフォローしていない場合に描画される」ように実装したのでしたよね。なお、参考として、
app/views/users/_follow_form.html.erb
そのものの実装内容は以下のようになっていることを明記しておきます。app/views/users/_follow_form.html.erb<% unless current_user?(@user) %> <div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> </div> <% end %>[Follow] ボタン (Ajax編)
現状の[Follow]/[Unfollow]ボタンの実装の問題点
現状の[Follow]/[Unfollow]ボタンの実装では、「ボタンをクリックした後、ログインユーザー自身のプロフィールページにリダイレクトされる」という動作になっています。
しかしながら、[Follow]/[Unfollow]ボタンが表示されているのは、専用の投稿フォームではなく、任意のユーザーのプロフィールページです。「何らかのアクションをとると、勝手にページの移動が発生する」という挙動は、フォーム以外に内容のないページならともかく、そうでないページの場合はユーザーの期待に反する動作である可能性が高いです。
このような場合は、「ページ移動が発生せず、[Follow]/[Unfollow]ボタンのあるページに留まる」という実装のほうが望ましいのではないでしょうか。
Ajaxを使えば、上記の問題点に対し、より望ましい形の実装に持っていける
Ajaxを使えば、WebブラウザとWebサーバーの間での「非同期」処理が可能になります。「ページを移動することなくリクエストを送信する」という処理です。「ページ移動が発生せず、[Follow]/[Unfollow]ボタンのあるページに留まる」という処理も、Ajaxによって実現が可能です。
RailsにおけるAjaxの利用
Railsにおいても、Ajaxの利用は容易に可能です。ビューに記述されている
form_for
メソッドにremote: true
というオプションを追加すれば、それだけでRailsアプリケーションは自動的にAjaxを使うようになります。form_for ..., remote: trueAjaxを使ったフォローフォーム・フォロー解除フォーム
Ajaxを使ったフォローフォームのコードは以下のようになります。
app/views/users/_follow.html.erb- <%= form_for(current_user.active_relationships.build) do |f| %> + <%= form_for(current_user.active_relationships.build, remote: true) do |f| %> <div><%= hidden_field_tag :followed_id, @user.id %></div> <%= f.submit "Follow", class: "btn btn-primary" %> <% end %>一方、Ajaxを使ったフォロー解除フォームのコードは以下のようになります。
app/views/users/_unfollow.html.erb<%= form_for(current_user.active_relationships.find_by( followed_id: @user.id), - html:{ method: :delete }) + html:{ method: :delete }, + remote:true) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %>上記埋め込みRubyで生成されるHTMLの内容
上記の埋め込みRubyでは、例えば以下のようなHTMLが生成されます。
<form class="new_relationship" id="new_relationship" action="/relationships" accept-charset="UTF-8" data-remote="true" method="post"> ...略 </form>「
form
タグの内部でdata-remote="true"
が設定されている」というのがポイントです。この属性設定は、「JavaScriptによるフォーム操作を許可することをRailsに知らせる」という意味があります。コントローラー側のAjax対応
respond_toメソッド
Ajaxに対応するためには、コントローラー側の実装も一部変更する必要があります。具体的には、「
respond_to
メソッドを使い、リクエストの種類によって応答を場合分けする」という実装が必要になります。respond_to
メソッドの基本的な用法は以下のようになります。respond_to do |format| format.html { redirect_to user } format.js end
respond_to
は引数としてブロックを取りますが、その動作は「ブロック内のコードのうち、いずれかの1行が処理される」というものになります。Relationshipsコントローラーの実装を変更する
Relationshipsコントローラーの実体である
app/controllers/relationships_controller.rb
の内容は、以下のように変更します。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end end「Relationshipsコントローラーのアクションで使われる変数を、ローカル変数の
user
ではなくインスタンス変数の@user
に変更した」という点には注意が必要です。
user
を@user
に変更した理由を説明するにあたっては、「app/views/users/_follow_form.html.erb
というビューは、@user
の内容に応じて動作を分岐させるという実装である」というのが重要なポイントです。この実装を踏まえると、「ページ遷移が発生することなしに、[follow]/[unfollow]ボタンの描画状態に変化が発生する」というユースケースを実現するためには、Relationshipsコントローラーのアクションで直接@user
を書き換える必要が出てきます。そのためuser
を@user
に変更する必要が発生した、という次第です。WebブラウザでJavaScriptが無効に設定されていた場合のための、Railsの設定の変更
Webブラウザ側でJavaScriptが無効にされていると、当然ながらWebブラウザでAjaxリクエストを発行することはできません。RailsアプリケーションでAjax対応を前提とした実装を行った場合、JavaScript無効のWebブラウザでアプリケーションを動かすためには、Rails側の設定を変更する必要があります。具体的には、「認証トークンがremoteフォームに埋め込まれるようにする」必要があります。
# 認証トークンをremoteフォームに埋め込む config.action_view.embed_authenticity_token_in_remote_forms = true変更対象となるファイルは
config/application.rb
です。config/application.rbrequire_relative 'boot' require 'rails/all' ...略 module SampleApp class Application < Rails::Application ...略 + + # 認証トークンをremoteフォームに埋め込む + config.action_view.embed_authenticity_token_in_remote_forms = true end end
Ajaxリクエストを受信したときに呼び出される埋め込みRubyファイルの実装
生成すべきファイルの名前
まずは前提知識から。RailsアプリケーションがHTTPの
GET
リクエストを受信すると、対応するアクション(index
、show
、new
、edit
)と同じ名前を持つHTML用の埋め込みRuby(例えばshow
アクションに対するshow.html.erb
)が自動で呼び出されます。Railsチュートリアルを第14章まで進めてきた人であれば、ここまでは既知かと思います。RailsアプリケーションがAjaxリクエストを受信した場合も、その動作はHTTPの
GET
リクエストに対する動作と酷似したものになります。すなわち、「対応するアクションと同じ名前を持つ埋め込みRubyが自動で呼び出される」という動作をするのです。但し、Ajaxリクエストに対する動作の場合は、「呼び出される埋め込みRubyは、HTML用ではなくてJavascript用のものとなる」という違いがあります。「Ajaxリクエストに対して実行される動作の内容の定義と、各動作に対応するerbファイルの名前」は、今回の場合、以下のような関係になります。
動作 対応するアクションの内容 対応するファイル名 フォロー Relationshipオブジェクトの create
app/views/relationships/create.js.erb
フォロー解除 Relationshipオブジェクトの destroy
app/views/relationships/destroy.js.erb
jQueryによるDOM操作
前提となる記法
$("#follow_form")上記の
$(#follow_form
)というオブジェクトは、「follow_form
というCSS idを持つ要素」を指します。フォームそのものを指すものではありません。なお、クラスを指す場合は、#
の代わりに.
を用います。こちらもCSSと同様ですね。$("#follow_form").html("foobar")例えば、
follow_form
というCSS idを持つフォロー用フォーム全体を"foobar"
という文字列で置き換えたい場合、以上のようなコードを使います。
create.js.erb
とdestroy.js.erb
の実際の中身app/views/relationships/create.js.erb$("#follow_form").html("<%= escape_javascript(render('user/unfollow')) %>"); $("#followers").html('<%= @user.followers.count %>');app/views/relationships/destroy.js.erb$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>"); $("#followers").html("<%= @user.followers.count %>");上記コードのポイントは以下です。
- JS-ERbでは、素のJavaScriptとは異なり、組み込みRubyを使うことができる
- JS-ERbでRailsの
render
メソッドを使ってJavaScriptファイル内にHTMLを挿入する際には、escape_javascript
メソッドで「JavaScriptのダメ文字」をエスケープする必要があるAjaxによる[Follow]/[Unfollow]ボタンの実装における注意事項
Ajaxによる[Follow]/[Unfollow]ボタンの実装が完了したら、一旦開発環境のサンプルアプリケーションからログアウトした上で、Railsサーバーを再起動し、再度ログインしましょう。
Ajaxによる[Follow]/[Unfollow]ボタンの実装後、[Unfollow]ボタンから
DELETE
リクエストを発行する動作が正常に行われるようにするためには、おそらく「ログアウト→再ログイン」という操作が必要となります。そうでないと、「/relationships/:id に対し、DELETE
ではなくPOST
を発行してしまい、ActionController::RoutingError
が発生する」という事態になります。演習 - [Follow] ボタン (Ajax編)
1. ブラウザから /users/2 にアクセスし、うまく動いているかどうか確認してみましょう。
followersの数は0で、[Follow]ボタンが表示されています。
ここで[Follow]ボタンをクリックしてみます。次に出た画面のスクリーンショットは以下です。
followersの数が1増え、[Follow]ボタンが[Unfollow]ボタンに変わりました。
ここで[Unfollow]ボタンをクリックしてみます。次に出た画面のスクリーンショットは以下です。
followersの数が1減り、[Unfollow]ボタンが[Follow]ボタンに変わりました。
2. 先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認してみましょう。
フォローを実行した直後のログ
[Follow]ボタンをクリックし、
POST
リクエストが発行されてから、リクエストが完了するまでのRailsサーバーのログを以下に示します。Started POST "/relationships" ...略 Processing by RelationshipsController#create as JS ...略 Rendering relationships/create.js.erb ...略 Rendered users/_unfollow.html.erb (4.6ms) ...略 Rendered relationships/create.js.erb (30.4ms) Completed 200 OK in 137ms (Views: 66.4ms | ActiveRecord: 35.8ms)「
relationships/create.js.erb
の描画が行われ、その中でusers/_unfollow.html.erb
の描画が行われる」という順序でテンプレートの描画が行われたことがわかります。フォロー解除を実行した直後のログ
[Follow]ボタンをクリックし、
POST
リクエストが発行されてから、リクエストが完了するまでのRailsサーバーのログを以下に示します。Started DELETE "/relationships/93" ...略 Processing by RelationshipsController#destroy as JS ...略 Rendering relationships/destroy.js.erb Rendered users/_follow.html.erb (1.5ms) ...略 Rendered relationships/destroy.js.erb (30.9ms) Completed 200 OK in 178ms (Views: 78.8ms | ActiveRecord: 50.1ms)「
relationships/destroy.js.erb
の描画が行われ、その中でusers/_follow.html.erb
の描画が行われる」という順序でテンプレートの描画が行われたことがわかります。フォローをテストする
ユーザーのフォローに対するテスト
ユーザーのフォローに対するテストの核心は以下のコードです。
assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } endテストの構造は、「/relationships に対して
POST
リクエストを発行し、それに対してRDBのレコード数が増えていることをテストする」というものになります。Ajax版のテストは、以下の内容になります。通常版のテストとの違いは、
post
メソッドにおけるxhr: true
というオプションの有無だけです。assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id }, xhr: true endユーザーのフォロー解除に対するテスト
relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end「HTTPリクエストを発行し、それに対するRDBのレコード数の増減をテストする」というテストの構造は、前述「ユーザーのフォローに対するテスト」と類似したものとなります。より具体的な手順は以下の通りになります。
- 1人のユーザーをフォローする
- 1.で生成されたRelationshipモデルのオブジェクトを、
relationship
変数に代入するrelationship
変数を引数としてDELETE
リクエストを発行し、フォロー数が1減ったことをテストする2.の操作は、「ログインユーザーの能動的リレーションシップから、1.でフォローしたユーザーのidを検索する」という操作により行われます。
relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true endAjax版のテストは、以下の内容になります。通常版のテストとの違いは、
post
メソッドにおけるxhr: true
というオプションの有無だけです。
test/controllers/relationships_controller_test.rb
に対する変更の内容上記を踏まえると、
test/controllers/relationships_controller_test.rb
全体に対する変更の内容は、以下のようになります。test/controllers/relationships_controller_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other = users(:mkirisame) log_in_as(@user) end test "following page" do get following_user_path(@user) assert_not @user.following.empty? assert_match @user.following.count.to_s, response.body @user.following.each do |user| assert_select "a[href=?]", user_path(user), minimum: 2 end end test "followers page" do get followers_user_path(@user) assert_not @user.followers.empty? assert_match @user.followers.count.to_s, response.body @user.followers.each do |user| assert_select "a[href=?]", user_path(user), minimum: 2 end end + + test "should follow a user the standard way" do + assert_difference '@user.following.count', 1 do + post relationships_path, params: { followed_id: @other.id } + end + end + test "should follow a user with Ajax" do + assert_difference '@user.following.count', 1 do + post relationships_path, xhr: true, params: { followed_id: +other_id } + end + end + test "should unfollow a user the standard way" do + @user.follow(@other) + relationship = @user.active_relationships.find_by(followed_id: +other.id) + assert_difference '@user.following.count', -1 do + delete relationship_path(relationship) + end + end + test "should unfollow a user with Ajax" do + @user.follo(@other) + relationship = @user.active_relationships.find_by(followed_id: other.id) + assert_difference '@user.following count', -1 do + delete relationship_path(relationship), xhr: true + end + end end
演習 - フォローをテストする
別記事で解説します。
- 投稿日:2020-02-21T06:33:54+09:00
Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」
1. リスト 14.36の
respond_to
ブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?
RelationshipsController#create
において、format.html
以下の行が欠落している場合
RelationshipsController#create
に以下の変更を加えた場合の動作から見てみましょう。app/controllers/relationships_controller.rb#createdef create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| - format.html { redirect_to @user } format.js end end
上記変更を適用した上で、
test/integration/following_test.rb
を対象としてテストを実行した結果は以下です。# rails test test/integration/following_test.rb Running via Spring preloader in process 1590 Started with run options --seed 42916 ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 3.4918121000082465] test_should_follow_a_user_the_standard_way#FollowingTest (3.49s) ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat app/controllers/relationships_controller.rb:7:in `create' test/integration/following_test.rb:30:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:29:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.89168s 6 tests, 13 assertions, 0 failures, 1 errors, 0 skipsエラー内容が
ActionController::UnknownFormat
であることがポイントです。上記エラーログでは端折られていますが、今回発生したActionController::UnknownFormat
エラーのより詳細なエラーメッセージは以下のようになります。ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []「RelationshipsController#create is missing a template(略)」とありますね。
create
コントローラーに対するビューのテンプレート、ファイル名としてはapp/views/relationships/create.html.erb
が存在しないためのエラーです。
RelationshipsController#create
において、format.js
以下の行が欠落している場合続いて、
RelationshipsController#create
に以下の変更を加えた場合の動作です。app/controllers/relationships_controller.rb#createdef create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } - format.js end end
上記変更を適用した上で、
test/integration/following_test.rb
を対象としてテストを実行した結果は以下です。# rails test test/integration/following_test.rb Running via Spring preloader in process 1604 Started with run options --seed 30420 6/6: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.26699s 6 tests, 14 assertions, 0 failures, 0 errors, 0 skipsテストは正常に完了します。テスト「should follow a user with Ajax」が落ちることを期待していたのですが、違いますね。
RelationshipsController#destroy
において、format.html
以下の行が欠落している場合今度は
RelationshipsController#destroy
に以下の変更を加えた場合の動作です。app/controllers/relationships_controller.rb#destroydef destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| - format.html { redirect_to @user } format.js end end
上記変更を適用した上で、
test/integration/following_test.rb
を対象としてテストを実行した結果は以下です。# rails test test/integration/following_test.rb Running via Spring preloader in process 559 Started with run options --seed 5514 ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.025347000000693] test_should_unfollow_a_user_the_standard_way#FollowingTest (4.03s) ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat app/controllers/relationships_controller.rb:16:in `destroy' test/integration/following_test.rb:46:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:45:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.16709s 6 tests, 13 assertions, 0 failures, 1 errors, 0 skips
ActionController::UnknownFormat
エラーでテストが落ちています。「RelationshipsController#create
において、format.html
以下の行が欠落している場合」と同様の挙動ですね。
RelationshipsController#destroy
において、format.js
以下の行が欠落している場合最後は
RelationshipsController#destroy
に以下の変更を加えた場合の動作です。app/controllers/relationships_controller.rb#destroydef destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } - format.js end end
上記変更を適用した上で、
test/integration/following_test.rb
を対象としてテストを実行した結果は以下です。# rails test test/integration/following_test.rb NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01. Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17. NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01. Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17. Running via Spring preloader in process 573 Started with run options --seed 63825 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.95860s 6 tests, 14 assertions, 0 failures, 0 errors, 0 skipsテストは正常に完了します。
発展 -
2. リスト 14.40の
xhr: true
がある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。
test/integration/following_test.rb
の内容を以下のように改変した場合、テストの結果はどうなるでしょうか。「テスト『should follow a user with Ajax』において、xhr: true
がある行を削除する」という改変です。test/integration/following_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other = users(:mkirisame) log_in_as(@user) end ...略 test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do - post relationships_path, xhr: true, params: { followed_id: @other.id } end end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end end end
上記テストの実行結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 586 Started with run options --seed 63280 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.9365238999998837] test_should_follow_a_user_with_Ajax#FollowingTest (1.94s) "@user.following.count" didn't change by 1. Expected: 3 Actual: 2 test/integration/following_test.rb:36:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.76945s 6 tests, 14 assertions, 1 failures, 0 errors, 0 skips上記テストでは、テスト「should follow a user with Ajax」において、
POST
リクエスト自体が発行されていません。POST
リクエストが発行されないことには、RelationshipsController#create
メソッドも実行されません。当該RelationshipsController#create
の内容は以下の通りです。RelationshipsController#createdef create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end endRDB上のデータにフォローを反映する処理は、上記コードのうち
current_user.follow(@user)
となります。current_user.follow(@user)
が実行されなければ、@user.following.count
の値も変化することはありません。結果、「@user.following.count
の値が想定と違う」という理由でテストが落ちることになります。逆に、「テスト『should unfollow a user with Ajax』において、
xhr: true
がある行を削除する」という改変を加えた場合(下記ソース)も、同様の形でテストが落ちます。test/integration/following_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other = users(:mkirisame) log_in_as(@user) end ...略 test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do - delete relationship_path(relationship), xhr: true end end end
上記テストの実行結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 599 Started with run options --seed 27288 FAIL["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.007498499999201] test_should_unfollow_a_user_the_standard_way#FollowingTest (4.01s) "@user.following.count" didn't change by -1. Expected: 2 Actual: 3 test/integration/following_test.rb:45:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.10546s 6 tests, 14 assertions, 1 failures, 0 errors, 0 skips今度は、テスト「should unfollow a user with Ajax」中で
DELETE
リクエストが発行されなくなりました。RelationshipsController#destroy
中にあるcurrent_user.unfollow(@user)
という処理が実行されず、@user.following.count
の数が変化しなかった結果、テストが落ちたのですね。RelationshipsController#destroydef destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end発展 -
FollowingTest
で、より適切なテストを行えるようにしてみるRailsチュートリアル本文リスト 14.40のテストでは、例えば
create.js.erb
やdestroy.js.erb
の記述内容にまで踏み込んだテストができていませんでした。別記事「Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良」にて、当該テストの問題点や改良策について記述しています。
- 投稿日:2020-02-21T06:28:26+09:00
Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良
前提となるテストの内容
Railsチュートリアル本文、リスト 14.40記載のテストコードです。
test/integration/following_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other = users(:mkirisame) log_in_as(@user) end # ...略 test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end end end
RelationshipsController#create
において、format.js
以下の行が欠落していても上記テストが落ちない理由前提として、
RelationshipsController#create
の実装は以下のようになっています。app/controllers/relationships_controller.rb#createdef create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end
create
メソッド内2行目の時点で、follow
メソッドの処理に問題がなければ、@user.following.count
の数は1増えます。ここで、
format.js
以下の行が欠落している場合、RelationshipsController#create
は、「Ajaxによる呼び出しであっても、非Ajax(通常のリダイレクトを伴う呼び出し)として以降の処理を行います。となると、「format.js
以下の行が欠落していても、format.html
以下の行さえあれば、とりあえず当該テストは通ってしまう」という動作になるのです。現状の実装で、テスト「should follow a user with Ajax」はどんな場合に落ちるのか
Railsチュートリアル本文のリスト 14.40において、テスト「should follow a user with Ajax」のソースコードは、以下のようになっています。
test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end end「
RelationshipsController#create
において、format.js
以下の行が欠落していてもこのテストが落ちない」というのであれば、どのような場合にこのテストは落ちるのでしょうか。一つの答えは、「
app/views/relationships
ディレクトリに、create.js.erb
がなくてcreate.css.erb
だけが存在する場合」です。この場合、当該テストは以下のようなエラーで落ちることになります。# rails test test/integration/following_test.rb Running via Spring preloader in process 507 Started with run options --seed 49836 ERROR["test_should_follow_a_user_with_Ajax", FollowingTest, 4.884168800000225] test_should_follow_a_user_with_Ajax#FollowingTest (4.88s) ActionController::UnknownFormat: ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant. request.formats: ["text/javascript", "text/html", "application/xml", "*/*"] request.variant: [] test/integration/following_test.rb:37:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:36:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.96005s 6 tests, 13 assertions, 0 failures, 1 errors, 0 skips
ActionController::UnknownFormat
エラーでテストが落ちていますね。
test/integration/following_test.rb
の、より望ましいであろう実装ビューによりレンダリングされる内容に対してテストを行うのであれば、以下のような実装がより望ましいかと思います。
test/integration/following_test.rbrequire 'test_helper' class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other = users(:mkirisame) log_in_as(@user) end ...略 test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end + assert_redirected_to @other end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end + assert_match 'Unfollow', @response.body end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end + assert_redirected_to @other end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end + assert_match 'Follow', @response.body end end上記テストの追加内容は以下の通りです。
- 非Ajaxの場合、
@other
の内容たるユーザーのプロフィールページにリダイレクトされることをテストする- Ajaxの場合、以下の処理が行われることをテストする
- Followボタンが押された場合、「Unfollow」というキャプションを持つボタンを生成するJavaScriptコードが返されること
- Unfollowボタンが押された場合、「Follow」というキャプションを持つボタンを生成するJavaScriptコードが返されること
「より望ましいであろう実装」のテスト結果
「should follow a user the standard way」のテストが落ちるパターン
以下のように、「
RelationshipsController#create
において、format.html
以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| - format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end end実際にテスト「should follow a user the standard way」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 664 Started with run options --seed 25360 ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 4.109552399999302] test_should_follow_a_user_the_standard_way#FollowingTest (4.11s) ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat app/controllers/relationships_controller.rb:7:in `create' test/integration/following_test.rb:30:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:29:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.22645s 6 tests, 19 assertions, 0 failures, 1 errors, 0 skips「should follow a user with Ajax」のテストが落ちるパターン
以下のように、「
RelationshipsController#create
において、format.js
以下の行が欠落している」という場合、「should follow a user with Ajax」のテストが落ちることが期待されます。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } - format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end end実際にテスト「should follow a user with Ajax」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 677 Started with run options --seed 49139 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.8547959999996237] test_should_follow_a_user_with_Ajax#FollowingTest (1.85s) Expected /Unfollow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})". test/integration/following_test.rb:39:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.41998s 6 tests, 22 assertions, 1 failures, 0 errors, 0 skips「should unfollow a user the standard way」のテストが落ちるパターン
以下のように、「
RelationshipsController#destroy
において、format.html
以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| - format.html { redirect_to @user } format.js end end end実際にテスト「should unfollow a user the standard way」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 690 Started with run options --seed 23512 ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 3.8890324999993027] test_should_unfollow_a_user_the_standard_way#FollowingTest (3.89s) ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat app/controllers/relationships_controller.rb:16:in `destroy' test/integration/following_test.rb:46:in `block (2 levels) in <class:FollowingTest>' test/integration/following_test.rb:45:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.18608s 6 tests, 19 assertions, 0 failures, 1 errors, 0 skips「should unfollow a user with Ajax」のテストが落ちるパターン
以下のように、「
RelationshipsController#destroy
において、format.js
以下の行が欠落している場合」、「should unfollow a user with Ajax」のテストが落ちることが期待されます。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } - format.js end end end実際にテスト「should unfollow a user with Ajax」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb Running via Spring preloader in process 703 Started with run options --seed 27626 FAIL["test_should_unfollow_a_user_with_Ajax", FollowingTest, 1.7373090000000957] test_should_unfollow_a_user_with_Ajax#FollowingTest (1.74s) Expected /Follow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})". test/integration/following_test.rb:57:in `block in <class:FollowingTest>' 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.29870s 6 tests, 22 assertions, 1 failures, 0 errors, 0 skipsソースコードの実装に問題がない場合
最後に、
app/controllers/relationships_controller.rb
の内容が以下の通り問題なく記述されている場合、テストの結果はどうなるでしょうか。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end end実際にテストを実行してみます。
# rails test test/integration/following_test.rb Running via Spring preloader in process 716 Started with run options --seed 34373 6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04 Finished in 4.64541s 6 tests, 22 assertions, 0 failures, 0 errors, 0 skips無事テストが通りました。というわけで、テストの内容に問題はないようです。
- 投稿日:2020-02-21T02:43:49+09:00
[超短文]herokuでデータベース操作する方法
herokuでデプロイした後に、データベースをSQLコマンドで操作する画面に行く方法です。
$ heroku pg:psqlこのコマンドを実行すると操作画面に遷移します。
あとは
$ SELECT * FROM [テーブル名]でデータ取得するもよし、
$ DELETE FROM [テーブル名]でデータ削除するもよしです。
- 投稿日:2020-02-21T02:20:44+09:00
【Rails】自動で次のページへ!!jscrollによるページネーションの無限スクロール
自動で次のページに移行
ページネーションを実装して思うことは、最近は次へボタンなんて存在せず、スクロールするとどんどん新しい商品やツイートが表示されたりするということだ。
今回はそれを実装する。
使うのは、gem'kaminari'(ページネーション機能) + プラグイン”jScroll”(無限スクロール)である。まずはgem'kaminari'でページネーションを実装
参考になるページはここです。
【Rails初心者】ページネーションを実装して自分好みにデザインを変えるGemfilegem 'kaminari'gemを追記したら
ターミナル$ bundle installインストールが完了したら、コントローラーに記述する。
products_controller.rbclass UsersController < ApplicationController def index @products = Product.order(created_at: "DESC").includes(:host).page(params[:page]).per(1) #.page(params[:page]).per(1)は1ページ、1つのproductだけ表示の意味 end end次はページネーションの次へ、戻るなどのボタンを表示させる。
index.haml= render 'shared/main-header' .container-fluid .row.py-5.w-100 = render 'shared/side' .col-8.main .jscroll = render partial: 'user', collection: @products, as: "product", class: "jscroll" .skill-list = paginate @products # ここを追加するだけこれでページネーションの実装完了です。
kaminariは遅いので、加工します。
kaminariは便利ですが、読み込みが10秒ぐらいかかって非常に重い。
だから、記述を変えます。参考になるサイト:巨大レコードのkaminariページネーションは工夫が必要
products_controller.rbclass UsersController < ApplicationController def index @products = Product.order(created_at: "DESC").includes(:host).page(params[:page]).without_count.per(1) # .without_countを追加しています end end.without_countを追加することで、本来のkaminariの機能は使えなくなります。
次へボタンと戻るボタンは表示可能です。index.haml= render 'shared/main-header' .container-fluid .row.py-5.w-100 = render 'shared/side' .col-8.main .jscroll = render partial: 'user', collection: @products, as: "product", class: "jscroll" .skill-list // = paginate @products これを削除して、下記を追加 = link_to_prev_page @products, '前のページ', class: "prev" = link_to_next_page @products, '次のページ', class: "next"これで戻るボタンと次へボタンを表示させました。
1秒ほどで、ページを開けるようになりましたね!!では、本題の下にスクロールすると、勝手に次のページに移行するようにしましょう!!
無限スクロール
jScrollのインストール
CDNで追加する場合
スクリプトを読み込ませて簡単に導入しましょう!
application.html.haml%head %script{src: "https://cdnjs.cloudflare.com/ajax/libs/jscroll/2.4.1/jquery.jscroll.min.js"}直接JSファイルを導入する場合
jscrollのGithubからダウンロードしよう。
ZIPファイルを解答したら、
jquery.jscroll.min.jsをasset/javascripts配下におきます。そしたら、jScrollを読み込ませるようにします
assets/javascripts/application.js//= require jquery.jscroll.min.js
実はここが失敗が起こりやすい箇所です!!!!
解説します
私は下記のようにしてますapplication.js//= require jquery //= require rails-ujs //= require activestorage //= require turbolinks //= require_tree . //= require jquery.jscroll.min.js //= require popper //= require bootstrap-sprockets重要なのはここです。
application.js//= require jquery //= require jquery.jscroll.min.jsこれだけだとエラーなく利用できます。
エラーUncaught TypeError: $(...).jscroll is not a function // 「.jscrollってイベント処理ねええぞゴラアアアアアアア!!」と怒られます。これに苦戦しましたね。間違えて下記にしてしまうと使えません
失敗例//= require jquery //= require jquery.jscroll.min.js //= require jquery3//= require jquery と //= require jquery3が入っていて、
エラーが起こるパターンです。
もしも複数入っている場合、片方消しましょう。jQueryで無限スクロールの処理作成
では続きを進めていきます。
それではjQueryで無限スクロールの処理を作成します。jscroll用のjs$(document).on('turbolinks:load', function() { $('.jscroll').jscroll({ // 無限に追加する要素は、どこに入れる? contentSelector: '.jscroll', // 次のページにいくためのリンクの場所は? >aタグの指定 nextSelector: 'a.next', // 読み込み中の表示はどうする? loadingHtml: '読み込み中' }); });これでdiv.jscrollをスクロールすると.jscrollに次のページの要素を追加するよ。ということになります
詳しいオプションは下記のサイトが参考になります。
無限スクロールのプラグイン「jScroll」の使い方これで完了です!!
エラーが難しかったですが、原因がわかってよかったです。参考リンク
kaminari系
【Rails初心者】ページネーションを実装して自分好みにデザインを変える
gem'kaminari'のオプション
参考になるサイト:巨大レコードのkaminariページネーションは工夫が必要無限スクロール系
kaminari + jscrollを使った無限スクロールの実装
無限スクロールのプラグイン「jScroll」の使い方
Github:jscroll
- 投稿日:2020-02-21T01:56:06+09:00
Ruby on Railsについて 自分の中でのまとめ
TECH::EXPERTで学んだことに対して、自分用の備忘録、復習として色々と書いていこうと思います。
Ruby on Railsの概要
Ruby on RailsはRubyでWebアプリケーションを作成する際によく使用されるフレームワークです。
Webアプリケーションには共通している機能(ユーザー登録とかログインとか)が多くあるため、それらの機能をまとめてフレームワークという形で提供されています。つまりWebアプリケーションのフォーマットのような物です。最初に理解すべきこと
WEBアプリケーションにおける処理の流れを表したものとして、MVCモデルという物があります。これを理解することで、TECH::EXPERTでのRailsのカリキュラムはかなり分かりやすくなるのでは...と思いました。自分自身しっかりと理解しているとは言えないため、ブログという形で復習していきたいと思います。
MVCモデル
MVCモデルで出てくるキーワードは以下となります。
*ルーティング
*コントローラ
*ビュー
*モデルルーティング
クライアントから送られてきたリクエストからどのような処理をするべきか、という判断をしてコントローラ(後述)役目になります。通販サイトで商品をクリックし場合、商品の詳細ページを表示させろ!という判断をするわけです。
コントローラ
ルーティングで振り分けられたリクエストを実際に処理する部分です。リクエストによってトップページを出力させたり、データを削除したりと処理内容は変わってきます。
ビュー
クライアントへ返す見た目を設定するものです。hoge.html.erbというファイルでhtmlにRubyの処理を埋め込む事ができます。ERBファイルを元に作成されたhtmlファイルがレスポンスとしてクライアントに返されます。
モデル
データベースに保存してあるデータを参照する場合や更新する場合はモデルを介して処理が行われます。勉強不足でこれ以上の説明ができません...そのうち詳細に書きます。
まとめ
こんな感じでなるべく毎日ブログを書こうと思います。よろしくお願いします。
- 投稿日:2020-02-21T00:47:05+09:00
herokuのデータを上書き DBカラムに変更があった場合
herokuにデプロイしたアプリのDBカラムを追加、変更してデプロイし直した時に不具合(詰まった)があったので記載します。
2回目以降のデプロイ
$ heroku login $ git add . $ git commit -m "" $ git push heroku master $ heroku openここの部分の説明は割愛します。
1回目のデプロイはこちらを参考に進めます。
RailsDBをMySQLに変更してHerokuでデプロイまでする手順
Rails6 ローカルで頑張って作ったwebアプリをHerokuでデプロイした話詰まった点
変更したコードの部分は2回目以降も反映されたが、DBの変更(今回はカラムの追加)が反映されなかった。
logで確認すると、「そんなメソッド知らないよ」とか言っていました。
ローカルでは問題なく動いているのを確認した後で調べてみると、DBの変更は
heroku master
では反映されないのでリセットする必要があるとのこと。解決方法
$ rails db:migrate:reset
heroku master
の後に入力してリセットとマイグレーションを行いました。無事にheroku上でも動いています。
- 投稿日:2020-02-21T00:32:03+09:00
Rails6 テキストを一発でコピーできるボタンを実装する
目的
- テキストを一発でコピーできるボタンの実装方法をまとめる
作業をする前に
- 今回の作業方法は公式のGithubのREADMEに沿って説明する。
- clipboard-rails
実施方法
Gem
zeroclipboard-rails
をインストール
Gemfileに下記を追記する。
gem 'jquery-rails' gem 'clipboard-rails'下記コマンドを実行してGemfileを元にGemをインストールする。
$ bundle update設定ファイル記載
- 下記に存在するファイルをエディタで開く。
アプリ名/app/assets/javascripts
- /application.js
下記の内容を
application.js
に追記する。//= require jquery //= require jquery_ujs //= require clipboard $(document).ready(function(){ var clipboard = new Clipboard('.clipboard-btn'); console.log(clipboard); });下記に存在するファイルをエディタで開く。
アプリ名/app/views/layouts
- application.html.erb
<head>
の一番最初に下記を追記する。<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>ボタンを実装
任意のビューファイルに下記の記載を行う。
<div> <!-- Target --> <textarea id="bar" name="" rows="7" cols="100"> <p>コピーターゲット</p> </textarea> <!-- Trigger --> <button class="clipboard-btn" data-clipboard-action="copy" data-clipboard-target="#bar"> Copy to clipboard </button> </div>