20200208のRailsに関する記事は23件です。

READMEとは説明書。まず最初に読んでほしい

リードミー(Readme)とは、ソフトウェアを配布する際の添付文書のひとつ。配布物の一般的な情報を記載したファイルである。多くの場合、そのソフトウェアをインストールし使用する前に読むべきものとされている。(引用元:Wikipedia)

自分でテンプレートを作る、他人のテンプレートを使うことで作業効率アップ!

そのソフトウェアでなにができるのか、端的にシンプルな記述。

参考

https://cpp-learning.com/readme/

////rails修行中の大きなぼやき////

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでRuby on Railsの環境構築チートシート(http://localhost:3000/を立ち上げるまで)

MacでRuby on Railsの環境構築するときの簡単な手順です!とりあえず、何もないところからrails sでサーバーを立ち上げるまでです!

homebrewをインストール

http://brew.sh/index_ja.html

homebrewでMySQLをインストール

$ brew install mysql

MySQL起動

$ mysql.server start
Starting MySQL
SUCCESS!

homebrewを使用して、rbenvおよびruby-build(プラグイン)をインストール

rbenvインストール

$ brew install rbenv
$ brew install ruby-build

・ruby-buildは、rbenvのプラグイン。($rbenv installコマンドを使うためのプラグイン)
・rbenv installコマンドで、rubyのver2.6.4をインストール

※注意:
○Homebrewのパスを通す必要あり
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
$ source ~/.bash_profile→このコマンドで設定を保存

Rubyをインストール

rbenvでrubyのバージョン設定

$ rbenv version
を実行し、rbenvで管理されているrubyのバージョンを確認

→開発で使用したい該当のバージョンがなければインストールする必要あり。
$ rbenv install 2.6.5(バージョンは一例)

rubyのインストール時にエラーが出た場合

If you don't have the version you need, try upgrading ruby-build: brew update && brew upgrade ruby-build

このエラーが出れば、指摘通り以下のコマンド実行

$brew update

$brew upgrade ruby-build

その後、もう一度トライすると上手くいく

$rbenv install 2.6.5
$ rbenv install 2.6.1
→rbenvでrubyをインストールする

$ rbenv global 2.6.1
→rbenvのバージョンを切り替える

$ rbenv rehash
→反映

$ ruby -v
→rubyバージョンを確認

gemのインストール

$ gem install bundler
→bundlerインストール

$ bundle -v
→bundlerのバージョンを確認

補足
・gemを管理するシステムがRubyGems
・RubyGemsはRubyに標準添付されているシステムなのでRubyが使える状態ならすぐに使うことができる。→rubyに同梱(セット)
→同梱のバージョン確認には
$ gem update --system
・bundlerはgemの一種で、gemをデフォルトのRubyGemsより効率よく管理できるツール。まず最初に、RubyGemsを使ってbundlerをインストール
・bundlerを使ってgemをインストール
・BundlerはGemfileをもとにgemをインストールする。
・gemをシステムにインストールするのではなく、ディレクトリ内で管理する。
・そのためにpathオプションをつける。
$ bundle install --path vendor/bundleでpathオプションをつける。このことにより、コマンドの頭にbundle execコマンドをつけて、実行する。
・railsもgemの一種

$ bundle install --path vendor/bundle

※以下のようなbundlerのバージョンエラーが出たら

To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.0.2

$ gem install bundlerで最新のbundlerをインストール
$ rbenv rehash
$ bundle -vでbundlerのバージョン確認
→その後もう一度
$ bundle install --path vendor/bundle

Rails設定

  • 新アプリ名のディレクトリを作成
  • $ cd ~/<ディレクトリ名>コマンドで、アプリディレクトリに移動 $ bundle init →Gemfileを作成

できあがったGemfileのRailsのコメントアウトを外す
gem "rails"
→Gemfileでrailsのバージョンを確認

Railsインストール

$ bundle install --path=vendor/bundle

rails new

$ cd ~/<ディレクトリ名> で、アプリのディレクトリに移動

$ bundle exec rails new . -d mysql --skip-turbolinks --skip-test --skip-coffee
→.はカレントディレクトリを意味する
→gemfileの上書きを聞かれた場合は、yes入力でgemfileを上書き
→うまく行かなければ
$ bundle update

mysqlエラー出た場合
以下をやってみる
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"
$ bundle install

gemのインストール(もう一回)

$ bundle install --path=vendor/bundle

nodeを使う場合(nodeを使わないのであれば飛ばしてください)

nodeのバージョンなどをいじる場合は以下の記事を参照
https://qiita.com/kenkentarou/items/f27bd4e49af6fe429429

nodeとは
関係性
ruby <-> node
rbenv <-> nodebrewやnvm

nodeは一つの言語(node.jsと同義)で、nodeは、言語としても扱うし、サーバーサイドで動くためにコンパイルするシステムとしても使われる

データベースの設定

データベースの設定ファイルを作成

通常の現場では、datebase.yml.defaultというファイルを作成して、設定情報はrootなどセキュリティー上影響ない初期値を入れておき、こちらをgit管理します。

ローカルで作業する際には、datebase.yml.defaultをコピーして、database.ymlというファイルを作成し、自身のPC上の設定に合わせ修正したものを使います。自身の設定に合わせたdatabase.ymlを作成しないと、ローカルでデータベースを走らせることができません。

以下のコマンドを実行し、database.yml.defaultをコピーして、自身の環境に合わせたdatabase.ymlを作成しましょう。

$ cp config/database.yml.default config/database.yml
  • database.ymlの設定を自身の環境に合わせて修正 ユーザー名やパスワードの設定

ローカル上にデータベーステーブル作成

  • $ bundle exec rails db:create →データベーステーブル作成

$bundle exec rails db:migrate
→migrationが必要なファイルがあれば実行

サーバー立ち上げ

  • bundle exec rails sコマンドを実行し、railsサーバーの立ち上げ。

  • localhost:3000にアクセスし、完成形にある画面が表示されることを確認

git flowでの管理の場合

git flowがPCに入っている前提で、

$git flow init
→アプリのディレクトリ全体がgit管理対象になる
→→ローカルにmasterとdevelopがbranchが出来る

プッシュする前に確認すること

.gitignoreに入れておかなければいけないファイル、ディレクトリが無いかどうか

ex)
/vender等

リモートリポジトリにプッシュする

$ git add .
$ git commit -m "inital commit"

$ git remote add origin https://github.com/hoge/hoge.git

$git push --all
→ローカルにあるdevelopとmasterの両方をリモートにあげる

レビューをしやすくするためにrails newの段階でgit flow initをしてmasterとdevelopブランチを自身のパブリックリポジトリに対してプッシュしています!

(PCにgit flow入れてなければ$ brew install git-flow)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】MacでRuby on Railsの環境構築(http://localhost:3000/を立ち上げるまで)

MacでRuby on Railsの環境構築するときの簡単な手順です!とりあえず、何もないところからrails sでサーバーを立ち上げるまでです!

homebrewをインストール

http://brew.sh/index_ja.html

homebrewでMySQLをインストール

$ brew install mysql

MySQL起動

$ mysql.server start
Starting MySQL
SUCCESS!

homebrewを使用して、rbenvおよびruby-buildをインストール(インストール済みの場合は不要)

rbenvインストール

$ brew install rbenv
$ brew install ruby-build

・ruby-buildは、rbenvのプラグイン。($rbenv installコマンドを使うためのプラグイン)
・この後、$ rbenv installコマンドで、rubyをインストール

※注意:
○Homebrewのパスを通す必要あり

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
$ source ~/.bash_profile

→このコマンドで設定を保存

Rubyをインストール

rbenvでrubyをインストール

$ rbenv version
を実行し、rbenvで管理されているrubyのバージョンを確認

→開発で使用したい該当のバージョンがなければインストールする必要あり。
$ rbenv install 2.6.5(バージョンは一例)

$ rbenv global 2.6.1
→rbenvのバージョンを切り替える

$ rbenv rehash
→反映

$ ruby -v
→rubyバージョンを確認

rubyのインストール時にエラーが出た場合

If you don't have the version you need, try upgrading ruby-build: brew update && brew upgrade ruby-build

このエラーが出れば、指摘通り以下のコマンド実行

$brew update

$brew upgrade ruby-build

その後、もう一度トライすると上手くいく

$rbenv install 2.6.5
$ rbenv install 2.6.1
→rbenvでrubyをインストールする

Railsを設定

  • 新アプリ名のディレクトリを作成
  • $ cd ~/<ディレクトリ名>コマンドで、アプリディレクトリに移動 $ bundle init →Gemfileを作成

できあがったGemfileのRailsのコメントアウトを外す
gem "rails"
→Gemfileでrailsのバージョンを確認

bundlerのインストール

$ gem install bundler

$ bundle -v
→bundlerのバージョンを確認

補足
・gemを管理するシステムがRubyGems
・RubyGemsはRubyに標準添付されているシステムなのでRubyが使える状態ならすぐに使うことができる。→rubyに同梱(セット)
→同梱のバージョン確認には
$ gem update --system
・bundlerはgemの一種で、gemをデフォルトのRubyGemsより効率よく管理できるツール。まず最初に、RubyGemsを使ってbundlerをインストール
・bundlerを使ってgemをインストール
・BundlerはGemfileをもとにgemをインストールする。
・gemをシステムにインストールするのではなく、ディレクトリ内で管理する。
・そのためにpathオプションをつける。
$ bundle install --path vendor/bundleでpathオプションをつける。このことにより、コマンドの頭にbundle execコマンドをつけて、実行する。
・railsもgemの一種

Rails(gem)のインストール

$ bundle install --path vendor/bundle

※もし以下のようなbundlerのバージョンエラーが出たら

To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.0.2

$ gem install bundlerで最新のbundlerをインストール
$ rbenv rehash
$ bundle -vでbundlerのバージョン確認
→その後もう一度
$ bundle install --path vendor/bundle

rails new

$ cd ~/<ディレクトリ名> で、アプリのディレクトリに移動

$ bundle exec rails new . -d mysql --skip-turbolinks --skip-test --skip-coffee
→.はカレントディレクトリを意味する
→gemfileの上書きを聞かれた場合は、yes入力でgemfileを上書き
→うまく行かなければ
$ bundle update

mysqlエラー出た場合
以下をやってみる
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"
$ bundle install

gemのインストール(もう一回)

$ bundle install --path=vendor/bundle

nodeを使う場合(nodeを使わないのであれば飛ばしてください)

nodeのバージョンなどをいじる場合は以下の記事を参照
https://qiita.com/kenkentarou/items/f27bd4e49af6fe429429

nodeとは
関係性
ruby <-> node
rbenv <-> nodebrewやnvm

nodeは一つの言語(node.jsと同義)で、nodeは、言語としても扱うし、サーバーサイドで動くためにコンパイルするシステムとしても使われる

データベースの設定

データベースの設定ファイルを作成

通常の現場では、datebase.yml.defaultというファイルを作成して、設定情報はrootなどセキュリティー上影響ない初期値を入れておき、こちらをgit管理します。

ローカルで作業する際には、datebase.yml.defaultをコピーして、database.ymlというファイルを作成し、自身のPC上の設定に合わせ修正したものを使います。自身の設定に合わせたdatabase.ymlを作成しないと、ローカルでデータベースを走らせることができません。

以下のコマンドを実行し、database.yml.defaultをコピーして、自身の環境に合わせたdatabase.ymlを作成しましょう。

$ cp config/database.yml.default config/database.yml
  • database.ymlの設定を自身の環境に合わせて修正 ユーザー名やパスワードの設定

ローカル上にデータベーステーブル作成

  • $ bundle exec rails db:create →データベーステーブル作成

$bundle exec rails db:migrate
→migrationが必要なファイルがあれば実行

サーバー立ち上げ

  • bundle exec rails sコマンドを実行し、railsサーバーの立ち上げ。

  • localhost:3000にアクセスし、完成形画面が表示されることを確認

git flowでの管理の場合

git flowがPCに入っている前提で、
(PCにgit flow入れてなければ$ brew install git-flowでインストール)

$git flow init
→アプリのディレクトリ全体がgit管理対象になる
→→ローカルにmasterとdevelopがbranchが出来る

プッシュする前に確認すること

.gitignoreに入れておかなければいけないファイル、ディレクトリが無いかどうか

ex) /vender等

リモートリポジトリにプッシュする

$ git add .
$ git commit -m "inital commit"

$ git remote add origin https://github.com/hoge/hoge.git

$git push --all
→ローカルにあるdevelopとmasterの両方をリモートにあげる

レビューをしやすくするためにrails newの段階でgit flow initをしてmasterとdevelopブランチを自身のパブリックリポジトリに対してプッシュしています!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでRuby on Railsの環境構築(http://localhost:3000/を立ち上げるまで)

MacでRuby on Railsの環境構築するときの簡単な手順です!とりあえず、何もないところからrails sでサーバーを立ち上げるまでです!

homebrewをインストール

http://brew.sh/index_ja.html

homebrewでMySQLをインストール

$ brew install mysql

MySQL起動

$ mysql.server start
Starting MySQL
SUCCESS!

homebrewを使用して、rbenvおよびruby-build(プラグイン)をインストール

rbenvインストール

$ brew install rbenv
$ brew install ruby-build

・ruby-buildは、rbenvのプラグイン。($rbenv installコマンドを使うためのプラグイン)
・rbenv installコマンドで、rubyのver2.6.4をインストール

※注意:
○Homebrewのパスを通す必要あり
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
$ source ~/.bash_profile→このコマンドで設定を保存

Rubyをインストール

rbenvでrubyのバージョン設定

$ rbenv version
を実行し、rbenvで管理されているrubyのバージョンを確認

→開発で使用したい該当のバージョンがなければインストールする必要あり。
$ rbenv install 2.6.5(バージョンは一例)

rubyのインストール時にエラーが出た場合

If you don't have the version you need, try upgrading ruby-build: brew update && brew upgrade ruby-build

このエラーが出れば、指摘通り以下のコマンド実行

$brew update

$brew upgrade ruby-build

その後、もう一度トライすると上手くいく

$rbenv install 2.6.5
$ rbenv install 2.6.1
→rbenvでrubyをインストールする

$ rbenv global 2.6.1
→rbenvのバージョンを切り替える

$ rbenv rehash
→反映

$ ruby -v
→rubyバージョンを確認

gemのインストール

$ gem install bundler
→bundlerインストール

$ bundle -v
→bundlerのバージョンを確認

補足
・gemを管理するシステムがRubyGems
・RubyGemsはRubyに標準添付されているシステムなのでRubyが使える状態ならすぐに使うことができる。→rubyに同梱(セット)
→同梱のバージョン確認には
$ gem update --system
・bundlerはgemの一種で、gemをデフォルトのRubyGemsより効率よく管理できるツール。まず最初に、RubyGemsを使ってbundlerをインストール
・bundlerを使ってgemをインストール
・BundlerはGemfileをもとにgemをインストールする。
・gemをシステムにインストールするのではなく、ディレクトリ内で管理する。
・そのためにpathオプションをつける。
$ bundle install --path vendor/bundleでpathオプションをつける。このことにより、コマンドの頭にbundle execコマンドをつけて、実行する。
・railsもgemの一種

$ bundle install --path vendor/bundle

※以下のようなbundlerのバージョンエラーが出たら

To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.0.2

$ gem install bundlerで最新のbundlerをインストール
$ rbenv rehash
$ bundle -vでbundlerのバージョン確認
→その後もう一度
$ bundle install --path vendor/bundle

Rails設定

  • 新アプリ名のディレクトリを作成
  • $ cd ~/<ディレクトリ名>コマンドで、アプリディレクトリに移動 $ bundle init →Gemfileを作成

できあがったGemfileのRailsのコメントアウトを外す
gem "rails"
→Gemfileでrailsのバージョンを確認

Railsインストール

$ bundle install --path=vendor/bundle

rails new

$ cd ~/<ディレクトリ名> で、アプリのディレクトリに移動

$ bundle exec rails new . -d mysql --skip-turbolinks --skip-test --skip-coffee
→.はカレントディレクトリを意味する
→gemfileの上書きを聞かれた場合は、yes入力でgemfileを上書き
→うまく行かなければ
$ bundle update

mysqlエラー出た場合
以下をやってみる
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"
$ bundle install

gemのインストール(もう一回)

$ bundle install --path=vendor/bundle

nodeを使う場合(nodeを使わないのであれば飛ばしてください)

nodeのバージョンなどをいじる場合は以下の記事を参照
https://qiita.com/kenkentarou/items/f27bd4e49af6fe429429

nodeとは
関係性
ruby <-> node
rbenv <-> nodebrewやnvm

nodeは一つの言語(node.jsと同義)で、nodeは、言語としても扱うし、サーバーサイドで動くためにコンパイルするシステムとしても使われる

データベースの設定

データベースの設定ファイルを作成

通常の現場では、datebase.yml.defaultというファイルを作成して、設定情報はrootなどセキュリティー上影響ない初期値を入れておき、こちらをgit管理します。

ローカルで作業する際には、datebase.yml.defaultをコピーして、database.ymlというファイルを作成し、自身のPC上の設定に合わせ修正したものを使います。自身の設定に合わせたdatabase.ymlを作成しないと、ローカルでデータベースを走らせることができません。

以下のコマンドを実行し、database.yml.defaultをコピーして、自身の環境に合わせたdatabase.ymlを作成しましょう。

$ cp config/database.yml.default config/database.yml
  • database.ymlの設定を自身の環境に合わせて修正 ユーザー名やパスワードの設定

ローカル上にデータベーステーブル作成

  • $ bundle exec rails db:create →データベーステーブル作成

$bundle exec rails db:migrate
→migrationが必要なファイルがあれば実行

サーバー立ち上げ

  • bundle exec rails sコマンドを実行し、railsサーバーの立ち上げ。

  • localhost:3000にアクセスし、完成形にある画面が表示されることを確認

git flowでの管理の場合

git flowがPCに入っている前提で、

$git flow init
→アプリのディレクトリ全体がgit管理対象になる
→→ローカルにmasterとdevelopがbranchが出来る

プッシュする前に確認すること

.gitignoreに入れておかなければいけないファイル、ディレクトリが無いかどうか

ex)
/vender等

リモートリポジトリにプッシュする

$ git add .
$ git commit -m "inital commit"

$ git remote add origin https://github.com/hoge/hoge.git

$git push --all
→ローカルにあるdevelopとmasterの両方をリモートにあげる

レビューをしやすくするためにrails newの段階でgit flow initをしてmasterとdevelopブランチを自身のパブリックリポジトリに対してプッシュしています!

(PCにgit flow入れてなければ$ brew install git-flow)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Net::HTTPメソッドを使ってBasic認証を突破しよう

こんにちは。

今回、Rubyでgemを使わずにNet::HTTPメソッドを使ってBasic認証を突破し、スクレイピングしました。あまり文献がなかったので、誰かの参考になればと思いこの記事を書いた次第です。

Rubyのバージョンは2.6.5です。

1行1行丁寧に解説していこうと思います。コードだけ知りたい!という方は完成形のコードを参考に実行してみてください。
完成形のコードはこちら↓

scraping.rb
require "net/http"

username, password = "ユーザー名", "パスワード"
uri = URI.parse("スクレイピングしたいページのURL")
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
http_get = Net::HTTP::Get.new(uri.path)
http_get.basic_auth(username, password)
response = https.request(http_get)
response.body

それでは、それぞれ一行ずつ、何をしているのか確認していきます。

net/httpライブラリを読み込む

require “net/http”

net/httpとは、rubyにデフォルトで備わっているライブラリで汎用データ転送プロトコルHTTPを扱うライブラリです。

汎用データ転送プロトコルとは、要するにホームページを構成しているhtmlファイルや画像ファイルなどの「ホームページの部品」を自分のパソコンにデータとしてダウンロードする手順のことです。HTTPという共通の通信規約を定めることで、インターネットを利用する環境が異なっていても、同じ手順でホームページのデータをやり取りすることができます。

以下の記事を参考にしました。
https://cybersecurity-jp.com/security-measures/25772

ユーザー名・パスワードを変数に代入

username, password = “ユーザー名”, “パスワード”

URIを作成

uri = URI.parse(“スクレイピングしたいページのURL")

URIモジュールのparseメソッドを呼び出し、引数にURLを文字列として与えています。parseメソッドは、与えられたURIから該当するURI::Genericのサブクラスのインスタンスを生成して返します。それを変数uriに代入しています。

※URIとは、URL(web上の住所)とURN(we上での名前、シリアルナンバー)の総称です。

Net::HTTPクラスのインスタンスを生成

https = Net::HTTP.new(uri.host, uri.port)

Net::HTTPクラスのnewメソッドを呼び出し、第一引数にuri.hostを、第二引数にuri.portを代入しています。ここで、新しいNet::HTTPクラスのインスタンスを生成しています。

SSL接続を可能にする

https.use_ssl = true

新しく生成したNet::HTTPクラスのインスタンスに対して、use_sslをtrueにします。HTTPSを使う場合は、このコードが必要です。

GETリクエストを得る

http_get = Net::HTTP::Get.new(uri.path)

Net::HTTP::GETクラスのnewメソッドを呼び出し、引数にuri.pathを渡しています。Net::HTTP::GETクラスはHTTPのGETリクエストを表すクラスです。
uri.pathでは、uriのpathを文字列で返しています。pathとは、URLのドメイン名の下にあるものです。

Basic認証を突破したリクエスト送信

http_get.basic_auth(username, password)

http_getはNet::HTTP::GETクラスのインスタンスですが、このクラスはNet::HTTPHeaderクラスを継承しているため、Net::HTTPHeaderクラスのbasic_authメソッドを呼び出すことができます。第一引数にusername、第二引数にpasswordを渡します。
basic_authメソッドは、ヘッダをBASIC認証用にセットするメソッドです。

responseを受け取る

response = https.request(http_get)

responseを受け取ります。正常に接続できている場合は、200が返ってきます。このコードでは、Net::HTTPクラスのインスタンスに対してrequestメソッドを呼び出しており、引数にhttp_getを渡しています。(getリクエスト)

アクセスしたページのHTMLを取得

response.body

responseのクラスはNet::HTTPOKクラスですが、Net::HTTPResponseから継承しているbodyメソッドを呼び出すことができます。このメソッドは、レスポンスのbody、つまりアクセスしたページのhtmlを文字列として返します。

以上になります。間違えている点、補足等ございましたら、是非コメントして頂けると幸いです。

また、参考になったよ!と思った方はいいねをお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RSpec】spec/rails_helper.rbを和訳&補足してみた

はじめに

rails_helperの設定をこんな風にしていますという記事はあるのですが、そのオプションによって何をしているのか今一つわからなかったので、自分用に和訳&補足してみました。
忘れた頃の自分やRSpec初心者のためになればと思います。正確性に関しては自信ないです!

RSpecのバージョンは3.9です。

(2/9追記)編集リクエストを元に修正しました。

導入

$ rails g rspec:install

コマンドを打つと、.rspec, spec/rails_helper.rb, spec/spec_helper.rbファイルが生成されます。
rails_helper.rbはデフォルトではこのようになっています。

spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'

require File.expand_path('../config/environment', __dir__)

# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
end

このコメントアウトされた部分が今回のポイントです。
thisが何を示しているのかだとか、ちょっとした用語を噛み砕けるといいなと思います。

自分なりに和訳&補足してみた

rails generate rspec:installを実行すると、このファイルはspec/ディレクトリにコピーされます。

require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)

本番環境のときにデータベースのTRUNCATE1を防ぎます。

abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'

追加でrequireする場合はこの下に追加してください。この時点までRailsは読み込まれていません!

spec/support/配下のファイルを読み込む設定

カスタムマッチャやマクロ2などを記述したrubyファイルを読み込みたい場合、spec/support/配下に置いてください。

spec/配下にある_spec.rbで終わるファイルはbundle exec rspecコマンドを実行すると自動的に走ります。つまり、これをspec/support/内に置くと、読み込み時とテスト時の二回実行されることになります。
そのため、spec/support/_spec.rbで終わるファイルを置くのはやめましょう。

この様式3はコマンドを打つときに--patternを使うか、~/.rspec, .rspec, .rspec-localで設定できます。

以下の行は手間を省くために用意されています。この行を有効にするとspec/support/以下の全てのファイルが自動的に読み込まれるため、起動に時間がかかるという難点もあります。
代わりの方法として、それぞれの_spec.rbファイルでrequireを使って必要なファイルだけを読み込む方法もあります。

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

マイグレーションの設定

保留にされている4マイグレーションを確認し、テストを走らせる前にマイグレーションを適用します。

ActiveRecordを使わない場合、以下の行(beginからendまで)を削除できます。

begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end

(補足)
自動的にマイグレーションを行い、schemaとマイグレーションファイルに相違がある場合、例外を発生させます。

ActiveRecordのfixtureを使用する設定

もしActiveRecordやActiveRecordのfixtureを使用しない場合はこの行は必要ありません。

  config.fixture_path = "#{::Rails.root}/spec/fixtures"

(補足)例えばFactoryBotを使用する場合が当てはまりますが、他の人の設定を見たところ、わざわざ削除している人は少ないようです。

exampleごとにトランザクションを行う設定

ActiveRecordを使わない、またはトランザクション内で複数のexampleを走らせたい場合は、この行を削除するかオプションをtrueからfalseにしてください。

  config.use_transactional_fixtures = true

(補足)
デフォルトのtrueの場合、exampleごとにトランザクションが行われる設定になっています。つまり、exampleが始まるときにはきれいなデータベースが用意され、終わると全てのデータを削除します。
Database Cleanerなどを使って手動で削除する場合や、特定のSpecでだけトランザクションのロールバックを無効にする場合はfalseにします。

exampleについては以下の説明を参考にしてください。

itはテストをexampleという単位にまとめる役割をします。
it do ... endの中のエクスペクテーション(期待値と実際の値の比較)がすべてパスすれば、そのexampleはパスしたことになります。
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

ファイルの場所に応じた機能を使用する設定

RSpec Railsでは、ファイルの場所に応じた動作を使用することができます。
例えば、spec/controllers配下のファイルでは、getpostを使うことができる仕様となっています。

以下の行を削除するとこの仕様が無効になります。
代わりに、

  RSpec.describe UsersController, type: :controller do
    # ...
  end

type: :controllerのように、明示的にタイプを記述することができます。

利用可能なタイプは、以下のサイトに記述しています。
https://relishapp.com/rspec/rspec-rails/docs

  config.infer_spec_type_from_file_location!

バックトレースをフィルタリングする設定

Railsで読み込まれたgemによるバックトレースをフィルタリングします。

  config.filter_rails_from_backtrace!

特定のgemをフィルタリングする場合、次の設定をしてください。

  config.filter_gems_from_backtrace("gem name")

(補足)
テスト失敗時のノイズを減らすための設定です。
テスト実行時に--backtraceを付けると、フィルタリングされていないバックトレースが表示されます。

(和訳はここまで)

ちなみに

FactoryBotやモジュールを使う場合は、以下のように読み込んでください。

RSpec.configure do |config|
  ...
  # config.filter_gems_from_backtrace("gem name")
  # この下に記述している人が多い印象です

  config.include FactoryBot::Syntax::Methods
  config.include LoginHelpers
end

おわりに

自分で翻訳したり調べたりすると、なんとなくRSpecの仕様への理解が深まった気がします。
また、公式ドキュメントはGoogle翻訳でも読みやすかったです。
間違っていた場合はご指摘をお願いします。


  1. テーブルに格納されているデータを全削除すること 

  2. ヘルパーメソッド 

  3. テスト実行時にどのファイルを実行するか pattern - Configuration - RSpec Core 

  4. db:migrateされていない状態 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今日はクライアントサイドでセッション管理してもいいのか!!(あまりよくない)

これはなに?

セッションをクライアントサイドだけで管理するのは難しいのでやめようね。という話をする機会が稀にあるのですがどういうときに困るんだっけ?またはどういう値はクライアントサイドに持っていいんだっけ?というのを毎回自分で考えるのが面倒なので書きました。
ここではクライアントサイドだけでセッション管理するのが難しい理由について説明します。そのあとに翻って一般的にセッションはどういう性質を持っているのか(あるいは持っているべきか)という話をします。

クライアントサイドだけでやるセッション管理の例

Railsにはセッションストレージとして、CookieStoreというものがあります。説明に関しては
Railsセキュリティガイド: 2.3 セッションストレージ から引用しますが

RailsのCookieStoreはクライアント側のcookieにセッションハッシュを保存します。サーバーはこのセッションハッシュをcookieから取得することで、セッションIDの必要性を解消します。こうすることで、アプリケーションのスピードは著しく向上しますが、このストレージオプションについては議論の余地があるため、セキュリティ上の意味やストレージでの制約について以下の点を十分考えておかなければなりません

例えば、ログインが必要な機能にアクセスするごとに「このユーザはログインしてるんだっけ?」というチェックのためにサーバ側のストレージ(DB/KVSなど)にアクセスが走ると、同時接続数が多いアプリケーションだとそれだけでDBのread負荷が上がる可能性があります。このような時に例えばCookieStoreを使うとリクエストに乗ってくるcookieを見るだけで済むのでサーバサイドの負荷を軽減することができる、という利点があります。
あとは、jwtの中にセッション情報を全部ぶち込んでいるケースとかも同様だと思ってもらって良さそうです。

Cookie Storeを使うと難しいポイント

CookieStoreを使うとパフォーマンス向上してよかったね〜〜って話で終わるかというとそうではなくて、クライアントサイドでcookieを管理しているため前提としてcookieの有効状態を制御することは(少なくともサーバサイドにストレージがある時にくらべて)難しいです。
上で引用した箇所の下にも

  • セッションcookieはひとりでに失効することはないため、悪用目的で使い回される可能性もあります。保存済みのタイムスタンプを利用して古いセッションcookieをアプリケーションで失効させるのもよい方法かもしれません。

という文章があります。

Cookie Storeに関して想定される攻撃例

具体的にどういう攻撃が考えられるか、またその対策については railsguides.jp のRailsセキュリティガイドの中から1つ、別のサイトから1つ紹介します。

再生攻撃

Railsセキュリティガイド: 2.5 CookieStoreセッションに対する再生攻撃 から引用します

再生攻撃のしくみは次のとおりです。
* ユーザーがクレジットを受け取る。総額はセッションに保存されているとする (これはあくまで説明のためのものであり、やってはいけません)。
* ユーザーがクレジットで何かを購入する。
* つかった分減ったクレジットがセッションに保存される。
* ここでユーザーの暗黒面が発動する。最初にブラウザに保存されていたcookieをコピーしてあったものを、現在のブラウザのcookieと差し替える。
* ユーザーのクレジット額が元に戻る。

とあり、これについての対策は

結論から言うと、 この種のデータはセッションではなくデータベースに保存するのが最善です。この場合であれば、クレジットをデータベースに保存し、logged_in_user_idをセッションに保存します。

上の説明にも (これはあくまで説明のためのものであり、やってはいけません)。 と書いてあるように、この例では自明ですが色々なタイミングで「ユーザは任意のタイミングまでセッションを巻き戻せる状態だけどこれセッションに持たせていいんだっけ?」みたいなことを考えていく必要があります。

セッションハイジャックされた時に追い出せないよ問題

また、セッション管理がクライアントサイドで完結しているとログアウト/パスワード変更などに対してセッションを無効化する処理ができないという問題があります。
Rails SessionにCookieStore使った時の問題点 から引用しますが(これの元ネタの記事はどうも消されてしまっているようです)

server-sideではstate管理しないので、当然remoteでセッションの無効化はできません。 つまりログアウトしてもsession cookieのtoken自体は無効化されません。

という話があり、特に困りそうなポイントとしてはその後に書いてある、

問題は、session cookieが無効化できないのに、それが永遠に有効であること。 FBとかY!Jとかでやってる「パスワード変更したら既存のsessionが無効になる」ってのはRailsは一切面倒見てくれないので、パスワード変えても漏洩したsession cookieは有効なままです。 なので、ひとたびsession cookieが漏れたら、完全にアウト。 なにやってもアカウント乗っ取られたまんま。永遠に。

こういうケースです。
で、これはどう対策すればいいの?という話ですが、この記事の中ではセッションにnonceをつければいいじゃないというアイデアが書いています。(これ自体はジャストアイデア的に書かれていて、その後にもっとシンプルで効果的な対策が書かれています)
OpenID Connectの名前にも触れていることからDBにnonceを保存して、セッション中にnonceがあったらDBと照合する、というような実装を想像しました。実はRailsセキュリティガイドの再生攻撃のあたりにもnonceを使えば防げるっちゃ防げるみたいな話は書いてます(この話は後ほど触れます)
そうすると、攻撃者が古いcookieを持ってきても「そのnonceはもうさっき見たので無理で〜す」みたいな感じで弾けるんですが、ここで :thinking: みたいな顔になるポイントがあります。

人類はどうしてcookie storeを使っていたのか

という話に立ち戻るとRailsセキュリティガイドの中に

サーバーはこのセッションハッシュをcookieから取得することで、セッションIDの必要性を解消します。こうすることで、アプリケーションのスピードは著しく向上しますが

ということでクライアントサイドにセッションを完結させるのはレスポンス速度向上のためなんですね(ここが違うとこの後の話もおかしなことになりますが...)
再生攻撃のあたりでnonceって単語が出てるところを見ると

この再生攻撃は、セッションにnonce (1回限りのランダムな値) を含めておくことで防ぐことができます。nonceが有効なのは1回限りであり、サーバーはnonceが有効かどうかを常に追跡し続ける必要があります。複数のアプリケーションサーバーで構成された合いの子アプリケーションの場合、状況はさらに複雑になります。nonceをデータベースに保存してしまうと、せっかくデータベースへのアクセスを避けるために設置したCookieStoreを使う意味がなくなってしまいます。

こういう話があり、個人的にはCookieStoreの利点が薄れる + 実装の複雑度が増すので、nonceを使うというジャッジをするケースは少ないかなと思います

では、パスワードリセットなどの後にセッションを無効化するには?

CookieStore単体では(そして、クライアントサイドに完結したセッションストレージでは)セッションハイジャック対策として行われるセッション無効化ができないということがわかりました。
では、どうすればいいのかというとクライアントサイドでセッションを管理することを諦めてしまうのが一番簡単ではないかと思います。
引用元のサイトでも、このように触れられています(パフォーマンスに関しては、どういうケースでどういうボトルネックがあって、セッションの一部だけをクライアントサイドに逃がすとどういう風に解消しそうか、という話はまた別途議論が待たれる)

もしくはCookieStoreの代わりにMemcacheStore使うようにしてもいいです。 sessionをserver-sideで管理するようにさえすれば、この問題はそもそも発生しないですし。 パフォーマンスに影響しますけど。

セッションってどうあるべき?

ここまで、セッションをクライアントサイドだけで管理すると落とし穴あるよ!そもそもセッションの無効化ができないのである程度複雑な機能が入ってくるとクライアントサイドだけでの管理は無理だよ!って話をしましたが、じゃあお前はセッションをどうしたいの?というと

巻き戻ってもいい値を除いてユーザの状態はサーバサイドで管理すべき
だと思います。
ここでいう巻き戻ってもいい(または巻き戻っても特に変化しない)値の例としては

  • ユーザID/名前
  • ログイン日時

などが考えられます。このあたりをクライアントサイドに持たせてうまくストレージへのアクセス方法を減らすことはできるかもしれません

結論

ということでサーバサイドにセッションのストレージ(DBなりKVSなり)は用意したほうが良いと思います

参考

jwtでセッションを管理するのはやめようね、という話: Stop using JWT for sessions, part 2: Why your solution doesn't work - joepie91's Ramblings

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gemfile version 指定

~>x.x.x version x.x.x以上、x.x.x+1.0 未満

Gemfile
gem 'rails', '~> 5.2.4'
rails 5.2.4以上 5.3未満
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コード書いたことないPdMやPOに捧ぐ、Rails on Dockerハンズオン vol.4 - Static pages -

この記事はなにか?
この記事はが社内のプログラミング未経験者、ビギナー向けに開催しているRuby on Rails on Dockerハンズオンの内容をまとめたものです。ていうかこの記事を基にそのままハンズオンします。ハンズオンは
1回の内容は喋りながらやると大体40~50分くらいになっています。お昼休みに有志でやっているからです。
現在進行形なので週1ペースで記事投稿していけるように頑張ります。
ビギナーの方のお役にたったり、同じように有志のハンズオンをしようとしている人の参考になれば幸いです。

他のハンズオンへのリンク
Vol.1 - Introduction -
Vol.2 - Hello, Rails on Docker -
Vol.3 - Scaffold, RESTful, MVC -
・ Vol.4 - Static pages -

$, #, >について
$: ローカルでコマンドを実行するときは、頭に$をつけています。
#: コンテナの中でコマンドを実行するときは、頭に#をつけています。
>: Rails console内でコマンド(Rubyプログラム)を実行するときは、頭に>をつけています。

はじめに

第4回目は、Static pages(静的なページ)を作ることにチャレンジしてみましょう!
そもそも静的なページとは、アクセスするユーザーや時間帯に関係なく同じコンテンツが表示されるページのことです。対義語は動的なページは例えばマイページのようなユーザーごとにコンテンツが変わってくるようなページのことですね。
静的なページのみで構築されているサイトを『Webサイト』、動的なページも存在するサイトを『Webアプリケーション』と呼び分けたりします。

今回は、静的なトップページを作っていきますよ!

Railsの初期設定

とその前に。
今回は日本で使われ日本で運用するアプリを想定するのですが、Railsはデフォルトでは英語が使われていたりUTC(協定世界時)が使われていたりと、そのまま日本でサービス展開しようとすると面倒な部分があります。
最初にこの設定をローカライズ(日本化)しておきましょう!

Timezoneの設定

Dockerfiledocker-compose.ymlでコンテナのタイムゾーンは日本に設定していましたね(TZ=Asia/Tokyo)。Railsアプリも同じように設定してあげます。

config/application.rb
...
class Application < Rails::Application
  ...
  config.time_zone = 'Tokyo'
  config.active_record.default_timezone = :local
  ...
end

time_zoneはRailsアプリが時間を扱うときにどのtimezoneで動くかを指定する設定でデフォルトだとUTCになってます。例えばTime.currentで現在の時間を表示できたりするのですが、この結果をどのtimezoneで表示するかがこの設定で決まります。この後、Modelを保存したりするときなどもその作成日時などがこのtimezoneで設定されます。

active_record.default_timezoneはDBに書き込まれている時間をどのtimezoneとして扱うかの設定です。

言語の設定

Railsではエラーメッセージなどが準備されているのですが、デフォルトでは英語で定義されています。日本でサービス展開する場合「?」となってしまうので、日本語化します。

config/application.rb
...
class Application < Rails::Application
  ...
  config.i18n.default_locale = :ja
  ...
end

この設定で、config/locales/にあるyamlファイルの中からja:で定義されている文字列が表示されるようになるんです。日本語版のファイルを作ってくださっている方がいらっしゃいますのでベースとして利用させていただきましょう。

rails-i18n/ja.yml at master · svenfuchs/rails-i18n

こちらのファイルをconfig/locales/ja.ymlとして保存することで日本語化完了です!

ちなみに『i18n』は『internationalization』のことで『国際化』と訳されます。アクセスする国によって時間や言語を変えたりすることです(今回は日本オンリーに対応ですが...笑い)。頭の『i』とお尻の『n』の間に18文字あるので『i18n』と表現します。技術系だとこういうの最近多いっすよね?

Bootstrap

まだまだ静的なページの作成には入りませんよ!次は、Bootstrapを使えるようにしていきましょう。

BootstrapはTwitter社が開発したCSSフレームワークです。
レスポンシブデザインに標準で対応されており、CSSだけでなくJavascript(jQuery)も含まれています。
多くの人に使われているためインターネット上に情報があふれていますので初心者にも安心です。一方で、多くの人に使われているため似通ったデザインになってしまうので少し慣れてくると敬遠されがちな印象です。

Boostrapをyarnでインストール

Rails5まではgemでインストールするのが主流だったと思うのですが、Rails6ではWebpackerが必須になったこともありパッケージマネージャー経由でインストールするのが主流になっていくと思われますのでその方法で。

まだコンテナを立ち上げていなかったですね。それではコンテナを立ち上げてコンテナの中でコマンドを実行していきましょう!

$ docker-compose up -d
$ docker-compose exec web ash
# yarn add bootstrap jquery popper.js

これでBootstrap関連のライブラリをインストールできたのでRailsアプリで使えるように設定していきます。

まずapplication.jsでBootstrapを読み込みます。

app/javascript/packs/application.js
...
require("bootstrap")
...

次にBootstrapをSCSSで読み込むためにapplication.cssapplication.scssにリネームします。

# mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss

ファイルの中身はCSSの記法で書かれているものなので一度全て削除して以下のように書き換えましょう。

app/assets/stylesheets/application.scss
@import 'bootstrap/scss/bootstrap';

最後にBootstrapと依存関係にあるjQuerypopper.jsの設定を追加してあげます。

config/webpack/environment.js
const { environment } = require('@rails/webpack')

const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default']
}))

module.exports = environment

やばいですね。たったこれだけでBootstrapの準備が整いました。Bootstrapで用意されているあらゆるスタイルシートやjavascriptが利用可能になっちゃいました。
BootstrapがどんなことができるかはBootstrap公式のDocumentationを見るのが一番いいと思っているのですが、これだけの表現が上の設定でできるようになってしまったのです。

Topページを作成する

いよいよ静的なページを作っていきます。
静的なページではModelのような動的なものはいらないのでMVCのVCがあればOKですね。
VC(ルーティングも!)はrails generate controllerコマンドで作成することができます。今回はstatic_pages controllerでhome actionを作ってみましょう。

# rails generate controller static_pages home

rails generate controller NAME [action action]の形式でコマンドを実行できます。actionは複数指定可能です。
慣習的にNAMEは複数形を用います。

このコマンドだけですでにhttp://localhost:3000/static_pages/homeへのRouting, Controller#Action, Viewが出来上がっているのでアクセスできるようになっていますね。
image.png

このコマンドで作成・更新されたファイルの中身をちょっとみていきましょう!

Routing

config/routes.rb
Rails.application.routes.draw do
  get 'static_pages/home'
end

get 'static_pages/home'の行が追加されてます。前回はresoucesメソッドを使ったルーティングの指定の仕方でしたが、このように一つ一つのルートを定義することもできるのです。
これだけで「static_pages/homeのパスにgetメソッドできたリクエストをstatic_pages controllerのhome actionにルーティングする」ことを定義しています。

Controller

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  def home
  end
end

homeアクションが定義されていますね。特段処理はないので、app/views/static_pages/home.html.erbをレンダリングするのみです。

View

app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>

app/views/static_pages/home.html.erbファイルが生成されています。
中身も先ほどhttp://localhost:3000/static_pages/homeと同じものが表示されていることがわかりますね。

ルートパスをTopページにする

現状ルートパス(http://localhost:3000)にアクセスするとHello worldのページが表示されています。本当はこのURLにアクセスしたときにTopページを表示させたいのでルーティングの設定を更新していきます。

config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
end

ルートパスの設定の仕方は`root '[controller_name]#[action_name]'とするだけです!

試しにhttp://localhost:3000/にアクセスしてみてください。Topページが表示されるようになりましたね。
またhttp://localhost:3000/static_pages/homeにもう一度アクセスしてみましょう。get 'static_pages/home'を削除してるので以下のようにそんなルートないよとエラーになります。
image.png

Bootstrapでページを装飾する

にしても味気ないページですよね。ということでBootstrapのCSSを使いながらいい感じのページに装飾していきましょう!

CSSはHTMLのスタイル(色とか大きさとか)をまとめた変数みたいなものです。HTMLタグ(<h1>とか<p>とか)にclass属性をつけることでそのスタイルが適用されます。SCSSはCSSを書きやすくしたものです(なので本質はCSS)。

<h1 class="hoge">Hello.</h1>
.hoge {
  font-size: 32px;
  color: red;
}

これで32pxの赤文字で"Hello."が表示されるようになるってイメージですね。

Topページのコンテンツを装飾する

ではまず、app/views/static_pages/home.html.erbを更新していい感じのTopページを作っていきましょう。

app/views/static_pages/home.html.erb
<div class="jumbotron mb-0">
  <div class="container text-center">
    <h1>Welcome to Sample App.</h1>
    <h2>Twitterみたいなアプリです。</h2>
    <%= link_to "Sign up now!", "#", class: "btn btn-lg btn-primary mt-5" %>
  </div>
</div>

ここに出てくるjumbotron, container, text-center, btn, btn-lg, btn-primary, mt-5はすべてBootstrapのclassです。Bootstrapのclassの使い方はしっかりと公式のDocumantationがあるのでそちらをみてみましょう。実際に自分で何かを作るとなるとこういう公式ドキュメントと向き合うことが一番の近道だったりするのでここでは説明しないっす。

1つだけ、ERBコードがありますね。

app/views/static_pages/home.html.erb
<%= link_to "Sing up now!", "#", class: "btn btn-lg btn-primary mt-5" %>

link_toはリンクを作るためのメソッドです。HTML的にいえば<a>タグを作ってくれるということです。
link_to [表示文字], [リンク先], [options]の構文になってまして、今回の例だと以下のような<a>タグが出来上がります。

<a href="#" class="btn btn-lg btn-primary mt-5">Sign up now!</a>

リンク文字列やリンク先などに変数を指定でき(例えばルートパスはroot_pathが変数として割り当てられている)るのでERBファイルでリンクを作成する場合多用するコードなので覚えておきましょう。
リンク先として#を指定していますが、まだ遷移先のページを作成していないので仮置きしているだけです(#を指定しておくとリンクを押しても今いるページから遷移しないです。)

編集ができたらトップページをリロードしてみましょう。以下のページになっていれば成功です!
image.png

ふへー。ぽくなってきましたね。

ただHeaderとかFooterとかも欲しくなってきました。

ヘッダーを装飾する

突然ですが、ブラウザでトップページのソースコードをみてみましょう。(右クリックで「ページのソースを表示する」を選択するとみれます。)
app/views/static_pages/home.html.erbに全く書いた記憶がないもの、例えば<head>タグとかがありますよね。

Topページ
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="3702TRDf2wiVe1ErXV1NWoksR1sWw1+LO31miZuonos9K8zOmMwJ+VotF3ZXCPRP+g9IiKoZovoA8HpIjLjqmA==" />


    <link rel="stylesheet" media="all" href="/assets/application.debug-9519f0a34796e622c941135a612890361ca8a44f05d715927aaa1575ce29c235.css" data-turbolinks-track="reload" />
    <script src="/packs/js/application-923746675d9ea6d857bd.js" data-turbolinks-track="reload"></script>
  </head>

  <body>
    <div class="jumbotron">
      <div class="container text-center">
        <h1>Welcome to Sample App.</h1>
        <h2>Twitterみたいなアプリです。</h2>
        <a class="btn btn-lg btn-primary mt-5" href="#">Sign up now!</a>
      </div>
    </div>
  </body>
</html>

これはなんでしょう?:thinking:

実はRailsアプリケーションではViewファイルの中でもLayoutと呼ばれるやつがいます。こいつはなにかというと、ページで共通な部分を表現してくれるものです。デフォルトでapp/views/layouts/application.html.erbのレイアウトファイルが利用されています。ちょっと中を覗いてみましょう。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

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

ERBコードが入っているので少し表現は違いますが、Topページのソースコードと似た構文をしていることがわかります。実は最終的に表示されているHTMLはapp/views/static_pages/home.html.erbがこのapp/views/layouts/application.html.erb<%= yield %>に代入されたものです。

少しだけapp/views/layouts/application.html.erbの中身をご紹介。
まず全体の構文ですが、HTMLの基本の構文が記述されていますね。

<!DOCTYPE html>
<html>
  <head>
    ...
  </head>

  <body>
    ...
  </body>
</html>

<body>の中のyeildについては上で話した通りで、actionのviewファイルの中身が代入されるようになっています。

<head>の中はどうでしょうか?

title

titleはブラウザのタブのところに表示される文字列です。今だとAppと指定されているのでそれが表示されていますね。

scrf_meta_tags

CSRF(Cross-Site Request Forgery)対策のための記述です。今回のTopページではあまり活躍しませんが、POSTリクエストを飛ばすページで活躍します。簡単にいえば、正しいユーザーからのリクエストなのかというリクエストの真正性(Authenticity)を検証するために必要なことを開発者が意識せずともRailsがカバーしてくれるようになるとのこと。

csp_meta_tag

CSP(Content Security Policy)に必要なタグを生成してくれるコードです。CSPはXSS(Cross Site Scripting)を防ぐためのもので、コンテンツの提供元や取得方法を制限する手法です。別途、制限を設定することで動作するようになります。ここでは紹介程度。

stylesheet_link_tag

stylesheetの読み込み。app/assets/stylesheets/application.css[scss]を読み込んでくれている。

javascript_pack_tag

javascriptの読み込み。app/javascript/packs/application.jsを読み込んでくれている。

ってな具合です。

さて、長々と話してしまいましたが、本当に言いたかったことは『ヘッダーやフッターは各ページ個別のものではなく、サイトで共通のものであるはずなので、レイアウトファイルに記述するんだよ』ということです。

では、ヘッダーをapp/views/layouts/application.html.erbに記述します!

app/views/layouts/application.html.erb
...
<body>
  <header class="navbar navbar-dark navbar-expand bg-dark">
    <div class="container">
      <%= link_to "sample app", root_path, class: "navbar-brand" %>
      <ul class="navbar-nav">
        <li class="nav-item"><%= link_to "Home", root_path, class: "nav-link" %></li>
        <li class="nav-item"><%= link_to "Sign in", "#", class: "nav-link" %></li>
      </ul>
    </div>
  </header>

  <%= yield %>
</body>
...

このあたりもBootstrapの公式ドキュメントを参考に記述してみました => Navbar · Bootstrap

Topページにアクセスしてみましょう。以下のようになっていれば成功です!
image.png
おー、どんどんぽくなってきましたね!

フッターを装飾する

最後にフッターも付けちゃいましょう!今回はコピーライトを書いているくらいのフッターをば。

フッターは<footer>タグを使います。

app/views/layouts/application.html.erb
...
<body>
  ...
  <%= yield %>

  <footer class="bg-dark">
    <p class="text-center text-white py-2 mb-0">(c) Hoge Inc. All Rights Reserved.</p>
  </footer>
</body>
...

特別難しいところはありませんね。再度Topページにアクセスします。
image.png
おー、ぽい。ぽいのですが、ブラウザの下の方に余白ができているのが気になりますね...
表示するコンテンツがブラウザの縦サイズに合わない場合は、フッターは画面の最下部に表示されるようにしたい...

ということで少しCSSをいじってみましょう。

app/assets/stylesheets/application.scss
@import 'bootstrap/scss/bootstrap';

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

footer {
  margin-top: auto;
}

本当はfooter用のファイルを作ってappliction.scss@importする方が整理されるのでいいのですが、今回は分量も少ないのでひとまずapplication.scssに記述します。

これで何をしているかというと、まずbodyタグに対してdisplay: flex;をあててます。これはFlexboxという要素をきれいに横並びや縦並びでレイアウトしてくれるレイアウトモジュールです。
flex-direction: column;と定義しているので縦方向に要素を並べてくれます。
そして、min-height: 100vh;を定義しているのでbodyタグで囲まれた要素は最低ブラウザいっぱいの高さを持つ要素に指定されたことになります。

今、bodyタグの中にはheader, <%= yeild %>の中のdiv, footerの3つの要素が同じレベルに存在しています。これを縦方向に並べていることになるんですね。
この時、Flexboxの親要素(今回だとbody)をコンテナ、子要素(今回だとheader, div, footer)をアイテムと呼びます。

footerにはmargin-top: auto;が定義されています。これは親要素に対して、この要素の上部に最大限のmargin(余白)を付与することを表していて、簡単に言うと親要素の一番下に配置するってことになります。
今、親要素のbodyは縦に最小で100vhの高さを持ちます。コンテンツが足りない場合はbodyは100vhの高さになるのでfooterは100vhの一番下に位置します。コンテンツが100vh以上の場合はbodyheader, div, footerの3要素の高さの合計が高さになるので、footerは自然な形でdivのすぐ下に配置されることになります。

さてさて、ではTopページをリロードしてみてください!
image.png
とてもいい感じになりましたね!

スマートフォンに対応する

PCだといい感じに表示されるのですが、スマートフォンだと実はとても見にくい状態になっています。ChromeのDeveloper toolsなどで確かめてみてください(Chrome DevTools での Device Mode によるモバイル端末のシミュレート

BootstrapはもともとResponsive Design(ブラウザの横幅に合わせてCSSが変わる)に対応しているので、Viewport設定の1行をheadタグ内に挿入するだけでいい感じのデザインになります。

app/views/layouts/application.html.erb
...
<head>
  ...
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  ...
</head>
...

呪文みたいな感じで書いちゃうことが多いのですが、意味は「もう逃げない。HTMLのviewportをちゃんと理解する - Qiita」の記事が参考になりました。

スマホモードでリロードしてみましょう。
image.png
文字の大きさもいい感じになりましたね。

Google Fontsでフォントを変える

んー。デザインはよくなってきたけどフォントがデフォルトっぽくて気になるなぁ...
フォントはこのアプリを使うユーザーの端末にフォントがインストールされていないと使えなかったりするのであんまり冒険できないところだったりします。アプリケーションの中でフォントを配布してもいいのですが、まぁ若干面倒ですね。
そこで便利なのがGoogle Fontsです。『Webフォント』と呼ばれますが、インターネット上ですでに公開されているため端末のインストールなしで利用でき、どの端末からでも同じフォントで表現ができるようになります。

app/assets/stylesheets/application.scssでGoogle Fontsの中からNoto Sans JPをインポートしてbodyfont-familyに指定するだけなんですね簡単です。

app/assets/stylesheets/application.scss
@import 'bootstrap/scss/bootstrap';
// Google Fontsから"Noto Sans JP"をインポート
@import url('https://fonts.googleapis.com/css?family=Noto+Sans+JP&display=swap');

body {
  // "Noto Sans JP"を全体のデフォルトフォントに指定
  font-family: 'Noto Sans JP', sans-serif!important;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

footer {
  margin-top: auto;
}

少し特別な書き方として、font-familyのところで!importantと末尾に記述してます。
!importantはCSSの適用優先度を決めるための記述で、最優先で反映させるものに使います。つまり、他にfont-familyを記述したとしてもこいつが適用されるってことです。
Bootstrapでもfont-familyを定義してくれていたりするので、こっちを最優先にするために記述してます。

Google Fontsのサイトでお好きなフォントを探してみて適用してみてください!日本語フォントの場合は、『Language』で『Japanese』を選択すれば日本語フォントありに絞られます。『+』で使用フォントを追加するとインポートの仕方とかも教えてくれるので気軽に使えます。もっと詳しくはこちらの記事から↓
【2019年版】Google Fontsの使い方:初心者向けに解説!

まとめ

今回は、Railsの初期設定とBootstrapのインストール、Topページのデザインをやってみました。
静的なページではありますが、コーディングした内容が反映されてどんどん変わっていくのを実感する楽しみを感じてもらえたんじゃないかと思います。

次回は今回触れていなかったModelが主役です。Modelの作成からModelに用意されているメソッドを使ってデータの作成や更新を遊んでみようと思います。

では、次回も乞うご期待!ここまでお読みいただきありがとうございました!

Reference

P.S. 間違っているところ、抜けているところ、説明の仕方を変えるとよりわかりやすくなるところなどありましたら、優しくアドバイスいただけると助かります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Ajaxを用いた非同期投稿機能といいね機能の実装

実装すること

Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。 
①新規の投稿が新着投稿一覧の一番前に画面のリロードなしで表示される
②いいねのハートの色といいね数を画面のリロードなしで変更する

ER図

User:Item = 1:N
Item:Like = 1:N
User:Like = 1:N
LikeテーブルがItemとUserの中間テーブルになります。
いいね非同期.png

ページ設計

・[items/index]投稿一覧でいいねができる
・[items/show]投稿詳細でいいねができる

モデルの作成

今回はUserモデル、Itemモデル、Likeモデルを作成します。
Userモデルはdeviseを使用してモデルを作成していきます。

deviseとjQueryの導入・Userモデルの作成

Gemfile.
gem 'devise'
gem 'jquery-rails'
$ bundle install
$ rails g devise:install
$ rails g devise user
$ rails g devise:views

Itemモデル・Likeモデルの作成

$ rails g model item title:string image_id:string user_id:integer
$ rails g model like item_id:integer user_id:integer

アソシエーションの確認

Userモデル

userはそれぞれたくさんのitemを投稿をすることができ、投稿にいいねができるというイメージです。
dependent: :destroyは、itemがuserに依存していることから、userが消えればitemも消えるようにするためです。

app/models/user.rb
class User < ApplicationRecord
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable

 has_many :items, dependent: :destroy
 has_many :likes, dependent: :destroy

 #既にいいねしているかどうか
 def already_liked?(item)
   self.likes.exists?(item_id: item.id)
 end
end

Itemモデル

app/models/item.rb
class Item < ApplicationRecord
 belongs_to :user
 has_many :likes, dependent: :destroy
end

Likeモデル

validates_uniqueness_ofによって、item_idとuser_idの組が1組しかないようにバリデーションをかけます。

app/models/like.rb
class Like < ApplicationRecord
 belongs_to :item
 belongs_to :user

 validates_uniqueness_of :item_id, scope: :user_id
end

コントローラーの作成

$ rails g controller users
$ rails g controller items index show
$ rails g controller likes

ルーティングの作成

投稿一覧、投稿詳細、投稿作成、投稿へのいいね、いいねの取り消しができるようにしていきます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  root 'items#index'

  resources :users
  resources :items, only: [:index, :show, :create] do
    resource :likes, only: [:create, :destroy]
  end

end

コントローラーとビューの編集(新規投稿・投稿一覧・投稿詳細)

[新規投稿]非同期で投稿一覧の新着順の一番最新に表示されるようにする
[投稿一覧]新着順で8投稿だけ表示されるようにする

items_controller.rb

app/controllers/items_controller.rb
class ItemsController < ApplicationController

   def new
        @item = Item.new
   end

   def create
        @item = Item.new(item_params)
          if @item.save
               @items = Item.order(created_at: :desc).limit(8)
               render :create
          else
               render :new
          end
        end
    end

   def index
        #新着順
        @items = Item.order(created_at: :desc).limit(8)
    end

   def show
        @item = Item.find(params[:id])
    end

    private
    def item_params
        params.require(:item).permit(:title, :user_id)
    end
end

items/index.html.erb

投稿部分はパーシャルにします。

app/views/items/index.html
  <div id="item_index_new">
    <%= render 'item_new', items: @items %>
  </div>

items/_item_new.html.erb

いいね部分はパーシャルにします。
id="index_like_<%= item.id %>" この部分で投稿それぞれに一位な値を付与しています。

app/views/items/_item_new.html
<h2>新着投稿</h2>
     <% items.each do |item| %>
        <%= link_to item_path(item) do %>
            <%= attachment_image_tag item, :image, size: "190x190", class: "coffee_image_media" %>
        <% end %>
        <%= link_to user_path(item.user_id) do %>
            <%= attachment_image_tag item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "30x30" %>
            <%= item.user.name %>
        <% end %>
        <div id="index_like_<%= item.id %>">
            <%= render 'likes/like', item: item %>
        </div>
     <% end %>

items/show.html.erb

いいね部分はパーシャルにします。
id="show_like_<%= item.id %>" この部分で投稿それぞれに一位な値を付与しています。

app/views/items/show.html
<h1>投稿詳細ページ</h1>
<%= attachment_image_tag @item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "100x100" %>
<%= @item.user.name %>

<div id="show_like_<%= @item.id %>">
  <%= render 'likes/like', item: @item %>
</div>

items/new.html.erb

hidden_fieldでuser_idにcurrent_user.idを代入しています。

app/views/items/new.html
<%= form_for(@item, url: items_path, remote: true) do |f| %>
  <p>画像投稿</p>
        <%= f.attachment_field :image %>
  <p>タイトル</p>
        <%= f.text_field :name, class: "form-control" %
        <%= f.hidden_field :user_id, :value => current_user.id %>
        <%= f.submit "保存", class:"form-control" %>
<% end %>

items/create.js.erb

id=item_index_newの一番前に追加します(後ろに追加するときはappend/全体の差し替えはhtml)
items_controller.rbのcreateアクションが呼ばれた時は、views/items/create.js.erbが呼ばれます。他のコントローラの時も同様で、likes_controller.rbのindexアクションが呼ばれた時は、views/items/index.js.erbが呼ばれます。
※ファイルがあれば呼び出されます。

app/views/items/create.js
$("#item_index_new").html("<%= escape_javascript(render 'users/items/item_new', items: @items) %>")

コントローラとビューの編集(いいね)

likes_controller.rb

app/controllers/likes_controller.rb
class LikesController < ApplicationController

  before_action :item_params
  def create
      like = current_user.likes.new(item_id: @item.id)
      like.save
  end

  def destroy
    @like = Like.find_by(user_id: current_user.id, item_id: @item.id).destroy
  end

  private
  def item_params
    @item = Item.find(params[:item_id])
  end

end

likes/_like.html.erb

・今回のいいね処理ではすでにあるパーシャルを切り替えるだけなので、何も返さないが、表示は切り替えます。切り替えるのにjsを使います。
if user_signed_in?で、もしユーザーがログインしていなければ、ハートが表示はされるがいいねはできないようにしています。
already_liked?で、Userモデルのメソッドを呼び出し、既にいいねしているかどうかを確認しています。
remote: trueで、リンクを非同期化しています。
item.likes.countで、いいね数を取っています。
fa fa-heartは、いいねの表示にfontawesomeを使用しています。

app/views/likes/_like.html
<% if user_signed_in? %>
    <% if current_user.already_liked?(item) %>
        <%= link_to item_likes_path(item), method: :delete, remote: true do %>
            <i class="fa fa-heart" aria-hidden="true" style="color: red;">
                <%= item.likes.count %>
            </i>
        <% end %>
    <% else %>
        <%= link_to users_item_likes_path(item), method: :post, remote: true do %>
            <i class="fa fa-heart" aria-hidden="true" style="color: #C0C0C0;">
                <%= item.likes.count %>
            </i>
        <% end %>
    <% end %>
<% else %>
    <i class="fa fa-heart" aria-hidden="true">
            <%= item.likes.count %>
    </i>
<% end %>

likes/create.js.erb

いいねをする

app/views/likes/create.js
//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");

likes/destroy.js.erb

いいねを取り消す

app/views/likes/destroy.js
//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show/like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");

最後に

最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装

実装すること

Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。 
①新規の投稿が新着投稿一覧の一番前に画面のリロードなしで表示される
②いいねのハートの色といいね数を画面のリロードなしで変更する

ER図

User:Item = 1:N
Item:Like = 1:N
User:Like = 1:N
LikeテーブルがItemとUserの中間テーブルになります。
いいね非同期.png

ページ設計

・[items/index]投稿一覧でいいねができる
・[items/show]投稿詳細でいいねができる

モデルの作成

今回はUserモデル、Itemモデル、Likeモデルを作成します。
Userモデルはdeviseを使用してモデルを作成していきます。

devise・jQuery・refile,refile-mini_magickの導入・Userモデルの作成

Gemfile.
gem 'devise'
gem 'jquery-rails'
gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick"
$ bundle install
$ rails g devise:install
$ rails g devise user
$ rails g devise:views

Itemモデル・Likeモデルの作成

$ rails g model item title:string image_id:string user_id:integer
$ rails g model like item_id:integer user_id:integer

アソシエーションの確認

Userモデル

userはそれぞれたくさんのitemを投稿をすることができ、投稿にいいねができるというイメージです。
dependent: :destroyは、itemがuserに依存していることから、userが消えればitemも消えるようにするためです。

app/models/user.rb
class User < ApplicationRecord
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable

 has_many :items, dependent: :destroy
 has_many :likes, dependent: :destroy

 #既にいいねしているかどうか
 def already_liked?(item)
   self.likes.exists?(item_id: item.id)
 end
end

Itemモデル

app/models/item.rb
class Item < ApplicationRecord
 belongs_to :user
 has_many :likes, dependent: :destroy
 #refile
 attachment :image
end

Likeモデル

validates_uniqueness_ofによって、item_idとuser_idの組が1組しかないようにバリデーションをかけます。

app/models/like.rb
class Like < ApplicationRecord
 belongs_to :item
 belongs_to :user

 validates_uniqueness_of :item_id, scope: :user_id
end

コントローラーの作成

$ rails g controller users
$ rails g controller items index show
$ rails g controller likes

ルーティングの作成

投稿一覧、投稿詳細、投稿作成、投稿へのいいね、いいねの取り消しができるようにしていきます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  root 'items#index'

  resources :users
  resources :items, only: [:index, :show, :create] do
    resource :likes, only: [:create, :destroy]
  end

end

コントローラーとビューの編集(新規投稿・投稿一覧・投稿詳細)

[新規投稿]非同期で投稿一覧の新着順の一番最新に表示されるようにする
[投稿一覧]新着順で8投稿だけ表示されるようにする

items_controller.rb

app/controllers/items_controller.rb
class ItemsController < ApplicationController

   def new
        @item = Item.new
   end

   def create
        @item = Item.new(item_params)
          if @item.save
               @items = Item.order(created_at: :desc).limit(8)
               render :create
          else
               render :new
          end
        end
    end

   def index
        #新着順
        @items = Item.order(created_at: :desc).limit(8)
    end

   def show
        @item = Item.find(params[:id])
    end

    private
    def item_params
        params.require(:item).permit(:title, :user_id)
    end
end

items/index.html.erb

投稿部分はパーシャルにします。

app/views/items/index.html
  <div id="item_index_new">
    <%= render 'item_new', items: @items %>
  </div>

items/_item_new.html.erb

いいね部分はパーシャルにします。
id="index_like_<%= item.id %>" この部分で投稿それぞれに一位な値を付与しています。

app/views/items/_item_new.html
<h2>新着投稿</h2>
     <% items.each do |item| %>
        <%= link_to item_path(item) do %>
            <%= attachment_image_tag item, :image, size: "190x190", class: "coffee_image_media" %>
        <% end %>
        <%= link_to user_path(item.user_id) do %>
            <%= attachment_image_tag item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "30x30" %>
            <%= item.user.name %>
        <% end %>
        <div id="index_like_<%= item.id %>">
            <%= render 'likes/like', item: item %>
        </div>
     <% end %>

items/show.html.erb

いいね部分はパーシャルにします。
id="show_like_<%= item.id %>" この部分で投稿それぞれに一位な値を付与しています。

app/views/items/show.html
<h1>投稿詳細ページ</h1>
<%= attachment_image_tag @item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "100x100" %>
<%= @item.user.name %>

<div id="show_like_<%= @item.id %>">
  <%= render 'likes/like', item: @item %>
</div>

items/new.html.erb

hidden_fieldでuser_idにcurrent_user.idを代入しています。

app/views/items/new.html
<%= form_for(@item, url: items_path, remote: true) do |f| %>
  <p>画像投稿</p>
        <%= f.attachment_field :image %>
  <p>タイトル</p>
        <%= f.text_field :name, class: "form-control" %
        <%= f.hidden_field :user_id, :value => current_user.id %>
        <%= f.submit "保存", class:"form-control" %>
<% end %>

items/create.js.erb

id=item_index_newの一番前に追加されるように、内容を差し替えます。
items_controller.rbのcreateアクションが呼ばれた時は、views/items/create.js.erbが呼ばれます。他のコントローラの時も同様で、likes_controller.rbのindexアクションが呼ばれた時は、views/items/index.js.erbが呼ばれます。
※ファイルがあれば呼び出されます。

app/views/items/create.js
$("#item_index_new").html("<%= escape_javascript(render 'items/item_new', items: @items) %>")

コントローラとビューの編集(いいね)

likes_controller.rb

app/controllers/likes_controller.rb
class LikesController < ApplicationController

  before_action :item_params
  def create
      like = current_user.likes.new(item_id: @item.id)
      like.save
  end

  def destroy
    @like = Like.find_by(user_id: current_user.id, item_id: @item.id).destroy
  end

  private
  def item_params
    @item = Item.find(params[:item_id])
  end

end

likes/_like.html.erb

・今回のいいね処理ではすでにあるパーシャルを切り替えるだけなので、何も返さないが、表示は切り替えます。切り替えるのにjsを使います。
if user_signed_in?で、もしユーザーがログインしていなければ、ハートが表示はされるがいいねはできないようにしています。
already_liked?で、Userモデルのメソッドを呼び出し、既にいいねしているかどうかを確認しています。
remote: trueで、リンクを非同期化しています。
item.likes.countで、いいね数を取っています。
fa fa-heartは、いいねの表示にfontawesomeを使用しています。

app/views/likes/_like.html
<% if user_signed_in? %>
    <% if current_user.already_liked?(item) %>
        <%= link_to item_likes_path(item), method: :delete, remote: true do %>
            <i class="fa fa-heart" aria-hidden="true" style="color: red;">
                <%= item.likes.count %>
            </i>
        <% end %>
    <% else %>
        <%= link_to users_item_likes_path(item), method: :post, remote: true do %>
            <i class="fa fa-heart" aria-hidden="true" style="color: #C0C0C0;">
                <%= item.likes.count %>
            </i>
        <% end %>
    <% end %>
<% else %>
    <i class="fa fa-heart" aria-hidden="true">
            <%= item.likes.count %>
    </i>
<% end %>

likes/create.js.erb

いいねをする

app/views/likes/create.js
//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");

likes/destroy.js.erb

いいねを取り消す

app/views/likes/destroy.js
//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show/like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");

最後に

最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mysql2::Error at /auth/twitter/callback Incorrect string value: 【Rails】

Mysql2::Error at /auth/twitter/callback Incorrect string value:

MtSQLのエラーで、保存する値に絵文字:cake::cake::cake:が含まれていると起きる。

絵文字を扱うことが出来る文字コードはutf8mb4だがデータベースに設定されていた文字コードはutf8だった。それが原因。

MySQLに設定された文字コードを調べる

$ show variables like "chara%";

文字コードを変更する

MySQLのファイルはルートディレクトリの/etc/my.cnfにある。

$ vim my.cnf

以下のようにutf8utf8mb4に変更と追記。

[mysqld]
(省略)
character-set-server=utf8mb4

[client]
default-character-set=utf8mb4

DBの再起動

$ systemctl restart mysqld.service

RailsAppのdatabase.ymlを編集

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: 5
  username: root
  password: <%= ENV['MYSQL2_PASSWORD'] %>
  host: localhost

encoding:の部分をutf8mb4に変更する。

データベースの作り直し

私は既存のデータベースの文字コードを変更したので、データベースも今一度作り直す必要があった。

$ rails db:drop
$ rails db:create
$ rails db:migrate

当然ではあるが、格納していたデータはすべて消える。マイグレーションファイルは残っているためrails db:migrateすればモデルは直ぐに作り直せる。

参考

https://babiy3104.hateblo.jp/entry/2014/02/13/000219

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails レイアウトテンプレート

アプリケーションのビューファイルの共通部分をまとめたもの。

yield メソッド

bodyタグ(

...)の間に、<%= yield %>と記載がある。この記述があることによって、「ここに全てのビューファイルが集約される」という仕組みができる。
a.png

a.png
上記のようにapplication.html.erbに記述
そうするとOur Blogと新規投稿のヘッダー表示はどの画面でも共通。

リセットCSS

CSSファイルはapp/assets/stylesheets/というディレクトリに配置する。application.html.erbのstylesheet_link_tagの部分にapplicationと書いてあるので、このHTMLからはapp/assets/stylesheets/application.cssというCSSファイルを読み込むことを示す。

application.cssを開く

a.png

require_tree

引数として与えられたディレクトリ以下のCSSファイルをアルファベット順に全て読み込む。引数.(ドット)はカレントディレクトリを表す。
この記述によってapp/assets/stylesheetsというディレクトリにあるCSSファイルやSCSSファイルは全て読み込まれることになる。
a.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails マイグレーションファイルの変更

今回はスペルミスしたと仮定
a.png
実際にDBを開くと
a.png

修正するには

rails db:rollback コマンドを実行する。
流れとしてはロールバック→修正→マイグレート。

マイグレーションがすでにマイグレート済みかどうか

rails db:migrate:statusで調べれる。upなら適用、downなら適用されていないので修正可能。
修正したら再びマイグレーションを実行
rails db:migrate

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RESTを分解してみた

はじめに

Railsチュートリアルをやっていて
「セッションをRESTfulなリソースとしてモデリングできると、他のRESTfulリソースと統一的に理解できて便利です。」
みたいな文章があってRESTとかRESTfulとかの意味を調べてもしっくりこなかったので自分なりにRESTを噛み砕いてアウトプットしようと思います。

結論

まずはじめに結論から述べると、

「クライアントとサーバー間で行われる送受信は、決められた形式(HTTP)で行われている。ということを表現したもの。」

という自分なりの解釈になりました。

経緯

上記の結論に至った経緯を書いて行きます。
まずは自分なりにREST(Representational State Transfer)を英訳してみました。

Google翻訳にてシンプルに『Representational State Transfer』と翻訳
↓↓
結果「代表的な状態の移転」
というなんのこっちゃ分からない表現に。。。

そこで一つ一つを分解して英訳を実行。
①Representational→代表的な、具象の(はっきりした姿、形)
②State→状態
③Transfer→移送、移転(送受信のやりとりをあらわしてる??)

つなぎ合わせると
『はっきりした形式での送受信』

『決められた形式(HTTP)で送受信のやりとりをするシステムのこと??』

  • HTTPについてはざっくりいうと通信するときのお約束事です。詳細は調べてみてください。
  • ちなみに送受信はクライアントとサーバーとの間で行われることを指します。

以上の結果から
RESTってクライアントとサーバー間で行われる送受信が決められた形式(HTTP)で行われていることを表しているのかなあと。

最後に

ちなみにRESTの由来はFieldingが大学院のときに発表した論文内での造語であるらしい。
造語ってことは正式な定義みたいなのは存在しないのかな?

詳しいかた、知ってるよ!なかた、アドバイスや指摘していただけると嬉しいです。
以上となります。ではまた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】param is missing or the value is empty:について

問題箇所のコード

controller.rb
  def create
    @collect = Collect.create(collect_params)
    if @collect.save
      redirect_to "/group/show/#{@collect.group_id}"
    else
      render "new"
    end
  end
(省略)
  private
  def collect_params
    params.require(:collect).permit(:url, :group_id)
  end

主にcollect_paramsが悪いはず。

param is missing or the value is empty:

意味は単純にparamsが存在しないか空ですとなる。

パラメータを確認するとこんな感じ。ちゃんと情報渡せてるように見えるけどfalseになってる。

<ActionController::Parameters {"authenticity_token"=>"Ze5qfbN7z11+vpQ0BRGuCOV8U6m9e/aH6B7QuXFkJ13sXD66fpls3yxpB8fxymNI3XqOR6KiBmemdPRC++aVTw==", "url"=>"https://(省略)", "group_id"=>"5", "commit"=>"Save ", "controller"=>"collect", "action"=>"create"} permitted: false>

原因

params.require(:collect)の部分が不要なんだと思う。
(もともとCollectテーブルに保存されるものとして送信されているのに、params.require(:collect)と書くとだぶるのかなあ)

解決

controller.rb
  private
  def collect_params
    params.permit(:url, :group_id)
  end

params.require(:collect)を削除した。参考は下記のURL

https://stackoverflow.com/questions/47391168/param-is-missing-or-the-value-is-empty-in-nested-resource-method

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacでDocker+Rails+MySQL環境構築手順メモ

勉強するときに環境構築にかなりつまづいたので、その手順をメモしときたいと思います。

環境構築手順

前提として、rails開発用のディレクトリが用意されているものとします。

以下の手順に従ってファイル作成やコマンドを実行していけば問題なく手順は整うと思います。

Dockerfileの作成(以下をコピペ)

# rubyはお好みのバージョンで(ローカルのバージョンがいいと思います)

FROM ruby:2.6.3

# 必要なパッケージのインストール(基本的に必要になってくるものだと思うので削らないこと)

RUN apt-get update -qq && \

    apt-get install -y build-essential \

                       libpq-dev \        

                       nodejs           


# 作業ディレクトリの作成、設定

RUN mkdir /app_name

##作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照

ENV APP_ROOT /app_name

WORKDIR $APP_ROOT


# ホスト側(ローカル)のGemfileを追加する

ADD ./Gemfile $APP_ROOT/Gemfile

ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock


# Gemfileのbundle install

RUN bundle install

ADD . $APP_ROOT

Gemfileの作成(以下をコピペ)

source 'https://rubygems.org'

#好きなバージョンを指定

gem 'rails', '5.2.2'

空のGemfile.lockの作成

何も書かなくていいです。

docker-compose.ymlを作成(以下をコピペ)

docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.0.17
    command: mysqld --default-authentication-plugin=mysql_native_password
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"

  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db

ターミナルでrails newを実行

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

上のコマンドを打ってしばらく待つ

database.ymlを修正(一度全て消してから以下をコピペ)

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
  password: <%= ENV.fetch("MYSQL_PASSWORD", "root") %>
  host: <%= ENV.fetch("MYSQL_HOST", "db") %>

development:
  <<: *default
  database: app_name_development

test:
  <<: *default
  database: app_name_test

production:
  <<: *default
  database: app_name_production
  username: app_name
  password: <%= ENV['APP_NAME_DATABASE_PASSWORD'] %>

ターミナルでDockerを起動

docker-compose build

しばらく待つ。できたら以下を実行。

docker-compose up

DB作成

別のターミナルを開いて以下を実行

docker-compose run web rails db:create

ブラウザでlocalhost:3000にアクセス

サーバーが起動してたらおk。

サーバーを止める

docker-compose down

サーバーを再起動

docker-compose up

まとめ

これでrailsアプリ作成の準備が整うはずです。
環境構築は完全に初見ごろしだと思うので誰かの助けに慣れたら嬉しいです。
また、何かおかしい点とかあれば言って頂けるとありがたいです。

お付き合い頂きありがとうございました。

参考サイト

https://qiita.com/azul915/items/5b7063cbc80192343fc0

https://qiita.com/take18k_tech/items/ada21511c551bcfe0b8c

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

外部キー作成の時の注意事項

テーマ「データベース 頭文字は大文字か小文字か」

この投稿はRailsのデータべース作成に関する記事です。
データベース周辺は間違えると後のアプリ開発に悪い影響を与えてしまいます。
このミスを参考にみなさんは出来る限りデータベース周辺では間違わないようにしてください!

今回の主犯

外部キーの頭文字を大文字にしてしまったため、seed.rbからデータを入力できなくなった。
実物はこちらです。Faculity_id←これ。。。本来はfaculity_idとなるべきはず

scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

ミスの原因

1ヶ月ぶりにRailsを触り、フレームワークの使い方を忘れてしまったこと:sob:
(初心者は毎日触らなけれぼなりませんね。思い知りました?)

ターミナルで入力した間違いのコマンド

referencesの前を大文字にしてしまったため、外部キーも大文字になった。

terminal.
$ rails g migration AddFaculityToMajors Faculity:references

本来は$ rails g migration Add(頭文字大カラム名)To(頭文字大テーブル名) (小文字カラム名):references
というコマンドの構成です。(下記のコマンドが正しい)

terminal.
$ rails g migration AddFaculityToMajors faculity:references

seed.rbからデータを入力すると。。。

seed.rbからデータが送れない。外部キーが合わないため当然ちゃ当然ですね。

scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

こちらは小文字なので、キーが探せずrollbackされてしまった。

seed.rb
Major.create!(id: 1, name: '経営', faculity_id: 1)

ここでもう1つ問題が。。。Faculity_id(主犯)が消えない:cry:

Faculity_idの外部キーを削除するために、削除コマンドを試したが、、、

terminal.
$ rails g migration RemoveFaculity_idFromMajor faculity:references                                                            
Running via Spring preloader in process 8806
      invoke  active_record
      create    db/migrate/20200207160003_remove_faculity_id_from_major.rb
$ rails db:migrate
== 20200207160003 RemoveFaculityIdFromMajor: migrating ========================
-- remove_reference(:majors, :faculity, {:foreign_key=>true})
   -> 0.0078s
== 20200207160003 RemoveFaculityIdFromMajor: migrated (0.0083s) ===============
scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

依然として、Faculity_idがある。なぜーーーーー:sob:(ぴえーん)

もうこれはテーブル丸ごと消してしまおう!!!!!(テーブル削除に初挑戦)

削除コマンドはこちらを参考にしました。
[Rails]不要になったmodelの削除方法(https://bokuranotameno.com/post-9880/)

terminal.
$ rails g migration drop_table_(テーブル名複数形)

migrationファイルに削除するテーブル名を記述し$ rails db:migrateを実行します。

20200207073446_○○_majors.rb
def change
    drop_table :majors(テーブル名複数形)
end

一連のコマンドの流れ

terminal.
$ rails g migration drop_table_majors
Running via Spring preloader in process 9120
      invoke  active_record
      create    db/migrate/20200207160634_drop_table_majors.rb
$ rails db:migrate    
== 20200207160634 DropTableMajors: migrating ============================== 
== 20200207160634 DropTableMajors: migrating ==================================
-- drop_table(:majors)
   -> 0.0020s

これでようやくFaculity_idは削除されました!!!(テーブルごと消えてしまったけど(笑))

modelの関連ファイルも削除します。
・modelファイルの削除 app/models/major.rb
・testファイルの削除 test/models/major_test.rb
・fixturesファイルの削除 test/fixtures/majors.yml

新しいmodelを作成しよう!だがまた新たな問題が、、、

terminal.
$ rails g model Major name:string faculity:references
Running via Spring preloader in process 9606
      invoke  active_record
    conflict    db/migrate/20200207161950_create_majors.rb
Another migration is already named create_majors: /home/ec2-user/environment/review/db/migrate/20200207072743_create_majors.rb. Use --force to replace this migration or --skip to ignore conflicted file.

どうやらmigrateフォルダ内にある前回Major modelを作成したmigrationファイルが残っていることが原因

migrateフォルダからmigrationファイルを削除すると、herokuにアップした時などデータベースが上手く作られない可能性があるが、新しくmodelを作成するので大丈夫だと信じる:angel:

migrationファイル削除後、もう一度試す!!!

$ rails g model Major name:string faculity:references                                                               
Running via Spring preloader in process 10134
      invoke  active_record
      create    db/migrate/20200207162947_create_majors.rb
      create    app/models/major.rb
      invoke    test_unit
      create      test/models/major_test.rb
      create      test/fixtures/majors.yml
$ rails db:migrate
== 20200207162947 CreateMajors: migrating =====================================
-- create_table(:majors)
   -> 0.0034s
== 20200207162947 CreateMajors: migrated (0.0040s) ============================

無事できた!!!

seedファイルも大丈夫かどうか確かめる。

terminal.
$ rails db:seed

こちらも無事エラーが出ない!!!!!

(教訓)データベースを作成する時大文字小文字に注意

モデルの作成は大文字

terminal.
$ rails g model User(大文字)

カラムの作成は小文字

terminal.
$ rails g migration AddPostToUser user:references(小文字)

皆さんも、これらの項目に注意をして、楽しくアプリ開発してください!!!
これが参考にされば幸いです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】mechanizeを使えばrailsスクレイピングが余裕な件

スクレイピングに初挑戦したとき、検索上位にあがるサイトはどれもnokogiriを使用するのですが、dockerだとうまく利用できず四苦八苦していました。

その中で見つけたのがmechanizeというgemです。

自分用メモのためにも今回はコードを書いときたいと思います。

前提

railsアプリは作成済み
gem mechanize インストール済み

Controller

controllerでは.getメソッドとsearchメソッドを使ってページ情報を取得します。

def home

  agent = Mechanize.new

  page = agent.get("https://nemlog.nem.social/")

  @elements= page.search('.visit-w')

end

あとはインスタンス変数@elementsを利用してviewに表示するだけです。

View

<%@elements.each do|element|%>
  <ul>
    <li>
      <%=link_to(element.inner_text,element[:href])%>
    </li>
  </ul>
<%end%>

.inner_textはテキスト部分だけ抜き出してくれます。
また、element[:href]は指定したhtml(この場合はclass=visit-w)の属性を抜き出します。

まとめ

結論、mechanizeは神ですね。
スクレイピングをお手軽に試してみたい人はぜひ使ってみてください。

おまけ

これを利用して簡単なnemlolgのスクレイピング サイトを作りました。
コードも確認できるので参考になればと思います。

github↓
https://github.com/soehina/searchnemlog

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rubocopで複数行の引数の最後の値にカンマを付ける

複数行の配列や引数を書くとき

最後にカンマを付けない派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password'
)

rubocop -aで自動整形すると、デフォルトでは上記のように整形されます。
ruby以外の言語だと、引数や配列の最後にはカンマを付けるとエラーになったりするので、一般的に使われている書き方だと思います。

最後にカンマを付ける派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password',
)

分かりづらいですが、先程の例と比べると、最後の引数の後にカンマが付いているのが分かります。
rubyでは上記のように最後の引数にカンマを付けても問題なく動きます。
慣れていないと気持ち悪く感じるかもしれませんが、引数を追加したい時などに素早くコピペできるので便利だったりします。

最後にカンマを付けるためのrubocopの設定

Style/TrailingCommaIn~~ を設定する

引数/配列/ハッシュでそれぞれ設定するスタイルが異なります。

種類 スタイル名
引数 Style/TrailingCommaInArguments
配列 Style/TrailingCommaInHashLiteral
ハッシュ Style/TrailingCommaInArrayLiteral

それぞれEnforcedStyleForMultilineプロパティをcommaまたはconsistent_commaに設定すればOKです。

rubocop.yml
Style/TrailingCommaInArguments:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInArrayLiteral:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInHashLiteral:
  EnforcedStyleForMultiline: comma

これでrubocop -aを実行したときに引数/配列/ハッシュの値にカンマがつくようになります。commaconsistent_commaにしても同様の結果が得られます。

consistent_commacomma の違い

こちらのようなドキュメントを読んでも違いがわからなかったのでいくつか試してみたところ、複数行に引数を書きつつも、1行に複数の引数がある場合に違いが出る事がわかりました。

seeds.rb
# consistent_comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password',
)

# comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password'
)

分かりづらいですが、最後2つの引数を同じ行に書いています。
このようなときconsistent_commaだと最後の引数にカンマが付き、commaだと付きません。

ちょっとハマったところ

下記の様にメソッドの行から引数を書き始めると、どの設定にしても下記のように整形されてしまいますのでご注意ください。

seeds.rb
User.create!(name: 'foo bar',
             email: 'foo@baa.com',
             password: 'password',
             password_confirmation: 'password')

なお古いバージョンのrubocopでは引数/配列/ハッシュでのカンマ有無をすべてStyle/TrailingCommaで設定していたようですが、現在こちらは削除されているので使えません。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【RuboCop】複数行の引数の最後の値にカンマを付けたい

複数行の配列や引数を書くとき

最後にカンマを付けない派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password'
)

rubocop -aで自動整形すると、デフォルトでは上記のように整形されます。
ruby以外の言語だと、引数や配列の最後にはカンマを付けるとエラーになったりするので、一般的に使われている書き方だと思います。

最後にカンマを付ける派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password',
)

分かりづらいですが、先程の例と比べると、最後の引数の後にカンマが付いているのが分かります。
rubyでは上記のように最後の引数にカンマを付けても問題なく動きます。
慣れていないと気持ち悪く感じるかもしれませんが、引数を追加したい時などに素早くコピペできるので便利だったりします。

最後にカンマを付けるためのrubocopの設定

Style/TrailingCommaIn~~ を設定する

引数/配列/ハッシュでそれぞれ設定するスタイルが異なります。

種類 スタイル名
引数 Style/TrailingCommaInArguments
配列 Style/TrailingCommaInHashLiteral
ハッシュ Style/TrailingCommaInArrayLiteral

それぞれEnforcedStyleForMultilineプロパティをcommaまたはconsistent_commaに設定すればOKです。

rubocop.yml
Style/TrailingCommaInArguments:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInArrayLiteral:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInHashLiteral:
  EnforcedStyleForMultiline: comma

これでrubocop -aを実行したときに引数/配列/ハッシュの値にカンマがつくようになります。commaconsistent_commaにしても同様の結果が得られます。

consistent_commacomma の違い

こちらのようなドキュメントを読んでも違いがわからなかったのでいくつか試してみたところ、複数行に引数を書きつつも、1行に複数の引数がある場合に違いが出る事がわかりました。

seeds.rb
# consistent_comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password',
)

# comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password'
)

分かりづらいですが、最後2つの引数を同じ行に書いています。
このようなときconsistent_commaだと最後の引数にカンマが付き、commaだと付きません。

ちょっとハマったところ

下記の様にメソッドの行から引数を書き始めると、どの設定にしても下記のように整形されてしまいますのでご注意ください。

seeds.rb
User.create!(name: 'foo bar',
             email: 'foo@baa.com',
             password: 'password',
             password_confirmation: 'password')

なお古いバージョンのrubocopでは引数/配列/ハッシュでのカンマ有無をすべてStyle/TrailingCommaで設定していたようですが、現在こちらは削除されているので使えません。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】ActiveStorage::Blob#purge すると FrozenError

環境

  • Ruby: 2.6.3
  • Rails: 6.0.2.1

入力エラー時の purge でエラーが発生するようになった

こんな感じの has_one_attached の項目を持つmodelで、

post.rb
class Post < ApplicationRecord
  has_one_attached :image
  # 他にもいくつか項目・・
end

入力エラー時に has_one_attached の項目を purge するとエラーが発生しました。
※Rails5.2.4.1までは正常に動作していました

posts_controller.rb
def create
  Post.create!(post_params)
rescue ActiveRecord::RecordInvalid => e
  post = e.record
  post.image&.purge # <= ここでエラー!!
end

エラーメッセージはこんな感じでした。

FrozenError
can't modify frozen Hash

ほう、Rails6からFrozenになったのか? :thinking:

調査してみると既にissueがありました。
ActiveStorage has_one_attached not purging attachments · Issue #37069 · rails/rails

以下のコメントに原因がめちゃめちゃ詳しく書いてありました。

https://github.com/rails/rails/issues/37069#issuecomment-525972179
image.png

今回の例に合わせて要約すると、

  • ActiveStorage::Blob#purge は、blobレコード(imageの情報を持っているレコード)を destroy し、そのレコードの項目をFreezeします。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L232-L239
def purge
  destroy # <= ココ!!
  delete
rescue ActiveRecord::InvalidForeignKey
end
  • その後、 delete で保存されたファイルを削除しますが、Postレコードが保存されていないため、blobにはキーがありません。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L227-L230
def delete
  service.delete(key) # <= ココ!! #key を呼び出す
  service.delete_prefixed("variants/#{key}/") if image?
end
  • ActiveStorage::Blob#delete#key を呼び出します。 #key は、blob にまだキーがない場合にキーを生成しようとします。 生成されたキーをblobのFreezeされた項目に設定しようとするとエラー発生します。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L115-L118
def key
  # We can't wait until the record is first saved to have a key for it
  self[:key] ||= self.class.generate_unique_secure_token
  # ^ self[:key]が無いので右辺が実行されるが、selfの項目はFreeze済みなので FrozenError!!
end

原因はそんなところです。

回避策としては、 #purge を呼ぶ代わりに nil を設定すればOKです。

posts_controller.rb
def create
  Post.create!(post_params)
rescue ActiveRecord::RecordInvalid => e
  post = e.record
  post.image& = nil # <= purge を呼ぶ代わりに nil 設定で回避
end

異常系のテストもしっかりしとかないとダメですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6 テーブル作成時のmigrateでエラーが出た話

目的

  • テーブル作成を行なった際にmigrateでエラーが出た話をまとめる

エラー内容

  • 下記コマンドでモデルファイルを作成した。

    $ rails g model user name:string
    
  • その後、下記コマンドでmigreteを実行した。

    $ rails db:migrate
    
  • 下記のエラーが発生した

    [14:50:10]MacBook-miriwo~/workspace/study/rails/tropical_fish_sns_of_rails$ rails db:migrate
    == 20200206055010 CreateUsers: migrating ======================================
    -- create_table(:users)
    rails aborted!
    StandardError: An error has occurred, all later migrations canceled:
    
    Mysql2::Error: Table 'users' already exists
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/db/migrate/20200206055010_create_users.rb:3:in `change'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/rails:9:in `<top (required)>'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/spring:15:in `<top (required)>'
    bin/rails:3:in `load'
    bin/rails:3:in `<main>'
    
    Caused by:
    ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/db/migrate/20200206055010_create_users.rb:3:in `change'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/rails:9:in `<top (required)>'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/spring:15:in `<top (required)>'
    bin/rails:3:in `load'
    bin/rails:3:in `<main>'
    
    Caused by:
    Mysql2::Error: Table 'users' already exists
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/db/migrate/20200206055010_create_users.rb:3:in `change'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/rails:9:in `<top (required)>'
    /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails/bin/spring:15:in `<top (required)>'
    bin/rails:3:in `load'
    bin/rails:3:in `<main>'
    Tasks: TOP => db:migrate
    (See full trace by running task with --trace)
    

原因調査

  • 下記コマンドを実行して現在のmigrateのステータスを確認した。

    $ rails db:migrate:status
    
  • 以前にmigrateを実施した際のmigrationファイルのが無いことが判明した。

    [14:52:57]MacBook- 
    miriwo~/workspace/study/rails/tropical_fish_sns_of_rails/db/migrate$ rails db:migrate:status
    
    database: tropical_fish_sns_development
    
     Status   Migration ID    Migration Name
    --------------------------------------------------
       up     20200206025324  ********** NO FILE **********
       up     20200206025916  ********** NO FILE **********
      down    20200206055010  Create users
    

解決法

  • 下記コマンドを実行してDBのリセットを行なった。

    $ rails db:migrate:reset
    
  • DBの状態を確認した。

    [14:54:33]MacBook-miriwo~/workspace/study/rails/tropical_fish_sns_of_rails/db/migrate$ rake 
    db:migrate:status
    (in /Users/admin/workspace/study/rails/tropical_fish_sns_of_rails)
    
    database: tropical_fish_sns_development
    
     Status   Migration ID    Migration Name
    --------------------------------------------------
       up     20200206055010  Create users
    
  • 正常に反映されていることをがわかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む