over 2 years ago

備註1:每次開始前,記得先弄一個新的branch。$ git checkout -b modeling-users
重點1Controller的名字要加sModel的名字不加s
不過,migration的檔案裡面,create_table的時候,Table名稱都會加s。(create_table :users do |t|)
所以,在做relation的時候,就不用加s。(has_many :item)
(參考RailsFun.tw 新手教學_day2 HD 1:35:00的地方)
重點2migrate的兩種寫法:$ bundle exec rake db:migrate$ bundle exec rake db:rollback,會去修改這個檔案:development.sqlite3
重點3$ rails console --sandbox sandbox不會儲存資料到資料庫。heroku 也有sandbox $ heroku run console --sandbox
重點4:新增資料的2個方法:
- 2步驟:new + save :user = User.new + user.save
- 1步驟:user = User.create
重點5:搜尋的方法: findfind_byUser.find(3)User.find_by(email: "mhartl@example.com")
重點6:更新的2個方法:
- 方法1:只更新1個attribute,記得最後要有.save,不然資料庫不會更新。save完之後,update_at也會更新。
- 方法2:更新多個attribute,user.update_attributes,1個步驟更新,這個就不用.save了。
重點7:測試技巧:TDD適合用在測試Model。先以一個有效(valid)的Model Object開始,然後再把其中一個屬性(attribute)設成無效,然後讓測試顯示他是無效的,之後再寫CODE讓他變有效。 -> 一次測試一個屬性。
- 步驟1: 先以一個有效的Object開始: setup method每次都會先執行。
- 步驟2: 不同的test方法:$ bundle exec rake test:models$ bundle exec rake test:integration$ bundle exec rake test
- 步驟3: assert_not:測試object是不是無效的
- 步驟4: 用errors.full_messages可以知道哪個特定項目出現錯誤。有錯誤就不能存檔 >> user.save => false
- 步驟5: %w[]:變成字串的array。
- 步驟6: assert可以自訂錯誤訊息assert @user.valid?, "#{valid_address.inspect} should be valid"
- 步驟7: 要測試唯一性,首先要先把物件存到資料庫裡。@user.save
- 步驟8: 幫email加上indexuniqueness:we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column (Box 6.2), and then require that the index be unique.
-步驟9: callback:在某個特定時點先做某件事情。在存入資料庫之前,先把他們都弄成一樣的:before_save { self.email = email.downcase }這個方法叫做callback

重點8:validations(認可、確認),Rubular
- presence -> presence: true -> validates :name, presence: truevalidates(:name, presence: true)
- length -> length: { maximum: 50 } -> validates :name, presence: true, length: { maximum: 50 }
- format -> format: { with: VALID_EMAIL_REGEX }, VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
- uniqueness -> uniqueness: trueuniqueness: { case_sensitive: false }
- confirmation -> has_secure_password method

重點9has_secure_password method:有三個功能:
- a. 將password_digest hash後存到資料庫。password_digest會顯示hash過的亂數
- b. 一套屬性:password and password_confirmation。
- c. 認證功能:authenticate method:用戶輸入密碼後,回傳 true or false。如果密碼錯誤,authenticate會顯示false
- 步驟1:password_digest:Model要新增這個attribute後,has_secure_password才能運作 '$ rails generate migration add_password_digest_to_users password_digest:string'
- 步驟2:要記得db:migrate
- 步驟3:新增一個gem: gem 'bcrypt', '3.1.7'
- 步驟4:在User model裡面寫下: has_secure_password
- 步驟5:限制密碼的最小長度:validates :password, presence: true, length: { minimum: 6 }

重點10:Active Record validations allow us to place constraints on the data in our models.
重點11:Common validations include presence, length, and format.
重點12:Regular expressions are cryptic but powerful.
重點13:Defining a database index improves lookup efficiency while allowing enforcement of uniqueness at the database level.
重點14:We can add a secure password to a model using the built-in has_secure_password method.

  1. 資料庫儲存長期資料,並以Active Record這個library來存取資料庫
    The default Rails solution to the problem of persistence is to use a database for long-term data storage, and the default library for interacting with the database is called Active Record.

  2. Active Record: 提供可以直接創造、儲存與搜尋資料物件的一些method,而不必用到關聯資料庫的SQL語法。
    Active Record comes with a host of methods for creating, saving, and finding data objects, all without having to use the structured query language (SQL)2 used by relational databases.

  3. SQLite:Development。PostgreSQL (via Heroku) :Deployment

  4. Controller用複數,Model用單數。 (參考RailsFun.tw 新手教學_day2 HD 1:35:00的地方)
    $ rails generate controller Users new
    $ rails generate model User name:string email:string
    (Note that, in contrast to the plural convention for controller names, model names are singular: a Users controller, but a User model.)
    (JC建議在做relation的時候,就不用加s。(has_many :item))

  5. Here the table name is plural (users) even though the model name is singular (User), which reflects a linguistic convention followed by Rails: a model represents a single user, whereas a database table consists of many users.

  6. $ bundle exec rake db:migrate
    $ bundle exec rake db:rollback

  7. migration: it updated a file calleddevelopment.sqlite3 by creating a table users with columns id, name, email, created_at, and updated_at.

  8. $ rails console --sandbox

    Loading development environment in sandbox
    Any modifications you make will be rolled back on exit
    
  9. 新增的方法:2步驟 - new + save :user = User.new之後,雖然是valid的,但還沒有存到資料庫裡面去,所以id、created_at跟updated_at都是 nil,要user.save之後,created_at跟updated_at才會有日期。

    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>
    
     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
    
  10. 新增的方法:1步驟 - user = User.create = > user = User.new + user.save 。
    create之後,會回傳物件的值。

  11. The inverse of create is destroy。
    destroy之後,雖然還會存在記憶體內,但是已經從database裡面刪除了。

  12. 如何找到用戶: find 跟 find_by
    User.find(3)
    User.find_by(email: "mhartl@example.com")

  13. 更新的方法:單一attribute,記得最後要有.save,不然資料庫不會更新。save完之後,update_at也會更新。

    >> 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
    
  14. 更新的方法:multiple attributes = > user.update_attributes,1個步驟更新,這個就不用.save了。

    >> user.update_attributes(name: "The Dude", email: "dude@abides.org")
    => true
    >> user.name
    => "The Dude"
    >> user.email
    => "dude@abides.org"
    

    The update_attributes method accepts a hash of attributes, and on success performs both the update and the save in one step (returning true to indicate that the save went through). Note that if any of the validations fail, such as when a password is required to save a record (as implemented in Section 6.3), the call to update_attributes will fail. If we need to update only a single attribute, using the singular update_attribute bypasses this restriction:

  15. User validations:

    • name: should be non-blank,名字不能空白
    • email: should match the specific format characteristic of email addresses,e-mail有特定格式
    • email addresses: as unique usernames when users log in, we shouldn’t allow email duplicates in the database,email用作用戶名,必須要獨一無二
    • Active Record allows us to impose such constraints using validations
      • validating presence.
      • validating length.
      • validating format.
      • validating uniqueness.
      • validating confirmation.
  16. 測試技巧:TDD適合用在測試Model。先以一個有效(valid)的Model Object開始,然後再把其中一個屬性(attribute)設成無效,然後讓測試顯示他是無效的,之後再寫CODE讓他變有效。 -> 一次測試一個屬性。
    As noted in Box 3.3, test-driven development isn’t always the right tool for the job, but model validations are exactly the kind of features for which TDD is a perfect fit. It’s difficult to be confident that a given validation is doing exactly what we expect it to without writing a failing test and then getting it to pass.
    Our method will be to start with a valid model object, set one of its attributes to something we want to be invalid, and then test that it in fact is invalid. As a safety net, we’ll first write a test to make sure the initial model object is valid. This way, when the validation tests fail we’ll know it’s for the right reason (and not because the initial object was invalid in the first place).

  17. 先以一個有效的Object開始: setup method每次都會先執行。
    To write a test for a valid object, we’ll create an initially valid User model object @user using the special setup method (discussed briefly in the Chapter 3 exercises), which automatically gets run before each test.

    def setup
        @user = User.new(name: "Example User", email: "user@example.com")
    end
    
  18. 不同的test方法:

    $ bundle exec rake test:models
    

    Section 5.3.4

    $ bundle exec rake test:integration
    
    $ bundle exec rake test
    
  19. presence: true 不能空白的測試:
    assert_not:測試object是不是無效的
    assert_not method:check that the resulting User object is not valid.
    將@user.name設定成空白字串,但是因為還沒有規定name一定要有東西(presence),所以就算是空白字串,他也會是有效的。
    (@user.valid? 的結果會是true,可是assert_not是希望後面的變數是false,所以這個測試會fail掉)

    會fail
    @user.name = "     "
    assert_not @user.valid?
    

    加上

    app/models/user.rb
    class User < ActiveRecord::Base
      validates :name, presence: true
    end
    
    app/models/user.rb
    class User < ActiveRecord::Base
      validates(:name, presence: true)
    end
    

    在sandbox裡面測試,user.valid?就會變成falseassert_not @user.valid?就會變成true了。

    在sandbox裡面測試,`user.valid?`就會變成`false`,`assert_not @user.valid?`就會變成`true`了。
    $ rails console --sandbox
    >> user = User.new(name: "", email: "mhartl@example.com")
    >> user.valid?
    => false
    

    errors.full_messages可以知道哪個特定項目出現錯誤。

    用`errors.full_messages`可以知道哪個特定項目出現錯誤。
    user.errors.full_messages
    => ["Name can't be blank"]
    
    會不能存檔
    >> user.save
    => false
    

    不能空白的測試:

    app/models/user.rb
     class User < ActiveRecord::Base
    validates :name,  presence: true
    validates :email, presence: true
    end
    
  20. length: { maximum: 50 } 長度的測試。

    app/models/user.rb
     class User < ActiveRecord::Base
    validates :name,  presence: true, length: { maximum: 50 }
    validates :email, presence: true, length: { maximum: 255 }
    end
    
  21. %w[]:變成字串的array。
    %w[] technique for making arrays of strings, as seen in this console session:

    >> %w[foo bar baz]
    => ["foo", "bar", "baz"]
    >> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
    => ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
    >> addresses.each do |address|
    ?>   puts address
    >> end
    USER@foo.COM
    THE_US-ER@foo.bar.org
    first.last@foo.jp
    
  22. assert可以自訂錯誤訊息

    assert @user.valid?, "#{valid_address.inspect} should be valid"
    
  23. VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    參考最後的表格

  24. Rubular

  25. app/models/user.rb
    class User < ActiveRecord::Base
        VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
        validates :email, presence: true, length: { maximum: 255 },
                            format: { with: VALID_EMAIL_REGEX }
    end
    
  26. Uniqueness validation:這邊好難寫筆記
    要測試唯一性,首先要先把物件存到資料庫裡。@user.save

    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 "email addresses should be unique" do
                            duplicate_user = @user.dup
                            @user.save
                            assert_not duplicate_user.valid?
    end
    end
    
    app/models/user.rb
     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, length: { maximum: 255 },
                            format: { with: VALID_EMAIL_REGEX },
                            uniqueness: true
    end
    
    app/models/user.rb
     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, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
    end
    
  27. 幫email加上index,uniqueness
    we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column (Box 6.2), and then require that the index be unique.

    $ rails generate migration add_index_to_users_email
    

    需要手動加上unique: true
    Unlike the migration for users, the email uniqueness migration is not pre-defined, so we need to fill in its contents with Listing 6.28.16

    db/migrate/[timestamp]_add_index_to_users_email.rb
    class AddIndexToUsersEmail < ActiveRecord::Migration
        def change
            add_index :users, :email, unique: true
        end
    end
    
  28. 要記得去調整fixtures檔案 test/fixtures/users.yml
    fixtures, which contain sample data for the test database. User fixtures were generated automatically in Listing 6.1, and as shown in Listing 6.29 the email addresses are not unique. (They’re not valid either, but fixture data doesn’t get run through the validations.)

  29. 我們的網站對大小寫不敏感,不過有些網站的資料庫會敏感。
    所以要在存入資料庫之前,先把他們都弄成一樣的:before_save { self.email = email.downcase }這個方法叫做callback
    Some database adapters use case-sensitive indices, considering the strings “Foo@ExAMPle.CoM” and “foo@example.com” to be distinct, but our application treats those addresses as the same.
    callback在某個特定時點先做某件事情。
    which is a method that gets invoked at a particular point in the lifecycle of an Active Record object.
    (where self refers to the current user), but inside the User model the self keyword is optional on the right-hand side:
    1跟2是可以的,3不行。
    We encountered this idea briefly in the context of reverse in the palindrome method (Section 4.4.2), which also noted that self is not optional in an assignment, so 3 wouldn’t work. (We’ll discuss this subject in more depth in Section 8.4.)
    第1. self.email = self.email.downcase
    第2. self.email = email.downcase
    第3. email = email.downcase

  30. has_secure_password method:

    class User < ActiveRecord::Base
    .
    .
    .
    has_secure_password
    end
    

    has_secure_password有三個功能:
    a. 將password_digesthash後存到資料庫。
    The ability to save a securely hashed password_digest attribute to the database
    b. 一套屬性:password and password_confirmation
    A pair of virtual attributes (password and password_confirmation), including presence valid ations upon object creation and a validation requiring that they match
    c. 認證功能:An authenticate method that returns the user when the password is correct (and false otherwise)

  31. password_digest:Model要新增這個attribute後,has_secure_password才能運作:

    $ rails generate migration add_password_digest_to_users password_digest:string
    
    db/migrate/[timestamp]_add_password_digest_to_users.rb
     class AddPasswordDigestToUsers < ActiveRecord::Migration
    def change
    add_column :users, :password_digest, :string
    end
    end
    

    要記得db:migrate

    $ bundle exec rake db:migrate
    
  32. 還要新增一個gem: gem 'bcrypt', '3.1.7'

  33. 然後在model裡面寫下:

    app/models/user.rb
    class User < ActiveRecord::Base
      before_save { self.email = email.downcase }
      validates :name, presence: true, length: { maximum: 50 }
      VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
      validates :email, presence: true, length: { maximum: 255 },
                        format: { with: VALID_EMAIL_REGEX },
                        uniqueness: { case_sensitive: false }
      has_secure_password
    end
    

    34.限制密碼的最小長度:validates :password, presence: true, length: { minimum: 6 }

    app/models/user.rb
    class User < ActiveRecord::Base
      before_save { self.email = email.downcase }
      validates :name, presence: true, length: { maximum: 50 }
      VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
      validates :email, presence: true, length: { maximum: 255 },
                        format: { with: VALID_EMAIL_REGEX },
                        uniqueness: { case_sensitive: false }
      has_secure_password
      validates :password, presence: true, length: { minimum: 6 }
    end
    
  34. password_digest會顯示hash過的亂數

    >> user = User.find_by(email: "mhartl@example.com")
    >> user.password_digest
    => "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"
    
  35. 如果密碼錯誤,authenticate會顯示false

    >> user.authenticate("not_the_right_password")
    false
    >> user.authenticate("foobaz")
    >> user.authenticate("foobar")
    => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
    created_at: "2014-07-25 02:58:28", updated_at: "2014-07-25 02:58:28",
    password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">
    
  36. heroku 也有sandbox

    $ heroku run console --sandbox
    >> User.create(name: "Michael Hartl", email: "michael@example.com",
    ?>             password: "foobar", password_confirmation: "foobar")
    => #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
    created_at: "2014-08-29 03:27:50", updated_at: "2014-08-29 03:27:50",
    password_digest: "$2a$10$IViF0Q5j3hsEVgHgrrKH3uDou86Ka2lEPz8zkwQopwj...">
    
Expression Meaning
/\A[\w+-.]+@[a-z\d-.]+.[a-z]+\z/i full regex
/ start of regex
\A match start of a string
[\w+-.]+ at least one word character, plus, hyphen, or dot
@ “at sign”
[a-z\d-.]+ at least one letter, digit, hyphen, or dot
. literal dot
[a-z]+ at least one letter
\z match end of a string
/ end of regex
i case-insensitive
← ROR TUTORIAL (3RD ED.) Ch5 Filling in the layout ROR TUTORIAL (3RD ED.) Ch7: Sign up →