- 编写商铺表相关的迁移文件并使用Rails提供的命令创建商铺表;
- 编写商铺模型的验证;
- 编写商铺模型的单元测试,完成相关测试。
终于来到了商铺模块,我们还是从商铺模型开始开发。
关于商铺模块,我们可以确认一下基本功能。首先除了管理员之外的用户都可以开店,但是一个用户只能开一个店!用户可以修改商铺的部分信息,并且只要商铺主人可以注销商铺。
- 商铺需要标题,所以需要
name
字段; - 商铺需要绑定用户,所以需要
user_id
字段; - 商铺需要统计商品的总数,所以需要
products_count
字段; - 商铺需要统计订单的总数,所以需要
orders_count
字段; - Rails还会自动帮我们维护两个字段:created_at, updated_at 。
字段 | 类型 | 长度 | 注释 | null | 默认值 |
---|---|---|---|---|---|
id | integer | 11 | 主键id,自动增长 | 否 | 0 |
user_id | integer | 11 | 用户id, 关联用户表 | 否 | 0 |
name | string | 50 | 店铺名 | 否 | 空字符串 |
products_count | integer | 11 | 商品数 | 否 | 0 |
orders_count | integer | 11 | 订单数 | 否 | 0 |
- 用户创建和修改店铺:name字段信息必须提供并且不能重复;
- 一个用户只能创建一个商铺:user_id 不能重复;
- products_count 和 orders_count 作为统计数据:应该是整数。
-
模型A与模型B的一对一关系的创建
# A拥有B class A < ApplicationRecord has_one :b end # B属于A class B < ApplicationRecord belongs_to :a end
-
如上关联的A与B获取
a = A.find(1) b = a.b b = B.find(1) a = b.a
$ git checkout -b chapter06
$ rails generate model Shop name:string products_count:integer orders_count:integer user_id:integer
Running via Spring preloader in process 7669
invoke active_record
create db/migrate/20210512060044_create_shops.rb
create app/models/shop.rb
invoke test_unit
create test/models/shop_test.rb
create test/fixtures/shops.yml
可以看到命令行帮我们生成了
db/migrate/20210512060044_create_shops.rb
商铺模型的迁移文件。还帮我们生成了
app/models/shop.rb
用户模型文件。
db/migrate/20210512060044_create_shops.rb
class CreateShops < ActiveRecord::Migration[6.1]
def change
create_table :shops do |t|
t.string :name, null:false, default:''
t.index :name
t.integer :products_count, null:false, default:0
t.integer :orders_count, null:false, default:0
t.integer :user_id, null:false, default:0
t.timestamps
end
end
end
我们这里创建了shops表,为email字段添加了唯一索引
$ rails db:migrate
== 20210512060044 CreateShops: migrating ======================================
-- create_table(:shops)
-> 0.0245s
== 20210512060044 CreateShops: migrated (0.0247s) =============================
执行结果显示创建了 shops表。
思路解析
一个用户只能创建一个商铺,一个商铺只能被一个用户所有,当然管理员是不能创建用户的,但这不妨碍普通用户和商铺之间是1对1的关系,所以我们就需要在模型里使用一对一关联User和Shop模型。
app/models/user.rb
class User < ApplicationRecord
# 其它代码
has_one :shop, dependent: :destroy
# 其它代码
end
用户删除了,那么商铺也应该被释放,所以 使用 dependent: :destroy
来实现这个功能:用户删除,商铺一起被删除。
app/models/shop.rb
class Shop < ApplicationRecord
# 其它代码
belongs_to :user
# 其它代码
end
思路分析
我们需要做的验证:
- name:不能为空,不能重复, 必须符合格式;
- user_id: 不能重复,并且用户角色不能是管理员;
- products_count: 必须为整数;
- orders_count: 必须为整数;
app/models/shop.rb
class Shop < ApplicationRecord
belongs_to :user
validates :name, presence: true,
uniqueness: true,
format: { with: /\w+[a-zA-Z]{3,9}/ }
validates :products_count, numericality: { only_integer: true}
validates :orders_count, numericality: { only_integer: true }
validates :user_id, uniqueness: true
validate :user_can_not_be_admin, on: :create
def user_can_not_be_admin
if user.role == 0
errors.add(:user_id, "can't be admin")
end
end
end
$ rails c
Loading development environment (Rails 6.1.3.2)
2.7.2 :001 > s = Shop.new({name:"t", user_id:4, products_count:0, orders_count:0})
=> #<Shop id: nil, name: "t", products_count: 0, orders_count: 0, created_at: nil, updated_at: nil, user_id: 4>
2.7.2 :002 > s.valid?
Shop Exists? (0.6ms) SELECT 1 AS one FROM `shops` WHERE `shops`.`name` = 't' LIMIT 1
=> false
2.7.2 :003 > s.errors.messages
=> {:name=>["is invalid"]}
通过错误信息,可知验证都未通过。
和前面一样,我们使用Rails内置的 Minitest 测试框架来编写以及测试我们的应用。
针对模型单元测试的编写,主要是对模型中对字段的相关 validates 的针对性测试!
- 成功的场景
- 使用全部合法的参数(
合法的name,合法的user_id,合法的products_count, 合法的orders_count
)创建商铺,断言:通过验证
- 使用全部合法的参数(
- 失败的场景
- 使用
非法的name,合法的user_id,合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证 - 使用
重复的name,合法的user_id,合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证 - 使用
非法的user_id,合法的name, 合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证 - 使用
重复的user_id,合法的name, 合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证 - 使用
非法的products_count,合法的name, 合法的user_id, 合法的orders_count
创建商铺,断言:未通过验证
- 使用
在使用Rails的命令创建模型Shop时,Rails还帮我们自动创建了测试文件已经与模型对应的测试用预定义数据文件:
test/fixtures/shops.yml
我们可以修改这个文件的内容如下:
one:
name: 'shop1'
products_count: 1
orders_count: 1
user: two
two:
name: 'shop2'
products_count: 1
orders_count: 1
user: three
这里定义了一个商铺:商铺one
, 然后我们就可以在测试文件中通过 shops(:one)
来获取第一个商铺的对象信息了。
要关联某个用户,直接添加对应字段和选取要对应的用户名即可。
我们还需要再在user的数据中添加几个新用户:
test/fixtures/users.yml
我们可以修改这个文件的内容如下:
three:
email: '[email protected]'
password_digest: <%= BCrypt::Password.create('123456') %>
role: 1
four:
email: '[email protected]'
password_digest: <%= BCrypt::Password.create('123456') %>
role: 1
five:
email: '[email protected]'
password_digest: <%= BCrypt::Password.create('123456') %>
role: 1
six:
email: '[email protected]'
password_digest: <%= BCrypt::Password.create('123456') %>
role: 1
此时可以先运行下测试,有时候我们添加了数据也会导致测试错误。这里添加用户并没有影响测试。
测试的编写我们首先要确定文件路径:test/models/shop_test.rb
, 如果你仔细观察,这个文件也是Rails帮我们自动创建的!现在开始编写相关测试吧!
-
使用全部合法的参数(
合法的name,合法的user_id,合法的products_count, 合法的orders_count
)创建商铺,断言:通过验证test/models/shop_test.rb
class ShopTest < ActiveSupport::TestCase # 使用合法参数 test 'valid: shop with all valid things' do shop = Shop.new(name: 'shoptest01', products_count:0, orders_count:1) shop.user = users(:four) assert shop.valid? end #... end
-
使用
重复的name,合法的user_id,合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证test/models/shop_test.rb
class ShopTest < ActiveSupport::TestCase test 'invalid: shop with taken name' do shop = Shop.new(name: shops(:one).name, products_count:0, orders_count:1) shop.user = users(:five) assert_not shop.valid? end end
-
使用
非法的user_id,合法的name, 合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证test/models/shop_test.rb
class ShopTest < ActiveSupport::TestCase test 'invalid: shop with invalid user_id' do shop = Shop.new(name: 'shoptest02', products_count:0, orders_count:1) shop.user = users(:one) assert_not shop.valid? end end
-
使用
重复的user_id,合法的name, 合法的products_count, 合法的orders_count
创建商铺,断言:未通过验证test/models/shop_test.rb
class ShopTest < ActiveSupport::TestCase test 'invalid: shop with taken user_id' do shop = Shop.new(name: 'shoptest03', products_count:0, orders_count:1) shop.user = shops(:one).user assert_not shop.valid? end end
-
使用
非法的products_count,合法的name, 合法的user_id, 合法的orders_count
创建商铺,断言:未通过验证test/models/shop_test.rb
class ShopTest < ActiveSupport::TestCase test 'invalid: shop with invalid products_count' do shop = Shop.new(name: 'shoptest04', products_count:0, orders_count:1.55) shop.user = users(:six) assert_not shop.valid? end end
$ rails test
Finished in 2.128407s, 11.2760 runs/s, 13.6252 assertions/s.
24 runs, 29 assertions, 0 failures, 0 errors, 0 skips
我们这里顺利通过测试。
$ git add .
$ git commit -m "set shops model"
我们完成了商铺模型相关功能,并添加了和用户模型的惯量关系, 下节课我们将开发商铺控制器相关功能!一定要跟上脚步,认真练习!