Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # 学習用メモ
- ***
- ## 6章.ユーザーのモデルを作成する
- ### 理解できたもの
- この章では、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学んでいく。
- *Userモデル*
- 今のままでは新しいユーザーの情報を受け取っても保存する場所がないので、いきなりページを作成するわけには行かない。ユーザー登録でまず初めにやることは、それらの情報を保存するためのデータ構造を作成すること。
- Railsでは、データモデルで使用するデフォルトのデータ構造のことをモデルと呼ぶ。Railsでは、データを永続化するデフォルトの解決策として、データベースを使用してデータを長期間保存する。また、データベースとやりとりするデフォルトのRailsライブラリはActive Recordと呼ばれる。Active Recordは、データオブジェクトの作成/保存/検索のためのメソッドを持っている。
- さらに、Railsにはマイグレーションという機能がある。データの定義をRubyで記述することができ、SQLのDDL (Data Definition Language)を新たに学ぶ必要がない。
- *ユーザーをモデリングするためのトピックブランチを作成する。*
- ```
- $ git checkout master
- $ git checkout -b modeling-users
- ```
- *データベースの移行*
- この節での目的は、簡単に消えることのないユーザのモデルを構築すること。
- nameとemailの2つの属性からなるユーザーをモデリングするところから始める。後者のemailを一意のユーザー名として使用する。
- Railsでユーザーをモデリングするときは、属性を明示的に識別する必要がない。上で述べたように、Railsはデータを保存する際にデフォルトでリレーショナルデータベースを使用する。リレーショナルデータベースは、データ行で構成されるテーブルからなり、各行はデータ属性のカラム (列) を持つ。たとえば、nameとemailを持つユーザーを保存するのであれば、nameとemailのカラムを持つusersテーブルを作成する。
- ユーザーコントローラを作成した時は以下のコマンドを使った。
- ```
- $ rails generate controller Users new
- ```
- モデルを作成するときは、上と似たようなパターンでgenerate modelというコマンドを使う。さらに、今回はnameやemailといった属性を付けたUserモデルを使いたいので、実際に打つコマンドは以下になる。
- *Userモデルを生成する*
- ```
- $ rails generate model User name:string email:string
- invoke active_record
- create db/migrate/20140724010738_create_users.rb
- create app/models/user.rb
- invoke test_unit
- create test/models/user_test.rb
- create test/fixtures/users.yml
- ```
- *コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れておく。コントローラはUsersでモデルはUser*
- name:stringやemail:stringオプションのパラメータを渡すことによって、データベースで使用したい2つの属性をRailsに伝える。このときに、これらの属性の型情報も一緒に渡す。
- generateコマンドの結果のひとつとして、マイグレーションと呼ばれる新しいファイルが生成される。マイグレーションは、データベースの構造をインクリメンタルに変更する手段を提供する。それにより、要求が変更された場合にデータモデルを適合させることができる。このUserモデルの例の場合、マイグレーションはモデル生成スクリプトによって自動的に作られる。
- 以下のようにnameとemailの2つのカラムを持つuserテーブルを作成する。
- *usersテーブルを作るためのUserモデルのマイグレーション*
- ```
- db/migrate/[timestamp]_create_users.rb
- class CreateUsers < ActiveRecord::Migration
- def change
- create_table :users do |t|
- t.string :name
- t.string :email
- t.timestamps null: false
- end
- end
- end
- ```
- マイグレーションファイル名の先頭には、それが生成された時間のタイムスタンプが追加される。これによって、複数のプログラマが同じ整数を持つマイグレーションファイルの生成するというコンンフリクトを避けられる。
- イグレーション自体は、データベースに与える変更を定義したchangeメソッドの集まり
- 上のコードの場合、changeメソッドはcreate_tableというRailsのメソッドを呼び、ユーザーを保存するためのテーブルをデータベースに作成する。create_tableメソッドはブロック変数を1つ持つブロックを受け取る。ここでは (“table”の頭文字を取って) t。そのブロックの中でcreate_tableメソッドはtオブジェクトを使って、nameとemailカラムをデータベースに作る。型はどちらもstringです4。モデル名は単数形 (User) だが、テーブル名は複数形 (users)。これはRailsで用いられる言葉の慣習を反映している。モデルはひとりのユーザーを表すのに対し、データベースのテーブルは複数のユーザーから構成される。ブロックの最後の行t.timestampsは特別なコマンドで、created_atとupdated_atという2つの「マジックカラム」を作成する。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプ。
- マイグレーションは、以下のようにrakeコマンドを使って実行することができる。これを「マイグレーションの適用 (migrating up)」と呼ぶ。
- ```
- $ bundle exec rake db:migrate
- ```
- 初めてdb:migrateが実行されると、db/development.sqlite3という名前のファイルが生成される。。これはSQLite5データベース。
- Railsチュートリアルで使用されているものすべてを含め、ほとんどのマイグレーションが可逆。これは、db:rollbackというRakeタスクで変更を取り消せることを意味する。これを“マイグレーションの取り消し (migrate down) と呼ぶ。
- ```
- $ bundle exec rake db:rollback
- ```
- *modelファイル*
- Userモデルの作成によってどのようにマイグレーションファイルが作成されるかを見てきた。そしてこのマイグレーションを実行した結果を見た。
- また、モデル用のuser.rbも作られた。ここでは、このモデル用ファイルを理解することに専念する。
- まずは、app/models/ディレクトリにある、user.rbファイルに書かれたUserモデルのコードを見てみる。
- *生成されたばかりのUserモデル*
- ```
- app/models/user.rb
- class User < ActiveRecord::Base
- end
- ```
- class User < ActiveRecord::Baseという構文で、UserクラスはActiveRecord::Baseを継承するので、Userモデルは自動的にActiveRecord::Baseクラスのすべての機能を持つ。
- *ユーザーオブジェクトを作成する*
- Railsコンソールを使用してデータモデルを調べてみる。現時点ではデータベースを変更したくないので、コンソールをサンドボックスモード(ここで行われた変更は終了時にロールバッkされる)で起動。
- ```
- $ rails console --sandbox
- Loading development environment in sandbox
- Any modifications you make will be rolled back on exit
- >>
- ```
- モデルを使う場合。Railsコンソールは起動時にRailsの環境を自動的に読み込むため、その環境にはモデルも含まれます。つまり、新しいユーザーオブジェクトを作成するときに余分(ファイルを明示的にrequireする)な作業を行わずに済む。
- ```
- >> User.new
- => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
- ```
- User.newを引数なしで呼んだ場合は、すべての属性がnilのオブジェクトを返す。
- 以下のように引数を持たせると
- ```
- >> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
- => #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
- created_at: nil, updated_at: nil>
- ```
- nameとemail属性が期待どおり設定されていることがわかる。
- Active Recordを理解する上で、「有効性 (Validity)」という概念も重要。まず先ほどのuserオブジェクトが有効かどうか確認してみる。確認するためにはvalid?メソッドを使う。
- ```
- >> user.valid?
- true
- ```
- 現時点ではまだデータベースにデータは格納されていない。User.newはメモリ上でオブジェクトを作成しただけで、user.valid?という行はただオブジェクトが有効かどうかを確認しただけ。
- データベースにUserオブジェクトを保存するためには、userオブジェクトからsaveメソッドを呼び出す必要がある。
- ```
- >> user.save
- (0.2ms) begin transaction
- User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users".
- "email") = LOWER('mhartl@example.com') LIMIT 1
- SQL (0.5ms) INSERT INTO "users" ("created_at", "email", "name", "updated_at)
- VALUES (?, ?, ?, ?) [["created_at", "2014-09-11 14:32:14.199519"],
- ["email", "mhartl@example.com"], ["name", "Michael Hartl"], ["updated_at",
- "2014-09-11 14:32:14.199519"]]
- (0.9ms) commit transaction
- => true
- ```
- saveメソッドは、成功すればtrueを、失敗すればfalseを返す。
- 作成した時点でのユーザーオブジェクトは、id属性、マジックカラムであるcreated_at属性とupdated_at属性の値がいずれもnilであった。
- saveメソッドを実行されたことで、idには1という値が代入され、一方でマジックカラムには現在の日時が代入されているのがわかる。
- Userモデルのインスタンスはドット記法を用いてその属性にアクセスすることができる。
- ```
- >> user.name
- => "Michael Hartl"
- >> user.email
- => "mhartl@example.com"
- >> user.updated_at
- => Thu, 24 Jul 2014 00:57:46 UTC +00:00
- ```
- 上で見たようにモデルの生成と保存を2つのステップに分けておくと何かと便利。しかし、Active RecordではUser.createでモデルの生成と保存を同時におこなう方法も提供されている。
- ```
- >> User.create(name: "A Nother", email: "another@example.org")
- #<User id: 2, name: "A Nother", email: "another@example.org", created_at:
- "2014-07-24 01:05:24", updated_at: "2014-07-24 01:05:24">
- >> foo = User.create(name: "Foo", email: "foo@bar.com")
- #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
- 01:05:42", updated_at: "2014-07-24 01:05:42">
- ```
- destroyはcreateの逆で削除する。
- ```
- >> foo.destroy
- => #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
- 01:05:42", updated_at: "2014-07-24 01:05:42">
- ```
- estroyはそのオブジェクト自身を返すが、その返り値を使用しても、もう一度destroyを呼ぶことはできない。さらに、削除されたオブジェクトは、以下のようにまだメモリ上に残っている。
- ```
- >> foo
- => #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
- 01:05:42", updated_at: "2014-07-24 01:05:42">
- ```
- では、オブジェクトが本当に削除されたかどうかをどのようにして知ればよいのか。そして、保存して削除されていないオブジェクトの場合、どうやってデータベースからユーザーを取得するのでしょうか。これらの問いに答えるためには、Active Recordを使ってUserオブジェクトを検索する方法について学ぶ必要がある。
- *ユーザオブジェクトを検索する*
- Active Recordには、オブジェクトを検索するための方法がいくつもある。これらの機能を使用して、過去に作成した最初のユーザーを探してみる。
- また、3番目のユーザー (foo) が削除されていることを確認する。まずは存在するユーザーから探してみる。
- ```
- >> User.find(1)
- => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
- created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
- ```
- User.findにユーザーのidを渡しています。その結果、Active Recordはそのidのユーザーを返す。
- id=3のユーザーがまだデータベースに存在するかどうかを確認。
- ```
- >> User.find(3)
- ActiveRecord::RecordNotFound: Couldn't find User with ID=3
- ```
- この場合見つからないので例外が起こる。
- 一般的なfindメソッド以外に、Active Recordには特定の属性でユーザーを検索する方法もある。
- ```
- >> User.find_by(email: "mhartl@example.com")
- => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
- created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
- ```
- そのほかにもfirstメソッド。
- ```
- >> User.first
- => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
- created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
- ```
- firstは単にデータベースの最初のユーザーを返す。
- allメソッド。
- ```
- >> User.all
- => #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl",
- email: "mhartl@example.com", created_at: "2014-07-24 00:57:46",
- updated_at: "2014-07-24 00:57:46">, #<User id: 2, name: "A Nother",
- email: "another@example.org", created_at: "2014-07-24 01:05:24",
- updated_at: "2014-07-24 01:05:24">]>
- ```
- User.allでデータベースのすべてのUserオブジェクトが返ってくることがわかる。また、返ってきたオブジェクトのクラスが ActiveRecord::Relationとなっている。これは、各オブジェクトを配列として効率的にまとめてくれるクラス。
- *ユーザーオブジェクトを更新する*
- 基本的な更新の方法は2つ。ひとつは、属性を個別に代入する方法。
- ```
- >> user # Just a reminder about our user's attributes
- => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
- created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
- >> 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"
- ```
- user.saveを実行したことでユーザーが更新できた。マジックカラムの更新日時も更新されている。
- ```
- >> user.created_at
- => "2014-07-24 00:57:46"
- >> user.updated_at
- => "2014-07-24 01:37:32"
- ```
- 属性を更新するもうひとつの方法は、update_attributesを使う。
- ```
- >> user.update_attributes(name: "The Dude", email: "dude@abides.org")
- => true
- >> user.name
- => "The Dude"
- >> user.email
- => "dude@abides.org"
- ```
- update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う (保存に成功した場合はtrueを返す)。ただし、検証に1つでも失敗すると、update_attributesの呼び出しは失敗する。
- #### ユーザーを検証する
- nameとemailにあらゆる文字列を許すのは避けるべき。これらの属性値には、何らかの制約を与える必要がある。
- Active Record では検証 (Validation) という機能を通して、こういった制約を課すことができるようになっている。
- + 存在性 (presence)の検証
- + 長さ (length)の検証
- + フォーマット (format)の検証
- + 一意性 (uniqueness)の検証
- *有効性のテスト*
- モデルのバリデーション機能は、テスト駆動開発とまさにピッタシの機能と言える。
- *具体的なテスト方法*
- まず有効なモデルのオブジェクトを作成し、その属性のうちの1つを有効でない属性に意図的に変更する。そして、バリデーションで失敗するかどうかをテストする、といった方針で進めていく。念のため、最初に作成時の状態に対してもテストを書いておき、最初のモデルが有効であるかどうかも確認しておく。このようにテストすることで、バリデーションのテストが失敗したとき、バリデーションの実装に問題があったのか、オブジェクトそのものに問題があったのかを確認することができる。
- まずはUser用テストの中身から見ていく。
- *デフォルトのUserテスト (モックのみ)*
- ```
- test/models/user_test.rb
- require 'test_helper'
- class UserTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
- end
- ```
- ここに、有効なオブジェクトに対してテストを書くために、setupという特殊なメソッドを使って有効なUserオブジェクト(@user)を作成する。
- setupメソッド内に書かれた処理は、書くテストが走る直前に実行される。@userはインスタンス変数だが、setupメソッド内で宣言しておけば、全てのテスト内でこのインンスタンス変数が使えるようになる。
- したがって、valid?メソッドを使ってUserオブジェクトの有効性をテストすることができる。
- *有効なUserかどうかをテストする*
- ```
- GREEN
- test/models/user_test.rb
- 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
- ```
- シンプルなassertメソッドを使ってテストする。@user.valid?がtrueを返すと成功し、falseを返すと失敗する。
- まだバリデーションがないためこのテストは成功する。
- ```
- $ bundle exec rake test:models
- ```
- rake test:modelsというコマンドを実行してルガ、これはモデルに関するテストだけを走らせるコマンド。
- *存在性を検証する*
- 最も基本的なバリデーションは「存在性 (Presence)」。これは単に、与えられた属性が存在することを検証する。この節では、ユーザーがデータベースに保存される前にnameとemailフィールドの両方が存在することを保証する。
- name属性の存在性に関するテストを追加する。
- まず@user変数のname属性に対して空白の文字列をセットする。そして、assert_notメソッドを使って Userオブジェクトが有効でなくなったことを確認する。
- *name属性にバリデーションに対するテスト*
- ```
- RED
- test/models/user_test.rb
- 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
- test "name should be present" do
- @user.name = " "
- assert_not @user.valid?
- end
- end
- ```
- この時点では、モデルのテストは失敗する。
- name属性の存在を検査する方法は、validatesメソッドにpresence: trueという引数を与えて使う。presence: trueという引数は、要素がひとつのオプションハッシュである。
- メソッドの最後の引数としてハッシュを渡す場合、波括弧を付けなくても問題ない。
- *name属性の存在性を検証する*
- ```
- GREEN
- app/models/user.rb
- class User < ActiveRecord::Base
- validates :name, presence: true
- end
- ```
- コンソールを起動して、Userモデルに検証を追加した効果を見てみる。
- ```
- $ rails console --sandbox
- >> user = User.new(name: "", email: "mhartl@example.com")
- >> user.valid?
- => false
- ```
- user変数が有効かどうかをvalid?メソッドでチェックすることができる。もしオブジェクトがひとつ以上の検証に失敗したときは、falseを返す。 また、すべてのバリデーションに通ったときにtrueを返す。
- どの検証が失敗したのかは。失敗した時に得られるerrorsオブジェクトを使って確認すればわかる。
- ```
- >> user.errors.full_messages
- => ["Name can't be blank"]
- ```
- Userオブジェクトは有効ではなくなったので、データベースに保存しようとすると自動的に失敗する。
- ```
- >> user.save
- => false
- ```
- この変更により、モデルのテストは成功する。
- email属性の存在性についてもテストを書いてみる。
- *email属性の検証に対するテスト*
- ```
- RED
- test/models/user_test.rb
- 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
- test "name should be present" do
- @user.name = ""
- assert_not @user.valid?
- end
- test "email should be present" do
- @user.email = " "
- assert_not @user.valid?
- end
- end
- ```
- *email属性の存在性を検証する*
- ```
- GREEN
- app/models/user.rb
- class User < ActiveRecord::Base
- validates :name, presence: true
- validates :email, presence: true
- end
- ```
- *長さを検証する*
- 名前の長さにも制限を与える必要がある。51文字の名前は長すぎることを検証する。
- また、255文字以上のメールアドレスについても制限を与える。
- *nameの長さの検証に対するテスト*
- ```
- RED
- test/models/user_test.rb
- require 'test_helper'
- class UserTest < ActiveSupport::TestCase
- def setup
- @user = User.new(name: "Example User", email: "user@example.com")
- end
- .
- .
- .
- 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
- end
- ```
- この時点でのテストは失敗する。
- これをパスさせるためには、長さを強制するバリデーションを指定する必要がある。
- *name属性に長さの検証を追加する*
- ```
- GREEN
- app/models/user.rb
- class User < ActiveRecord::Base
- validates :name, presence: true, length: { maximum: 50 }
- validates :email, presence: true, length: { maximum: 255 }
- end
- ```
- ### 理解できなかったもの
- ### 調べたもの
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement