今週からは、scaffoldで作成した大学データと、gemのdevise、Bootstrap等を組み合わせる。
#やった事
- Railsの命名規則(単数形と複数形)
- DBのカラム定義を後から変更
- render partial: 部分テンプレの参照
- validation
- UNSIGNEDという型が存在しないPostgreSQL
#使用環境
- ホストOS: Windows10 Home
- 仮想環境: Ubuntu Bento/Bionic
- Ruby:2.51
- Rails: 5.2.2
- gem 'devise' : ログイン等の機能用
- gem 'kaminari' : ページネーション
- DB: PostgreSQL
#Railsの命名規則(単数形と複数形)
rails gコマンドで、controller名やmodel名を指定する際に、混乱した。
# rails generate scaffold model名の単数形 フィールド名の型と並び
# rails g controller controller名の複数形
# カラムの追加
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
- model名は単数形で、頭文字を大文字にする
- scaffoldの場合、modelが基準
- modelは設計書であり、(テーブル1つに付き)1つなため
- controller名は複数形で、頭文字を大文字にする。
- 1つのcontrollerに複数のactionが含まれるため
#DBのカラム定義を後から変更
rails g scaffoldコマンド時に、ClubStudentの外部キーの定義をreferecesとミスタイプしていた。
###修正方法:app/db/migrate下のファイルを修正
class CreateClubStudents < ActiveRecord::Migration[5.2]
def change
create_table :club_students do |t|
#スペリングミス
#t.refereces :student
#訂正分
t.references :student
t.references :club, foreign_key: true
t.timestamps
end
end
end
なお、ALTTER TABLEコマンドを使って、あとから修正する方法は
DB内のデータを書き換えるだけで、アプリ自体のファイル等は編集されない。
# ALTER TABLE テーブル名 MODIFY COLUMN カラム名 新しい定義
ALTER TABLE ClubStudent MODIFY COLUMN student references
つまり、原因の根本的な部分を修正できないので、駄目
#render partial: 部分テンプレの参照
render レンダリング(render) - railsドキュメント
全てのページのヘッダー(上部)に、ログアウトや他のstudentやclubs等のリンクを乗せる
共通して表示させるので、/app/views/layouts/application.html.erb を編集する。
なお、部分テンプレファイル名は『_』アンダーバー始まり
<body>
# <%= render :partial => '部分テンプレ名' %>
<%= render :partial => 'shared/header' %>
</body>
表示させたいリンクを書きこむ。
<%= link_to 'Student list', students_path %>
<%= link_to 'subjects list', subjects_path %>
<%= link_to 'clubs list', clubs_path %>
<%= link_to 'exam_result list', exam_results_path %>
<%= link_to 'club_stdent list', club_students_path %>
<%= link_to 'Log Out', destroy_student_session_path, method: :delete %>
#validation
参考リンク Active Record Validations
バリデーションは有効なデータだけをDBに保存するのを確実にするための最善策。
##validate条件
###空でないこと
validates :name, presence: true
# 因みに、空が条件ならば
# validates :name, absence: true
###入力文字の長さ
文字の最大長は、データ型を要参照。varcharなら255文字まで
# 2文字以上
validates :name, length:{minimum:2}
# 255文字以上
validates :name, length:{maximum:255}
###exclusion含まない
授業ではしなかったが、クラブ名末尾に『部』を入れない、という条件を追加してみる
validates :name, exclusion: { in: %w(部 サークル) }
# 『含む』ならinclusion
##実装結果
空白や文字列長、『サークル』という語には、validatesが発動した
ただ、現状だと、『テニスサークル』の様に文字列と連結すると、validateが動かない
あとまわし
Club was successfully updated.
Name: サークル部
#type "unsigned" does not exist (※Postgresql)
validatesの実装していく最中に、エラーに気づいた
studentのeditページで更新すると、
ActiveRecord::StatementInvalid in StudentsController#show
PG::UndefinedObject: ERROR: type "unsigned" does not exist LINE 1: ...id as subject_id, CAST(AVG(exam_results.score) as unsigned) ... ^ : SELECT subjects.id as subject_id, CAST(AVG(exam_results.score) as unsigned) as avg_score, MAX(exam_results.score) as max_score, MIN(exam_results.score) as min_score FROM "students" INNER JOIN "exam_results" ON "exam_results"."student_id" = "students"."id" INNER JOIN "subjects" ON "subjects"."id" = "exam_results"."subject_id" GROUP BY subjects.id ORDER BY subjects.id
と、エラーを吐き、因みに、ブラウザの戻るボタンで戻ると、更新されている。
また、エラー原因であると思わる、StudentController#showは
def show
@students = Student.joins(:subjects)
.select('students.name, students.email, students.age, students.gender, students.opinion, subjects.id as subject_id')
.select('exam_results.name as exam_result_name, subjects.name as subject_name, exam_results.score')
.select('CAST((exam_results.score / subjects.max_score) * 100 as unsigned) as ratio')
.where(id: params[:id])
avg_result = Student.joins(:subjects)
.select('subjects.id as subject_id')
.select('CAST(AVG(exam_results.score) as unsigned) as avg_score')
.select('MAX(exam_results.score) as max_score')
.select('MIN(exam_results.score) as min_score')
.group('subjects.id')
.order('subjects.id')
(以下略)
因みに、このcontrollerは、以前の大学データのcontrollerからコピーしてきたものだ。
つまり、MySQLで動くアプリのcontroller。
##unsigned (MySQL)
- MySQLにおいては正と負の整数を扱うことができる。
- unsignedを指定すると、正の数しか格納できなくなり、代わりに範囲が2倍になる。
-
unsignedにした値が負になると、エラーを起こす
- UNSIGNEDは、マイナス値が入らないだけでなく、マイナスになる計算もできない。
- CASTで一時的に型を変える事で回避は可能。
Postgresqlにはunsined型は存在しない(最重要)
###対応策
まだ、試験結果のデータを入れてないので、功を奏すか分からないけれども
- unsignedをint等の型に置き換える
- 今回は試験点数を扱っていて、intで事足りると思われる。
- ただ、MySQLでint unsignedだと、範囲が正の方向に2倍になっている。
- 扱う数によっては、intより1つ上ののbigintに変える必要がある
- CAST as unsignedの部分を消す
- MySQLでCAST as unsingedは、一時的に型を指定している
前回の大学データに倣って、今回はcast as intに変更した
# (該当部分だけ抜き出し)
.select('CAST((exam_results.score / subjects.max_score) * 100 as int) as ratio')
.select('CAST(AVG(exam_results.score) as int) as avg_score')
正常に、studentデータのedit、updateが機能した。
#データ入力にはpassword情報が必要
deviseの関係上、パスワード情報入りのデータでないと、コンソールから入力できない。
##passwordカラムの追加
deviseのモデル等がある、Studentテーブルに、パスワードカラムを追加した。
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
rails g migration AddPasswordToStudents password:string
db/migrate下にファイルが生成される
class AddPasswordToStudents < ActiveRecord::Migration[5.2]
def change
add_column :students, :password, :integer
end
end
これで、パスワード情報入りの生徒データをDBに入力できる。
##データ入力
未だデータの無い、生徒データと試験結果データをコンソールで入力した。
(1..100).each do |num|
if num % 2 == 0 && num % 3 ==0
gen = 0
ag = 1
elsif num % 2 == 0
gen = rand(2)
ag = rand(3)
else
gen = 1
ag = 0
end
op = (1..10).map{('あ'..'わ').to_a[rand(26)]}.join
nm = (1..3).map{('あ'..'わ').to_a[rand(26)]}.join
user = Student.create!(name: "#{nm}", email: "#{nm}-#{rand(98)}@gmail.com", gender: gen, age: ag, opinion: op,password: 'password')
end
(1..100).each do |i|
student = Student.find(i)
1.upto(rand(0..4)) do
student.clubs << Club.find(rand(1..14))
student.save
end
end