Refer : http://blog.magica.me/idea/rails-exp.html
为了满足自己炒股兴趣的需要,根据《神奇公式》,对 A 股市场做了一个排序系统,既完成了一个对自己有效的需求,也从中学习 rails。
Source : https://coding.net/u/hwh008/p/mss/git
Site : http://www.magica.me
股票的财务数据都是从 sina 抓的,所以Nokogiri是首选,有了他,做点基础的爬虫实在太容易了。
用 whenever 每天定点去爬一下收盘价,顺便检测一些需要更新财务数据的股票。
StockInfo.tick_sheet
我有一个很耗时的 model 函数,也就是对所有股票进行排序的公式实现:
StockSheet.calc_better_cheap
我希望这个公式的结果页面显示后,可以重用这个页面,不要重新计算排序,因此我尝试了 action cache 和 fragment cache,然而并没有什么卵用,因为这两种cache都还是要执行controller#action的,费时的函数调用恰好放在action中。我可以把这个调用放到view里,这样就解决了问题,但是又涉及到请求参数变化的问题,而且这串调用放到view里也带来了限制,不方便在action里对数据做更多的修饰。
另外,我也搞不清楚 fragment cache 的实现机制和最佳实践。
经过搜索我找到了qor_cache,还是国人的优秀作品,有 3 点好处:
cache_key 'stock_update' do
StockInfo.first.updated_at
end
scope :stock_sheet do
cache_class_method :calc_better_cheap, 'stock_update'
end
函数的输入参数是一个 hash obj,qor_cache 将参数作为 cache key 的一部分,但是这个 hash obj 中,其实有些 key 并不影响 cache,因此我根据 qor_cache 做了一个 hack:
hash_obj.instance_eval do
# 为了优化qor cache,不是每个选项都影响cache key
def inspect
%Q(inspect redefine:#{self["mktcap_min"]},#{self["mktcap_max"]})
end
self
end
我没有使用 will_paginate,因为 will_paginate 只包装 relation 对象,但我要做的是对数据进行排序计算,已经将数据全部都取出来了,因此找了一个支持 page array 的 gem:kaminari,听这名字像日本人写的 gem。
def better_cheap
stocks = Kaminari.paginate_array StockSheet.calc_better_cheap(calc_params)
@stocks_page = stocks.page(params.fetch(:page, 1)).per 100
end
函数式编程其实就是好看,可以把对集合的一连串操作都写在一行里,长长的。
神奇公式的排序算法就是,将集合所有元素根据指标 A 排序,取排序顺序为分数 A,再根据指标 B 排序,取排序顺序为分数 B,根据分数 AB 之和再排序。写到一行里是这样的:
def self.calc_better_cheap(opt)
available(opt).sort { |a, b| a.better_v <=> b.better_v }.reverse.map.with_index(1) { |el, i| el.better_ord = i ; el}.sort { |a,b| a.cheap_v <=> b.cheap_v }.reverse.map.with_index(1) { |el, i| el.cheap_ord = i; el}.sort { |a,b| a.bc_value <=> b.bc_value }
end
生产部署用的是 passenger,很简单,安装就好了。就是环境变量遇上了一些问题,后来通过 dotenv-rails 解决。
有两种不漂亮的写法,一种是在 view 里写表达式,另一种是在 action 里把表达式算好的结果放到 instance var。
不过 erb 模版对 Helper 的支持还是不够漂亮,我比较倾向的是那种管道式的,这样可以把一串 helper 像羊肉串一样串起来。