- 投稿日:2021-02-28T23:46:39+09:00
【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 enddb/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 enddb/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_to
はreferences
のエイリアスです。GitHub rails/…/schema_definitions.rbmodels
app/models/physician.rbclass Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments endapp/models/appointment.rbclass Appointment < ApplicationRecord belongs_to :physician belongs_to :patient endapp/models/patient.rbclass Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments enddb
【 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.rbclass 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: :patientPhysician.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.rbclass Author < ApplicationRecord has_many :books, -> { order(:publication_date) } endとすることで、
author.booksとしたとき、
書籍: book
を出版日: publication_date
の順番で取得することができます。
これを利用して多対多のアソシエーションの中間テーブルでの絞り込みを行います。
今回は中間テーブルである診察予約: appointment
の日付でクエリメソッドwhere
を使用したいため、app/models/physician.rbclass 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
のデータを取得。SELECT
でphysicians
の全てのカラム*
を取得しています。FROM
はテーブルの指定です。2行目は
診察予約: appointment
のデータを取得しています。これも同じくSELECT
で全てのカラム*
を取得していますが、WHERE
で絞り込みを行っています。
`appointments`.appointment_date` >= '2021-02-28'
は、app/models/physician.rb
に記載したスコープのクエリメソッドになります。
さらに、AND
で`appointments`.`physician_id` IN (1, 2)
とすることで、1行目で取得した医師: physician
のid
に関連するデータのみを取得しています。最後に、3行目で
患者: patient
のデータ取得です。WHERE
で`patients`.`id` IN (2, 3)
と条件付けすることにより、patients
のid
が、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.rbclass 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.rbclass 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.today
やtrue
false
など、値が決まっていたらhas_manyのスコープが使えるのですが、where
で日付を指定するなどはできませんでした。# これはできません。 class Author < ApplicationRecord has_many :books, ->(date) { where(created_at: date) } end
引数?として使用できるのはそのモデルのデータだけのようです。
GitHub rails/…/associations.rb最後に
言い回しや解釈の間違っているところ、さらに良い方法がございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。
- 投稿日:2021-02-28T23:37:41+09:00
Association ; 1対1、1対多、多対多について
Association
Rails には関連 (Association) という仕組みがある。関連付けを用いて二つのモデルをつなぐことで、コードをシンプル、かつ簡単に書くことができる。
関連付けの関係は大きく分けて以下の三種類に分類される。
- 1 対 1
- 1 対 多
- 多 対 多
1 対 1 (has_one, belongs_to)
例えば、Teacher, Class, Student, Subject のモデルで以下の図のような関係性があったとします。
ここで表されているのは、
- 原田先生 は 2-1 を
- 山川先生 は 2-2 を
担任しているということがわかります。
この場合、Teacherモデルのデータひとつに対し、Classモデルのデータがひとつだけ割り当てられているため、1対1の関係性となっているということになります。
この関係性をコードで表すと以下のようになります。class Teacher < ApplicationRecord has_one :Class end class Class < ApplicationRecord belongs_to :Teacher end1 対 多 (has_many, belongs_to)
ここで表されているのは
- 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の関係性を見てみます。
線が多いため、Studentモデルのデータごとに色分けしております。
ここで表されているのは
- 田中さんは 国語、英語、数学 を受講する
- 矢島さんは 国語、英語、数学 を受講する
- 中村さんは 国語、英語、数学 を受講する
- 木下さんは 国語、英語、数学 を受講する
ということがわかります。
ここで、線の色分けをSubjectモデルのデータごとに変えてみます。
この場合、表されているのは
- 国語を受講するのは 田中さん、矢島さん、中村さん、木下さん
- 英語を受講するのは 田中さん、矢島さん、中村さん、木下さん
- 数学を受講するのは 田中さん、矢島さん、中村さん、木下さん
ということになります。
つまり、Studentモデルのデータひとつに対し、Subjectモデルのデータが複数割り当てられており、またSubjectモデルのデータひとつに対し、Studentモデルのデータが複数割り当てられています。そのため、この関係性は多対多ということになります。
この関係性をコードで表すと以下のようになります。class Student < ApplicationRecord has_and_belongs_to_many :Subjects end class Subject < ApplicationRecord has_and_belongs_to_many :Students end
- 投稿日:2021-02-28T23:34:09+09:00
【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.rbclass 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.rbRails.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を採用しました。
ご自身の用途に合わせて選択していただければ良いかと思います。
scope
、namespace
、module
について分かりやすくまとめてくださっているサイトがあり、参考にさせていただきましたので、下記にURLを掲載しておきます。(参考)
・Railsのroutingにおけるscope / namespace / module の違い
https://qiita.com/ryosuketter/items/9240d8c2561b5989f049
終わり
今回は以上になります。
ざっくりとした説明で分かりにくい点もあるかと思いますが、申し訳ありません。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思っています。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。
- 投稿日:2021-02-28T22:55:41+09:00
railsのgem「pagy」を使ってページネーション
私がページネーションにpagyを選んだ理由
下記のリンクにて、メモリの負荷がwill-paginateやkaminariより軽いらしいとのことです。
記事の中に以下のグラフを見つけました。ページ数と使用メモリ数の関係をグラフ化したものですが、Pagyが圧倒的です。こりゃPagyを選びますね。
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>NoMethodErrorぁぁぁぁぁエラー発動ぅぅぅぅぅ!!!
pagy のbootstrapのオフィシャルっぽいところの記事を参照し
<%== pagy_nav_bootstrap(@pagy) %>のところを
<%== pagy_bootstrap_nav(@pagy) %>に変えてOK!
いい感じにページネーションできました。
- 投稿日:2021-02-28T22:46:34+09:00
【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少しだけ、このファイルの記述がスッキリした気がする。
何度も同じ記述を書くのはエンジニアとして、あまり優秀ではない気がしているので、できるだけ同じ処理はまとめていきましょう。
- 投稿日:2021-02-28T22:08:26+09:00
【Railsチュートリアル】第10章 ユーザーの更新・表示・削除②
10.3 すべてのユーザーを表示する
index
アクションを追加して、すべてのユーザーを一覧表示する。データベースにサンプルデータを追加する方法や、将来ユーザー数が膨大になってもindexページを問題なく表示できるようにするためのユーザー出力のページネーション(pagination=ページ分割)の方法を学ぶ。10.3.1 ユーザーの一覧ページ
ユーザーのindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。
indexアクションが正しくリダイレクトするか検証するテストを書く。
test/controllers/users_controller_test.rbrequire '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.rbclass 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 . . . endindexビューの実装。User.allを使ってデータベース上の全ユーザーを取得し、ビューで使えるインスタンス変数@usersに代入する。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update] . . . def index @users = User.all end . . . endapp/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.rbrequire '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 end10.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.rbrequire '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 endFAIL["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.rbtest "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 => 成功 endassert_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チュートリアルをはじめたばかりの頃はエラーが出ただけで焦っていました。
ですが今では冷静にエラーを読む、わからなければ検索するというのが自然とできるようになりました。
コードもだんだんと読み下せるようになってきました。一歩ずつ前に進んでいる気がします。次章もがんばります!
- 投稿日:2021-02-28T21:52:57+09:00
一覧表示での進捗率ゲージの作成
はじめに
オリジナルアプリでクラウドファンディングサイトを作成した際に、
jQueryを使って進捗率ゲージを作成したので紹介します。
前回、詳細画面での作成方法を紹介しましたが
今回は一覧表示画面での作成方法です。詳細画面での作成はこちら→
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文の設定が自分では使ったことがなかったので苦労しました。
日々勉強します。
- 投稿日:2021-02-28T21:45:10+09:00
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.rbrequire '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.rbFactoryBot.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 endpasswordカラムとpassword_confirmationカラムは同じ値を入れるため、変数
password
に値を代入して、各カラムに変数password
を入れます。
Faker::
の後ろに続くName
やInternet.free_email
でどんな値をランダムに生成するかを指定しています。前者なら名前、後者ならメールアドレスですね。Fakerでは色んなタイプの値を生成してくれるので、Fakerの公式GitHubで見てみてください。
テストコードの設定
Userモデル(usersテーブル)にnameカラム、emailカラム、passwordカラムがあると想定して、
たとえば以下のようにユーザー新規登録機能のテストコードを記述します。spec/models/user_spec.rbrequire '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 endbefore 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の基本的な構文や便利な機能を理解する」
- 投稿日:2021-02-28T21:44:47+09:00
[解決] 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.rbconfig.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
- 投稿日:2021-02-28T17:56:36+09:00
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
何かと思ったら、リセットしていいのか確認してるだけでした。
roccky-bastion-3336画面の指示に従い上記コマンドを入力してリセット完了しました。
- 投稿日:2021-02-28T17:06:47+09:00
実装とリリースが前後するのを前提としたGit運用フロー
業務で主に Ruby On Rails での開発を行っていますが、そこで得た知見や、失敗への対応などについて記します。
今回は「実装とリリースが前後する」場合に Gitの運用フローをどのようにするか、についてです。
現状(要件)
弊社は アジャイルソフトウェア開発 のうち、とりわけ エクストリーム・プログラミング(XP) を主体とした開発プロセスを採用していますが、その結果として、常に
短い開発サイクルで頻繁に「リリース」する
状態になっています。
これは「Webアプリケーション」というサービス形態からも必要なことで、より多様なチャレンジをより早く提供するために最適な方法だと考えていますが、一方、実装がやや前倒し気味に行われることもあって、「実装はしたけれども、リリースは待つ」こともよく生じます。
これに対応する方法として、一般的に考えられるのは
- 新機能は「フラグで無効化」した上でリリースし、必要になれば「フラグを有効化」する
- 新機能を実装したブランチのマージを遅らせる(待つ)
の 2つで、このどちらを採用するのかで「Gitの運用方法」そのものも異なってきます。
この両者を比較しながら、弊社では 2. の「マージを遅らせる」方法を採っているので、その説明も合わせて行います。
1. 新機能は「フラグで無効化」した上でリリースし、必要になれば「フラグを有効化」する方法
これは フィーチャートグル と言われる方法で、有名なところでは
- Google (Google の巨大レポジトリとブランチ無し運用)
- Flickr (第3回 ブランチvs.フラグ)
などが採用しています。
つまるところ、
- 新機能を実装する場合には、その機能を「無効」にした上で、メインブランチにマージしてしまう
- 利用開始までは、その機能は「無効」状態のまま、サービス全体のリリースに含めてしまう
- 利用開始になれば、その機能を「有効」に変える
といった方法で、具体的な Gitの運用フローとしては github-flow という名称で知られています。
この方法の良い点は
- ブランチが事実上一つなので、マージ時のコンフリクトの可能性をかなり少なく出来る
- 本番機でも「一部のユーザー」に新機能の提供が出来る
- 「古い機能」と「新しい機能」の共存を前提とした実装になるので、反映時のトラブルの可能性を少なく出来る
ということになり、かなり良いことずくめな印象です。
しかし一方、逆の面としては
- 「古い機能」と「新しい機能」の共存を前提とした実装に手間がかかる
- 動作確認が「機能OFFのままリリースした時」と「機能ONにした時」と実質的に2倍になる
- アプリケーションの実装が「フラグで無効化されている部分も含む」となって見通しが悪くなる
こともあって、開発上の負担も心配になるところです。
2. 新機能を実装したブランチのマージを遅らせる(待つ)
これは フィーチャーブランチ と言われる方法で、 特に Github での Pull Request (PR) はこの「フィーチャーブランチ」での開発を前提としたものと言えます。
これは、
- 機能実装時にメインブランチから「フィーチャーブランチ」を分岐させ、そのブランチで開発を行う
- 必要に応じて適宜、インテグレーション用のブランチなどにマージする
- 最終的にリリース時に、メインブランチにマージすることによって、その新機能を利用開始状態にする
という方法で、具体的な Gitの運用フローとしては gitlab-flow という名称で知られています。
この方法は
- 「古い機能」と「新しい機能」の共存状態を考慮しなくて済む場合が多い
- 動作確認が「機能リリース時」だけに絞られる
- アプリケーションの実装内容が「使われている機能のみ」となって見通しが良くなる
といった面で開発面での負担軽減になりやすい方法ですが、一方で
- 実装に時間がかかったり、機能の利用開始時期が遅れるとマージ時にコンフリクトが発生する可能性が高い
- 「一部のユーザー」にだけ新機能の提供をする、といった方法でのサービス提供がしにくくなる
- 「古い機能」と「新しい機能」の共存を前提としない実装になるので、反映時にその部分の考慮不足があると不具合が生じる
などの面での注意を要します。
ではどちらを採用しているか
結論としては弊社は 2. の「フィーチャーブランチ」での開発を主体としています。
理由としては、
- 開発を早く廻すためには「ある程度のリスクが生じても、開発の負荷を減らす」ほうがメリットが多いと判断していること
- 特に「利用開始が遅れることによるコンフリクト発生の可能性」については、出来る限りの対処を行っていること(内容については次回に記します)
- 必要に応じて「フィーチャートグル」も取り入れて、一部テストや利用開始の遅延に対処するようにしていること(同上)
等があります。
実際にどのような工夫をして「フィーチャーブランチ」での開発を進めやすくしているかについてや、具体的に gitlab-flow をどのように用いているかについては、次回に記載します。
- 投稿日:2021-02-28T17:01:03+09:00
renderにおけるlocalsとcollectionについて
localsとcollectionのオプションについての理解を深めようとこの記事を書くことにしました。
1.localsについて
仮にposts_controllersに以下の記述があるとして
posts_controllers.rbdef show @post = Post.find(params[:id) endposts/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.rbdef index @posts = Post.all endposts/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%>
- 投稿日:2021-02-28T16:08:02+09:00
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 endENUMの型を増やすことはできますが、減らしたり、名称を変更しようとするとエラーになるので注意が必要です。
enumerizeも使えます
animal.rbclass Animal extend Enumerize enumerize :animal_type, in: [:dog, :cat, :bird], predicates: true, scope: true endanimal = 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
をカラム名にすることはできないようです。
- 投稿日:2021-02-28T15:55:30+09:00
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」と仲良くならないとなと実感しました。
参考文献
- 投稿日:2021-02-28T15:38:41+09:00
Railsのdeviseを使用して複数のログイン画面を実装する方法
deviseとは
Railsのgem(ライブラリ)の1つ。
ログイン機能の実装を簡単にしてくれるgemです。
これからdeviseを使用して複数のログイン画面を実装していきます。インストール・準備
Gemfileに追記
Gemfilegem 'devise'bundleまたはbundle install実行
$ bundle installdeviseを使用できるようにインストールします。
$ 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.rbconfig.action_mailer.default_url_options = { host: 'localhost', port: 3000 }続いて2です。ルーティングを指定しましょう。
config/routes.rbroot 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 Bdb/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.rbconfig.scoped_views = trueこうすることで複数モデルを可能にするようです。
Viewを生成する
rails g devise:views As(複数形) rails g devise:views Bs(複数形)ルーティングを生成する
config/routes.rbを以下のように書き換えてください。
config/routes.rbRails.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
- 投稿日:2021-02-28T14:44:17+09:00
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へのデプロイ完了しました。
- 投稿日:2021-02-28T13:40:12+09:00
[Rails]管理画面をActiveAdminで作りたいのは俺だけか?
はじめに
とあるプロジェクトで管理画面を作る!ってなったのですが、管理画面を作るのは初めてで、どうしたものかと調べると、ActiveAdminという便利なgemを見つけました。
それを使った経験を忘れないように備忘録を踏まえて、発信したいと思います。導入手順
1.Gemfileに以下を記述し、ActiveAdminとDeviceのインストール。
gem 'activeadmin' gem 'devise'こちらも忘れず実行する。
$ bundle install2.インストール完了後、ActiveAdminに必要なファイルを生成する。
$ rails g active_admin:install※既存のユーザークラスを使用する場合は、引数として指定します。
$ rails g active_admin:install Staff3.マイグレーションの実行と初期データの作成を行う。
※先ほどのコマンドでマイグレーションファイルが作成され、seeds.rbに記述が追記されています。$ rails db:migrate $ rails db:seed4.サーバを起動した後にこちらにアクセスすると、ログイン画面が表示されるので、下記のユーザ情報でログインする。
※もし、サーバーを起動していた場合は再起動する。User ... admin@example.com
Password ... password5.必要なモデルに対する管理ページを作成する。
$ rails g active_admin:resource User # 任意のモデル名6.先ほどのコマンドで作成されたファイルに、許可したいパラメータのみを記述する。
app/admin/user.rbActiveAdmin.register User do permit_params :name, :age, :email, { images_attributes: [ :image, :id, :_destroy ] } end7.ページが作成されるので、データを打ち込んでいく。
役立つ知識
ここでは、より使いやすい管理画面を作成するためのテクニックを記載しています。
フォームをカスタマイズしたいとき
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を使うとかなりスピーディに作成できるので、機会があれば使ってみてください!!参考
ActiveAdminでちょっと使い勝手の良い管理画面を作るTips
Active Adminのデザインをちょっとカッコよくするgemまとめ
- 投稿日:2021-02-28T12:53:28+09:00
ポートフォリオ要件定義
ポートフォリオ事前レビュー
【テーマ】
働く上で発生する様々な悩みを解決する。【ターゲット】
悩みをもつ働く人々【サービス内容】
・悩みを投稿して、それに対してコメント(解決方法)をいただき、悩みを解決することができる。
・過去に同じような悩みを経験した人や、同じような悩みを解決した人に対して、個別相談することができる。
・悩み検索によって、過去の悩みから自分が抱えている悩みと似通った悩みを探すことができる。
・悩みへの解決方法が浮かばず、ユーザーの力になってあげることができなくても、悩みを応援することで、「よく応援されている悩み一覧」へ表示され、他のユーザーの目にとまりやすくなり、悩み解決率を上げることができる。【テーマ背景】
私は現在、金融及び保険の営業をしています。しかし、先日、右膝の古傷が悪化したことで歩くことが難しくなり、営業活動が長期間できないという状態です。そんな時に色々と悩みが出てきました。
①怪我した状態で出勤してよいのか?
②出勤しない場合、どういった休暇扱いになるのか?
③補助金のような援助はでるのか?
④有給を使い切ってしまった場合はどうなるのか?
⑤結局どのように活動するのが、会社と自分にとって一番よいのか?
もちろん会社の人たちにも相談はしましたが、私のように仕事が長期間できなくなるという経験した方はいないので、解決方法を見出すことはできませんでした。
このような時に、自分の会社内の方だけではなく、会社外の方からも意見を頂戴することができれば、一番ベストな解決方法にたどり着くことができるのでは?と思いました。
その他にも、転職を考えている方であれば、具体的な退職手続きや退職前後の流れなど、社内の方には相談しにくいことが多々出てくると思います。そんなとき、自社とは全く関係のない社外の方であれば、気軽に相談することができると思います。
まとめると、悩みを解決する時に、情報収集の一つとしてご活用いただけるようなサービスになればと思い、今回「働く上で発生する様々な悩みを解決する。」というテーマに決定いたしました。
また、「悩み 社会人」と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に変更
- 投稿日:2021-02-28T12:00:21+09:00
Ruby on Railsの環境構築
私の今現在の環境の構築方法を忘れないためのメモです。
参考記事
- MacOSでCommand line tools for XcodeとHomebrewのインストール
- 【完全版】MacでRails環境構築する手順の全て
- Ruby初学者のRuby On Rails 環境構築【Mac】
ありがとうございます!
バージョン
Mac OS Catalina 10.15.17
MojaveやM1だと違う可能性があるのでご注意ください。
準備
Catalinaではzshというシェルを利用するので以下コマンドをターミナルで実行し、zshをデフォルトに設定します。
パスワードを求められる場合はPCのパスワードを入力します。% chsh -s /bin/zsh以下のコマンドでログインシェルを確認します。
% echo $SHELL # 以下のように表示されればOK /bin/zshCommand Line Toolsをインストール
開発に必要なもろもろのソフトウェアをダウンロードするためには
Command Line Tools
が必要なためインストールしていきます。ターミナルに以下コマンドを入力します。
% xcode-select --install下記画像のような画面になるので
インストール
を選択します。下記画像のような画面になるので
同意する
を選択します。ちょっと時間かかりますので待ちます。
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の土台となる
rbenv
とruby-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 -vMySQLのインストール
以下のコマンドを実行して、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 runningRailsのインストール
以下コマンドで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.0Node.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.3yarnをインストール
yarnについてよくわかっていなかったのですが、以下の記事がわかりやすかったです。
ありがとうございます!
npmとはyarnとは以下コマンドを実行します。
% brew install yarn # バージョンを確認 % yarn -v以上です。
- 投稿日:2021-02-28T11:32:53+09:00
Rails I18nを便利に使おう
アプリケーションを色々な国に展開する場合やメッセージを日本語化する際にI18nを使用すると思います。
下記のようなパターンでは
user_name
をtranslateに渡すと、"太郎"
が返ってきます。config/locales/ja.ymlja: user_name: "太郎"> I18n.t(:user_name) => "太郎"しかし、
user
が、太郎
だけであればこれでも良いのですが、複数いる場合がたいていのパターンだと思います。
これを少し拡張させて、どのuserでも対応できるようにしていきます。config/locales/ja.ymlja: user_name: "%{name}"> I18n.t(:user_name, name: "太郎") => "太郎"> I18n.t(:user_name, name: "次郎") => "次郎"上記のように変更すると、引数に名前を渡せば、どんな名前でも表示できるようになりました。
これで使いやすくなりました。さらにtranslateではこのようなこともできます。
config/locales/ja.ymlja: user_name_html: <span class="user">"%{name}"</span>このように最後に
_html
をつけることで、htmlごと(今回はspanタグごと)返すこともできます。
view自体が複雑でごちゃごちゃしている場合,こうゆうパターンで書くのも一つの手かもしれません。translateは調べるといろんなことができるので、色々試してやってみましょう!!
案外今やりたいことと合致するかもしれません参考記事
- 投稿日:2021-02-28T11:18:41+09:00
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"?
環境
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/
- 投稿日:2021-02-28T10:50:16+09:00
タグ機能
- 投稿日:2021-02-28T06:38:23+09:00
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
.Gemfilegroup :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-configconfig/application.rb
Scaffold 実行時に RSpec のスケルトンを自動生成するかどうかの設定を APP::Application クラス内に記入します。自動生成が不要な場合は
g.test_framework false
とします。
また、FactoryBot についても書いておきます。config/application.rbmodule 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 endrubocop ファイル
bundle exec rubocop --auto-gen-config
を実行することで .rubocop.yml と .rubocop_todo.yml ファイルが作成されます。. ├── rubocop.yml └── rubocop_todo.yml実際の 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.jsoncircleci/config.ymlversion: 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」ボタンを押下します。
その後、すでに config.yml は設定済みのため、次の画面で「Use Existing Config」ボタンを押下します。
簡単な警告ダイアログが出ますが、「Start Building」ボタンを押下して先に進めます。
この時点で、設定に問題がなければ CircleCI の Dashboard に最初のテスト結果が「Success」として表示されます。
また、GitHub 側のリポジトリにも安心のチェックマークが表示されるようになります。
RSpec の実装
rubocop は書式の問題なのでさておき、RSpec の動作確認だけ簡単に説明します。
前提
前回の最後に scaffold で作成した users テーブルがあることとします。
$ docker-compose run app rails g scaffold user name:string email:stringバリデーション追加
User モデルにバリデーションを追加します。
app/models/user.rbclass 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 } endFactoryBot
ユニットテストで使用するテストデータを事前に設定しておきます。
spec/factories/users.rbFactoryBot.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.rbrequire '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 rubocopGitHub へ push
問題がなければ git commit と git push を行います。
これだけで GitHub へ反映されると同時に CircleCI が RSpec と rubocop を自動実行してくれます。今回は RSpec と rubocop の実施だけですが、エラーがなかった場合にそのまま本番環境へのデプロイなどを行うことも可能です。
少し駆け足となってしまいましたが、今回は以上となります。
参考資料
以下の記事、情報を参考にさせていただきました。
ありがとうございます。
- 投稿日:2021-02-28T00:00:28+09:00
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 endGemfile
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