承接上文。
用户模型
创建并切换分支
git checkout -b modeling-users
生成用户模型
rails generate model User name:string email:string
数据库迁移
rails db:migrate
# rails db:rollback(向下迁移)
模型使用
打开控制台:
➜ microblog git:(modeling-users) rails c --sandbox # 沙盒模式启动控制台
Running via Spring preloader in process 23202
Loading development environment in sandbox (Rails 5.0.1)
Any modifications you make will be rolled back on exit
新建/保存用户:
2.3.3 :001 > User.new # 默认方式新建用户
=> #
2.3.3 :002 > User.new name:"Joshua", email:"[email protected]" # 指定信息新建用户(Hash为最后一个参数,可以省略{ })
=> #
2.3.3 :003 > user = User.new name:"Joshua", email:"[email protected]" # 新建用户并赋值
=> #
2.3.3 :004 > user.valid? # 验证是否有效
=> true
2.3.3 :005 > user.save # 保存数据库
(0.2ms) SAVEPOINT active_record_1
SQL (31.0ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Joshua"], ["email", "[email protected]"], ["created_at", 2017-01-06 14:55:02 UTC], ["updated_at", 2017-01-06 14:55:02 UTC]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true # 返回true/false
2.3.3 :006 > user # 再次查看用户信息(包含:ID/创建时间/更新时间)
=> #
2.3.3 :007 > user.name # 查看
=> "Joshua"
2.3.3 :008 > user.email #
=> "[email protected]"
2.3.3 :009 > user.updated_at #
=> Fri, 06 Jan 2017 14:55:02 UTC +00:00
2.3.3 :010 > user = User.create(name:"Joshuaber", email:"[email protected]") # 使用create等于new+save
(0.2ms) SAVEPOINT active_record_1
SQL (0.3ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Joshuaber"], ["email", "[email protected]"], ["created_at", 2017-01-06 14:58:04 UTC], ["updated_at", 2017-01-06 14:58:04 UTC]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> #
2.3.3 :011 > user #
=> #
2.3.3 :012 > user.destroy # 销毁
(0.2ms) SAVEPOINT active_record_1
SQL (0.3ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 2]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> #
2.3.3 :013 > user # 销毁后对象仍然在内存中
=> #
查找用户:
2.3.3 :014 > User.find(1) # 查找id=1的用户(从数据库)
User Load (573.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #
2.3.3 :015 > User.find(2)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=2
... ...
2.3.3 :016 > user # 可以看到id=2的用户仍然在内存
=> #
2.3.3 :017 > User.find(2) #
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=2
... ...
2.3.3 :018 > User.find_by(email:"[email protected]") # 使用find_by进行属性查找
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "[email protected]"], ["LIMIT", 1]]
=> #
2.3.3 :019 > User.first #
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #
2.3.3 :020 > User.last #
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #
2.3.3 :021 > User.all #
User Load (0.4ms) SELECT "users".* FROM "users"
=> #]>
修改用户信息:
2.3.3 :022 > user = User.find(1) # 查找并赋值
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #
2.3.3 :023 > user.email = "[email protected]" # 修改信息
=> "[email protected]"
2.3.3 :024 > user.save # 保存修改
(0.2ms) SAVEPOINT active_record_1
SQL (0.3ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "[email protected]"], ["updated_at", 2017-01-07 03:33:54 UTC], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.3.3 :025 > user.email = "[email protected]" # 修改信息
=> "[email protected]"
2.3.3 :026 > user.reload.email # 重新加载(放弃修改)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> "[email protected]"
2.3.3 :027 > user #
=> #
2.3.3 :028 > user.created_at #
=> Fri, 06 Jan 2017 14:55:02 UTC +00:00
2.3.3 :029 > user.updated_at #
=> Sat, 07 Jan 2017 03:33:54 UTC +00:00
2.3.3 :030 > user.update_attributes(name:"Joshuaber", email:"[email protected]")
(0.2ms) SAVEPOINT active_record_1
SQL (0.4ms) UPDATE "users" SET "email" = ?, "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "[email protected]"], ["name", "Joshuaber"], ["updated_at", 2017-01-07 03:37:20 UTC], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.3.3 :031 > user.update_attributes(email:"[email protected]") # 修改属性(直接保存)
(0.1ms) SAVEPOINT active_record_1
SQL (0.2ms) UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["email", "[email protected]"], ["updated_at", 2017-01-07 03:38:10 UTC], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.3.3 :032 > User.find(1) #
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #
代码比较简单,请看注释。
用户数据验证
使用TDD进行开发
执行测试命令
rails test:models
有效性测试
# test/models/user_test.rb
... ...
def setup
@user = User.new(name: "Example User", email: "[email protected]")
end
test "should be valid" do
assert @user.valid?
end
... ...
存在性验证
# test/models/user_test.rb
... ...
test "name should be present" do
@user.name = " "
assert_not @user.valid? # 应当无效
end
... ...
此时,测试是失败的,需要添加存在性验证:
# app/models/user.rb
validates :name, presence: true
同样的道理,添加email的存在性验证。
长度验证
# test/models/user_test.rb
... ...
test "name should not be too long" do
@user.name = "a" * 51 # 限制长度为50
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 256
assert_not @user.valid?
end
此时的测试是失败的,添加长度限制:
# app/models/user.rb
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
格式验证
# test/models/user_test.rb
test "email validation should accept valid addresses" do
valid_addresses = %w[[email protected] [email protected] [email protected] [email protected] [email protected]]
valid_addresses.each do |valid_address|
@user.email = valid_address
assert @user.valid?, "#{valid_address.inspect} should be valid"
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org [email protected]@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
添加格式限制:
# app/models/user.rb
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i # 简单的邮箱验证正则表达式
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }
唯一性验证
# test/models/user_test.rb
... ...
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
添加唯一性限制:
# app/models.user.rb
# 保存前将邮件转变为小写
before_save { self.email = email.downcase }
... ...
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
添加索引
生成迁移文件:
rails generate migration add_index_to_users_email
添加唯一性约束的迁移:
# db/migrate/20170107075525_add_index_to_users_email.rb
def change
add_index :users, :email, unique: true
end
执行数据库迁移:
rails db:migrate
添加安全密码
添加密码列
rails generate migration add_password_digest_to_users password_digest:string
rails db:migrate
添加gem
# Gemfile
... ...
gem 'bcrypt'
... ...
执行bundle:
bundle
添加代码
# app/models/user.rb
... ...
has_secure_password
end
修改测试
# test/models/user_test.rb
... ...
def setup
@user = User.new(name: "Example User", email: "[email protected]", password: "joshua", password_confirmation: "joshua")
end
... ...
密码最短长度
添加测试:
# test/models/user_test.rb
... ...
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5 # max_length = 6
assert_not @user.valid?
end
... ...
添加密码长度限制:
# app/models/user.rb
validates :password, length: { minimum: 6 }
创建用户
2.3.3 :001 > User.all #
User Load (17.5ms) SELECT "users".* FROM "users"
=> #
2.3.3 :002 > User.create(name:"Joshua", email:"[email protected]", password:"joshua", password_confirmation:"joshua") # 创建用户
(0.1ms) begin transaction
User Exists (24.0ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "[email protected]"], ["LIMIT", 1]]
SQL (12.9ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "Joshua"], ["email", "[email protected]"], ["created_at", 2017-01-08 04:40:15 UTC], ["updated_at", 2017-01-08 04:40:15 UTC], ["password_digest", "$2a$10$vWuRpVTzhdwVHzRlJBBJi.0jVUSuUJvIUnzybK6QQ1t8FQavFoz5i"]]
(123.8ms) commit transaction
=> #
2.3.3 :003 > User.find_by(email:"[email protected]") # 大小写敏感
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "[email protected]"], ["LIMIT", 1]]
=> nil
2.3.3 :004 > User.find_by(email:"[email protected]") # 查询
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "[email protected]"], ["LIMIT", 1]]
=> #
2.3.3 :005 > user = User.find_by(email:"[email protected]") #
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "[email protected]"], ["LIMIT", 1]]
=> #
2.3.3 :006 > user.password_ # 使用Tab补全命令
user.password_confirmation
... ...
2.3.3 :006 > user.password_digest # 查询
=> "$2a$10$vWuRpVTzhdwVHzRlJBBJi.0jVUSuUJvIUnzybK6QQ1t8FQavFoz5i"
2.3.3 :007 > user.authenticate("wrongpwd") # 错误认证
=> false
2.3.3 :008 > user.authenticate("joshua") # 正确认证
=> #
收尾
合并删除分支
git checkout master
git merge modeling-users
git branch -d modeling-user
git branch
部署到Heroku并测试
git push heroku master
heroku rake db:migrate
heroku run console --sandbox
参考
《Rails Tutorial 3th》