20210228のRailsに関する記事は26件です。

【Rails】関連先のデータを、中間テーブルで絞り込む。

目的

多対多のアソシエーションを組んでいて、関連テーブルのデータもincludeなどで取得するとき、
関連テーブル先のデータを中間テーブルのデータで絞り込む方法です。

前提

患者: patient医師: physicianとの診察予約: appointmentをとります。
診察予約が中間テーブルになります。
Railsガイドを参考にしました。

migrate

db/migrate/xxxxxxxxxxxxxx_create_physicians.rb
# 医師
class CreatePhysicians < ActiveRecord::Migration[6.1]
  def change
    create_table :physicians do |t|
      t.string :name

      t.timestamps
    end
  end
end
db/migrate/xxxxxxxxxxxxxx_create_appointments.rb
# 診察予約(中間テーブル)
class CreateAppointments < ActiveRecord::Migration[6.1]
  def change
    create_table :appointments do |t|
      t.belongs_to :physician, null: false, foreign_key: true
      t.belongs_to :patient, null: false, foreign_key: true
      t.datetime :appointment_date

      t.timestamps
    end
  end
end
db/migrate/xxxxxxxxxxxxxx_create_patients.rb
# 患者
class CreatePatients < ActiveRecord::Migration[6.1]
  def change
    create_table :patients do |t|
      t.string :name

      t.timestamps
    end
  end
end

belongs_toreferencesのエイリアスです。GitHub rails/…/schema_definitions.rb

models

app/models/physician.rb
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end
app/models/appointment.rb
class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end
app/models/patient.rb
class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

db

【 Physicians 】

id name
1 physician_A
2 physician_B

【 Appointments 】

id physician_id patient_id appointment_date
1 1 1 2021/01/10 10:00
2 1 2 2021/03/30 10:00
3 1 3 2021/03/20 14:30
4 2 2 2021/01/20 15:00

【 Patients 】

id name
1 patient_01
2 patient_02
3 patient_03

方法

app/models/physician.rb
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments

  has_many :future_appointments, -> { where(appointment_date: Date.today.. }, class_name: 'Appointment'
  has_many :future_patients, through: :future_appointments, source: :patient
Physician.preload(:future_patients)

使用オプション等

意味 参考URL
-> { } スコープ Railsガイド
.. 範囲式 リファレンスマニュアル
class_name 別名使用時のモデル名指定 Railsガイド
source 別名使用時のモデル名指定 Railsガイド

解説

取得したいデータは、

  • 全ての医師: physicianのデータ
  • 今日以降の診察予約: appointmentがある、患者: patientのデータ

です。

失敗例

初め、単純にwhereを繋げて絞り込みをしようとしましたが、失敗でした。

Physician.eager_load(:patients).where(appointments: { appointment_date: Date.today..Float::INFINITY})

上記の解説をすると、
まず、eager_loadで関連テーブルのデータも一緒に取得します。
今回は絞り込みも行いたいため、preloadではなくeager_loadを使用しています。includesでも結果は同じです。

次にwhereで絞り込みをします。
where(<テーブル名>: { <カラム名>: <条件> })で、関連テーブルでの絞り込みができます。Railsガイド
条件として、Date.todayで今日の日付を、..で範囲指定します。
Float::INFINITYは正の数の無限大です。リファレンスマニュアル
Float::INFINITYは省略することもできます。{ appointment_date: Date.today.. }

しかし、これで発行されるクエリは、

SELECT `physicians`.`id` AS t0_r0,
       `physicians`.`name` AS t0_r1,
       `physicians`.`created_at` AS t0_r2,
       `physicians`.`updated_at` AS t0_r3,
       `patients`.`id` AS t1_r0,
       `patients`.`name` AS t1_r1,
       `patients`.`created_at` AS t1_r2,
       `patients`.`updated_at` AS t1_r3
FROM `physicians`
LEFT OUTER JOIN `appointments` ON `appointments`.`physician_id` = `physicians`.`id`
LEFT OUTER JOIN `patients` ON `patients`.`id` = `appointments`.`patient_id`
WHERE `appointments`.`appointment_date` >= '2021-02-28'

SELECTで取得するカラムを指定。ASはカラムに別名をつけています。
FROMは取得するテーブル名。
LEFT OUTER JOINで同時に取得するテーブルを指定。ONで条件付けています。
そして、WHEREで今日以降のデータに絞り込んでいます。

となり、今日以降の診察予約がある、医師と患者のデータのみを取得し、全ての医師のデータを取得できません。

physician = Physician.eager_load(:patients).where(appointments: { appointment_date: Date.today..Float::INFINITY})

physician
=> [<id: 1, name: "physician_A">]

physician[0].patients
=> [<id: 2, name: "patient_02">,
    <id: 3, name: "patient_03">]

成功

スコープ

色々と調べた結果、スコープを使用することでうまくいきました。

class <モデル名> < ApplicationRecord
  has_many :<関連モデル名>, -> { <クエリメソッド> }
end

スコープを使用することで、モデルの関連モデルとして呼び出したとき、クエリメソッドが実行されます。

例えば、著者: author書籍: bookが1対多の関係であったとき、

models/author.rb
class Author < ApplicationRecord
  has_many :books, -> { order(:publication_date) }
end

とすることで、

author.books

としたとき、書籍: book出版日: publication_dateの順番で取得することができます。


これを利用して多対多のアソシエーションの中間テーブルでの絞り込みを行います。
今回は中間テーブルである診察予約: appointmentの日付でクエリメソッドwhereを使用したいため、

app/models/physician.rb
class Physician < ApplicationRecord
  has_many :appointments, -> { where(appointments_date: Date.today..) }
  has_many :patients, through: :appointments
end

となります。

Date.todayで今日の日付を取得し、..で範囲指定。末端を指定しないことで、以降全てが範囲となります。
(..の末端未指定はRuby 2.6.0以降でのみ使用可能です。それ以前のバージョンの場合は、Float::INFINITYを指定してください。リファレンスマニュアル)

データの取得は、

Physician.preload(:patients)

と記述します。

今回は、eager_loadではなくpreloadを使用しています。
whereによる絞り込みをappointmentsテーブル単体で行い、joinsなどで他テーブルを参照する必要がないためです。
これはincludesでも同じ結果となります。

このとき発行されるクエリは、

Physician Load (0.8ms)  SELECT `physicians`.* FROM `physicians`
Appointment Load (0.8ms)  SELECT `appointments`.* FROM `appointments` WHERE `appointments`.`appointment_date` >= '2021-02-28' AND `appointments`.`physician_id` IN (1, 2)
Patient Load (0.5ms)  SELECT `patients`.* FROM `patients` WHERE `patients`.`id` IN (2, 3)

となります。

1行目で医師: physicianのデータを取得。SELECTphysiciansの全てのカラム*を取得しています。FROMはテーブルの指定です。

2行目は診察予約: appointmentのデータを取得しています。これも同じくSELECTで全てのカラム*を取得していますが、WHEREで絞り込みを行っています。
`appointments`.appointment_date` >= '2021-02-28'は、app/models/physician.rbに記載したスコープのクエリメソッドになります。
さらに、AND`appointments`.`physician_id` IN (1, 2)とすることで、1行目で取得した医師: physicianidに関連するデータのみを取得しています。

最後に、3行目で患者: patientのデータ取得です。WHERE`patients`.`id` IN (2, 3)と条件付けすることにより、patientsidが、2行目で取得した診察予約: appointmentに関連するデータのみを取得しています。

これで、全ての医師: physicianと、本日以降の診察予約: appointmentがある患者: patientのデータを取得することができます。

physician = Physician.preload(:patients)

physician
=> [<id: 1, name: "physician_A">,
    <id: 2, name: "physician_B">]

physician[0].patients
=> [<id: 2, name: "patient_02">,
    <id: 3, name: "patient_03">]

physician[1].patients
=> []

しかし、今の状態では、アソシエーションで呼び出す全ての場合にクエリメソッドが効いてしまいます。
つまり、今日(2021/02/28)以前のデータをアソシエーションで呼び出すことができない状態です。

そのため、モデルの別名を作成し、絞り込みの際はそちらを使用することにします。

class_name

class_nameは、モデルを別名で使用する際、どのモデルのかを指定することができます。

class モデル名 < ApplicationRecord
  has_many :関連モデルの別名, class_name: '関連モデル名'
end

例えば、筆者: authorと1対多である書籍: bookがあり、
書籍: booksテーブルのcommicカラムがtrueのとき、漫画であるとすると、

app/models/author.rb
class Author < ApplicationRecord
  has_many :comics, -> { where(commic: true) }, class_name: 'Book'
end

と、モデルに記述することで、

author.commics

のように、モデルの別名を使用して、書籍: booksテーブルの中の漫画のみをアソシエーションで取得することができます。
class_nameのモデル名は文字列指定・頭大文字・単数形であることに注意してください。


しかし、今回の場合は多対多のアソシエーションを使用しています。
中間テーブルにはclass_nameのオプションでいいのですが、アソシエーション先でthroughを使用しているとうまくいきません。

source

アソシエーションでthroughを使用している場合、別名をつける際はclass_nameの代わりに、sourceを使用します。

class モデル名 < ApplicationRecord
  has_many :中間テーブルのモデルの別名, class_name: '中間テーブルのモデル名'
  has_many :関連モデルの別名, through: :中間テーブルのモデルの別名, source: :関連モデル名
end

今回のように、別名の中間テーブルのモデルを使用する場合、throughの名前も別名を指定しなければなりません。
また、class_nameのときとは違い、sourceでは、シンボル指定・頭小文字・単数形で指定しています。単数形であることは変わりません。

よって、

app/models/physician.rb
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments

  has_many :future_appointments, -> { where(appointment_date: Date.today..) }, class_name: 'Appointment'
  has_many :future_patients, through: :future_appointments, source: :patient
end

と記述し、

# 全て
physician.preload(:patients)

# 今日以降
physician.preload(:future_patients)

のように、使い分けることができます。

注意点: 引数は使用できません。

色々と調べたのですが、引数の使用はできないようでした。
Date.todaytrue falseなど、値が決まっていたらhas_manyのスコープが使えるのですが、whereで日付を指定するなどはできませんでした。

# これはできません。
class Author < ApplicationRecord
  has_many :books, ->(date) { where(created_at: date) }
end


引数?として使用できるのはそのモデルのデータだけのようです。
GitHub rails/…/associations.rb

最後に

言い回しや解釈の間違っているところ、さらに良い方法がございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。

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

Association ; 1対1、1対多、多対多について

Association

Rails には関連 (Association) という仕組みがある。関連付けを用いて二つのモデルをつなぐことで、コードをシンプル、かつ簡単に書くことができる。
関連付けの関係は大きく分けて以下の三種類に分類される。

  • 1 対 1
  • 1 対 多
  • 多 対 多

1 対 1 (has_one, belongs_to)

例えば、Teacher, Class, Student, Subject のモデルで以下の図のような関係性があったとします。
image.png

まずはTeacherとClassの関係性を見てみます。
image.png

ここで表されているのは、

  • 原田先生 は 2-1 を
  • 山川先生 は 2-2 を

担任しているということがわかります。
この場合、Teacherモデルのデータひとつに対し、Classモデルのデータがひとつだけ割り当てられているため、1対1の関係性となっているということになります。
この関係性をコードで表すと以下のようになります。

class Teacher < ApplicationRecord
    has_one :Class
end

class Class < ApplicationRecord
    belongs_to :Teacher
end

1 対 多 (has_many, belongs_to)

次に ClassとStudentの関係性を見てみます。
image.png

ここで表されているのは

  • 2-1 には 田中さんと矢島さん
  • 2-2 には 中村さんと木下さん

が属しているということがわかります。
この場合、Classモデルのデータひとつに対し、Studentモデルのデータが複数割り当てられているため、1対多の関係性になっているということになります。
この関係性をコードで表すと以下のようになります。

class Class < ApplicationRecord
    has_many :Students
end

class Student < ApplicationRecord
    belongs_to :Class
end

多 対 多 (has_and_belongs_to_many)

最後に、StudentとSubjectの関係性を見てみます。
image.png

線が多いため、Studentモデルのデータごとに色分けしております。
ここで表されているのは

  • 田中さんは 国語、英語、数学 を受講する
  • 矢島さんは 国語、英語、数学 を受講する
  • 中村さんは 国語、英語、数学 を受講する
  • 木下さんは 国語、英語、数学 を受講する

ということがわかります。
ここで、線の色分けをSubjectモデルのデータごとに変えてみます。
image.png

この場合、表されているのは

  • 国語を受講するのは 田中さん、矢島さん、中村さん、木下さん
  • 英語を受講するのは 田中さん、矢島さん、中村さん、木下さん
  • 数学を受講するのは 田中さん、矢島さん、中村さん、木下さん

ということになります。
つまり、Studentモデルのデータひとつに対し、Subjectモデルのデータが複数割り当てられており、またSubjectモデルのデータひとつに対し、Studentモデルのデータが複数割り当てられています。そのため、この関係性は多対多ということになります。
この関係性をコードで表すと以下のようになります。

class Student < ApplicationRecord
    has_and_belongs_to_many :Subjects
end

class Subject < ApplicationRecord
    has_and_belongs_to_many :Students
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】 Deviseを複数導入する場合

はじめに

deviseを導入すると、簡単に認証機能が実装できて本当に便利なgemですよね!
Ruby on Railsを学び始めて2ヶ月目の頃、ユーザーと管理者に分けてdevise機能を2つ実装したい、こういった時はどうしたら良いのか、また複数のdevise間で両者とも使用するモデルに関してはどうしたら良いのか、と少し悩んだことがありましたので、今回の記事のテーマに選定致しました。

前提

今回説明するモデルを以下の三点に絞らせて頂いております。
● devise User
● devise Admin
● 両者に共通してItemモデルを使用


開発環境

ruby 2.6.3
Rails 5.2.4.4

gemを導入

Gemfile.
gem 'devise'
terminal.
$ bundle install


ユーザー用、管理者用のdevise機能実装

deviseを複数実装することは意外と簡単です!
User、Adminのように適宜名前を付け、各コマンドを実行すればモデル、ビュー、コントローラーは作成出来ます。

terminal.
$ rails g devise:install

# ユーザー用のdevise作成
$ rails g devise User name:string 
$ rails db:migrate
$ rails g devise:views users
$ rails g devise:controllers users

# 管理者用のdevise作成
$ rails g devise Admin
$ rails db:migrate
$ rails g devise:views admins
$ rails g devise:controllers admins


model / controller

ここで少し頭を悩ませることになります。
ECサイトを例にあげると、Item(商品)モデルを必ず作成するかと思います。このItemモデルの働きをユーザーと管理者それぞれの立場で考えてみましょう。

● ユーザー
 (例)商品を閲覧する、商品の詳細画面へ飛ぶ、注文画面へ遷移する

● 管理者
 (例)商品を登録する

つまり、Itemモデルひとつをとっても、ユーザーと管理者で見る画面も違ければ、コントローラーの記述も異なるわけです。では、ビューとコントローラーをそれぞれで作成するにはどうしたら良いのかをご説明致します。答えは、ビューとコントローラーで「user」、「admin」という親ディレクトリを用意して、その中に各モデルのビューやコントローラーを作成するという形になります。

※ 「users」、「admins」というディレクト名だと先ほど作成したdivise用のものと被ってしまい作成できません。そのため単数形の名称にしてあります。どうしても違和感があり気に入らないという場合でしたら、userの代わりに「customers」、「members」などにしても良いかと思います。

terminal.
$ rails g model Item

$ rails g controller user::items
$ rails g controller admin::items

(補足)
● モデルは共通で使用できるので、1つのみの作成で大丈夫です。

rails g controller (親ディレクトリ名)::(その中に作成するディレクトリ名)
  上記のように記述することで、自動的に親ディレクトを作成し、その中にコントローラーを作成してくれます。

user/items_controller.rb
class User::ItemsController < ApplicationController

end

コントローラーのディレクトリを確認すると、userディレクトリ内にitems_controller.rb、adminディレクトリ内にitems_controller.rbとひとつのモデルに対し、2つのコントローラーを作成することが出来ました。
ちなみに、コントローラーを作成するときのコマンドでコロンを2つ(::)入力しましたが、これらはコントローラーのファイルのClass名の部分にもきちんと反映されています。どのディレクトリに準ずるコントローラーか、ということが明確になっております。

View

先ほどコントローラーを作成した際に自動的にuserディレクトリadminディレクトリが作成されましたが、それに伴い、viewの方にも空のuser/itemsディレクトリadmin/itemsディレクトリが作成されています。
それぞれのディレクトリの上で右クリックを押し、必要なファイルを作成してください。

ルーティング

次に悩んだポイントがルーティングの作成です。
共通で使用しているモデルのルーティングに関しても、別々に作成する必要があります。
ルーティング作成時のポイントを3つ簡単に押さえておきます。

scope

URL: 変える   (例) user/items, user/item/:id
ファイル構成: 変えない

namespace

URL: 変える   (例) user/items, user/item/:id
ファイル構成: 指定のパスに変える

module

URL: 変えない  (例) /items, /item/:id
ファイル構成: 指定のパスに変える

config/routes.rb
Rails.application.routes.draw do
  root 'homes#top'

  devise_for :admins
  namespace :admin do
    resources :items
  end

  devise_for :users
  namespace: :user do
    resources :items
  end
end

(補足)
今回は、自分自身が分かりやすようにディレクトリを分け、またURLにもuserとadminと表示させ分かりやすくしたかった為、namespaceを採用しました。
ご自身の用途に合わせて選択していただければ良いかと思います。
scopenamespacemoduleについて分かりやすくまとめてくださっているサイトがあり、参考にさせていただきましたので、下記にURLを掲載しておきます。

(参考)
・Railsのroutingにおけるscope / namespace / module の違い
https://qiita.com/ryosuketter/items/9240d8c2561b5989f049

終わり

今回は以上になります。
ざっくりとした説明で分かりにくい点もあるかと思いますが、申し訳ありません。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思っています。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

railsのgem「pagy」を使ってページネーション

私がページネーションにpagyを選んだ理由

下記のリンクにて、メモリの負荷がwill-paginateやkaminariより軽いらしいとのことです。

記事の中に以下のグラフを見つけました。ページ数と使用メモリ数の関係をグラフ化したものですが、Pagyが圧倒的です。こりゃPagyを選びますね。

image.png

railsのgem「pagy」を使ってページネーション

以下の記事を参考にしたら初学者の私でも10分でページネーションできました。

2番目の記事が私的におすすめです。githubにもソースコードが上がっているので。

注意、bootstrapでエラーでます!

ちなみにですが、bootstrapを導入する際は以上の記事を参考にするとエラーがでました。
1年前の記事なので、pagyのbootstrapのソースコードが少し変わった??ようです。

私の場合ユーザー一覧のページネートを作るべく、記事にしたがって下記のコードを書きました。

 <% provide(:title, 'All users') %>
<h1>ユーザー一覧</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= link_to user.name, user %>
    </li>
  <% end %>
  <div class="d-flex justify-content-left mb-2">
    <%== pagy_nav_bootstrap(@pagy) %>
  </div>

しかしながらスクリーンショット 2021-02-28 22.31.04.png

NoMethodErrorぁぁぁぁぁエラー発動ぅぅぅぅぅ!!!

pagy のbootstrapのオフィシャルっぽいところの記事を参照し

 <%== pagy_nav_bootstrap(@pagy) %>

のところを

 <%== pagy_bootstrap_nav(@pagy) %>

に変えてOK!

スクリーンショット 2021-02-28 22.36.50.png

いい感じにページネーションできました。

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

【Ruby on Rails】before_actionを使って全てのアクションで同じ処理を行う

同じ処理をbefore_actionで定義する。

あるコントローラー内の全てのアクションで1つの処理を使い回したい時。

class Api::V1::Admin::CompanyAdminsController < ApplicationController

  def index
    company_admins = CompanyAdmin.where(company_id: params[:company_id])
    json_response(200, CompanyAdminIndexSerializer.new(company_admins).to_h)
  end

  def show 
    company_admin = CompanyAdmin.find params[:id]
    json_response(:ok, serialize_data(CompanyAdminIndexSerializer, company_admin))
  end

  def destroy
    company_admin = CompanyAdmin.find params[:id]
    company_admin.destroy!
    json_response(204)
  end

end

このコントローラー内を見てみると、show・destroyアクションで同じ処理が書かれている。

company_admin = CompanyAdmin.find params[:id]

これをまとめるために、before_actionと記述する。
これによって、全てのアクションで最初に行いたい処理を実行できる。

  def load_company_admin
    @company_admin = CompanyAdmin.find params[:id]
  end
end

例えば、上のようなメソッドをコントローラー内に記述する。
そして、コントローラー内の一番上にbefore_actionの記述を加える。

最終的なコード

class Api::V1::Admin::CompanyAdminsController < ApplicationController
  before_action :load_company_admin, only: [:show, :destroy] 
  ##onlyオプションを付け加えることで、showとdestroyアクションのときと指定ができる。

  def index
    company_admins = CompanyAdmin.where(company_id: params[:company_id])
    json_response(200, CompanyAdminIndexSerializer.new(company_admins).to_h)
  end

  def show 
    json_response(:ok, serialize_data(CompanyAdminIndexSerializer, @company_admin))
  end

  def destroy
    @company_admin.destroy!
    json_response(204)
  end

  private
  def load_company_admin ##処理の名前を定義する。
    @company_admin = CompanyAdmin.find params[:id]
  end
  ##ここで同じ処理を記述する。
end

少しだけ、このファイルの記述がスッキリした気がする。
何度も同じ記述を書くのはエンジニアとして、あまり優秀ではない気がしているので、できるだけ同じ処理はまとめていきましょう。

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

【Railsチュートリアル】第10章 ユーザーの更新・表示・削除②

10.3 すべてのユーザーを表示する

indexアクションを追加して、すべてのユーザーを一覧表示する。データベースにサンプルデータを追加する方法や、将来ユーザー数が膨大になってもindexページを問題なく表示できるようにするためのユーザー出力のページネーション(pagination=ページ分割)の方法を学ぶ。

10.3.1 ユーザーの一覧ページ

ユーザーのindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。

indexアクションが正しくリダイレクトするか検証するテストを書く。

test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end

  test "should get new" do
    get signup_path
    assert_response :success
  end

  test "should redirect index when not logged in" do
    # ログインしていない場合はインデックスをリダイレクトするテストを行う
    get users_path
    assert_redirected_to login_url
  end
  .
  .
  .

beforeフィルターのlogged_in_userにindexアクションを追加して、このアクションを保護。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
    # 「indexアクションが呼び出されたらlogged_in_userアクションを実行する」を追加
  before_action :correct_user,   only: [:edit, :update]

  def index
  end

  def show
    @user = User.find(params[:id])
  end
  .
  .
  .
end

indexビューの実装。User.allを使ってデータベース上の全ユーザーを取得し、ビューで使えるインスタンス変数@usersに代入する。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    @users = User.all
  end
  .
  .
  .
end
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

users_helper.rb, custom.scss, _header.html.erbにコードを追加してrails.test

演習 1

レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。

test/integration/site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", login_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
    assert_select "title", full_title("Sign up")
  end

  def setup
    @user = users(:michael)
  end

  test "layout links when loged in" do
    log_in_as(@user)
      # => テストユーザーでログイン
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", users_path
      # => /users ユーザー一覧ページ
    assert_select "a[href=?]", user_path(@user)
      # => /users/:id Profileページ
    assert_select "a[href=?]", edit_user_path(@user)
      # => /users/1/:id Settingsページ
    assert_select "a[href=?]", logout_path
  end
end

10.3.2 サンプルのユーザー

indexページに複数のユーザーを表示させる。

演習 1

試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。
確認のみなので省略。

10.3.3 ページネーション

1つのページに一度に30人だけユーザーを表示するというようなページネーション(pagination)を実装する。

演習 1

Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。

>> User.paginate(page: nil)
   (0.1ms)  begin transaction
  User Load (2.9ms)  SELECT "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
   (1.7ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-02-26 14:35:28", updated_at: "2021-02-26 14:35:28", password_digest: [FILTERED], remember_digest: nil>, #<User id: 2, name: "William Bartell", email: "example-1@railstutorial.org", created_at: "2021-02-26 14:35:30", updated_at: "2021-02-26 14:35:30", password_digest: [FILTERED], remember_digest: nil>, #<User id: 3, name: "Carter Feest", email: "example-2@railstutorial.org", created_at: "2021-02-26 14:35:30", updated_at: "2021-02-26 14:35:30", password_digest: [FILTERED], remember_digest: nil>, #<User id: 4, name: "Arlinda Douglas Sr.", email: "example-3@railstutorial.org", created_at: "2021-02-26 14:35:30", updated_at: "2021-02-26 14:35:30", password_digest: [FILTERED], remember_digest: nil>, #<User id: 5, name: "Dr. Monika Goyette", email: "example-4@railstutorial.org", created_at: "2021-02-26 14:35:31", updated_at: "2021-02-26 14:35:31", password_digest: [FILTERED], remember_digest: nil>, #<User id: 6, name: "Mr. Hilda Prohaska", email: "example-5@railstutorial.org", created_at: "2021-02-26 14:35:31", updated_at: "2021-02-26 14:35:31", password_digest: [FILTERED], remember_digest: nil>, #<User id: 7, name: "Theo Russel", email: "example-6@railstutorial.org", created_at: "2021-02-26 14:35:31", updated_at: "2021-02-26 14:35:31", password_digest: [FILTERED], remember_digest: nil>, #<User id: 8, name: "Robbie Littel", email: "example-7@railstutorial.org", created_at: "2021-02-26 14:35:31", updated_at: "2021-02-26 14:35:31", password_digest: [FILTERED], remember_digest: nil>, #<User id: 9, name: "Azzie Walter", email: "example-8@railstutorial.org", created_at: "2021-02-26 14:35:32", updated_at: "2021-02-26 14:35:32", password_digest: [FILTERED], remember_digest: nil>, #<User id: 10, name: "Shanell Nicolas", email: "example-9@railstutorial.org", created_at: "2021-02-26 14:35:32", updated_at: "2021-02-26 14:35:32", password_digest: [FILTERED], remember_digest: nil>, ...]>

演習 2

先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。

class: User::ActiveRecord_Relation
User.allと同じ。

10.3.4 ユーザー一覧のテスト

ページネーションに対する簡単なテストも書いておく。
ログイン→indexページにアクセス→最初のページにユーザーがいることを確認→ページネーションのリンクがあることを確認の手順でテストしていく。

演習 1

試しにリスト 10.45にあるページネーションのリンク(will_paginateの部分)を2つともコメントアウトしてみて、リスト 10.48のテストが red に変わるかどうか確かめてみましょう。

 FAIL["test_index_including_pagination", #<Minitest::Reporters::Suite:0x000056137403b7a8 @name="UsersIndexTest">, 14.158692726000027]
 test_index_including_pagination#UsersIndexTest (14.16s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>'

red

演習 2

先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが green のままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。

test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2 # 「count: 2」を追記
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end
 FAIL["test_index_including_pagination", #<Minitest::Reporters::Suite:0x0000561374244360 @name="UsersIndexTest">, 16.980905499999608]
 test_index_including_pagination#UsersIndexTest (16.98s)
        Expected exactly 2 elements matching "div.pagination", found 1..
        Expected: 2
          Actual: 1
        test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>'

10.3.5 パーシャルのリファクタリング

一覧ページのリファクタリングを行う。

演習 1

リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が red に変わることを確認してみましょう。

REDになりました。

10.4 ユーザーを削除する

削除を実行できる権限を持つ管理(admin)ユーザーのクラスを作成する。
次にユーザーを削除するためのリンクを追加し、削除を行うのに必要なdestroyアクションも実装する。

10.4.1 管理ユーザー

Boolean型 とは?
真理値の「真 = true」と「偽 = false」という2値をとるデータ型のこと。

toggle!メソッド とは?
toggle!(:flag)と書き、属性(:flag)を反転し保存するメソッド。saveに成功したらtrueを返す。

演習 1

Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL(/users/:id)に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は red になるはずです。最後の行では、更新済みのユーザー情報をデータベースから読み込めることを確認します( 6.1.5)。

test/controllers/users_controller_test.rb
  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
      # テストユーザー(@other_user)でログイン
    assert_not @other_user.admin?
      # @other_userは管理者か?=> 管理者ではない => false
      # @other_user.admin?がfalse => 成功
    patch user_path(@other_user), params: { user: { password: "password", password_confirmation: "password", admin: true } }
      # @other_userに管理者権限を持たせる => admin: true
    assert_not @other_user.reload.admin?
      # 更新した@other_userは管理者か? => 管理者ではない => false
      # @other_user.reload.admin?がfalse => 成功
  end

assert_not とは?
assert_not hogeの場合 => hoge が trueなら失敗、falseなら成功。

10.4.2 destroyアクション

destroyアクションへのリンクを追加する。
ユーザーindexページの各ユーザーに削除用のリンクを追加し、続いて管理ユーザーへのアクセスを制限する。

演習 1

管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?

Started DELETE "/users/4" for 10.0.2.2 at 2021-02-28 19:28:49 +0900
Cannot render console from 10.0.2.2! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by UsersController#destroy as HTML
  Parameters: {"authenticity_token"=>"ZUaUvmxMfqGHERKzsE1flF1BHJkxUkLesSKKx047CAkzYAOz6jB3wQrDRQ2xHloAba4P/r9CRI1XAATfArRZTw==", "id"=>"4"}
  User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   app/helpers/sessions_helper.rb:18:in `current_user'
  User Load (1.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  ↳ app/controllers/users_controller.rb:52:in `destroy'
   (0.1ms)  begin transaction
  ↳ app/controllers/users_controller.rb:52:in `destroy'
  User Destroy (7.1ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 4]]
   app/controllers/users_controller.rb:52:in `destroy'
   (13.6ms)  commit transaction
  ↳ app/controllers/users_controller.rb:52:in `destroy'
Redirected to http://localhost:3000/users
Completed 302 Found in 46ms (ActiveRecord: 22.7ms | Allocations: 4317)

10.4.3 ユーザー削除のテスト

演習 1

試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が red に変わることを確認してみましょう。

確認のみなので省略。

さいごに

Webアプリケーションとしての基礎を整えられたようです。
Railsチュートリアルをはじめたばかりの頃はエラーが出ただけで焦っていました。
ですが今では冷静にエラーを読む、わからなければ検索するというのが自然とできるようになりました。
コードもだんだんと読み下せるようになってきました。一歩ずつ前に進んでいる気がします。次章もがんばります!

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

一覧表示での進捗率ゲージの作成

はじめに

オリジナルアプリでクラウドファンディングサイトを作成した際に、
jQueryを使って進捗率ゲージを作成したので紹介します。
前回、詳細画面での作成方法を紹介しましたが
今回は一覧表示画面での作成方法です。

Image from Gyazo

詳細画面での作成はこちら→
https://qiita.com/takiguchiharuna1221/items/df517f2a9e4b74299328#comment-bd0d8efa2c7c3dc7651e
※こちらではlinear-gradientを使った別の方法も投稿していただいています。

初学者なのでより良い方法が他にもあるかもしれませんが、どなたかの参考になれば嬉しいです。

環境

・Rails 6.0.0
・Ruby 2.6.5
・jQuery

作成手順

【view】

index.html.haml
.Contents__projects
  - @projects.each do |project| 
  # 一部省略
    .Projects 
      = link_to display_project_path(project.id) , class: "Box3"do
        .Box1
          = image_tag project.image.url.to_s, class: "Box1__image"
        .Box2
          %p.Box2__title= project.title
          %p.Box2__percent__contents 進捗率
          - @percent = number_to_percentage(@total.to_f/project.target_amount*100,precision: 1)
          = @percent
          .contents-pacent__box2__graph1
            .contents-pacent__box2__graph2
              %input{name: "percent", type: "hidden", value:@percent, class: 'percent' }

①ゲージを挿入したい部分にクラスを設定します。
 →.contents-pacent_box2_graph2の部分

②inptを用いてDBのインスタンス変数の値を取得します。
 →inputのvalueに変数を設定することでjQueryで変数を使えるようにします。
  ※今回は変数の詳細は割愛します。
 参考記事:https://qiita.com/Kohei_Kishimoto0214/items/d919b00d75dec0699cf0

【CSS】

_project.scss
.contents-pacent__box2__graph2{
  background-color: #ea662d;
  height: 15px;
  border-radius: 6px;
  max-width: 100%;
}

③CSSは以上の通りです。
 進捗率が100%以上になっても突き抜けてしまわないように
 max-width: 100%;を設定しています。

【jQuery】

project.js
$(function() {
  $('.percent').each(function(i){
    $(this).attr('id', `percent_${i}`);
  });
  $('.contents-pacent__box2__graph2').each(function(i){
    $(this).attr('id', `graph_${i}`);
  });

  (window.onload = function() {
    var ele = document.getElementsByClassName("Projects");
    for(var i = 0; i < ele.length; i++){
      $("#graph_"+i).css({ 'width' : $( "#percent_"+ i).val() } );
    };
  });
});

④.percentと.contents-pacent_box2_graph2のそれぞれにidを振ります。
⑤リロードのタイミングでProjectsの数を数え、その回数だけ
”.contents-pacent
_box2_graph2のwidthの値を@percentからとる”
を繰り返すように①で付与したidを使ってfor文で設定します。

最後に

idの使い方やfor文の設定が自分では使ったことがなかったので苦労しました。
日々勉強します。

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

RSpec:モデルの単体テストの記述例

今回は前回の記事「RSpecの導入&関係の深いgemについて」の続きで、モデルの単体テストを行います。

前回の記事の各gemのインストール、初期設定が完了していることを前提としています。

モデルのテストファイルを作成

ターミナルでrails g rspec model モデル名コマンドを入力してテストファイルを生成します。

ターミナル
$ rails g rspec model モデル名
#以下のように表示されればテストファイルの生成が成功しています。
Running via Spring preloader in process 1234
      create  spec/models/モデル名_spec.rb
      invoke  factory_bot
      create    spec/factories/モデル名の複数形.rb

たとえばuserモデルのテストファイルを生成するとこうなります。

ターミナル
$ rails g rspec:model user
Running via Spring preloader in process 1234
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb #Factory Botをインストールしておくと自動生成される

ちなみに事前にFactory Botもインストールしておくと、spec/factories/users.rbは自動生成されます。

生成したuser_spec.rbにはこのような記述がされています。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end

do〜endの間にテストコードを記述していきます。

また1行目のrequire 'rails_helperは、前回の記事で行ったrails g rspec:installコマンドで生成したrails_helper.rbを読み込んでいます。

FactoryBotの設定

まずは、FactoryBotでUserモデルのテストで使用するインスタンスを設定してまとめます。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name }
    email { Faker::Internet.free_email }
    password = Faker::Internet.password(min_length: 6)
    password { password }
    password_confirmation { password }
  end
end

passwordカラムとpassword_confirmationカラムは同じ値を入れるため、変数passwordに値を代入して、各カラムに変数passwordを入れます。

Faker::の後ろに続くNameInternet.free_emailでどんな値をランダムに生成するかを指定しています。前者なら名前、後者ならメールアドレスですね。

Fakerでは色んなタイプの値を生成してくれるので、Fakerの公式GitHubで見てみてください。

テストコードの設定

Userモデル(usersテーブル)にnameカラム、emailカラム、passwordカラムがあると想定して、
たとえば以下のようにユーザー新規登録機能のテストコードを記述します。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  before do
    # 各テストコードが実行される前にFactoryBotで生成したインスタンスを@userに代入
    @user = FactoryBot.build(:user)
  end

  # describeでテスト対象をグループ分け
  describe '新規登録' do
    # contextでどのような状況なのかをグループ分け
    context '新規登録できるとき' do
      # itで何をテストしているのかをグループ分け
      it '全ての項目が入力されていれば登録できる' do
        expect(@user).to be_valid
      end
    end

    context '新規登録できないとき' do
      it 'nameが空では登録できない' do
        @user.name = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("Name can't be blank")
      end
      it 'emailが空では登録できない' do
        @user.email = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("Email can't be blank")
      end
      it '重複したemailが存在する場合登録できない' do
        @user.save
        another_user = FactoryBot.build(:user)
        another_user.email = @user.email
        another_user.valid?
        expect(another_user.errors.full_messages).to include('Email has already been taken')
      end
      it 'emailに@が含まれていないと登録できない' do
        @user.email.slice!('@')
        @user.valid?
        expect(@user.errors.full_messages).to include('Email is invalid')
      end
      it 'passwordが空では登録できない' do
        @user.password = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("Password can't be blank")
      end
      it 'passwordが5文字以下では登録できない' do
        @user.password = '123ab'
        @user.valid?
        expect(@user.errors.full_messages).to include('Password is too short (minimum is 6 characters)')
      end
      it 'passwordが存在してもpassword_confirmationが空では登録できない' do
        @user.password_confirmation = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("Password confirmation doesn't match Password")
      end
    end
  end
end

before do ~ end

テストコードの最初のbefore do ~ endは、describe以下の各テストコードが実行される前に処理される共通の内容を記述します。
今回の場合は登録するユーザー情報を変数@userに代入する処理を記述しています。
これで各テストコードでユーザー情報が入った変数@userを使えるようになりました。

describeメソッド、contextメソッド、itメソッド

テストコードのグループ分けに使用するメソッドで、

  • describeメソッドはどのような機能なのか(テスト対象)
  • contextメソッドはどのような状況なのか
  • itメソッドは何をテストしているのか

をそれぞれグループ分けしています。

expectation(エクスペクテーション)

一番最初の「describe新規登録、context新規登録できるとき、it全ての項目が入力されていれば登録できる」の中にあるような以下のような記述の部分をexpectationといい、テストで得られた結果が期待通りなのかを確認する構文です。

expect(@user).to be_valid

上記の記述のうち、be_validの部分をmatcher(マッチャ)といいます。

matcher

「expectの引数(カッコの中の記述)」と「想定した挙動」が一致しているか判断します。
expectの引数にはテストで得られた実際の挙動を指定し、matcherにはどのような挙動を想定しているかを記述します

単体テストでよく使うmatcherの例

matcher 想定する挙動 記述例
include Xの中にYという文字列が含まれているか expect(X).to include(Y)
be_valid expectのインスタンスが正しく保存される expect(X).to be_valid

テストコードの実行

少し補足説明がなりましたが、上記のテストコードの実行をします。
ターミナルで以下のコマンドを入力します。

ターミナル
$ bundle exec rspec spec/models/user_spec.rb

# 実行結果
User
  新規登録
    新規登録できるとき
      全ての項目が入力されていれば登録できる
      passwordが6文字以上の半角英数字であれば登録できる
      identityが空で登録できる
    新規登録できないとき
      nameが空では登録できない
      emailが空では登録できない
      重複したemailが存在する場合登録できない
      emailに@が含まれていないと登録できない
      passwordが空では登録できない
      passwordが5文字以下では登録できない
      passwordが存在してもpassword_confirmationが空では登録できない

Finished in 0.07492 seconds (files took 1.3 seconds to load)
10 examples, 0 failures

上記ではエラーなくテストが完了しましたが、expectationに記述されているmatcherのエラーメッセージなどに記述ミスがある場合は修正する必要があります。

テストコードの修正にはpry-railsというデバッグ用のgemを使用するのですが、それは別の記事で紹介したいと思います。

RSpecのシリーズ記事

第1回:RSpecの導入&関係の深いgemについて
第2回:RSpec:モデルの単体テストの記述例 ※この記事

参考資料

RSpec: Behaviour Driven Development for Ruby
Qiita:
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

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

[解決] Railsの言語設定エラー 'translation missing: ja.datetime.distance_in_words.x_days ago' 

現状確認

ruby 2.6.6
Rails 6.1.0

インスタンス変数は、正しく定義されている。

[1] pry(#<#<Class:0x00007ff69c4e9978>>)> @post.created_at
=> Sun, 31 Jan 2021 08:28:26.434149000 UTC +00:00
[2] pry(#<#<Class:0x00007ff69c4e9978>>)> time_ago_in_words(@post.created_at)
=> "translation missing: ja.datetime.distance_in_words.x_days"

ファイルを見ると、日本語になっている(自分がen→jaに変えたことに気づいていなかった)

application.rb
config.i18n.default_locale = :ja

解決

今までtime_ago_in_wordsは英語設定だったから、上記の日本語変更でエラーが起きたっぽい

time_ago_in_wordsを日本語化するする

locales/ja.ymlにこれを書く

ja:
  datetime:
    distance_in_words:
      half_a_minute: "30秒前後"
      less_than_x_seconds:
        one:   "1秒"
        other: "%{count}秒"
      x_seconds:
        one:   "1秒"
        other: "%{count}秒"
      less_than_x_minutes:
        one:   "1分"
        other: "%{count}分"
      x_minutes:
        one:   "約1分"
        other: "%{count}分"
      about_x_hours:
        one:   "約1時間"
        other: "約%{count}時間"
      x_days:
        one:   "1日"
        other: "%{count}日"
      about_x_months:
        one:   "約1ヶ月"
        other: "約%{count}ヶ月"
      x_months:
        one:   "1ヶ月"
        other: "%{count}ヶ月"
      almost_x_years:
        one:   "1年弱"
        other: "%{count}年弱"
      about_x_years:
        one:   "約1年"
        other: "約%{count}年"
      over_x_years:
        one:   "1年以上"
        other: "%{count}年以上"

ブラウザで確認した、エラーは無くなっていた。

参考

【Rails】time_ago_in_words メソッドで created_atを「〜前」と表示する | RemoNote

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

heroku pg:reset DATABASEでのエラー?

Railsチュートリアルを進めていたところ以下の流れでDBをリセットする必要があったので手順通り進めていたところ警告が出ました

$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed

警告が出たのはこのDBのリセットコマンドを実行した時
heroku pg:reset DATABASE

警告
スクリーンショット 2021-02-27 11.57.00.png

何かと思ったら、リセットしていいのか確認してるだけでした。

roccky-bastion-3336

画面の指示に従い上記コマンドを入力してリセット完了しました。

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

実装とリリースが前後するのを前提としたGit運用フロー

業務で主に Ruby On Rails での開発を行っていますが、そこで得た知見や、失敗への対応などについて記します。

今回は「実装とリリースが前後する」場合に Gitの運用フローをどのようにするか、についてです。

現状(要件)

弊社は アジャイルソフトウェア開発 のうち、とりわけ エクストリーム・プログラミング(XP) を主体とした開発プロセスを採用していますが、その結果として、常に

短い開発サイクルで頻繁に「リリース」する

状態になっています。

これは「Webアプリケーション」というサービス形態からも必要なことで、より多様なチャレンジをより早く提供するために最適な方法だと考えていますが、一方、実装がやや前倒し気味に行われることもあって、「実装はしたけれども、リリースは待つ」こともよく生じます。

これに対応する方法として、一般的に考えられるのは

  1. 新機能は「フラグで無効化」した上でリリースし、必要になれば「フラグを有効化」する
  2. 新機能を実装したブランチのマージを遅らせる(待つ)

の 2つで、このどちらを採用するのかで「Gitの運用方法」そのものも異なってきます。

この両者を比較しながら、弊社では 2. の「マージを遅らせる」方法を採っているので、その説明も合わせて行います。

1. 新機能は「フラグで無効化」した上でリリースし、必要になれば「フラグを有効化」する方法

これは フィーチャートグル と言われる方法で、有名なところでは

などが採用しています。

つまるところ、

  1. 新機能を実装する場合には、その機能を「無効」にした上で、メインブランチにマージしてしまう
  2. 利用開始までは、その機能は「無効」状態のまま、サービス全体のリリースに含めてしまう
  3. 利用開始になれば、その機能を「有効」に変える

といった方法で、具体的な Gitの運用フローとしては github-flow という名称で知られています。

この方法の良い点は

  • ブランチが事実上一つなので、マージ時のコンフリクトの可能性をかなり少なく出来る
  • 本番機でも「一部のユーザー」に新機能の提供が出来る
  • 「古い機能」と「新しい機能」の共存を前提とした実装になるので、反映時のトラブルの可能性を少なく出来る

ということになり、かなり良いことずくめな印象です。

しかし一方、逆の面としては

  • 「古い機能」と「新しい機能」の共存を前提とした実装に手間がかかる
  • 動作確認が「機能OFFのままリリースした時」と「機能ONにした時」と実質的に2倍になる
  • アプリケーションの実装が「フラグで無効化されている部分も含む」となって見通しが悪くなる

こともあって、開発上の負担も心配になるところです。

2. 新機能を実装したブランチのマージを遅らせる(待つ)

これは フィーチャーブランチ と言われる方法で、 特に Github での Pull Request (PR) はこの「フィーチャーブランチ」での開発を前提としたものと言えます。

これは、

  • 機能実装時にメインブランチから「フィーチャーブランチ」を分岐させ、そのブランチで開発を行う
  • 必要に応じて適宜、インテグレーション用のブランチなどにマージする
  • 最終的にリリース時に、メインブランチにマージすることによって、その新機能を利用開始状態にする

という方法で、具体的な Gitの運用フローとしては gitlab-flow という名称で知られています。

この方法は

  • 「古い機能」と「新しい機能」の共存状態を考慮しなくて済む場合が多い
  • 動作確認が「機能リリース時」だけに絞られる
  • アプリケーションの実装内容が「使われている機能のみ」となって見通しが良くなる

といった面で開発面での負担軽減になりやすい方法ですが、一方で

  • 実装に時間がかかったり、機能の利用開始時期が遅れるとマージ時にコンフリクトが発生する可能性が高い
  • 「一部のユーザー」にだけ新機能の提供をする、といった方法でのサービス提供がしにくくなる
  • 「古い機能」と「新しい機能」の共存を前提としない実装になるので、反映時にその部分の考慮不足があると不具合が生じる

などの面での注意を要します。

ではどちらを採用しているか

結論としては弊社は 2. の「フィーチャーブランチ」での開発を主体としています。

理由としては、

  1. 開発を早く廻すためには「ある程度のリスクが生じても、開発の負荷を減らす」ほうがメリットが多いと判断していること
  2. 特に「利用開始が遅れることによるコンフリクト発生の可能性」については、出来る限りの対処を行っていること(内容については次回に記します)
  3. 必要に応じて「フィーチャートグル」も取り入れて、一部テストや利用開始の遅延に対処するようにしていること(同上)

等があります。

実際にどのような工夫をして「フィーチャーブランチ」での開発を進めやすくしているかについてや、具体的に gitlab-flow をどのように用いているかについては、次回に記載します。

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

renderにおけるlocalsとcollectionについて

localsとcollectionのオプションについての理解を深めようとこの記事を書くことにしました。

1.localsについて

仮にposts_controllersに以下の記述があるとして

posts_controllers.rb
def show
  @post = Post.find(params[:id)
end
posts/foobar.html.erb
<%= render partial: 'post_partial', locals: { post: @post } %>

または省略形で
<%= rendner 'post_partial', post: @post %>
posts/_post_partila.html.erb
<%= post.title%>
<%= post.body %>
公式(省略形)
<%= render 'パーシャル名', ローカル変数: インスタンス変数名 %>

つまりlocalsオプションを使用することで
@postというインスタンス変数をpostというローカル変数に変換することができる。

2.collectionについて

仮にposts_controllersに以下の記述があるとして

posts_controllers.rb
def index 
  @posts = Post.all
end 
posts/index.html.erb
<%= render partial: 'each_post', collection: @posts %>

応用形 asオプションでローカル変数を指定することができる
<%= render partial: 'each_post', collection: @posts, as: :post%>

collectionオプションを使用しなかった場合は以下の記述になるが

posts/_each_post.html.erb
#collectionを使わなければ
<% @posts.each do |post| %>
  <%= post.body%>
<% end %>

collectionオプションを使用するで
eachメソッドを省くことができるため簡潔に記述することができる

posts/_each_post.html.erb
#collectionを使用することで
<%= post.body%>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのenumの可読性をあげる

はじめに

Rails(ActiveRecord)でenumを扱おうとすると、db上のデータはintegerとして扱うことが多いと思います。
しかしこれだとdb上のデータが何を意味しているかわかりづらく、可読性が下がります。
そこで今回はMySQLを使うことを前提に、MySQLのENUM型を定義する方法をメモしておこうと思います。

環境

ruby 3.0.0
rails 6.1.1
MySQL 5.7

手順

class CreateAnimals < ActiveRecord::Migration[6.1]
  def change
    create_table :animals do |t|
      t.string :name
      t.column :animal_type, "ENUM('dog', 'cat', 'bird')", null: false

      t.timestamps
    end
  end
end

t.column :animal_type, "ENUM('dog', 'cat', 'bird')", null: false
この部分で定義しています。
t.columnでMySQLのENUM('dog', 'cat', 'bird')という構文を直接実行できます。

途中からカラムを追加するには少し工夫が必要です。
change_tableで追加できますが、rollbackできないのでdef upで定義しましょう。

class ChangeAnimals < ActiveRecord::Migration[6.1]
  def up
    change_table :animals do |t|
       t.column :animal_type, "ENUM('dog', 'cat', 'bird')", null: false
    end
  end

  def down 
    remove_column :animals, :animal_type
  end
end

途中でENUMを拡張

class ChangeAnimals < ActiveRecord::Migration[6.1]
  def up
    change_table :attendance_punches do |t|
      t.change :animal_type, "ENUM('dog', 'cat', 'bird', 'fish')", null: false
    end
  end
end

ENUMの型を増やすことはできますが、減らしたり、名称を変更しようとするとエラーになるので注意が必要です。

enumerizeも使えます

animal.rb
class Animal
  extend Enumerize

  enumerize :animal_type, in: [:dog, :cat, :bird],
                          predicates: true,
                          scope: true
end
animal = Animal.new

animal.dog?   # => false
animal.cat? # => false

animal.animal_type = 'dog'

animal.dog?   # => true
animal.cat? # => false

Animal.with_animal_type(:bird)
# SELECT "animals".* FROM "animals" WHERE "animals"."animal_type" IN ('bird')

おわりに

カラム名をtypeにしたらエラーが出てmigrateできませんでした。どうやら予約語になっているらしく、typeをカラム名にすることはできないようです。

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

Railsでリンクを押すとなぜかcssが適用されてしまう??

概要

現在Railsでアプリを作成しているのですが、
Vue.jsを導入してから、リンクを押すとcssのスタイルが適用されてしまうバグが起きるようになりました。

試したこと

HTML,CSS,Vue.jsを少しずつ削って、どこが原因か探る作業を行いました。
(めちゃくちゃ時間かかった、、、)

結局解決できず。

解決策

application.jsのTurbolinksを無効化することで解決しました。

application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import "bootstrap/dist/js/bootstrap"

Rails.start()
// ↓Turbolinksを無効化
// Turbolinks.start()
ActiveStorage.start()

require("../stylesheets/application.scss")

参考文献の方のように、自分も「turbolinks」と仲良くならないとなと実感しました。

参考文献

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

Railsのdeviseを使用して複数のログイン画面を実装する方法

deviseとは

Railsのgem(ライブラリ)の1つ。
ログイン機能の実装を簡単にしてくれるgemです。
これからdeviseを使用して複数のログイン画面を実装していきます。

インストール・準備

Gemfileに追記

Gemfile
gem 'devise'

bundleまたはbundle install実行

$ bundle install

deviseを使用できるようにインストールします。

$ rails g devise:install

上記のコマンドを実行すると、ターミナルに下記の文字が表示されます。

===============================================================================

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

     * Not required *

===============================================================================

では1から順に見ていきましょう!
config/environments/development.rbのendの上に記述してください。

config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

続いて2です。ルーティングを指定しましょう。

config/routes.rb
root to: "home#index"

続いて3です。こちらはログインした時にお知らせをしてくれます。

app/views/layouts/application.html.erb
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

ここで一つ注意なのですが、複数のログイン画面を実装するときは4は実行しないようにしましょう。

Modelを生成する

ここで2つのモデルを生成します。
モデルを生成するときは$ rails g devise モデル名です。

$ rails g devise A
$ rails g devise B

db/migrateに2つのファイルが生成されます。
設定を加えたい場合は今のうちに書き換えておきましょう。
そして $ rails db:migrate をお忘れなく!

Controllerを生成する

$ rails g devise:controllers As(複数形)
$ rails g devise:controllers Bs(複数形)

複数形にするのをお忘れなく!

設定ファイルを変更する

config/initializers/development.rbを開いて下記を見つけ出す。Macの方はコマンド⌘ + Fで簡単に探せますよ!

config/initializers/devise.rb
# config.scoped_views = false

上記のコメントアウトを外して、falseをtrueに書き換えます。

config/initializers/devise.rb
config.scoped_views = true

こうすることで複数モデルを可能にするようです。

Viewを生成する

rails g devise:views As(複数形)

rails g devise:views Bs(複数形)

ルーティングを生成する

config/routes.rbを以下のように書き換えてください。

config/routes.rb
Rails.application.routes.draw do
  devise_for :B, controllers: {
                         sessions: "Bs/sessions",
                         passwords: "Bs/passwords",
                         registrations: "Bs/registrations",
                       }
  devise_for :As, controllers: {
                       sessions: "As/sessions",
                       passwords: "As/passwords",
                       registrations: "As/registrations",
                     }
  root to: "home#index"
end

その他

それぞれの新規登録画面・ログイン画面を編集したい場合は、
[Aの新規登録画面]
app/views/As/registrations/new.html.rb
[Aのログイン画面]
app/views/As/sessions/new.html.rb
[Bの新規登録画面]
app/views/Bs/registrations/new.html.rb
[Bのログイン画面]
app/views/Bs/sessions/new.html.rb

rails routesでを見たらURLの指定もわかりやすいかと思います。

rails sでサーバーを起動して以下のようにURLを打ち込んで確認してください。
[Aの新規登録画面]
http://localhost:3000/As/sign_up
[Aのログイン画面]
http://localhost:3000/As/sign_in
[Bの新規登録画面]
http://localhost:3000/Bs/sign_up
[Bのログイン画面]
http://localhost:3000/Bs/sign_in

 参照

https://qiita.com/4npei/items/9066afb75d6a2ad9c661
https://qiita.com/Densetsu/items/2dc6a010809ae70c079e

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

通知機能

参考にしたサイト

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

heroku上にアプリケーションをアップする

herokuへアップする!

まず自身のherokuアカウントへログインします。

% heroku login --interactive

カレントディレクトリがアップするアプリケーション上であることを確認。

% heroku create アプリケーション名
% git config --list | grep heroku

上記のコマンドで正しく設定できていることを確認する

% heroku addons:add cleardb

MySQLを使うためにアドオンを追加します。

% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

MySQLを使うためにGemに考慮した設定の変更を行います。

% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

ClearDBデータベースのURLを変数heroku_cleardbに格納。

config/credentials.yml.encの中身を確認します。

% EDITOR="vi" bin/rails credentials:edit

確認後esc, :qで脱出できます。
環境変数をセットしていきます。

環境変数
外部に漏らしたくない情報を環境変数にセットします。

% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

上記の記述でRAILS_MASTER_KEYを設定。この時公開鍵や秘密鍵を扱う場合は一緒にセットしておきます。

% heroku config:set PAYJP_SECRET_KEY= 'sk_test_-----'
% heroku config:set PAYJP_PUBLIC_KEY= 'pk_test_-----'

今回はpayjpを用いた開発だったので環境変数上にセットします。

% heroku config

上記で環境変数が正しく反映されているかを確認します。

アプリケーション情報をプッシュする

% git push heroku master

上記でherokuにアプリケーションを追加。

マイグレーションファイルを実行
この段階ではデータベースにはマイグレーションの情報が反映されていません。

% heroku run rails db:migrate

ここまでで正しい情報になっているかを確認します。

% heroku apps:info

この時表示されるURLへアクセス!

変更をプッシュする
その後変更などを加える必要がある場合は以下の処理を行う

% git add .
% git commit -m "コミット名"
% git push heroku master

処理を反映します。

stackの変更を加える必要がある場合

% heroku stack:set heroku-18 -a アプリケーション

今回の場合だとheroku-18に変更。
Stackとは、Herokuにおけるアプリケーションの動作環境のこと。Stackはデプロイされたアプリケーションを読み取り正常に稼働させるために用意されています。

以上で無事herokuへのデプロイ完了しました。

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

[Rails]管理画面をActiveAdminで作りたいのは俺だけか?

はじめに

とあるプロジェクトで管理画面を作る!ってなったのですが、管理画面を作るのは初めてで、どうしたものかと調べると、ActiveAdminという便利なgemを見つけました。
それを使った経験を忘れないように備忘録を踏まえて、発信したいと思います。

導入手順

1.Gemfileに以下を記述し、ActiveAdminとDeviceのインストール。

gem 'activeadmin'
gem 'devise'

こちらも忘れず実行する。

$ bundle install

2.インストール完了後、ActiveAdminに必要なファイルを生成する。

$ rails g active_admin:install

※既存のユーザークラスを使用する場合は、引数として指定します。

$ rails g active_admin:install Staff

3.マイグレーションの実行と初期データの作成を行う。
※先ほどのコマンドでマイグレーションファイルが作成され、seeds.rbに記述が追記されています。

$ rails db:migrate

$ rails db:seed

4.サーバを起動した後にこちらにアクセスすると、ログイン画面が表示されるので、下記のユーザ情報でログインする。
※もし、サーバーを起動していた場合は再起動する。

User ... admin@example.com
Password ... password

5.必要なモデルに対する管理ページを作成する。

$ rails g active_admin:resource User # 任意のモデル名

6.先ほどのコマンドで作成されたファイルに、許可したいパラメータのみを記述する。

app/admin/user.rb
ActiveAdmin.register User do
  permit_params :name, :age, :email, { images_attributes: [ :image, :id, :_destroy ] }
end

7.ページが作成されるので、データを打ち込んでいく。

役立つ知識

ここでは、より使いやすい管理画面を作成するためのテクニックを記載しています。

フォームをカスタマイズしたいとき

  • input

    • テキストフィールド。
    • asを使うことで、file_fieldなど、指定したinputのtype属性を指定できる。
  • allow_destroy: true

    • 削除ボタンを生成。
  • input_html

    • inputタグに詳細情報を設定する。
  • hint

    • 注釈を入れる。
  • f.has_many

    • has_manyな関係のテーブルについてのフォームを作成。
  • f.actions

    • submitタグの生成。
ActiveAdmin.register User do
  permit_params :name, :age, :email, { images_attributes: [ :image, :id, :_destroy ] }

  form do |f|
    f.inputs  do
      f.input :name
      f.select :age
      f.input :email
    f.inputs do
      f.has_many :images , allow_destroy: true do|t|
        t.input :image, as: file, input_html: { accept: 'image/*' },
                        :hint => t.object.new_record? ? "プロフィール画像を指定して下さい" : image_tag(t.object.image.url{:thumb})
      end
    end
    f.actions
  end
end

終わりに

いかがでしたでしょうか?
一から管理画面作成となると、なかなか骨が折れる作業ですが、gemを使うとかなりスピーディに作成できるので、機会があれば使ってみてください!!

参考

Railsで最速で管理画面を作る!

Active Admin 徹底解説

ActiveAdminでちょっと使い勝手の良い管理画面を作るTips

公式ドキュメント

activeadmin.info

ActiveAdmin を導入します-関連モデルの導入

Active Adminのデザインをちょっとカッコよくするgemまとめ

Ruby on Rails "Active Admin" + "CarrierWave"で画像アップロード

Rails に ActiveAdmin で管理画面を追加してみた

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

ポートフォリオ要件定義

ポートフォリオ事前レビュー

【テーマ】
働く上で発生する様々な悩みを解決する。

【ターゲット】
悩みをもつ働く人々

【サービス内容】
・悩みを投稿して、それに対してコメント(解決方法)をいただき、悩みを解決することができる。
・過去に同じような悩みを経験した人や、同じような悩みを解決した人に対して、個別相談することができる。
・悩み検索によって、過去の悩みから自分が抱えている悩みと似通った悩みを探すことができる。
・悩みへの解決方法が浮かばず、ユーザーの力になってあげることができなくても、悩みを応援することで、「よく応援されている悩み一覧」へ表示され、他のユーザーの目にとまりやすくなり、悩み解決率を上げることができる。

【テーマ背景】
私は現在、金融及び保険の営業をしています。しかし、先日、右膝の古傷が悪化したことで歩くことが難しくなり、営業活動が長期間できないという状態です。そんな時に色々と悩みが出てきました。
①怪我した状態で出勤してよいのか?
②出勤しない場合、どういった休暇扱いになるのか?
③補助金のような援助はでるのか?
④有給を使い切ってしまった場合はどうなるのか?
⑤結局どのように活動するのが、会社と自分にとって一番よいのか?
もちろん会社の人たちにも相談はしましたが、私のように仕事が長期間できなくなるという経験した方はいないので、解決方法を見出すことはできませんでした。
このような時に、自分の会社内の方だけではなく、会社外の方からも意見を頂戴することができれば、一番ベストな解決方法にたどり着くことができるのでは?と思いました。
その他にも、転職を考えている方であれば、具体的な退職手続きや退職前後の流れなど、社内の方には相談しにくいことが多々出てくると思います。そんなとき、自社とは全く関係のない社外の方であれば、気軽に相談することができると思います。
まとめると、悩みを解決する時に、情報収集の一つとしてご活用いただけるようなサービスになればと思い、今回「働く上で発生する様々な悩みを解決する。」というテーマに決定いたしました。
また、「悩み 社会人」とGoogleで検索したとき、転職サービスを運営している企業が作成したまとめサイトのようなものしかなく、生のユーザーの声をそのまま使ったサイトはあまり見られませんでした。なので、生のユーザーの声を大切にしたサービスにしたいと強く思っています。

【機能一覧】
《基本機能》
・ユーザー新規登録/ログイン/ログアウト(devise)
・ユーザープロフィール登録/編集/詳細機能(名前/プロフィール文)
・プロフィール画像アップロード機能(carrierwave)
・悩み新規投稿/編集/削除/詳細機能
・悩みへのタグ付け機能
・この悩み応援します機能(いいね)
・よく応援されている悩み順に一覧表示
・悩みを新着順に一覧表示
・悩みへのコメント機能
・個別相談機能(非同期通信)
・悩みキーワード検索機能(ransack)
・悩みをタグから検索できる機能(ransack)

《応用機能》
・かんたんログイン
・悩み一覧にページネーションを使う
・レスポンシブ対応
・通知機能

《その他》
・単体テスト
・結合テスト

【使用技術一覧】
《フロントエンド》
・HTML/CSS
・JavaScript
・Bootstrap(5.0)(カスタマイズして使用する)

《バックエンド》
・Ruby(3.0.0)
・Ruby on Rails(6.0.3.1)
・MySQL(8.0)

《インフラ》
・heroku(開発初期)
・AWS(VPC/EC2/S3/RDS)
・Nginx(Webサーバー)
・Puma(アプリケーションサーバー)

《テスト》
・RSpec

《CI/CD》
・CircleCI(自動テスト)
・Capistrano(自動デプロイ)
・Rubocop(コーディングチェック)

《バージョン管理》
・Git/GitHub

《開発環境》
・VScode
・Docker

【開発フェーズ】
《第一段階》
・基本機能実装
・テストの作成
・UI/UXのブラッシュアップ
・コーディングチェック
・herokuへのデプロイ

《第二段階》
・応用機能実装
・UI/UXのブラッシュアップ
・コーディングチェック

《第三段階》
・インフラをherokuからAWSに変更(VPC/EC2/S3/RDS)
・Docker/docker-composeの導入
・CI/CDの導入

《第四段階》
・EC2からECSに変更

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

DM機能

参考にしたサイト

新しく覚えたこと

アソシエーション

Image from Gyazo

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

Ruby on Railsの環境構築

私の今現在の環境の構築方法を忘れないためのメモです。

参考記事

ありがとうございます!

バージョン

Mac OS Catalina 10.15.17

MojaveやM1だと違う可能性があるのでご注意ください。

準備

Catalinaではzshというシェルを利用するので以下コマンドをターミナルで実行し、zshをデフォルトに設定します。
パスワードを求められる場合はPCのパスワードを入力します。

% chsh -s /bin/zsh

以下のコマンドでログインシェルを確認します。

% echo $SHELL

# 以下のように表示されればOK
/bin/zsh

Command Line Toolsをインストール

開発に必要なもろもろのソフトウェアをダウンロードするためにはCommand Line Toolsが必要なためインストールしていきます。

ターミナルに以下コマンドを入力します。

% xcode-select --install

下記画像のような画面になるのでインストールを選択します。

スクリーンショット 2021-02-28 11.01.39.png

下記画像のような画面になるので同意するを選択します。

スクリーンショット 2021-02-28 11.02.10.png

ちょっと時間かかりますので待ちます。

Homebrewのインストール

ソフトウェア管理ツールであるHomebrewをインストールします。

以下の公式サイトのインストールの下のスクリプトをコピーし貼り付けします。
Homebrew

私の場合は以下です。

# ホームディレクトリで行います
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

パスワードを求められたり、注意書きが出たりします。
Press RETURN to continue or any other key to abortと出たら続けるにはエンターキーを、やめるにはそれ以外の入力をという意味なのでエンターキーを押下します。

以下コマンドでインストールできたか確認できます。

% brew -v

# 以下のようになればOK(数字は違う可能性があります)
Homebrew 2.5.1

以下コマンドで最新バージョンにします。

% brew update

以下コマンドで権限を変更します。

%  sudo chown -R `whoami`:admin /usr/local/bin

パスワードを求められるのでPCのパスワードを入力します。

Rubyをインストール

Rubyをインストールしていきます。
このためにHomebrewが必要でした。

以下のコマンドでRubyの土台となるrbenvruby-buildをHomebrewを用いてインストールします。

% brew install rbenv ruby-build

以下のコマンドでPCのどこからでもrbenvを利用できるようにします。

% echo 'eval "$(rbenv init -)"' >> ~/.zshrc

以下のコマンドでzshrcを再読み込みし変更を反映させます。

% source ~/.zshrc

以下のコマンドでターミナルのirb上で日本語入力を可能にする設定を行います。readlineをインストールします。

% brew install readline

以下のコマンドでreadlineをPCのどこからでも利用できるようにします。

% brew link readline --force

以下のコマンドでrbenvを利用してRubyをインストールします。
今回は2.6.5のバージョンです。

% RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"

# バージョンを指定しインストール
% rbenv install 2.6.5

# インストールしたRuby 2.6.5を使用するために以下コマンドを実行
% rbenv global 2.6.5

# 以下コマンドでrbenvを読み込む
% rbenv rehash

# 以下コマンドでバージョンの確認ができる
% ruby -v

MySQLのインストール

以下のコマンドを実行して、MySQLをインストールします。

% brew install mysql@5.6

以下のコマンドで自動で起動するように設定します。

% mkdir ~/Library/LaunchAgents 
% ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
% launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist

以下のコマンドでPCのどこからでもMySQLが使えるようにします。

# mysqlのコマンドを実行できるようにする設定
% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc

#  設定を読み込むコマンド
% source ~/.zshrc

# mysqlのコマンドが打てるか確認する
% which mysql
# 以下のように表示されればOK
/usr/local/opt/mysql@5.6/bin/mysql

# MySQLの状態を確認するコマンド
% mysql.server status
# 以下のように表示されればOK
SUCCESS! MySQL running

Railsのインストール

以下コマンドでgemを管理するためのbundlerバンドラーをインストールします。

% gem install bundler --version='2.1.4'

以下コマンドでRailsをインストールします。

% gem install rails --version='6.0.0'

# rbenvの再読み込みをして設定を反映させる
% rbenv rehash

# バージョン確認
% rails -v
# 以下のように表示されればOK
Rails 6.0.0

Node.jsのインストール

RailsでJavaScriptを動かすために必要なNode.jsをインストールします。
以下コマンドを実行します。

% brew install node@14

# パスを設定する
% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc

# バージョンを確認
% node -v
#以下のように表示されればOK
v14.15.3

yarnをインストール

yarnについてよくわかっていなかったのですが、以下の記事がわかりやすかったです。
ありがとうございます!
npmとはyarnとは

以下コマンドを実行します。

% brew install yarn

# バージョンを確認
% yarn -v

以上です。

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

Rails I18nを便利に使おう

アプリケーションを色々な国に展開する場合やメッセージを日本語化する際にI18nを使用すると思います。

下記のようなパターンではuser_nameをtranslateに渡すと、"太郎"が返ってきます。

config/locales/ja.yml
ja:
  user_name: "太郎"
> I18n.t(:user_name)
=> "太郎"

しかし、userが、太郎だけであればこれでも良いのですが、複数いる場合がたいていのパターンだと思います。
これを少し拡張させて、どのuserでも対応できるようにしていきます。

config/locales/ja.yml
ja:
  user_name: "%{name}"
> I18n.t(:user_name, name: "太郎")
=> "太郎"
> I18n.t(:user_name, name: "次郎")
=> "次郎"

上記のように変更すると、引数に名前を渡せば、どんな名前でも表示できるようになりました。
これで使いやすくなりました。

さらにtranslateではこのようなこともできます。

config/locales/ja.yml
ja:
  user_name_html: <span class="user">"%{name}"</span>

このように最後に_htmlをつけることで、htmlごと(今回はspanタグごと)返すこともできます。
view自体が複雑でごちゃごちゃしている場合,こうゆうパターンで書くのも一つの手かもしれません。

translateは調べるといろんなことができるので、色々試してやってみましょう!!
案外今やりたいことと合致するかもしれません

 参考記事

あなたはいくつ知っている?Rails I18nの便利機能大全!

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

PG::ConnectionBad の解決→ sudo だった!かというお話

Railsの勉強中の身で、db、PostgreSQLがどうだという事はまだまだ理解が浅いプログラミング初心者です。
練習中に発生したトラブルで3日間もがき苦しみましたので、備忘録として初Qiita投稿です。誰かのためになると嬉しいです!

発生状況

作業中、何かのはずみでPCが突然ダウン、自動的に再起動。その後突然 PG::ConnectionBad のエラー表示が出るようになった。

PG::ConnectionBad: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

スクリーンショット 2021-02-23 16.57.02.png

環境

macOS Catalina 1o.15.6
ruby 2.6.3
Rails 6.0.3.4
PostgreSQL 13.2

解決までの道のり

% postgres -D /usr/local/var/postgres

2021-02-23 14:04:19.147 JST [90270] FATAL: could not remove old lock file "/tmp/.s.PGSQL.5432.lock": Permission denied

なるほど!このファイルが取り除けないのね…権限がない?…どういう事だろう?とりあえず…
% rm /usr/local/var/postgres/postmaster.pid

rm: /usr/local/var/postgres/postmaster.pid: No such file or directory

No such fileと言われてしまう…権限?
$ chmod 1777 /tmp
をやった後、
% rm /usr/local/var/postgres/postmaster.pid
を実行!今度こそ!

rm: /usr/local/var/postgres/postmaster.pid: No such file or directory

あれ?!やっぱり権限がない?さすればということで、このページを参考にしてシステム環境設定でターミナルの設定を変えてみた。
で、上記と同じことをしてもダメ!

ここに書いてないこともいっぱい試し、3日間もがいたけどいよいよ詰んだか…と思った時、ハタと思いついた。権限なんだったらひょっとしてsuper do ?!
% sudo chmod 1777 /tmp
% rm /usr/local/var/postgres/postmaster.pid
お!うまくいったかも!!忘れずに…
% rails db:create
% rails db:migrate
お〜〜〜〜〜〜動いた!!

参考にしたサイト

https://stackoverflow.com/questions/13410686/postgres-could-not-connect-to-server
https://qiita.com/great084/items/98c83364f246473249c4
https://make-from-scratch.com/error-connections-on-unix-domain-socket-tmp-s-pgsql-5432/

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

タグ機能

参考にしたサイト

新しく覚えたこと

add_index(テーブル名,カラム名,オプション) #テーブルにカラムを追加する
unipue: true #一意性制限

モデル.pluck(カラム名) #指定したカラムのレコードの配列を取得
モデル.find_or_create_by(条件) #指定した条件が存在すれば取得し、なければ作成

タグ付け機能のビューにはjQeryのTag-itと使う

アソシエーション

Image from Gyazo

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

Ruby 3.x, Rails 6.x, MySQL 8.x のプロジェクトを CircleCI に設定し RSpec と Rubocop を実施する。

概要

前回の記事 Ruby 3.x, Rails 6.x, MySQL 8.x の Docker 環境構築。 を引き継いで、プロジェクトを CircleCI に設定し、GitHub リポジトリへの push 時に RSpec と Rubocop を自動実施するように設定します。

基本的に以下の CircleCI 公式ドキュメントを参考にしていますので、初期設定の方法などはそちらをご確認ください。
大きく違うのは、Ruby on Rails で MySQL に対応した点です。

前提

下記の環境が準備されていること。

  • GitHub のアカウントを持っていること(無料版でも大丈夫)
  • 上記の GitHub アカウントに紐づいた CircleCI のアカウントを持っていること(無料版でも大丈夫)
  • Docker を利用した Ruby on Rails のプロジェクトが作成されていること

今回は CircleCI の設定が主になるため、Docker や RSpec、Rubocop の細かい説明は省きます。

RSpec と Rubocop の組み込み

Gemfile を編集し、:development と :test 環境に rubocop(ついでに pry-byebug と factory_bot_rails)を、:test 環境に rspec-rails と database_cleaner-active_record を追加します。

gem についてはプロジェクトごと、必要に応じて対応してください。

.Gemfile

.Gemfile
group :development, :test do
  # 略
  gem 'pry-byebug'
  gem 'factory_bot_rails'

  # https://github.com/rubocop-hq/rubocop
  gem 'rubocop', '~> 1.10.0', require: false
end

group :test do
  # 略
  gem 'rspec-rails', '4.0.2'

  gem 'database_cleaner-active_record'
end

設定後、bundle install と RSpec のインストール rails g rspec:install、rubocop の設定ファイル作成 bundle exec rubocop --auto-gen-config を実行します。
前回のプロジェクトは Docker で作成していたため、その場合は以下のように docker-compose を使用します。
なお、minitest 用の test フォルダが不要な場合は削除してください。

$ docker-compose run app bundle install
$ docker-compose run app rails g rspec:install
$ docker-compose run app bundle exec rubocop --auto-gen-config

config/application.rb

Scaffold 実行時に RSpec のスケルトンを自動生成するかどうかの設定を APP::Application クラス内に記入します。自動生成が不要な場合は g.test_framework false とします。
また、FactoryBot についても書いておきます。

config/application.rb
module App
  class Application < Rails::Application
    config.generators do |g|
      g.test_framework :rspec,
                       feature_specs: true,
                       fixtures: false,
                       view_specs: true,
                       helper_specs: false,
                       routing_specs: false,
                       controller_specs: false,
                       request_specs: true,
                       system_specs: true

      g.factory_bot false
      g.factory_bot dir: 'spec/factories'
    end
  end
end

rubocop ファイル

bundle exec rubocop --auto-gen-config を実行することで .rubocop.yml と .rubocop_todo.yml ファイルが作成されます。

.
├── rubocop.yml
└── rubocop_todo.yml

実際の rubocop の運用については公式サイトを確認してください。

cf. rubocop / rubocop

大まかな手順としては、rubocop --auto-gen-config 実施時にエラーになった箇所が _todo.yml の方に記載されるので、そこから修正するつもりがない設定を .rubocop.yml に移動し、それ以外は _todo.yml からは削除し、エラー要素を実ファイルから修正していくことになります。

CircleCI の設定

本題の CircleCI の設定となります。

.circleci/config.yml

.
└── .circleci/
    └── config.yml

.circleci/config.yml は Docker の docker-compose.yml と少し似ています。
今回はリポジトリへの push 時に RSpec と rubocop を自動実施するためのモノなので、設定する環境は test となります。

注意点としては、この設定では MySQL および Docker の仕組み上、CircleCI 実施時に MySQL のコンテナで頻繁に mbind: Operation not permitted という警告が出ますが、いまのところ無視するしかないようです。
(Docker だけの対応では cap_add: - SYS_NICE を付加することで警告が非表示になるようですが、CircleCI では設定できません)

cf. circleci コンテナの立ち上げ失敗&ビルドがキャンセルされる?

また、イメージのタグは以下の一覧から 2021/02 現在の安定最新版を探しました。
cf. https://circleci.com/docs/ja/2.0/docker-image-tags.json

circleci/config.yml
version: 2.1

orbs:
  ruby: circleci/ruby@1.1.2
  node: circleci/node@4.2.0

jobs:
  build:
    docker:
      - image: cimg/ruby:3.0.0-node
    steps:
      - checkout
      - ruby/install-deps
      - node/install-packages:
          pkg-manager: yarn
  test:
    docker:
      - image: cimg/ruby:3.0.0-node
        environment:
          RAILS_ENV: test
      - image: circleci/mysql:8.0.4
        name: db
        command: mysqld --default-authentication-plugin=mysql_native_password
        environment:
          MYSQL_DATABASE: app_test
          MYSQL_USER: user
          MYSQL_PASSWORD: password
    environment:
      BUNDLE_JOBS: "3"
      BUNDLE_RETRY: "3"
    steps:
      - checkout
      - ruby/install-deps
      - node/install-packages:
          pkg-manager: yarn
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://db:3306 -timeout 1m
      - run:
          name: Database setup
          command: bundle exec rails db:schema:load --trace
      - ruby/rspec-test
      - ruby/rubocop-check

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test:
          requires:
            - build

ここまで追加したものを登録済みの GitHub に push しておきます。

CircleCI サイトでのプロジェクトの設定

https://app.circleci.com/ にログインし、GitHub と連携したプロジェクト(リポジトリ)を設定します。
詳細な手順は 入門ガイド を確認してください。

GitHub と連携できている場合は Projects ページにリポジトリの一覧が表示されるため、対象となるもので「Set Up Project」ボタンを押下します。

image.png

その後、すでに config.yml は設定済みのため、次の画面で「Use Existing Config」ボタンを押下します。

image.png

簡単な警告ダイアログが出ますが、「Start Building」ボタンを押下して先に進めます。

image.png

この時点で、設定に問題がなければ CircleCI の Dashboard に最初のテスト結果が「Success」として表示されます。

image.png

また、GitHub 側のリポジトリにも安心のチェックマークが表示されるようになります。
image.png

RSpec の実装

rubocop は書式の問題なのでさておき、RSpec の動作確認だけ簡単に説明します。

前提

前回の最後に scaffold で作成した users テーブルがあることとします。

$ docker-compose run app rails g scaffold user name:string email:string

バリデーション追加

User モデルにバリデーションを追加します。

app/models/user.rb
class User < ApplicationRecord
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :name, presence: true, length: { maximum: 250 }
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }

  before_save { self.email = email.downcase }
end

FactoryBot

ユニットテストで使用するテストデータを事前に設定しておきます。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "test name#{n}" }
    sequence(:email) { |n| "test#{n}@example.com" }

    trait :empty do
      name { nil }
      email  { nil }
    end

    trait :invalid_email do
      sequence(:email) { |n| "test#{n}" }
    end
  end
end

モデルテスト

(ごく単純なものとなりますが、)モデルのテストを設定します。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe '#valid?' do
    let!(:user) { described_class.new(attributes) }
    subject { user.valid? }

    context 'successful' do
      let!(:attributes) { attributes_for(:user) }
      it { is_expected.to be true }
    end

    context 'failure' do
      let!(:attributes) { attributes_for(:user, :invalid_email) }
      it { is_expected.to be false }
    end
  end
end

ローカルでの確認

ローカルで RSpec(と rubocop)を実施する場合は、以下のコマンドを実行して下さい。

$ docker-compose run app rspec
$ docker-compose run app rubocop

GitHub へ push

問題がなければ git commit と git push を行います。
これだけで GitHub へ反映されると同時に CircleCI が RSpec と rubocop を自動実行してくれます。

今回は RSpec と rubocop の実施だけですが、エラーがなかった場合にそのまま本番環境へのデプロイなどを行うことも可能です。

少し駆け足となってしまいましたが、今回は以上となります。

参考資料

以下の記事、情報を参考にさせていただきました。
ありがとうございます。

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

Rspec 結合テストコードにてセレクトボックスがある場合の記述方法

テックキャンプではカリキュラム外であったために自分で調査

※セレクトボックスの結合テストのみの解説なので、それ以外は省略

実行コマンド

bundle exec rspec spec/system/users_spec.rb

アプリ名/spec/system/users_spec.rb


require 'rails_helper'

RSpec.describe 'ユーザー新規登録', type: :system do
  before do
    @user = FactoryBot.build(:user)
  end

  context 'ユーザー新規登録ができるとき' do
    it '正しい情報を入力すればユーザー新規登録ができてトップページに移動する' do
      fill_in 'nickname', with: @user.nickname
#⇐この行----------------------------------------------------------
      select '男性', from: 'user[seibetu_id]'    
#---------------------------------------------------------------
    end
  end
end

アプリ名/spec/factories/users.rb

FactoryBot.define do
  factory :user do
    nickname              { Faker::Name.initials(number: 10) }
    seibetu_id            { 2 }
  end
end

アプリ名/app/models/seibetu.rb

class Seibetu < ActiveHash::Base
  self.data = [
    { id: 1, name: '--' },
    { id: 2, name: '男性' },
    { id: 3, name: '女性' },
    { id: 4, name: 'ゲイ' },
    { id: 5, name: 'レズ' },
    { id: 6, name: '性別は答えたくない' }
  ]

  include ActiveHash::Associations
  has_many :users
  has_many :loves
end

Gemfile

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

  gem 'rspec-rails', '~> 4.0.0'
  gem 'factory_bot_rails'
  gem 'faker'
end

group :development do

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end


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