今天小伙伴需要的功能是:API 返回的数据,要有一个总数字段。这样他在手机端就可以判断,如果已经查看到最后一组产品了,往下拉动就不再去服务器请求数据了。为服务器减少不必要的请求压力。
嗯,看起来很完美,而且实现起来似乎很简单的样子。我回了他一句,妥妥的,给我 30 分钟。然后起身温盏,洗茶,泡了一盏茶之后,盘腿坐好,点开 focus booster 。打算在一个番茄时间内搞定一切。
我完全没过大脑,在 controller 里面写下了如下的代码
resources :products do
desc "Listing of products."
params do
optional :tag, type: String, desc: "Tag name."
optional :page, type: Integer, desc: "Page number."
optional :per, type: Integer, default: 10, desc: "Per page value."
end
get "/", jbuilder: 'products/products' do
@products = Product.tagged_with(tags, :any => true).page(params[:page]).per(params[:per])
@total_number = Product.tagged_with(tags, :any => true).count
end
end
然后在模板中添加
json.products @products do |product|
json.partial! "products/product", product: product
end
json.total_number @total_number
恩,看起来我已经完成了。随手点两下,不报错。提交算了,不用写测试了。茶叶刚好能喝,我喊小伙伴去测试 API。我就喝茶吧。
慢吞吞喝完茶,小伙伴给了反馈。total_number 返回的数据比实际 products 数组的多。total_number 显示 8,可 products 才返回 7 条。擦,我又仔细看了看我的代码。直觉没啥错误啊。
加了一个断点,打算把程序打断看看情况。我用的是 pry 。
get "/", jbuilder: 'products/products' do
binding.pry
@products = Product.tagged_with(tags, :any => true).page(params[:page]).per(params[:per])
@total_number = Product.tagged_with(tags, :any => true).count
end
再次刷新 API 的请求 URL 之后。程序卡在断点了。我先去掉了分页,自己执行了一下
products = Product.tagged_with(tags, :any => true)
total_number = Product.tagged_with(tags, :any => true).count
products.size
结果果然不同,total_number 返回 8,products.size 返回 7。
我的程序是跑在 Rails4 和 Ruby2 以及 Mysql5.5 上的。最先怀疑的是 Product 用了某些 Gem 是为 Rails3 写的,没有兼容 Rails4。所以我过滤了一遍 Gemfile
gem 'acts-as-taggable-on'
gem 'paranoia', '~> 2.0'
product 模型只用了这两个 gem,我回断点的地方粗略的扫了一眼生成的 sql 都正确的加入 WHERE `products`.`deleted_at` IS NULL
这个条件了。莫非 size 和 count 在新版本的 rails 和 ruby 中做了什么了不起的修改。是我们不知道的?所以我试着
products.count
products.size
一个 8 一个 7。看看 语句 A Product.tagged_with(tags, :any => true)
和 语句 B Product.tagged_with(tags, :any => true).count
生成的 sql 有啥区别吧。
语句 A 结果如下
SELECT DISTINCT products.* FROM `products` JOIN taggings produ_taggings_652eb4a ON produ_taggings_652eb4a.taggable_id = products.id AND produ_taggings_652eb4a.taggable_type = 'Product' WHERE `products`.`deleted_at` IS NULL AND (略) ORDER BY rank DESC
语句 B 结果如下
SELECT COUNT(*) FROM `products` JOIN taggings produ_taggings_243b36e ON produ_taggings_243b36e.taggable_id = products.id AND produ_taggings_243b36e.taggable_type = 'Product' WHERE `products`.`deleted_at` IS NULL AND (略)
差了一个 DISTINCT。看起来如果使用 count 方法的话,果然有我这个菜鸟不懂的地方啊。打开 Dash 看看文档吧。定位到 rails4 的文档然后输入 count 之后。出现将近 30 个备选项。立刻想跪了。耐心从第一个往下捋顺吧。看到 Calculation 这个词。好像跟计算有关,点开看看。
Person.count
# => the total count of all people
Person.count(:age)
# => returns the total count of all people whose age is present in database
Person.count(:all)
# => performs a COUNT(*) (:all is an alias for '*')
Person.distinct.count(:age)
# => counts the number of different age values
看来我需要加一个 distinct。废话不多说,加上试试看。听说如果少取几个字段还能优化 sql 的性能,真的吗?我也试试吧。
@products = Product.tagged_with(tags, :any => true).page(params[:page]).per(params[:per])
@total_number = Product.tagged_with(tags, :any => true).select(:id).distinct.count
结果正确了,俩都是 7。
完,番茄钟结束。起来溜达一圈。