Skip to content

Latest commit

 

History

History
452 lines (292 loc) · 11.9 KB

07 商铺模块-模型相关功能开发.md

File metadata and controls

452 lines (292 loc) · 11.9 KB

使用rails6 开发纯后端 API 项目

商铺模块: 模型


本节目标
  • 编写商铺表相关的迁移文件并使用Rails提供的命令创建商铺表;
  • 编写商铺模型的验证;
  • 编写商铺模型的单元测试,完成相关测试。

终于来到了商铺模块,我们还是从商铺模型开始开发。

关于商铺模块,我们可以确认一下基本功能。首先除了管理员之外的用户都可以开店,但是一个用户只能开一个店!用户可以修改商铺的部分信息,并且只要商铺主人可以注销商铺。

1. 功能分析

1.1 商铺需要具有的字段
  • 商铺需要标题,所以需要name字段;
  • 商铺需要绑定用户,所以需要user_id字段;
  • 商铺需要统计商品的总数,所以需要 products_count 字段;
  • 商铺需要统计订单的总数,所以需要 orders_count 字段;
  • Rails还会自动帮我们维护两个字段:created_at, updated_at 。
1.2 shops:商铺表
字段 类型 长度 注释 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
1.3 要验证的字段
  • 用户创建和修改店铺:name字段信息必须提供并且不能重复;
  • 一个用户只能创建一个商铺:user_id 不能重复;
  • products_count 和 orders_count 作为统计数据:应该是整数。

2. 模型相关功能开发

2.1 知识点预热

2.1.1 Rails 相关
  • 模型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

2.2 文件版本管理

2.2.1 切换分支到 chapter06
$ git checkout -b chapter06

2.3 用户模型迁移编写

2.3.1 使用命令创建商铺模型迁移文件
$ 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 用户模型文件。

2.3.2 编辑商铺模型的迁移文件

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字段添加了唯一索引

2.3.3 执行迁移命令
$ rails db:migrate
== 20210512060044 CreateShops: migrating ======================================
-- create_table(:shops)
   -> 0.0245s
== 20210512060044 CreateShops: migrated (0.0247s) =============================

执行结果显示创建了 shops表。

2.4 构建User模型和Shop模型关系


思路解析

一个用户只能创建一个商铺,一个商铺只能被一个用户所有,当然管理员是不能创建用户的,但这不妨碍普通用户和商铺之间是1对1的关系,所以我们就需要在模型里使用一对一关联User和Shop模型。


2.4.1 修改User模型关联Shop

app/models/user.rb

class User < ApplicationRecord
  # 其它代码
  has_one :shop, dependent: :destroy
  # 其它代码
end

用户删除了,那么商铺也应该被释放,所以 使用 dependent: :destroy 来实现这个功能:用户删除,商铺一起被删除。

2.4.1 修改Shop模型关联User

app/models/shop.rb

class Shop < ApplicationRecord
  # 其它代码
  belongs_to :user
  # 其它代码
end

2.5 为Shop模型添加验证

2.5.1 修改模型文件,添加字段验证

思路分析

我们需要做的验证:

  • 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
2.5.2 在 Rails console 中简单测试
$ 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"]}

通过错误信息,可知验证都未通过。

2.6 为模型验证编写单元测试


*基本思路分析

和前面一样,我们使用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创建商铺,断言:未通过验证

2.5.1 准备测试用用户预定义数据

在使用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

此时可以先运行下测试,有时候我们添加了数据也会导致测试错误。这里添加用户并没有影响测试。

2.5.2 编写测试

测试的编写我们首先要确定文件路径: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
2.5.3 运行测试
$ rails test
Finished in 2.128407s, 11.2760 runs/s, 13.6252 assertions/s.
24 runs, 29 assertions, 0 failures, 0 errors, 0 skips

我们这里顺利通过测试。

2.6 文件版本管理

2.6.1 添加所有变动文件到git管理
$ git add .
2.6.2 提交所有变动文件
$ git commit -m "set shops model"

3. 总结

我们完成了商铺模型相关功能,并添加了和用户模型的惯量关系, 下节课我们将开发商铺控制器相关功能!一定要跟上脚步,认真练习!