Rails Tutorial 6章 編集中

ここで主に扱うのは、ユーザーのデータモデルの作成と、ユーザーを保存する手段の確保。



Userモデル

$ rails g model User name:string email:string

※コントローラー名には複数形、モデル名には単数形

すると、こんなマイグレーションファイルが作成される

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

このファイルを

$ rails db:migrate

で実行。そうすることで、この変更データがDBに反映される。

ちなみに、マイグレーションを元に戻すには、

$ rails db:rollback

初めてdb:migrateが実行されると、 db/development.sqlite3 という名前のファイルが生成され、このファイルからDBの構造を確認できる。



modelファイル

app/models/user.rb
class User < ApplicationRecord
end

つまり、User < ApplicationRecord < ActiveRecord::Base

では、ActiveRecord::Baseでは何ができるのか?

1、オブジェクトの作成
例えば、

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

オブジェクトの有効性

>> user.valid?
true

オブジェクトをDBに保存

>>user.save
 ~~~~
=>true

saveメソッドは、成功すればtrueを、失敗すればfalseを返す。(timestanpが更新されたり)

また、

>>User.create(name: "A Nother", email: "another@example.org")

では、true/falseではなく、オブジェクト自身を返す。

2、オブジェクトの検索

User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">

DBに存在しないオブジェクトを検索した場合、findメソッドは例外を発生。

>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

findメソッド以外にも、特定の属性でユーザーを検索する方法も

>> User.find_by(email: "mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">

また、こんなのも

>>User.first
>>User.all

3、オブジェクトの更新

属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う。しかし、検証に失敗すると、update_attributesの呼び出しは失敗する。

>>user.update_attributes(name: ~~, email: ~~)
=>true

そこで、update_attributeを使えば検証を回避できる。(特定の属性のみを更新)

>>user.update_attribute(name: ~~)
=>true

ユーザーを検証
app/models/user.rb

<存在性のバリデーション>
presence: t/f

validates :name, presence: true
validates :email, presence: true
#またはvalidates(:~~~, presence: true)

<長さ>
length: { }

validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }

<emailのフォーマット>
正規表現というものを使ってメールアドレスフォーマットを指定。詳しくはまだ手をつけないほうがいいらしい。

VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }

<emailの一意性を検証>
これを加えるだけ。

uniqueness: true

通常、メールアドレスは大文字小文字が区別されないので、

uniqueness: { case_sensitive: false }

と書き換える。
trueをcase_sensitive: falseに置き換えただけだが、Railsは:uniquenessをtrueと判断する。



しかし、DBのレベルでは一意性を保証していないため、素早くリクエストが複数送信された際に同じ値を持つユーザーレコードが作成されてしまう。

そこで、DBにインデックスというものを追加。
(本でいう索引みたいなものらしい)
これによって、DBに一意性の設定が可能&検索効率の向上

データモデリングの変換が必要なので、

$ rails g migration add_index_to_users_email

オプション unique :true で一意性を強制

db/migrate/[timestamp]_add_index_to_users_email.rb
def change
 add_index :users, :email, unique: true
end
rails db:migrate


また、データベースによっては常に大文字と小文字を区別するとは限らないため、アプリケーション側でメールアドレスをすべて小文字にすることによって対策。

app/models/user.rb
 before_save { self.email = email.downcase }

※ Userモデルの中では右式のselfを省略できる





ユーザーが有効かどうかは valid?メソッドで確認でき、検証に失敗した時はfalse、全てのバリデーションに通った時はtrueを返す。

>>User.valid?
=>true







これの有効性・存在性のテスト
 → setup にてあらかじめユーザーを作成

test/models/user_test.rb
def setup
 @user = User.new(name: "Example User", email: "user@example.com")
end

def test "should be valid" do
 assert @user.valid?
end

def test "name should be present" do
 @user.name = "  "
 assert_not @user.valid?
end

def test "email should be present" do
 @user.email = "     "
 assert_not @user.valid?
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

Rails Tutorial 9章 編集中

Remember_me 機能
よくあるやつ。次回以降ログインを省略みたいな。

手順としては、
1、記憶トークンを作成
2、ユーザーと関連づける
3、CookieメソッドでDBに保存

$ rails g migration add_remember_me_digest_to_users remember_digest:string
$ rails db:migrate

記憶トークンには、安全性の高いものを使いたい。Ruby標準ライブラリのurlsafe_base64メソッドがいい。

def User.new_token
 SecureRandom.urlsafe_base64
end

ここから、 user.remember メソッドを作成して、これで記憶トークンをユーザーと関連づけ、トークンに対応する記憶ダイジェストをDBに保存
しかし、保存場所であるremember_token属性が追加されていない。
そこで、user.remember_token でトークンにアクセスできるようにする。(DBには保存しないように)
パスワードで使ったように、仮想のpasswordを作ったみたいに。

rememberメソッドをUserモデルに追加する

 attr_accessor :remember_token
.
.
def remember
 self.remember_token = User.new_token
 update_attribute(:remember_digest, User.digest(remember_token))
end

※ DBにアクセスする必要がないため、attr_accesorでremember_tokenを定義
※ update_attribute はバリデーションを通過可能(passにアクセスしなくていい)

ここで、Cookieメソッドの使い方。
sessionのようにハッシュで使用可能で、一つの値と、オプションのexpiresで構成されている

cookies[:remember_token] = { value: remember_token, 
                                       expires: 20.years.from_now.utc }

これを permanent メソッドで書き換えて、

cookies.permanent[:remember_token] = remember_token


ユーザーIDをCookieに保存するには(署名あり)

 cookies.signed[:user_id] = user_id

記憶トークンとペアで使うために

cookies.permanent.signed[:user_id] = user_id

以上より、以後のページでcookieからユーザーを取り出すことができる。

んで、なんたらかんたら

app/models/user.rb
def authenticated?(remember_token)
 BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

この remember_token は、さっきのアクセサのやつじゃなくただのローカル変数。
remember_digest は、self.remember_digestと同じ使い方らしい。

app/controllers/sessions_controller.rb
def create
.
.
remember user
.
end
app/helpers/sessions_helper.rb
#ユーザーのセッションを永続的にする
def remember(user)
 cookies.permanent.signed[:user_id] = user_id
 cookies.permanent[:remember_token] = user.remember_token
end