はじめに
今日もチュートリアルに取り組んでいく。
今回はモデルを主に扱っているようだ
Userモデル
・Railsでは、データモデルとして扱うデフォルトのデータ構造のことを モデル(Model) と呼びます
・データベースを使うことで長時間データを保存することができる
・データベースをやりとりするライブラリ(テンプレのようなもの)を Active Record という
・ActiveRecordにはデータの作成、保存、検索のメソッドが用意されている
・これらをつかうためにSQLを意識する必要がない!!
・マイグレーション = Rubyでデータのはいったテーブル(表)を操作する仕組み
Railsにおける属性
・第4章ではattr_accesor
を用いてUserクラスにnameとemail属性を定義した。
・しかしこれはRubyだから必要なのであって、実はRailsでは属性を定義する必要がない(デフォルトでその仕組が提供されている)
モデル作成
・ここでこれらのユーザー情報を保存するモデルを作成する
・Controllerを作るときと要領は同じ
rails generate controller Users new
さらにこのモデルにはnameとemail属性を持たせたいので
rails generate model User name:string email:string
このように記述する(モデル名は必ず単数形に!)
・モデル作成のときに属性も一緒に渡す
・この時これらの属性の型情報も一緒に渡します(今回は両方とも文字列なのでstringを渡す)
データベースのマイグレーション
・generate
コマンドを使うと、同時にマイグレーションと呼ばれるファイルも作成される
・マイグレーションは、データベースの構造を段階的に変更する手段を提供します
・マイグレーションファイルの冒頭には、作成した時間がタイムスタンプとして名付けられる
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
・コードを見て分かる通り、マイグレーションファイルはデータの変更を行うchangeメソッドの集まりである
・create_table
メソッドは、ユーザーを保存するためのテーブル(表)をデータベースに作成するメソッド
・create_tableメソッドはブロック変数を1つ持つブロックを受け取ります(今回はt)
・このコードは、:users
テーブルに:name
と:email
要素をブロック変数を使ってそれぞれusersテーブルに作成します
・テーブル内には複数のユーザーを追加するので、単数形ではなく複数形を使っている。
・モデル自体は一人のユーザーを表す!
・最後のt.timestamps
はテーブルにcreated_atとupdated_atという要素を作成する
・created_atとupdated_atは「マジックカラム(Magic Columns)」と呼ばれる
・以上でマイグレーションを用いてデータモデルを作成することができた。
・つまりマイグレーションファイルでデータベースを操作する!!
マイグレーションの実行
しかしまだこれではマイグレーションは実行されていない。そこで
rails db:migrate
・このコマンドを実行することで、マイグレーションが実行される
・実行すると、development.sqlite3というファイルが実行される
・ここから考えるに、マイグレーションでSQLiteを操作している、?
・エディタではこのファイルは見ることができないが、 Browser for SQLiteというツールを使うとデータベースの中身を見ることができる。
ユーザーオブジェクトの作成
・User.newを引数なしで読んだ場合はすべての属性がnilになる
・しかしUser.newを実行しただけではデータベースに保存されていない!(ただオブジェクトを作成しただけ)
・保存するためには、saveメソッドを呼び出す必要がある
・saveメソッドは保存が成功したらtrue 失敗したらfalseを返す
> user = User.new(name: "Michael Hartl", email: "michael@example.com")
> user
=> #<User id: nil, name: "Michael Hartl", email: "michael@example.com",
created_at: nil, updated_at: nil>
コンソールでUser.newを起動するとこのようになる
User.newでuserオブジェクトを作成した時点ではまだid
created_at
updated_at
がnilになっている。
しかしsaveメソッドを取り出すと、
> user.save #saveを実行
> user #その後userを呼び出すと,,,
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2022-03-11 01:51:03", updated_at: "2022-03-11 01:51:03">
このようにid
created_at
updated_at
がそれぞれ作成された
もちろん今後userの内容が更新されるとupdated_atのタイムスタンプは変更される。
また、Userモデルのインスタンスはドットを用いて属性にアクセスできる
>> user.name
=> "Michael Hartl"
>> user.email
=> "michael@example.com"
>> user.updated_at
=> Fri, 11 Mar 2022 01:51:03 UTC +00:00
先程はUser.new(インスタンスの作成)をした後にUser.save(インスタンスをデータベースに保存)し、順序を踏んだがこれを同時に行うメソッドが存在する。
create
メソッドを使うことにより、この2つを同時に実行することができる。
save
の際はtrueとfalseを返していたが、その代わりにcreate
ではユーザーオブジェクト自身を返す
>> User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2022-03-11 01:53:22", updated_at: "2022-03-11 01:53:22">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2022-03-11
01:54:03", updated_at: "2022-03-11 01:54:03">
また、上のfooのように変数を代入することもできる
destroy
メソッドも存在するが、これは完全にcreate
の逆であり、オブジェクトを削除する。
ユーザーオブジェクトの検索方法
早速ユーザーの作成ができたところで、作成したユーザーを検索していきたい
findメソッド
find
メソッドでは引数にidを渡して適合するユーザーを返します
> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2022-03-11 01:51:03", updated_at: "2022-03-11 01:51:03">
find_byメソッド
find_by
メソッドではオブジェクトの属性を指定して適合するユーザーを返す
>> User.find_by(email: "michael@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2022-03-11 01:51:03", updated_at: "2022-03-11 01:51:03">
→おそらくユーザー名検索機能はこれを使っているのではないか...?
first,allメソッド
first
は単にデータベースの最初のユーサーを返します
all
メソッドもその名の通りすべてのユーザーを返します
ユーザーの更新
基本的な更新の方法は2つある
1つは属性を個別に代入していく方法
>> user # userオブジェクトが持つ情報のおさらい
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2022-03-11 01:51:03", updated_at: "2022-03-11 01:51:03">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true
最後にsaveを実行してデータベースに保存することをお忘れなく。
保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みするので、次のように変更が取り消されます。
>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"
もう1つの方法は、update
を使う方法
update
メソッドは属性のハッシュを受取り、更新を行う
このとき更新と保存を同時に行うため、saveはいらない
>> user.update(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"
特定の属性のみを変更したいときは、update_attribute
メソッドを使用する
ユーザーの検証 "Validation"
先ほどname
とemail
属性を持ったユーザーデータを作成しました。
しかし現時点ではどんな値にすることもできてしまいます。
つまりemailをabcdという値にすることだってできてしまう。
それだと全然emailアドレスではないのでフォーマットに従わせる必要がある
またname
は空の値ではいけないし、ユーザー名は重複しないようにする必要がある(今回はemailをユーザー名とする)
そこで使う要素がValidationだ
Validation(検証)とは?
→今回においてはそれぞれのデータの属性に対する値に何かしらの成約を与えることをいう
制約の内容は、先ほど取り上げたメールアドレスのフォーマットや重複しないという意味の一意性、空白であってはいけない存在性*、文章の長さを制限する長さ**など
このような制約をすることがValidationだ
最後に最終検証として確認という作業もある
早速検証してみよう
私もこれを書いていて気付いたが、これこそテスト駆動開発と相性がいいじゃないか!!
例えばテストでユーザー名が空白でないか調べるテストを先に書いたら、後々楽になるじゃないか!!
もちろん同じことがチュートリアルにも書いてありました。(今度チュートリアルのテストの部分をまとめて復習できる記事を書きたい)
テスト方法 ※重要
1.テストするバリデーションに適合するモデルのオブジェクトを作成
2.その属性のうちの1つを有効でない属性に意図的に変更する
3.バリデーションで失敗するか検証する
・バリデーションのコードが問題なのか、オブジェクトがバリデーションに適合してないだけなのかどちらの問題なのか切り離して考えられる
・わざとバリデーションが有効でない属性に変更したけど、成功する場合はバリデーションコードが間違っているということ(逆に適合させてもテストが失敗する場合も同じ)
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
end
これはバリデーションに適合するオブジェクトをテストするテストコードである(つまり1の部分)
@user
でバリデーションに適合するユーザーオブジェクトを作成しています。これはテスト内だけでテストのために仮に使われるオブジェクトです(つまりこのオブジェクトがバリデーションに適合する"答え"なのである)
またsetup
メソッドとは「テストが実行される前に実行されるメソッド」なので、テストが走る前に適合するユーザーオブジェクトを作成しているということ。
その後シンプルなassert
メソッドを使い、@user.valid?
で@user
がバリデーションに適合していますか?ということをテストしている(valid?
でバリデーションに合っているか調べている)
現在は何もバリデーションを設定していないので、無条件にテストは成功する
存在性のテスト
先ほども触れたが、そのオブジェクトの属性が存在するかどうかをテストしていく
つまり、空白ではエラーになってしまうようにしたい。
先にテストを書いていく。
test "name should be present" do
@user.name = " "
assert_not @user.valid?
end
これは先程のテストファイルに追加する。
まず@user
のname属性が空であることを示している
今回はassert_not
なので、つまり 「バリデーションに適応しなかったらテストはクリアする」 ということである。(失敗したら成功!みたいな感じ)
つまり「テストします!今回@user
のname属性に何も入ってません! そこで、バリデーションの条件に適合していなかったらこのテストは成功ですよ!」といっているようなもの
バリデーションの作成
バリデーションはuser.rbファイルの中に作成する。(app/models/user.rb)
バリデーションとは、単なるvalidate
メソッドなのである。
class User < ApplicationRecord
validates :name, presence: true
end
今回のpresence:true
が存在性を示すvalidateのオプションハッシュなのである。
第4章でやったとおりカッコがすべて省略されている。
validates (:name, presence: true)
カッコを省略しないとこのようになる。
つまり今回はバリデーションを:name
要素に適用し、その効果はpresence: true
(空であってはいけませんよ)という意味になる
この調子で:email
属性にも存在性のテストとバリデーションを追加した(本書6.12)
長さの検証
文字の長さにも制約を与えます。
今回はユーザー名を50文字、メールアドレスを255文字まで設定可能ということにします。
つまりそれぞれ51,256文字だったらアウトにしたいのです
test "name should not be too long" do
@user.name = "a" * 51
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 244 + "@example.com"
assert_not @user.valid?
end
先程のテストが読めたら、これも簡単に読めるのではないでしょうか
たとえばnameだったら、aを51回かけることによって文字数がオーバーしていることを表しています
それぞれ最後にvalid?でバリデーションに適合しているかテストしている
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
end
気づいてるかもですが、基本的にvalidates :name or :email
まで使いまわしていることがわかります。
validatesの引数にこのように渡してあげることで1行で書くことができるのです
class User < ApplicationRecord
validates :name, (presence: true, length: { maximum: 50 })
validates :email, (presence: true, length: { maximum: 255 })
end
わかりやすくかっこをつけてあげると、このようになる(はず)
長さを表すためには、length:{maximum: 文字数}
と書く
おそらくこの要領で最小文字数もいけるはず
残りのテストに関しては一旦寝て明日やる