Skip to content

Latest commit

 

History

History
374 lines (238 loc) · 11.7 KB

10 商品模块-模型相关功能开发.md

File metadata and controls

374 lines (238 loc) · 11.7 KB

使用rails6 开发纯后端 API 项目

商品模块: 模型


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

我们已经开发完了用户和商铺模块,今天我们来开发商品模块。开发前我们需要明确我们已经开发的用户模型和商铺模型与今天要开发的商品模型之间的关系。

用户有3个基本角色:管理员,商铺拥有者和普通的用户。商铺拥有者拥有商铺,在我们的系统中一个用户最多只能拥有一个商铺,而商品是属于商铺的,一个商铺可以拥有多件商品,而一件商品只能属于一个商铺,所以商铺和商铺是多对一的关系。至于用户模型,其实任何人都是可以购买商品的,所以实际上用户要购买商品可以购买多个,而一件商品可以被多个用户购买,但这种关系是要体现在订单模块中,用户模型和商品并不产生之间的关系。

所以在本节中,我们需要确定的商铺模型与商品模型的关系。

废话不多说,让我们开始今天的开发!

1. 功能分析

1.1 商品需要具有的字段
  • 商品需要标题,所以需要标题字段;
  • 商品需要有价格,所以需要价格字段;
  • 商品需要有上下架的状态,所以需要上架状态字段;
  • 商品是属于某个商铺的,需要 商铺ID 字段来标记;
  • 每个商品都应该由唯一的id,Rails已经为我们提供了默认的id字段,并且是主键,所以我们在这里不用自定义,直接使用默认即可;
  • Rails还会自动帮我们维护两个字段:created_at, updated_at 。
1.2 商品表:products
字段 类型 长度 注释 null 默认值
id integer 11 主键id,自动增长 0
title string 100 商品名称 空字符串
price int 11 商品价格:精确到分 空0串
published unsigned tiny_int 2 上架状态:0,未上架 1 已上架 1
shop_id integer 11 商铺id 1
created_at timestamp --- 创建时间 当前时间
updated_at timestamp --- 修改时间 当前时间
1.3 要验证的字段
  • 添加和修改商品:title字段信息必须提供
  • 添加和修改商品:price 字段信息大于等于0
  • 添加和修改商品:published值只能为0或者1

2. 模型相关功能开发

2.1 文件版本管理

2.1.1 切换分支到 chapter08
$ git checkout -b chapter08

2.2 商品模型迁移编写

2.2.1 使用命令创建商品模型迁移文件
$ rails generate model product title:string price:integer published:integer shop_id:integer
Running via Spring preloader in process 2244
      invoke  active_record
      create    db/migrate/20210520075256_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml

可以看到命令行帮我们生成了 db/migrate/20210520075256_create_products.rb 商品模型的迁移文件。

还帮我们生成了 app/models/product.rb 商品模型文件。

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

20210520075256_create_products.rb

class CreateProducts < ActiveRecord::Migration[6.1]
  def change
    create_table :products do |t|
      t.string :title, null: false, default: ''
      t.integer :price, null: false, default:0
      t.integer :published, null:false, default:1
      t.integer :shop_id, null:false, default:0

      t.timestamps
    end
      
    add_index :products, [:shop_id, :title]
    add_index :products, :title
  end
end

我们这里创建了products表, 并添加了标题和商铺id的关联索引,这是因为同一家店铺内不允许商品同名,但是允许不同商铺的商品同名。

2.3.3 执行迁移命令
$ rails db:migrate                                                          
== 20210507090011 Createproducts: migrating ======================================
-- create_table(:products)
   -> 0.0154s
== 20210507090011 Createproducts: migrated (0.0155s) =============================

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

2.4 为product模型添加验证

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

思路分析

我们需要做的验证:

  • title:不能为空;
  • title:同一家店铺的标题不允许重复;
  • price:不能为空,整数,必须大于等于0。

app/models/product.rb

class Product < ApplicationRecord
  validates :title, presence: true
  validates :price, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
  validates :published, inclusion: { in: [0, 1], message:"published can be only in [0 1]" }
  validate :title_cannot_be_taken_in_self_shop

  private
    def title_cannot_be_taken_in_self_shop
      if self.class.exists?(title: title, shop_id: shop_id)
        errors.add(:title, "title cannot be taken in self shop")
      end
    end
end

我们利用了自定义方法title_cannot_be_taken_in_self_shop来验证在同一家店铺中标题不重复。

2.4.2 在 Rails console 中简单测试
$ rails console
2.7.2 :001 > p = Product.new({title:"", price:-1, published:2, shop_id:1})
 => #<Product id: nil, title: "", price: -1, published: 2, shop_id: 1, created_at: nil, updated_at: nil> 
2.7.2 :002 > p.valid?
 => false 
2.7.2 :003 > p.errors.messages
 => {:title=>["can't be blank"], :price=>["must be greater than or equal to 0"], :published=>["published can be only in [0 1]"]} 

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

2.4.3 构建商铺和商品之间的关系

就像前面我们已经分析的:商铺和商品是一对多的关系,商品和店铺是一对一的关系。并且我们应该知道当商铺被删除,所属的商品也应该被删除。

2.4.3.1 修改商铺模型
# app/models/shop.rb
class Shop < ApplicationRecord
  # ......
  has_many :products, dependent: :destroy

  end
end
2.4.3.2 修改商品模型
# app/models/product.rb
class Product < ApplicationRecord
  # ... ...

  belongs_to :shop
  # ... ...
end

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


*基本思路分析

我们使用Rails内置的 Minitest 测试框架来编写以及测试我们的应用。

对于单元测试的编写,我们需要进行两种基本类型场景测试:成功的场景和失败的场景。而模型的验证,在这里主要是针对模型中对字段的相关 validates 的针对性测试!

  • 成功的场景
    • 使用全部合法的参数(合法的title,合法的price,合法的published, 合法的shop_id)创建商品,断言:通过验证
  • 失败的场景
    • 使用 非法的title,合法的price,合法的published,合法的shop_id创建商品,断言:未通过验证
    • 使用 重复的title和shop_id,合法的price,合法的published创建商品,断言:未通过验证
    • 使用 非法的published,合法的title,合法的price ,合法的shop_id创建商品,断言:未通过验证
    • 使用 非法的price ,合法法的published,合法的title ,合法的shop_id创建商品,断言:未通过验证

2.5.1 准备测试用商品预定义数据

在使用Rails的命令创建模型product时,Rails还帮我们自动创建了测试文件已经与模型对应的测试用预定义数据文件:

test/fixtures/products.yml

我们可以修改这个文件的内容如下:

one:
  title: 'first product'
  price: 1
  published: 1
  shop: one

two:
  title: 'second product'
  price: 1
  published: 1
  shop: one

这里定义了两个商品:商品one 和 商品 two, 然后我们就可以在测试文件中通过 products(:one)来获取第一个商品的对象, products(:two)来获取第二个商品的对象信息了。

2.5.2 编写测试

测试的编写我们首先要确定文件路径:test/models/product_test.rb, 如果你仔细观察,这个文件也是Rails帮我们自动创建的!现在开始编写相关测试吧!

  • 使用全部合法的参数(合法的title,合法的price,合法的published, 合法的shop_id)创建商品,断言:通过验证

    test/models/product_test.rb

    class productTest < ActiveSupport::TestCase
      setup do
        @p_one = products(:one)
      end
    
      # 使用合法参数
      test 'valid: product with all valid things' do
        product = Product.new(title: "title123", price: 1, published: 1, shop_id: @p_one.shop.id)
        assert product.valid?
      end
      #...
    end

    上面的测试首先定义setup方法创建一些公用数据。

  • 使用 非法的title,合法的price,合法的published,合法的shop_id创建商品,断言:未通过验证

    test/models/product_test.rb

    test 'invalid: product with invalid title' do
        product = Product.new(title: "", price: 1, published: 1, shop_id: @p_one.shop.id)
        assert_not product.valid?
    end
  • 使用 重复的title和shop_id,合法的price,合法的published创建商品,断言:未通过验证

    test/models/product_test.rb

    test 'invalid: product with taken title and shop_id' do
        product = Product.new(title: @p_one.title, price:1, published: 1, shop_id: @p_one.shop.id)
        assert_not product.valid?
    end
  • 使用 非法的published,合法的title,合法的price ,合法的shop_id创建商品,断言:未通过验证

    test/models/product_test.rb

    test 'invalid: product with invalid published' do
        product = Product.new(title: 'first test', price: 1, published: 2, shop_id: @p_one.shop.id)
        assert_not product.valid?
    end
  • 使用 非法的price ,合法的published,合法的title ,合法的shop_id创建商品,断言:未通过验证

    test/models/product_test.rb

    test 'invalid: product with invalid price' do
        product = Product.new(title: 'first test', price: -100, published: 1, shop_id: @p_one.shop.id)
        assert_not product.valid?
    end
2.5.3 运行测试
$ rails test
... ...
Finished in 2.117990s, 17.4694 runs/s, 25.0237 assertions/s.
37 runs, 53 assertions, 0 failures, 0 errors, 0 skips

我们这里顺利通过测试。也许你在调试过程中会遇到许多小问题,慢慢解决,从中会学会很多东西。

关于上面的测试,我建议一个一个完成,不要都写完才测试!

2.6 文件版本管理

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

3. 总结

我们完成了商品模型以及模型验证并完成了模型的相关测试,下节课我们将开发商品控制器相关功能!一定要跟上脚步,认真练习!!