2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

14日目:Scaffoldで作成したサイトにgem devise等を組み合わせていく

Last updated at Posted at 2019-03-26

今週からは、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下のファイルを修正

db/migrate/20190326030303_create_club_students.rb
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内のデータを書き換えるだけで、アプリ自体のファイル等は編集されない。

mysql
# ALTER TABLE テーブル名 MODIFY COLUMN カラム名 新しい定義
ALTER TABLE ClubStudent MODIFY COLUMN student references

つまり、原因の根本的な部分を修正できないので、駄目

#render partial: 部分テンプレの参照
render レンダリング(render) - railsドキュメント

全てのページのヘッダー(上部)に、ログアウトや他のstudentやclubs等のリンクを乗せる
tempsnip.jpg

共通して表示させるので、/app/views/layouts/application.html.erb を編集する。
なお、部分テンプレファイル名は『_』アンダーバー始まり

/app/views/layouts/application.html.erb
<body>
# <%= render :partial => '部分テンプレ名' %>
  <%= render :partial => 'shared/header' %>
</body>

表示させたいリンクを書きこむ。

/app/views/shared/_header.html.erb
<%= 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に保存するのを確実にするための最善策。

今回の実装先:clubの新規作成ページ
newclub_form.JPG

##validate条件
###空でないこと

validates :name, presence: true
# 因みに、空が条件ならば
# validates :name, absence: true

###入力文字の長さ
文字の最大長は、データ型を要参照。varcharなら255文字まで

/app/models/club.rb
# 2文字以上
validates :name, length:{minimum:2}
# 255文字以上
validates :name, length:{maximum:255}

###exclusion含まない
授業ではしなかったが、クラブ名末尾に『部』を入れない、という条件を追加してみる

/app/models/club.rb
validates :name, exclusion: { in: %w(部 サークル) }
# 『含む』ならinclusion

##実装結果
空白や文字列長、『サークル』という語には、validatesが発動した
validate-clubnew.JPG

ただ、現状だと、『テニスサークル』の様に文字列と連結すると、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は

app/controllers/students_controller.rb

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に変更した

app/controllers/students_controller.rb
# (該当部分だけ抜き出し)
.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テーブルに、パスワードカラムを追加した。

terminal
# rails generate migration AddカラムToモデル名の複数形 フィールド名と並び
rails g migration AddPasswordToStudents password:string

db/migrate下にファイルが生成される

/db/migrate/20190327144825_add_password_to_students.rb
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
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?