チュートリアルをやってみる。ユーザモデル編
これはチュートリアルをやってみたログである。
第6章 ユーザーのモデルを作成する | Rails チュートリアル
RailsではモデルをActive Recordとして扱い、SQLを意識せずモデリングできる。
例えば開発環境でSQLite、本番環境のHerokuでPostgreSQLといった使い方ができる。
モデルの作成
nameとemailの属性があるユーザモデルを作る。
rails generate model User name:string email:string
モデル名には単数形(コントローラには複数形)をつける。
マイグレーションファイルが生成された。
db/migrate/20150914082003_create_users.rb
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
つまり、usersテーブル(複数形になっている)が生成され、name,emailというカラム、定義されている。
t.timestampsでcreated_atとupdate_atというdatetime属性のカラムができる。
マイグレーションの適用
マイグレーションを実行する。
bundle exec rake db:migrate
db/development.sqlite3が生成されているのでsqlitebrowser等で確認する。
bundle exec rake db:rollback
ユーザオブジェクトをコンソールで触ってみる。
コンソールをサンドボックスモードで起動
rails console --sandbox
ユーザオブジェクトを作成してみる
User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
user = User.new(name: "hoge fuga", email: "hoge@hoge.com") => #<User id: nil, name: "hoge fuga", email: "hoge@hoge.com", created_at: nil, updated_at: nil>
セーブする。
user.save (0.1ms) SAVEPOINT active_record_1 SQL (69.3ms) INSERT INTO "users" ("created_at", "email", "name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Mon, 14 Sep 2015 08:38:39 UTC +00:00], ["email", "hoge@hoge.com"], ["name", "hoge fuga"], ["updated_at", Mon, 14 Sep 2015 08:38:39 UTC +00:00]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true
SQLでINSERTされた。
user => #<User id: 1, name: "hoge fuga", email: "hoge@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
idが1になった。日時なども記録されている。
user.name => "hoge fuga"
ドット記法でアクセスできる。
生成と保存をcreateで一度にやる。
User.create(name:"poi", email:"poi@poi.com") (0.1ms) SAVEPOINT active_record_1 SQL (0.8ms) INSERT INTO "users" ("created_at", "email", "name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Mon, 14 Sep 2015 08:41:43 UTC +00:00], ["email", "poi@poi.com"], ["name", "poi"], ["updated_at", Mon, 14 Sep 2015 08:41:43 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1 => #<User id: 2, name: "poi", email: "poi@poi.com", created_at: "2015-09-14 08:41:43", updated_at: "2015-09-14 08:41:43">
createの反対 destroy
user = User.create(name:"poge", email:"poge@poge.com")
とcreateしておいて、
user.destroy (0.1ms) SAVEPOINT active_record_1 SQL (0.1ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]] (0.0ms) RELEASE SAVEPOINT active_record_1 => #<User id: 3, name: "poge", email: "poge@poge.com", created_at: "2015-09-14 08:43:20", updated_at: "2015-09-14 08:43:20">
ユーザオブジェクトを検索してみる。
User.find(1) User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]] => #<User id: 1, name: "hoge fuga", email: "hoge@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
先ほど消したid=3は
User.find(3) User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]] ActiveRecord::RecordNotFound: Couldn't find User with id=3
例外が投げられる。
findで検索
User.find_by_email("hoge@hoge.com") User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'hoge@hoge.com' LIMIT 1 => #<User id: 1, name: "hoge fuga", email: "hoge@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
シンボルで
User.find_by(email: "hoge@hoge.com") User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'hoge@hoge.com' LIMIT 1 => #<User id: 1, name: "hoge fuga", email: "hoge@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
先頭
User.first User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 => #<User id: 1, name: "hoge fuga", email: "hoge@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
全部
User.all
ユーザオブジェクトを更新してみる。
user = User.first user.name => "hoge fuga"
user.email = "chopin@hoge.com"
としてみると
user => #<User id: 1, name: "hoge fuga", email: "chopin@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:38:39">
セーブする
user.save (0.1ms) SAVEPOINT active_record_1 SQL (0.2ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = 1 [["email", "chopin@hoge.com"], ["updated_at", Mon, 14 Sep 2015 08:52:15 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1 => true
user => #<User id: 1, name: "hoge fuga", email: "chopin@hoge.com", created_at: "2015-09-14 08:38:39", updated_at: "2015-09-14 08:52:15">
update_atも更新されていることがわかる。
update_attributesで更新を連続させることもできる(一つ失敗すると失敗になる)
user.update_attributes(name:"The hoge", email:"theHoge@hoge.dom") (0.1ms) SAVEPOINT active_record_1 SQL (0.2ms) UPDATE "users" SET "name" = ?, "email" = ?, "updated_at" = ? WHERE "users"."id" = 1 [["name", "The hoge"], ["email", "theHoge@hoge.dom"], ["updated_at", Mon, 14 Sep 2015 08:54:18 UTC +00:00]] (0.0ms) RELEASE SAVEPOINT active_record_1 => true
ユーザ検証(validation)
- メールアドレスはidのように使いたいので重複させない。一意性(uniquness)
- nameは空にさせたくない。存在性(presence)
存在の検証
class User < ActiveRecord::Base validates :name, presence: true end
これは以下と同じ
class User < ActiveRecord::Base validates(:name, presence: true) end
コンソールを立ち上げ
rails console --sandbox
名前を空のユーザを作ってみる。
user = User.new(name:"", email:"pot@pot.com")
セーブが失敗することを確認する。
user.save (0.1ms) SAVEPOINT active_record_1 (0.1ms) ROLLBACK TO SAVEPOINT active_record_1 => false
検証してみる
user.valid? => false
確かに検証に失敗している。
エラー内容を確認する。
user.errors.full_messages => ["Name can't be blank"]
emailのフォーマットを制限する。
class User < ActiveRecord::Base validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } end
正規表現でフォーマットを制限している。
\A | 文字列の先頭 |
[\w+\-.]+ | 英数字、アンダースコア、プラス、ハイフン、ドットのいずれかを1文字以上繰り返す |
@ | アットマーク |
[a-z\d\-.]+ | 英小文字を1文字以上繰り返す |
\z | 文字列の末尾 |
i | 大文字、小文字を無視するオプション |
emailの一意性(unique)検証
class User < ActiveRecord::Base validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true end
メールアドレスの大文字、小文字を無視した一意性にするなら
class User < ActiveRecord::Base validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end
一意性の注意点
ユーザ登録時にsubmitを素早く2回クリックすると二つのリクエストが検証にパスしメモリーに作成され、保存できてしまう。
データベースレベルでも一意性を強制するようにすること。
emailカラムにindexを作成する。
rails generate migration add_index_to_users_email
できたマイグレーションファイル
db/migrate/20150914092358_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration def change end end
を変更し
class AddIndexToUsersEmail < ActiveRecord::Migration def change add_index :users, :email, unique: true end end
これで、usersテーブルのemailカラムにindexが追加され、uniqueをtrueとすることで
一意性がデータベースレベルで強制される。
(indexを追加したことで、email属性が全表スキャンにならなくて済むようにもなった。)
マイグレート
bundle exec rake db:migrate
さらに、emailをデータベースにセーブする際に文字を小文字に変換する処理をいれておく。
app/models/user.rb
class User < ActiveRecord::Base before_save { self.email = email.downcase } .... end
このようにbefore_saveコールバックでの処理を書いておくと、セーブ前に小文字に変換してくれる。
セキュアなパスワードの追加
ユーザ属性にパスワードを追加したい。パスワードは暗号化されてデータベースに保存される。
認証は暗号化されたパスワード同士を比較することで行う。
railsではhas_secure_passwordメソッドで作れる。
(カラム名はpassword_digestにすることが重要)
まず、ハッシュ関数には最新のbcryptを使用するため、gemfileにbcrypt-rubyを追加
source 'https://rubygems.org' ruby '2.0.0' #ruby-gemset=railstutorial_rails_4_0 gem 'rails', '4.0.5' gem 'bootstrap-sass', '2.3.2.0' gem 'sprockets', '2.11.0' gem 'bcrypt-ruby', '3.1.2' ....
インストール
bundle install
カラム追加マイグレーションを生成
rails generate migration add_password_digest_to_users password_digest:string
db/migrate/20150914094154_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration def change add_column :users, :password_digest, :string end end
マイグレートする。
bundle exec rake db:migrate
ユーザクラスにhas_secure_passwordを追加する。
app/models/user.rb
class User < ActiveRecord::Base ... has_secure_password end
ユーザのパスワードがあっているかは
user.authenticate(password)
で確認できる。
パスワードの長さを最小でも6文字にする。
class User < ActiveRecord::Base ... has_secure_password validates :password, length: { minimum: 6 } end
コンソールで確認してみる。
ここまで。