Skip to content

Latest commit

 

History

History
559 lines (368 loc) · 18.6 KB

12 订单模块-模型相关功能开发.md

File metadata and controls

559 lines (368 loc) · 18.6 KB

使用rails6 开发纯后端 API 项目

订单模块: 模型


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

我们已经开发完了用户和商铺以及商品模块,终于可以开始购物了,这也就是我们今天要开发的模块:订单。

开发前我们还是要明确订单模块的功能以及订单模块和其它模块之间的关系,特别是模型之间关系的确立!

1. 功能分析

1.1 功能梳理

我们可以先从功能的梳理开始。

  • 用户进入店铺 --> 浏览商品 --> 选中商品 --> 用户下单;
  • 用户查看自己的订单列表;
  • 用户查看某个订单的详情。

当然我们这个需求非常的简单,如果要完成整个订单闭环,我们还有很多需求需要完成,但这里我们先完成这些。

基于以上需求和我们日常经验,我们可以分析出:

  • 用户和订单是有关系的:一个用户可以多次下单,一个订单只属于一个用户,所以用户和订单是一对多的关系;
  • 订单和商品之间是有关系的:一个订单可以包含多件商品,一种商品可以被多个订单包含,所以订单和商品是多对多的关系,这种多对多的关系,我们这里采用中间表的形式关联;
  • 用户和商品之间的联系是通过订单来关联的,并没有直接形成关系。

所以我们需要两张表:订单主表和订单详情表,其中订单详情表也用做订单和商品之间模型的关联,而订单主表就存储所属用户的信息以及订单的统计信息。

1.2 表设计

1.2.1 订单表
1.2.1.1 字段分析
  • 订单存储用户信息,所以需要标题字段;
  • 订单需要有价格,所以需要价格字段;
  • 订单需要有上下架的状态,所以需要上架状态字段;
  • 订单是属于某个商铺的,需要 商铺ID 字段来标记;
  • 每个订单都应该由唯一的id,Rails已经为我们提供了默认的id字段,并且是主键,所以我们在这里不用自定义,直接使用默认即可;
  • Rails还会自动帮我们维护两个字段:created_at, updated_at 。
1.2.1.2 订单表:orders
字段 类型 长度 注释 null 默认值
id unsigned integer 11 主键id,自动增长 0
user_id int 11 用户id 0
price_total int 11 订单总价价格:精确到分 0
created_at timestamp --- 创建时间 当前时间
updated_at timestamp --- 修改时间 当前时间
1.2.1.3 要验证的字段
  • 添加和修改订单:price_total字段信息必须提供
  • 添加和修改订单:price_total字段信息大于等于0
1.2.2 订单详情表
1.2.2.1 字段分析
  • 要关联订单,所以需要订单id字段;
  • 要关联商品,所以需要商品id字段;
  • 要记录订单购买的数量,所以需要数量字段;
  • Rails还会自动帮我们维护两个字段:created_at, updated_at 。
1.2.2.2 订单表:orders
字段 类型 长度 注释 null 默认值
order_id int 11 订单id 0
product_id int 11 商品id 0
quantity int 11 商品数量 0
created_at timestamp --- 创建时间 当前时间
updated_at timestamp --- 修改时间 当前时间

2. 订单模型相关功能开发

2.1 文件版本管理

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

2.2 订单模型迁移文件

2.2.1 使用命令创建订单模型迁移文件
$ rails generate model order user_id:integer price_total:integer
Running via Spring preloader in process 6566
      invoke  active_record
      create    db/migrate/20210521124134_create_orders.rb
      create    app/models/order.rb
      invoke    test_unit
      create      test/models/order_test.rb
      create      test/fixtures/orders.yml

可以看到命令行帮我们生成了 db/migrate/20210521124134_create_orders.rb 订单模型的迁移文件。

还帮我们生成了 app/models/order.rb 订单模型文件。

2.2.2 编辑订单模型的迁移文件

db/migrate/20210521124134_create_orders.rb

class CreateOrders < ActiveRecord::Migration[6.1]
  def change
    create_table :orders do |t|
      t.integer :user_id, null: false, default:0
      t.integer :price_total, null: false, default:0

      t.timestamps
    end
  end
end

我们这里创建了orders表。

2.2.3 执行迁移命令
$ rails db:migrate   
== 20210521124134 CreateOrders: migrating =====================================
-- create_table(:orders)
   -> 0.0289s
== 20210521124134 CreateOrders: migrated (0.0290s) ============================

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

2.3 为order模型添加验证

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

思路分析

我们需要做的验证:

  • price_total:不能为空,整数,必须大于等于0。

app/models/order.rb

class Order < ApplicationRecord
  belongs_to :user

  validates :price_total, presence: true, numericality: { greater_than_or_equal_to: 0}
end
2.3.2 在 Rails console 中简单测试
$ rails c
Running via Spring preloader in process 6723
2.7.2 :001 > o = Order.new({user_id:1, price_total:-1})
 => #<Order id: nil, user_id: 1, price_total: -1, created_at: nil, updated_at: nil> 
2.7.2 :002 > o.valid?
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
 => false 
2.7.2 :003 > o.errors.messages
 => {:price_total=>["must be greater than or equal to 0"]} 
2.3.3 构建用户和订单之间的关系

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

2.3.3.1 修改用户模型
# app/models/user.rb
class User < ApplicationRecord
  # ......
  has_many :orders, dependent: :destroy
  # ......
  end
end
2.3.3.2 修改订单模型
# app/models/order.rb
class Order < ApplicationRecord
  belongs_to :user
  # ... ...
end
2.3.4 为模型验证编写单元测试

*基本思路分析

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

在这里需要测试两种场景:

  • 成功的场景
    • 使用全部合法的参数(合法的user_id,合法的price_total)创建订单,断言:通过验证
  • 失败的场景
    • 使用 非法的price_total,合法的user_id创建订单,断言:未通过验证

2.3.5 准备测试用订单预定义数据

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

test/fixtures/orders.yml

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

one:
  user: one
  price_total: 1

two:
  user: two
  price_total: 1

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

2.3.6 编写测试

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

  • 使用全部合法的参数(合法的user_id,合法的price_total)创建订单,断言:通过验证

    test/models/order_test.rb

    class orderTest < ActiveSupport::TestCase
      setup do
        @order = orders(:one)
        @user = users(:one)
      end
    
      # 使用合法参数
      test 'valid: order with all valid things' do
        order = Order.new(user_id:@user.id, price_total:1)
        assert order.valid?
      end
      #...
    end

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

  • 使用 非法的price_total,合法的user_id创建订单,断言:未通过验证

    test/models/order_test.rb

    test 'invalid: order with invalid price_total' do
        order = Order.new(user_id:@user.id, price_total:-1)
        assert_not order.valid?
    end
2.3.7 运行测试
$ rails test

... ...
Finished in 2.319223s, 19.8342 runs/s, 29.7513 assertions/s.
46 runs, 69 assertions, 0 failures, 0 errors, 0 skips

我们这里顺利通过测试。

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

2.3.8 添加文件到版本管理
$ git add . && git commit -m "Generate orders"

接下来我们开发订单详情有关内容。

3. 订单详情模型功能开发

3.1 订单详情模型迁移文件

3.1.1 使用命令创建订单详情模型迁移文件
$ rails generate model placement order_id:integer product_id:integer quantity:integer
Running via Spring preloader in process 2304
      invoke  active_record
      create    db/migrate/20210522011606_create_placements.rb
      create    app/models/placement.rb
      invoke    test_unit
      create      test/models/placement_test.rb
      create      test/fixtures/placements.yml

可以看到命令行帮我们生成了 db/migrate/20210522011606_create_placements.rb 订单详情模型的迁移文件。

还帮我们生成了 app/models/placement.rb 订单详情模型文件。

3.1.2 编辑订单详情模型的迁移文件

db/migrate/20210521124134_create_placements.rb

class CreatePlacements < ActiveRecord::Migration[6.1]
  def change
    create_table :placements do |t|
      t.integer :order_id, null: false, default:0
      t.integer :product_id, null: false, default:0
      t.integer :quantity, null: false, default:0

      t.timestamps
    end

    add_index :placements, [:order_id, :product_id]
    add_index :placements, [:product_id, :order_id]
  end
end

我们这里创建了placements表。

3.1.3 执行迁移命令
$ rails db:migrate
== 20210522011606 CreatePlacements: migrating =================================
-- create_table(:placements)
   -> 0.0173s
-- add_index(:placements, [:order_id, :product_id])
   -> 0.0106s
-- add_index(:placements, [:product_id, :order_id])
   -> 0.0093s
== 20210522011606 CreatePlacements: migrated (0.0379s) ========================

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

3.2 利用placement作为中间表关联订单和产品

3.2.1 修改 placement 模型文件

思路分析

在这里我们使用Rails的规则,既然placement作为中间表关联多对多模型对象,我们分别让其中的字段 belongs_to 两张表。


app/models/placement.rb

class Placement < ApplicationRecord
  belongs_to :order
  belongs_to :product, inverse_of: :placements
end
3.2.2 修改 order 模型文件

app/models/order.rb

class Order < ApplicationRecord
  # ... ...
  has_many :placements, dependent: :destroy
  has_many :products, through: :placements
  
  # ... ...
end
3.2.3 修改 product 模型文件

app/models/product.rb

class Product < ApplicationRecord
  # ... ...
  has_many :placements, dependent: :destroy
  has_many :orders, through: :placements
  # ... ...
end
3.2.4 在 Rails console 中简单测试
$ rails c
Running via Spring preloader in process 4334
Loading development environment (Rails 6.1.3.2)

2.7.2 :001 > u = User.first
  User Load (0.4ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
  
2.7.2 :002 > new_order = u.orders.create(price_total:0)
  Order Create (0.6ms)  INSERT INTO `orders` (`user_id`, `created_at`, `updated_at`) VALUES (1, '2021-10-22 02:20:15.476568', '2021-10-22 02:20:15.476568')
  TRANSACTION (1.6ms)  COMMIT
 => #<Order id: 3, user_id: 1, price_total: 0, created_at: "2021-10-22 02:20:15.476568000 +0000", updated_at: "2021-05-22 02:20:15.476568000 +0000"> 
 
2.7.2 :003 > p = Product.first
 => #<Product id: 1, title: "123", price: 1, published: 1, shop_id: 1, created_at: "2021-10-20 11:53:59.663300000 +0000", updated_at: "2021-10-20 11:53:59.663300000 +0000"> 
 
2.7.2 :004 > new_placements = new_order.placements.create(product_id:p.id, quantity:5)
  TRANSACTION (0.2ms)  BEGIN
  Product Load (0.3ms)  SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1
  Placement Create (0.6ms)  INSERT INTO `placements` (`order_id`, `product_id`, `quantity`, `created_at`, `updated_at`) VALUES (3, 1, 5, '2021-05-22 02:20:53.684609', '2021-10-22 02:20:
  TRANSACTION (2.4ms)  COMMIT
  
2.7.2 :005 > new_order.price_total = new_placements.quantity * p.price
 => 5 
 
2.7.2 :006 > new_order.save
  TRANSACTION (0.2ms)  BEGIN
  TRANSACTION (5.0ms)  COMMIT
 => true 
 
2.7.2 :007 > new_order
 => #<Order id: 3, user_id: 1, price_total: 5, created_at: "2021-10-22 02:20:15.476568000 +0000", updated_at: "2021-10-22 02:21:14.398478000 +0000"> 
  Placement Load (0.5ms)  SELECT `placements`.* FROM `placements` WHERE `placements`.`order_id` = 3 /* loading for inspect */ LIMIT 11
 => #<ActiveRecord::Associations::CollectionProxy [#<Placement id: 1, order_id: 3, product_id: 1, quantity: 5, created_at: "2021-10-22 02:20:53.684609000 +0000", updated_at: "2021-10-22 02:20:53.684609000 +0000">]> 
 
2.7.2 :08 > new_order.products
  Product Load (0.8ms)  SELECT `products`.* FROM `products` INNER JOIN `placements` ON `products`.`id` = `placements`.`product_id` WHERE `placements`.`order_id` = 3 /* loading for inspe
 => #<ActiveRecord::Associations::CollectionProxy [#<Product id: 1, title: "123", price: 1, published: 1, shop_id: 1, created_at: "2021-10-20 11:53:59.663300000 +0000", updated_at: "2021-10-20 11:53:59.663300000 +0000">]> 
 
2.7.2 :09 > p2 = Product.find(2)
  Product Load (0.5ms)  SELECT `products`.* FROM `products` WHERE `products`.`id` = 2 LIMIT 1
  
2.7.2 :010 > new_placement2 = new_order.placements.create(product_id:p2.id, quantity:3)
  TRANSACTION (0.2ms)  BEGIN
  Placement Create (0.5ms)  INSERT INTO `placements` (`order_id`, `product_id`, `quantity`, `created_at`, `updated_at`) VALUES (3, 2, 3, '2021-05-22 02:25:24.690102', '2021-05-22 02:25:24.690102')
 => #<Placement id: 2, order_id: 3, product_id: 2, quantity: 3, created_at: "2021-10-22 02:25:24.690102000 +0000", updated_at: "2021-10-22 02:25:24.690102000 +0000"> 
 
2.7.2 :011 > new_order.price_total = new_order.price_total + new_placement2.quantity * p2.price
 => 8 
 
2.7.2 :012 > new_order.save
  TRANSACTION (0.2ms)  BEGIN
  Order Update (0.6ms)  UPDATE `orders` SET `orders`.`price_total` = 8, `orders`.`updated_at` = '2021-10-22 02:25:49.706885' WHERE `orders`.`id` = 3
  TRANSACTION (1.8ms)  COMMIT
  
2.7.2 :013 > new_order.placements
  Placement Load (0.5ms)  SELECT `placements`.* FROM `placements` WHERE `placements`.`order_id` = 3 /* loading for inspect */ LIMIT 11 02:20:53.684609000 +0000">, #<Placement id: 2, order_id: 3, product_id: 2, quantity: 3, created_at: "2021-10-22 02:25:24.690102000 +0000", updated_at: "2021-10-22 02:25:24.690102000 +0000">]> 
  
2.7.2 :014 > new_order.products
  Product Load (0.4ms)  SELECT `products`.* FROM `products` INNER JOIN `placements` ON `products`.`id` = `placements`.`product_id` WHERE `placements`.`order_id` = 3 /* loading for inspect */ LIMIT 11
 => #<ActiveRecord::Associations::CollectionProxy [#<Product id: 1, title: "123", price: 1, published: 1, shop_id: 1, created_at: "2021-10-20 11:53:59.663300000 +0000", updated_at: "2021-10-20 11:53:59.663300000 +0000">, #<Product id: 2, title: "firtst test", price: 1, published: 1, shop_id: 1, created_at: "2021-10-21 01:44:48.450398000 +0000", updated_at: "2021-10-21 01:44:48.450398000 +0000">]> 
 
2.7.2 :015 > u.orders
  Order Load (0.3ms)  SELECT `orders`.* FROM `orders` WHERE `orders`.`user_id` = 1 /* loading for inspect */ LIMIT 11
 => #<ActiveRecord::Associations::CollectionProxy [#<Order id: 1, user_id: 1, price_total: 100, created_at: "2021-10-22 01:56:37.357925000 +0000", updated_at: "2021-10-22 01:56:37.357925000 +0000">, #<Order id: 2, user_id: 1, price_total: 100, created_at: "2021-10-22 02:00:14.620024000 +0000", updated_at: "2021-10-22 02:00:14.620024000 +0000">, #<Order id: 3, user_id: 1, price_total: 8, created_at: "2021-10-22 02:20:15.476568000 +0000", updated_at: "2021-10-22 02:25:49.706885000 +0000">]> 
 
2.7.2 :016 > u.orders.count
   (0.7ms)  SELECT COUNT(*) FROM `orders` WHERE `orders`.`user_id` = 1
 => 3 
 
2.7.2 :017 > Placement.count
   (18.2ms)  SELECT COUNT(*) FROM `placements`
 => 2 
 
2.7.2 :018 > new_order.destroy
  TRANSACTION (0.3ms)  BEGIN
  Placement Load (0.4ms)  SELECT `placements`.* FROM `placements` WHERE `placements`.`order_id` = 3
  Placement Destroy (0.4ms)  DELETE FROM `placements` WHERE `placements`.`id` = 1
  Placement Destroy (0.6ms)  DELETE FROM `placements` WHERE `placements`.`id` = 2
  Order Destroy (0.6ms)  DELETE FROM `orders` WHERE `orders`.`id` = 3
  TRANSACTION (1.5ms)  COMMIT
 => #<Order id: 3, user_id: 1, price_total: 8, created_at: "2021-10-22 02:20:15.476568000 +0000", updated_at: "2021-10-22 02:25:49.706885000 +0000"> 
 
2.7.2 :019 > Placement.count
   (0.5ms)  SELECT COUNT(*) FROM `placements`
 => 0 
 
2.7.2 :020 > u.orders.count
   (0.4ms)  SELECT COUNT(*) FROM `orders` WHERE `orders`.`user_id` = 1
 => 2  

虽然测试简单,但是我们能看到订单和商品以及用户之间的数据都可以正确的添加,删除,查询等。

4. 文件版本管理

4.1 添加所有变动文件到git管理
$ git add . && git commit -m "set placements model "

4. 总结

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