20200909のRailsに関する記事は13件です。

技術記事を書く時に意識していること

自分のために書く

「思考の整理 + 未来の自分のための資料作りが目的で、周りからの反応はおまけ」
というスタンスでいると雑な記事をバンバン書ける。自分は執筆時間が5分かかっていない記事の方が多いと思う。そして記事数に対してLGTM等は少ない。

自分の言葉で書く

普段自分が使う言い回しをした方が、後から振り返った時に理解しやすいです。カッコつけない方が良い。
文章は、きれいさや構造の正しさより、テンポを意識。

Qiigleの検索に引っかかるようにする

将来検索に使いそうなワードは入れておく
https://qiigle.com/

さっさと本題に入る

「記事の対象者」「この記事でわかること」「この記事で触れないこと」とかが必要なのは超長文記事か、反感を買いかねない踏み込んだ記事を書くときだけで良い。
あいさつとかギャグ的なものは技術記事には必要ない。

その命名じゃないといけないのか、任意の名前で良いのかを明確にする

任意に名前をつけられる部分がアプリ名にちなんだ名前だったりすると、命名規則があるように見えてしまう。多少ダサくなってもわかりやすい方が良い。

参考記事を書く

モラル的な話だけじゃなくて、後から参照する時に執筆当時の状況を思い出しやすくなる。

記事投稿画面をブックマークバーにセット

1クリックで執筆開始できます。

伸ばしたい記事は拡散の努力をする

Qiitaではトレンドに入ると一気に伸びるので、サービス公開記事など伸ばしたい記事はTwitterやオンラインコミュニティ等でコメント付きで共有するなど、最大限拡散の努力をします。やりまくると嫌われるので、本当に伸ばしたい時だけ。

記事を伸ばすために学習するジャンルを変えない

経験上、Railsの記事を書くよりReact等の記事を書く方が全然伸びます。でもそれはそれなので、、
「記事が伸びる分野 = 自分が人柱になる確率が高い分野」で、今の自分は確実に技術力の土台を固めるフェーズなので、そこは履き違えないように注意してます。

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

yamlファイルのコメントアウトをエディタのショートカットで解除してエラーが出るときに確認すること

おことわり

Railsで遭遇したエラーではありますが、Rails以外でも起こりうるのでタイトルからRailsを外しています。

問題が起こった状況

Railsの設定ファイルの一つに、storage.ymlという、ストレージ周りの設定をするyamlファイルがあるのですが、
その中に、デフォルトではコメントアウトされているAWSのS3に関する設定があります。

今回、S3を設定する必要があったので、それを意気揚々とIDEのショートカットでコメントアウトを解除したときに問題が発生しました。

storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket

どうなったか

下記エラーが発生しました

RuntimeError: YAML syntax error occurred while parsing /project_dir/config/storage.yml. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Error: (<unknown>): did not find expected key while parsing a block mapping at line 1 column 1

どこが間違っていたか

storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
 amazon:
   service: S3
   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
   region: us-east-1
       bucket: your_own_bucket

この見た目ではわかりにくいですが、amazonというキーの行以降が半角スペース1つ分右にずれています。

そのため、yamlの解釈エラーが出てしまっていたということになります。

対処法

ずれている分の半角スペースを削除する

結論

エディタのコメントイン機能を使う際はインデントに注意してください。

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

deviseを使ったユーザー情報の変更をする場合パスワードが保存されない時のedit画面での設定

deviseを使ったユーザー登録情報の編集をする場合、edit→updateを行うときビュー画面のエラーメッセージがCurrent password can't be blankと表示され、編集画面が保存されない場合の解決方法。

ruby '2.6.5'
rails '6.0.0
devise 4.7.2

2つのdeviseをもつアカウントがあり、ユーザー登録情報を編集し、保存をしたい場合の設定の流れをまとめます。

①ルーティングの確認/rails routes でuserのregistrations/editのパスを確認します。
edit_user_registration GET    /users/edit(.:format)                                                          users/registrations#edit

edit_user_registration_path と確認が取れました。これをlink_toでパスを作成します。

②controllers/users/registrations_controller.rb ファイルです。

before_actionとconfigure_account_update_paramsの設定をします。今回は私のカラムを載せます。

before_action :configure_account_update_params, only: [:update]
def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update,  keys: [:nickname, :email, :password, :gender_id, :birth, :bloodtype_id, :emergencyperson, :emergencycall, :real_name, :real_name_kana, :phone_number])
  end
③次にビューです。

users/registrationsにedit.html.erbファイルを作成します。ほぼnew.html.erbをコピーしたものを使いますが、追加事項があります。私は2つのdeviseを使ってるのでディレクトリが違いますが1つのdeviseだとビューはdevise/registrationsにedit.html.erbを作成します。

form_withのパスも変更します。
参考サイト こちらです
結論から言うと、deviseのデフォルト設定なのですが、パスワード更新時は現在のパスワードを認証してupdateを行います。

<%= form_with model: @user, url: user_registration_path, method: :patch, class: 'registration-main', local: true do |f| %>
 <div class="field">
    <%= f.label :current_password %> 
    <%= f.password_field :current_password, autocomplete: "current-password" %>
  </div>

こちらを追加します。編集する場合は今までのパスワードを入れる項目が必要と言うわけです。

これを追加することによって無事に変更項目が保存されました。ですがパスワードを変更しない場合も必須なのでそこが?と言う感じです。
引き続き調べて学習していきます。

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

【Rails】rakeタスクを実装する

rakeタスクとは

ファイルに記述した処理をコマンドラインから実行する機能です。
ユーザーの属性に応じてステータスを変更する、CSVデータをインポートする、任意のタイミングでユーザーにメールを送る、などなど様々な用途で使われます。

基本的な使い方

タスクファイルを生成

$ rails g task qiita_task

実行したい処理を記述

namespace :qiita_task do
  desc 'hello worldします'
  task :hw do
    puts 'Hello World'
  end
end

実行

$ rake qiita_task:hw

その他

タスクの中にDBに接続する処理が含まれる場合

DBに接続する場合、以下のようにenvironmentと記述する

namespace :qiita_task do
  desc '最近登録したユーザーにメールを送信'
  task send_email_to_recent_users: :environment do
    recent_users = User.where('updated_at <= ?', Time.zone.parse('2020/09/08 15:50:00'))
    recent_users.each do |ru|
      ru.send_email
    end
  end   
end

本番環境で実行する

プロジェクトのルートディレクトリ(Gemfileとかがあるとこ)で、RAILS_ENV=productionをつけて実行。

$ rake qiita_task:hw RAILS_ENV=production

タスク一覧を表示

デフォルトで定義されているタスクと、自分が作成したタスクがずらっと表示されます。

$ rake -T

参考

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

[解答]rails にカート機能を追加する-モデル部分

購買者購入履歴のためのモデルを作る

ターミナル
$ rails g model UserProduct

モデルとマイグレーションファイルができたと思うので、マイグレーションファイルを編集する

db/migrate/20190831095111_create_tags.rb
class CreateUserProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :user_products do |t|
      t.integer :user_id #購入者
      t.integer :product_id #商品
      t.integer :lot #買った個数
      t.timestamps
    end
  end
end

編集し終えたら、データベースを更新するためにマイグレーションをかける

ターミナル
$ rails g db:migrate

購買者購入履歴の関係をmodelで定義する。
・あるユーザーは多くの購買者購入履歴(買った商品)を持っている
・ある商品は多くの購買者購入履歴(買ったユーザー)を持っているので
多対多の関係

app/model/user.rb
class User < ApplicationRecord
    devise :database_authenticatable, :registerable, :recoverable,
     :rememberable, :trackable, :validatable, :confirmable
    has_many :products
    has_many :user_products
end
app/model/product.rb
class Product < ApplicationRecord
    belongs_to :user
    has_many :user_products
end
app/model/user_product.rb
class UserProduct < ApplicationRecord
    belongs_to :user
    belongs_to :product
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

commit failed exit code 1のエラー解決方法

【概要】

1.結論

2.なぜ"commit failed exit code 1"になるのか

3.どのように解決するか

4.ここから学んだこと

1.結論

git cloneしたファイルを削除する。


2.なぜ"commit failed exit code 1"になるのか

スクリーンショット 2020-09-09 15.03.22.png

以前に

ターミナル
% git clone https://github.com/"githubのアカウント名"/"githubのリモートレポジトリファイル名”.git

行ったことが原因でした。
上記のようにgitcloneしたものがcommitしたいファイルに残っていたせいでした。


3.どのように解決するか

該当ファイルの中の場所に"clone_site"と作ってありました。そのファイルを削除して、再度新たにgithubに反映させたいファイルを指定してcommitするとエラーは解消しました。

4.ここから学んだこと

当時git cloneした際は一瞬だけ少し試したいことがあったため使用しました。なので、用済みであればそのファイルは消すか、commitしない自信がそのファイルは使用しない方向にするべきでした。そうしないと、今回のようにgit cloneしたことを忘れかけていたので土壺にはまり時間をうばわれかねません。

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

Docker上のRailsをherokuへデプロイする

herokuへデプロイする練習

Dcokerコンテナ上に、Railsアプリを組んでCircleciを使って自動デプロイまでを一気にやろうとしたら
迷宮入りしたので、原点回帰しようと思います。(第2段)

自分の環境

Ruby : 2.6.6
rails : 6.0.3.2
git : 2.23.0
heroku-cli : 7.42.13 darwin-x64 node-v12.16.2
Docker : 19.03.12

開発環境は、MySQLで
本番環境は、PostgreSQLというパターンで組んでみようと思います。

手元のDocker上にRailsアプリを用意する

まずは、アプリを作るディレクトリを作成し、そこにtouchコマンドで必要な諸々を用意します。

terminal
$ touch {Dockerfile,docker-compose.yml,Gemfile,Gemfile.lock,entrypoint.sh}
Dockerfile
FROM ruby:2.6

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update -qq \
    && apt-get install -y nodejs yarn \
    && mkdir /heroku_app
WORKDIR /heroku_app

COPY Gemfile //Gemfile
COPY Gemfile.lock /heroku_app/Gemfile.lock
RUN bundle install
COPY . /heroku_app

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3006

CMD ["rails", "server", "-b", "0.0.0.0"]
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql:cached
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3006 -b '0.0.0.0'"
    volumes:
      - .:/heroku_app
    ports:
      - "3006:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
    command: bundle exec rails server -b 0.0.0.0
volumes:
  mysql-data:
    driver: local
Gemfile
source 'https://rubygems.org'
rails ‘6.0.3’
entrypoint.sh
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /heroku_app/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

上記5点を用意できれば、下記コマンドを実行しRailsアプリを作成します。

terminal
$docker-compose run web rails new . --force --no-deps --database=mysql

併せて、テキトーな中身を作っておきます。

$ docker-compose run web rails g scaffold blog title:string body:text
$ docker-compose run web rails db:migrate
$ docker-compose up -d

スクリーンショット 2020-09-09 0.00.04.png

herokuへデプロイする準備

続いて、Railsアプリをherokuにデプロイする前に、本番環境用にpostgreSQLを用意します。
- config/database.ymlの設定
- Gemfile にpgを追加
- config/enviroments/deviropment.rbの設定

config/database.ymlの設定

config/database.yml
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

Gemfileの設定

本番環境用にgemファイルを用意します。
productionのグループにpgを追加します。
また、MySQLは開発環境用として扱う様にするために、group :development, :test doの中へ移動させます。

Gemfile
group :production do
  gem 'pg', '~> 0.19.0'
end

config/enviroments/deviropment.rbの設定

Rails6特有ですが、DNS離バインディング攻撃からの保護が入っているらしく、
hostを入れてあげる必要があります。

config/enviroments/deviropment.rb
config.hosts << "radiant-springs-45017.herokuapp.com"

下記記事を参考にさせていただきました。
https://qiita.com/kodai_0122/items/67c6d390f18698950440

編集を終えたら、ビルドします。

terminal
docker-compose build
$docker-compose run web rails db:create
$docker-compose up -d

あとは、コマンド打って、ルンルンとherokuへデプロイします。

$ docker-compose down #一度落としておかないとエラーになる可能性があるとのこと
$ heroku login
$ heroku create アプリ名もしくは空欄
$ heroku container:login
$ heroku container:push web
$ heroku adding:create heroku-postgresql:hobby-dev
$ heroku container:release web
$ heroku open

スクリーンショット 2020-09-09 10.44.50.png

終わり!

割愛しましたが、herokuへデプロイした際にエラーが発生しました。
その際は、ターミナル上で$ heroku logs --tail等を打ち、エラーを調べて解決させました。
成功された記事を参考した場合でも、環境の違い等でエラーが発生しうるので、理解のために都度調べる癖をつけるのは大事だと思った次第です。

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

【Rails】アソシエーションの種類(1対多 / 多対多)

脳内整理のために

プログラミング初学者がRailsを学び始めて最初に、ん?となるポイント(のはず)であるアソシエーション。
アソシエーションについて個々の記事はいくらでもあるので、全体像としてその種類を整理してみました。

アソシエーションの種類

アソシエーション表.png
表の通りに分類できます。(今回1対1のアソシエーションは省略しました。)

まとめ

  • 多対多は中間テーブルが必要
  • 自己結合は同じモデルを参照する外部キーが必要
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】関数における例外処理

はじめに

この記事は数多ある例外処理についての補足的なものです。具体的には、記事で紹介されてないパターンについて検証した結果を残します。

環境

  • Mac
  • Ruby 2.6.3

関数の中での使用

関数の中では以下のように書けます。

def method
  puts "hoge"
  a = 1 / 0
rescue ZeroDivisionError
  puts $!
end

begin, endを省いた形ですね。このような形の場合、

a = 1 / 0

がエラーを発生させるであろうことは一目瞭然です。しかし、以下の場合であればどうでしょうか。

def method
  # 膨大な処理1
  # エラーが発生しそうな処理
  # 膨大な処理2
rescue StandardError
  puts $!
end

さて、このコードを見た人がすぐにエラーの発生しそうな処理を見抜けるかというと怪しいところです。その場合はあえてbegin, endを書いたらいいと思います。

def method
  # 膨大な処理1
  begin
    # エラーが発生しそうな処理
  rescue StandardError
    puts $!
  end
  # 膨大な処理2
end

こうすればどこがエラー発生しそうか分かりますし、沢山スクロールしてrescueを見に行く必要もありません。
ちなみにrescueでreturnすると膨大な処理2は処理されないので、間違いなく処理してほしいならensureに入れましょう。

def method
  # 膨大な処理1
  begin
    # エラーが発生しそうな処理
  rescue StandardError
    puts $!
    return
  ensure
    # 膨大な処理2(rescueにreturnがあっても処理される)
  end
end

もしエラーの発生しそうな処理が複数ある場合は、関数の最後にまとめて書くのがシンプルでいいと思います。毎回begin, rescue, end書くのは流石に可読性下がりますので。何事も適切に。

def method
  # いろいろエラーが発生しそうな処理

  # その他処理

rescue ZeroDivisionError, ArgumentError
  puts $!
rescue StandardError
  puts $!
  puts $@
end

ちなみにStandardErrorを先に書くと大体のエラー処理はこいつがやってしまいます。なので子クラスから書いていきましょう。

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

素のrailsアプリをherokuにデプロイ

Dcokerコンテナ上に、Railsアプリを組んでCircleciを使って自動デプロイまでを一気にやろうとしたら
迷宮入りしたので、原点回帰しようと思います。

herokuへデプロイする練習

自分の環境

Ruby : 2.6.6
rails : 6.0.3.2
git : 2.23.0
heroku-cli : 7.42.13 darwin-x64 node-v12.16.2

Railsアプリを用意する

terminal
$ rails new heroku_app #今回はheroku_appというものを用意
$ cd heroku_app
$ rails g scaffold blog title:string body:text
$ rake db:migrate
$ rails s

Railsアプリをデプロイする準備

  • config/routes.rbの設定
  • Gemfileの設定
  • config/datebase.ymlの設定
  • config/enviroments/production.rbの設定

config/routes.rbの設定

config/routes.rb
Rails.application.routes.draw do
   resources :blogs
   root 'blogs#index' #トップページをblogsコントローラのindexアクションに設定
end

スクリーンショット 2020-09-09 0.00.04.png

Gemfileの設定

今回はherokuの無料枠で使えるpostgreSQLを使います。
ちなみにMySQLは有料枠になります。
※pg:production:本番環境のherokuは、postgreSQLを使うよって意味になります。

Gemfile
group :development, :test do
  gem 'sqlite3' #do ~ end内部に追加。他のgemは残してください。
end
group :production do
  gem 'pg'
end

さっき更新したGemfileの変更をbundle installする。

terminal
$ bundle install --without production

※ちなみに、このオプションは一度打ち込めば、アプリ内部の/bundle/configというところに書き込まれるため、
それ以降は--without producitonを打たなくても本番環境以外のgemをインストールしてくれるようになります。
その他のbundle install時のオプションは全てここに書き込まれます。

このようになっていたらOK

.bundle/config
BUNDLE_WITHOUT: "production"

cinfig/database.ymlの設定

実際にデータベースと接続する設定をします。

config/database.yml
production:
#半角スペース2個分の空白を開ける
<<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

adapter: postgresql - postgreSQLのデータベースに接続します。
encoding: unicode - unicodeという文字コードを使用します。
pool: 5 - DBに接続できる上限の数を決めます。今回は5。

config/environments/production.rbの設定

本番環境での動的な画像の表示をONにする。

config/enviroments/production.rb
#デフォルトでfalseとなっている以下の箇所をtrueに変更
  config.assets.compile = true

herokuへデプロイの準備を行う

git経由でアップロードをします。

teminal
$ git init #このフォルダをgitと紐付けます。(最初の一回だけ)

変更したら毎回やるやつ

terminal
$git add -A #保存するファイルの選択。-Aは全部
$git commit -m "first commit" #addしたファイルの保存の確定

herokuへ登録する

terminal
$heroku login
$heroku create アプリ名 #自由にどーぞ、空欄でも可。ちなみにすでに誰かが作って存在する場合はエラーが出るので、別名をトライ。
$git push heroku master

本番環境でのマイグレーション

terminal
$heroku run rails db:migrate

スクリーンショット 2020-09-09 10.44.50.png

終わり!

ちなみにheroku runをつけるとheroku上でrailsコマンドを打つことができます。
参考にさせていただいた記事通りに行えば、素のrailsをherokuにデプロイすることは容易にできました。
元記事の説明が大変丁寧でわかりやすく、感謝しています。
参考記事:https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39

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

Rails 6で認証認可入り掲示板APIを構築する #4 postのバリデーション、テスト実装

Rails 6で認証認可入り掲示板APIを構築する #3 RSpec, FactoryBot導入しpostモデルを作る

RSpecでmodelのテストを書く

とりあえずcreate, readが動くのは前回確認できました。
ここからはこの手順でいきます

  1. postのmodelテストを書く
  2. validationを実装する
  3. postのcontrollerテストを書く
  4. controller, routesを書く
  5. seedを書く

この記事ではとりあえず1.と2.の実装をして、3.以降はまた次回以降の記事で進めていきます。

rubocopをあらかじめつぶしておく

ドキュメンテーション書きなさいエラーがmigrationファイルにも出てくるので除外します。

.rubocop.ymlはこのように除外設定もできますが、本来必要なものも面倒がって除外していくとそもそもコーディング規約を守る意味が崩れるので、チーム開発で追加する際はしっかり議論しましょう。

.rubocop.yml
+ # ドキュメンテーション
+ Style/Documentation:
+  Exclude:
+    - "db/migrate/**/*"
...

まずmodelテストを書く

テスト駆動開発(TDD)っぽく、まずはRedのテストです。
バリデーション未実装なのでテストを書いて動かしてもRedとなるものを作ります。

一旦factory_bot使わずに普通のRailsチックに書いてみます。

spec/models/post_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  describe "subject" do
    context "blankの時に" do
      it "invalidになる" do
        post = Post.new(subject: "", body: "fuga")
        expect(post).not_to be_valid
      end
    end
  end
end
  • describeはテスト対象を示します
  • contextは条件を示します
  • it(もしくはexample)はテスト対象を示します
  • expect(post).not_to be_validは、postがbe_validと等しくないことをテストしています

なので上記コードは『subjectがblankの時にinvalidになる』ことをテストしています。
ですが今のところpostモデルのsubjectにバリデーションを入れていないのでpostは有効であり、「invalidになる」テストは失敗することになります。

$ rspec spec/models/post_spec.rb
...
Finished in 0.07805 seconds (files took 3.53 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/post_spec.rb:8 # Post subject blankの時に invalidになる

ec2-user:~/environment/bbs (master) $ rspec

本当にsubjectが空でも登録できるか試してみましょう。

$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
   (0.1ms)  BEGIN
  Post Create (2.5ms)  INSERT INTO "posts" ("subject", "body", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["subject", ""], ["body", "hoge"], ["created_at", "2020-09-06 01:07:52.628768"], ["updated_at", "2020-09-06 01:07:52.628768"]]
   (0.9ms)  COMMIT
=> #<Post:0x0000000005760700
 id: 2,
 subject: "",
 body: "hoge",
 created_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00,
 updated_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00>

保存できてしまいましたね。

ちなみにdescribeやcontextを使わずに

spec/models/post_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  it "subjectがblankの時にinvalidになる" do
    post = Post.new(subject: "", body: "fuga")
    expect(post).not_to be_valid
  end
end

というコードでもほぼ同じテストコードの挙動になります。なぜなら、itブロックでexpectを書けばテストができるためです。
しかし同カラムや同バリデーション条件等をグルーピングして記述する際に分かりづらくなるので、基本的にdescribeやcontextを入れ子にして記述するのがオススメです。

modelにバリデーションを追加する

blankをエラーとするバリデーション

app/models/post.rb
 class Post < ApplicationRecord
+  validates :subject, presence: true
 end

これで、subjectカラムに対してpresence(存在)に対してtrue, つまりblankでの登録ができなくなります。

試してみましょう。

$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
ActiveRecord::RecordInvalid: Validation failed: Subject can't be blank
from /home/ec2-user/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/validations.rb:80:in `raise_validation_error'

登録できないですね。

$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.05053 seconds (files took 1.63 seconds to load)
1 example, 0 failures

テストも通過しました。

最大文字数のバリデーション

文字数が無限に登録できると困るので制限を加えます。
こちらも先にテストから。
30文字以内ならOK、31文字以上はNGというバリデーションを追加する予定でテストを書いてみます。

spec/models/post_spec.rb
         expect(post).not_to be_valid
       end
     end
+    context "maxlengthにより" do
+      context "30文字の場合に" do
+        it "validになる" do
+          post = Post.new(subject: "あ" * 30, body: "fuga")
+          expect(post).to be_valid
+        end
+      end
+      context "31文字の場合に" do
+        it "invalidになる" do
+          post = Post.new(subject: "あ" * 31, body: "fuga")
+          expect(post).not_to be_valid
+        end
+      end
+    end
   end
 end

テスト実行してみましょう。

$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.03204 seconds (files took 1.42 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/post_spec.rb:21 # Post subject maxlengthにより 31文字の場合に invalidになる

まだvalidationを追加していないので、30文字はパスしますが31文字はコケますね。
modelにvalidationを追加します。

app/models/post.rb
 class Post < ApplicationRecord
-  validates :subject, presence: true
+  validates :subject, presence: true, length: { maximum: 30 }
 end
$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.02201 seconds (files took 1.4 seconds to load)
3 examples, 0 failures

テスト通りましたね。
これで31文字の際にエラーになります。rails cで試してみると良いでしょう。

FactoryBotに置き換える

例えば先程のテストコードにある

  post = Post.new(subject: "あ" * 30, body: "fuga")

ですが、毎回body指定するの面倒ですよね。
2カラムであればまだ大丈夫ですが、これが10カラムとか超えてくると無駄にコードが長くなります。
その際にfactoryBotを使います。

factoryBotはspec/factories/下を参照します。
今回はmodelを作った時の初期値から特に変える必要はありませんが、一応中身を見ておきます。

spec/factories/posts.rb
# frozen_string_literal: true

FactoryBot.define do
  factory :post do
    subject { "MyString" }
    body { "MyText" }
  end
end

post_spec.rbファイルを編集します。

spec/models/post_spec.rb
   describe "subject" do
     context "blankの時に" do
       it "invalidになる" do
-        post = Post.new(subject: "", body: "fuga")
+        post = build(:post, subject: "")
         expect(post).not_to be_valid
       end
     end
     context "maxlengthにより" do
       context "30文字の場合に" do
         it "validになる" do
-          post = Post.new(subject: "あ" * 30, body: "fuga")
+          post = build(:post, subject: "あ" * 30)
           expect(post).to be_valid
         end
       end
       context "31文字の場合に" do
         it "invalidになる" do
-          post = Post.new(subject: "あ" * 31, body: "fuga")
+          post = build(:post, subject: "あ" * 31)
           expect(post).not_to be_valid
         end
       end

buildはfactoryBotを使った.newに相当するものです。データベースへの保存は行われません。
今回の場合subjectを指定していますがbodyは未指定なので、factoryBotのbodyは"MyText"が入ります。

また、変更のたびにテスト実行してOKになることを確認してください。

変数をletに置き換える

とりあえず以下のように変更してみてください。

spec/models/post_spec.rb
 RSpec.describe Post, type: :model do
   describe "subject" do
     context "blankの時に" do
+      let(:post) do
+        build(:post, subject: "")
+      end
       it "invalidになる" do
-        post = build(:post, subject: "")
         expect(post).not_to be_valid
       end
     end
     context "maxlengthにより" do
       context "30文字の場合に" do
+        let(:post) do
+          build(:post, subject: "あ" * 30)
+        end
         it "validになる" do
-          post = build(:post, subject: "あ" * 30)
           expect(post).to be_valid
         end
       end
       context "31文字の場合に" do
+        let(:post) do
+          build(:post, subject: "あ" * 31)
+        end
         it "invalidになる" do
-          post = build(:post, subject: "あ" * 31)
           expect(post).not_to be_valid
         end
       end

letは同一describeやcontextのブロック内のスコープに限定される変数です。
Rubyは最後に評価された式が返り値となるので、

  let(:post) do
    build(:post, subject: "あ" * 31)
  end

の場合は、build実行結果のpostが、let(:post)によってpostという変数になります。

演習

bodyにも必須制限・100文字以内制限のテストとバリデーションを実装してみましょう。

body実装回答例
spec/models/post_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  describe "subject" do
    context "blankの時に" do
      let(:post) do
        build(:post, subject: "")
      end
      it "invalidになる" do
        expect(post).not_to be_valid
      end
    end
    context "maxlengthにより" do
      context "30文字の場合に" do
        let(:post) do
          build(:post, subject: "あ" * 30)
        end
        it "validになる" do
          expect(post).to be_valid
        end
      end
      context "31文字の場合に" do
        let(:post) do
          build(:post, subject: "あ" * 31)
        end
        it "invalidになる" do
          expect(post).not_to be_valid
        end
      end
    end
  end

  describe "body" do
    context "blankの時に" do
      let(:post) do
        build(:post, body: "")
      end
      it "invalidになる" do
        expect(post).not_to be_valid
      end
    end
    context "maxlengthにより" do
      context "100文字の場合に" do
        let(:post) do
          build(:post, body: "あ" * 100)
        end
        it "validになる" do
          expect(post).to be_valid
        end
      end
      context "101文字の場合に" do
        let(:post) do
          build(:post, body: "あ" * 101)
        end
        it "invalidになる" do
          expect(post).not_to be_valid
        end
      end
    end
  end
end

この時点でrspec実行するとコケる

app/models/post.rb
# frozen_string_literal: true

#
# 投稿クラス
#
class Post < ApplicationRecord
  validates :subject, presence: true, length: { maximum: 30 }
  validates :body, presence: true, length: { maximum: 100 }
end

rubocopがコケるので除外設定。testはDRYやらコーディング規約やら遵守すると逆効果のこともあるので、あまり厳しくしないほうがいいです。

.rubocop.yml
+ # ブロック長さ
+ Metrics/BlockLength:
+   Exclude:
+     - "spec/**/*"

この時点でrspec, rubocop実行すると通る

続き

Rails 6で認証認可入り掲示板APIを構築する #5 controller, routes実装

連載目次へ

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

rails tutorial 第7章

はじめに

独学でrails tutorialを進めていく過程を投稿していきます。

進めていく上でわからなかった単語、詰まったエラーなどに触れています。

個人の学習のアウトプットなので間違いなどあればご指摘ください。

初めての投稿なので読みにくいところも多々あるかと思いますがご容赦ください。

第7章 ユーザー登録

7.1.2 Usersリソース

RESTアーキテクチャの習慣に従ってユーザー情報をWebアプリケーション上に表示する。

RESTってなんだっけ、、、


RESTとは、アプリケーションを構成するコンポーネント(ユーザーやマイクロポストなど)を「リソース」としてモデル化することを指します。これらのリソースは、リレーショナルデータベースの作成/取得/更新/削除(Create/Read/Update/Delete: CRUD)操作と、4つの基本的なHTTP requestメソッド(POST/GET/PATCH/DELETE)の両方に対応しています。
コラム2.2(rails tutorial 2章より引用)


つまりはusersモデルをリソースとし、データ(作成、表示、更新、削除)操作とrequestメソッド(POST/GET/PATCH/DELETE)の両方に対応させるとういうイメージでしょうか。

そうすることで
・コントーラーやアクションの決定を簡略化
(リソースとすれば自動で設定してくれる)
・動的なWebページの作成
(ユーザー情報をWebアプリケーション上に表示するなど)
を実現できるということですかね。

usersモデルをリソースとする際はルーティングで

resources :users

と記入するようです。

7.2.2 フォームHTML

新規ユーザーの登録フォームを作りました。分かりにくかった所だけ補足します。

<%= form_with(model: @user, local: true) do |f| %>
    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :email %>
    <%= f.email_field :email %>

    <%= f.label :password %>
    <%= f.password_field :password %>

    <%= f.label :password_confirmation, "Confirmation" %>
    <%= f.password_field :password_confirmation %>

    <%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>

form_withはデフォルトで“remote” XHR requestを送信しますが、ここではエラーメッセージをほぼ確実に表示するために通常の“local”フォームリクエストを送信したいのです( 7.3.3)。(rails tutorial 7章より引用)


どういうことだろう、、、
“remote” XHR requestって??

調べました。
XHRはAjaxと呼ばれるようなものらしい。
そしてajaxとは非同期通信のことで、ざっくり言うとjs形式のリクエストによってページ遷移を行わずページの一部の表示を変更したりできるものらしい。

参考
https://qiita.com/__tambo__/items/45211df065e0c037d032

rails tutorialではエラーメッセージをほぼ確実に表示するためhtml形式のリクエスト、つまり通常のlocalフォームリクエストを送信したいようです。
なぜremoteリクエストだとエラーメッセージの表示が確実ではないのだろう、、、
まぁ今作成しているsample_appではlocalリクエストで十分というのはわかりますけれど。

余談ですがrails 5以前のform_tagやform_forではデフォルトがリモートフォームではなくローカルフォームだったようです。


次にHTMLのフォームタグについても無知でしたので調べました。

参考記事①
http://www.htmq.com/html5/form.shtml
フォームタグについてのざっくりとした説明。

参考記事②
https://developer.mozilla.org/ja/docs/Learn/Forms/How_to_structure_an_HTML_form
labelついてなども書かれています。


ブロック変数(f)はform_withの第一引数に渡したオブジェクト(今回で言えば@user)の属性に対するinputタグを生成するメソッドを呼び出すもの。

参考
https://rakuda3desu.net/rakudas-rails-tutorial7-2/


Railsは@userのクラスがUserであることを認識します。また、@userは新しいユーザーなので、 Railsはpostメソッドを使ってフォームを構築すべきだと判断します。(rails tutorial 7章より引用)]

この部分についてどういうことか調べました。

参考記事①
https://qiita.com/hmmrjn/items/24f3b8eade206ace17e2
関連するモデルがあり@userがDBに存在するときはupdateアクションに、ないときはcreateアクションに飛ぶということのようです。

参考記事②
https://pikawaka.com/rails/form_with
こちらの記事もわかりやすかったです。

コントローラーで作成したインスタンスがnewメソッドで新たに作成されて何も情報を持っていなければ自動的にcreateアクションへ、findメソッドなどで作成され、すでに情報を持っている場合はupdateアクションへ自動的に振り分けてくれます。(上の記事より引用)

大分よく理解出来た気がします、、、

7.3.1 正しいフォーム

paramsには複数のハッシュに対するハッシュ(hash-of-hashes: 入れ子になったハッシュ)が含まれます。デバッグ情報では、フォーム送信の結果が、送信された値に対応する属性とともにuserハッシュに保存されています。このハッシュのキーが、inputタグにあったname属性の値になります。例えば次のように

<input id="user_email" name="user[email]" type="email" />

"user[email]"という値は、userハッシュの:emailキーの値と一致します。(rails tutorial 7章より引用)]

ここ大事なポイント!!

7.3.2 Strong Parameters

params.require(:user).permit(:name, :email, :password, :password_confirmation)

requireメソッドって?
受け取るパラメータ群を指定

permitメソッドって?
利用可能なパラメータ名を指定

参考
https://techacademy.jp/magazine/22078

7.3.4 失敗時のテスト

演習1
エラー発生!!

test/integration/users_signup_test.rb
#リスト 7.25: エラーメッセージをテストするためのテンプレート
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#<CSS id for error explanation>'
    assert_select 'div.<CSS class for field with error>'
  end
  .
  .
  .
end

リスト7.25を記述してtestを行ったら

ERROR["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x000000000adddc40 @name="UsersSignupTest">, 0.3698949000099674]
 test_invalid_signup_information#UsersSignupTest (0.37s)
Nokogiri::CSS::SyntaxError:         Nokogiri::CSS::SyntaxError: unexpected '#' after '[#<Nokogiri::CSS::Node:0x000000000addfc70 @type=:ELEMENT_NAME, @value=["div"]>]'
            test/integration/users_signup_test.rb:13:in `block in <class:UsersSignupTest>'

とエラーが、、

リスト7.25ので追記されているは

assert_select 'div#<CSS id for error explanation>'
assert_select 'div.<CSS class for field with error>'

以上の2文です。

<CSS id for error explanation>

あ。。。
ここはヒントが書かれているだけであって自分でそのidを書かないといけないじゃん、、、
ということで

assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'

と修正しました。

7.4.1 登録フォームの完成

app/controllers/users_controller.rb
#リスト 7.26: 保存とリダイレクトを行う、userのcreateアクション
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

redirect_to @user

上記のコードについて何となく意味はわかるが詳しく書かれている記事があるため参考にしました。

参考
https://qiita.com/Kawanji01/items/96fff507ed2f75403ecb

しかし、 チュートリアル5.3.2 によると、Railsの規約では、基本的にはリンクには相対パスを使うべきですが、リダイレクトのリンクでは絶対パスを利用すべきとのことです。(上の記事より引用)

あれそうだったっけ?と調べると

Railsチュートリアルでは一般的な規約に従い、基本的には_path書式を使い、リダイレクトの場合のみ_url書式を使うようにします。これはHTTPの標準としては、リダイレクトのときに完全なURLが要求されるためです。(rails tutorial 5章より引用)]

本当でした、、、

7.4.3 実際のユーザー登録

問題発生!!
データベースをリセットするため、

rails db:migrate:reset

を実行。
しかしリセットできない。

ermission denied @ apply2files - C:/environment/sample_app/db/development.sqlite3
Couldn't drop database 'db/development.sqlite3'
rails aborted!
Errno::EACCES: Permission denied @ apply2files - C:/Users/81801/environment/sample_app/db/development.sqlite3
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:drop:_unsafe
(See full trace by running task with --trace)

解決
以下の記事を参考に解決しました。
https://teratail.com/questions/67393
https://qiita.com/Toshiki23/items/f366504844fd22ad87d9

どうやらwindowsでデータベースにsqlite3を利用していると
rails dropコマンド
rails db:resetコマンド
rails db:migrate:resetコマンド
において問題が発生してしまうようです。
悲しいかなwindows...

記事にあるようdb/development.sqlite3ファイルをエディタのエクスプローラーより手動で消し(コマンドプロンプトではrmコマンドがフォローされていなかったため)、

rails db:create db:migrate

を改めて実行したところ

Database 'db/development.sqlite3' already exists
Database 'db/test.sqlite3' already exists

あれ?消したはずなのに残っている、、、
使用しているエディタからは確かにdevelopment.sqlite3ファイルは消えている、、、

、、、、どうしよう!!!!!
とりあえず削除したdevelopment.sqlite3ファイルの行方を捜してみました。

参考
https://maitakeramen.hatenablog.com/entry/2018/02/08/131755

ゴミ箱を探してもなぜか見当たらない、、、
(やっぱり削除されていないからか??)

探しに探し、windowsのエクスプローラーからsample_app/dbディレクトリを見てみるとdevelopment.sqlite3ファイルがありました!!

とりあえず開いているエディタのdbディレクトリにコピーしました。

試しにDB Browser for SQliteでデータを覗いて見たらusersテーブルのデータは空になっていました。

原因を調べてみましたがわかりませんでした。
とりあえずデータベースのリセットは達成したこととし、次に進むことにします、、、

余談
この後に、Git Bashを使えばLinuxコマンドも使えるということを教えてもらい、以降はそちらも使って学習を進めることとしました。

7.4.4 成功時のテスト

演習2
演習は問題なくクリア出来ましたが、content_tagヘルパーについて調べました。
content_tagヘルパー
content_tag(:要素名, 表示させる内容, オプション)

参考
https://techacademy.jp/magazine/42167

終わりに

内容が急に難しくなってきました。
tutorialでは当たり前に使われていたUserリソースのような概念を理解するのも大変でした。
form_withヘルパーを使ったフォームの生成も一度ではなかなか理解できず、何度か読み返しました。
度々エラーにも遭遇し、時間がかかりました。

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

自分用 rails 定数の置き場所

置き場所

config/initializers/**.rb
module Constants
  CONSTANT = '定数だよー'
end
Constants.freeze

使い方

Constants::CONSTANT
#=> '定数だよー'

配列など場合によってfreezeを使い分ける

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