Ruby for Rails 最佳实践十五
2013-10-08 14:40
399 查看
第十五章 通过编程改进 ActiveRecord 模型
一、软模型改进与硬模型改进
1. 当在 ActiveRecord 模型类中编写一个新方法时,可以把方法区分为:被动方法(即那些仅仅获取数据并返回数据的方法)和主动方法(即那些生成新的数据结构的方法)class Composer < ActiveRecord::Base
has_many :works
# 软改进
def editions
works.map {|work| work.editions }.flatten.uniq
end
# 硬改进
def whole_name
first_name + " " +
(if middle_name then middle_name + " " else "" end) +
last_name
end
end
2. 软模型改进与硬模型改进:本质区别
本质区别在于:软改进描述给 ActiveRecord 提供辅助;硬改进涉及产生新的数据。
二、模型的软编程改进
1. 通过软改进细化 Work 模型(1)哪些发行商发行过该作品的版本
def publishers
editions.map {|e| e.publisher}.uniq
end
(2)该作品来自哪个国家
def country
composer.country
end
(3)哪些顾客订购过该作品
def ordered_by
editions.orders.map {|o| o.customer }.uniq
end
(4)该作品的基调是什么
def key
kee
end
2. 为顾客的业务建模
(1)该顾客有哪些未完成的订单
def open_orders
orders.find(:all, :conditions => "status = 'open'")
end
(2)该顾客当前订单中包含哪些版本
def editions_on_order
open_orders.map {|order| order.edition }.uniq
end
(3)该顾客曾经订购过哪些版本
def edition_history
orders.map {|order| order.edition }.uniq
end
(4)该顾客当前订单中包含哪些作品
def works_on_order
editions_on_order.map {|edition| edition.works }.flatten.uniq
end
(5)该顾客曾经订购过哪些作品
def work_history
edition_history.map {|edition| edition.works }.flatten.uniq
end
3. 改进 composer
(1)该作曲者的作品出现在哪些版本中
def editions
works.map {|work| work.editions }.flatten.uniq
end
(2)哪些发行商发行过包含该作曲者作品的版本
def publishers
editions.map{|edition| edition.publisher }.uniq
end
4. 对比软改进过程中的 Ruby 和 SQL
最高效、最快速的数据库记录提取方式是 SQL 代码。ActiveRecord 做的很多工作就是私底下将你的 Ruby 代码翻译为 SQL 语句,然后使用这些语句查询数据库。
为了提高执行速度,ActiveRecord 允许任何时候都可以使用 SQL。可是这样就失去了良好的 Ruby 外观,却获得了效率。
作为对比 Ruby/SQL 的例子,我们来看一下 Composer#editions 方法:
def editions
works.map {|work| work.editions }.flatten.uniq
end
该方法首先调用 works,无条件地搜集该作曲者的所有作品。调用 works 方法之后,ActiveRecord 的任务就完成了;该方法也是唯一的一个数据库查询,返回该作曲者的所有作品。剩下的都是纯 Ruby 代码:获取作品的所有版本(使用map),这些信息被存放在一个数组的数组中,然后使用 flatten 和 uniq 对该数组进行处理。最后,得到一个新数组。
下面是实现 editions 方法的另一种方式,使用 SQL 语句
def editions
Edition.find_by_sql("SELECT edition_id from editions_works
LEFT JOIN works ON editions_works.work_id = works.id
LEFT JOIN composers ON works.composer_id = composers.id
WHERE (composers.id = #{id})")
end
Ruby 程序员在使用 Ruby 编写程序时,知道它不是特别快的语言;在碰到严重的性能瓶颈时,他们把程序的某些部分编写为 C 扩展。而在 Rails 应用开发中,SQL 也扮演着类似的角色。
三、硬模型改进
1. 美化字符串属性(Work 类)(1)调整作品的各乐器名的格式
def nice_instruments
instrs = instruments.map {|inst| inst.name }
ordered = %w{ flute oboe violin viola cello piano orchestra }
instrs = instrs.sort_by {|i| ordered.index(i) || 0 }
case instrs.size
when 0
nil
when 1
instrs[0]
when 2
instrs.join(" and ")
else
instrs[0...-2].join(", ") + ", and " + instrs[-1]
end
end
代码中使用了 %w{…} 构造一个字符串数组,用于存放规范的乐器名顺序
ordered = %w{ flute oboe violin viola cello piano orchestra }
接着,根据 ordered 数组中各个乐器的出现顺序(乐器名在数组中的索引序号)对 instrs 排序,如果出现不存在的乐器名,则它将出现在排序结果的最后面
instrs = instrs.sort_by {|i| ordered.index(i) || 0 }
(2) 调整作品号的格式
def nice_opus
if /^\d/.match(opus)
"op. #{opus}"
else
opus
end
end
(3) 美化的作品标题
def nice_title
t,k,o,i = title, key, nice_opus, nice_instruments
"#{t} #{"in #{k}" if k}#{", #{o}" if o}#{", for #{i}" if i}"
end
(4) 美化的版本标题(Edition.rb)
def nice_title
(title || works[0].nice_title) +
" (#{publisher.name}, #{year})"
end
2. 计算作品的年代(Work 类)
(1)作品出自哪个世纪
def century
c = (year - 1).to_s[0,2].succ
c += case c
when "21" then "st"
else "th"
end
c + " century"
end
(2)作品的更富描述性的时代信息
在 Work 类中定义一个常量
PERIODS = { [1650..1750, %w{ EN DE FR IT ES NL}] => "Baroque",
[1751..1810, %w{ EN IT DE NL }] => "Classical",
[1751..1830, %w{ FR }] => "Classical",
[1837..1901, %w{ EN }] => "Victorian",
[1820..1897, %w{ DE FR }] => "Romantic" }
然后加入该方法
def period
pkey = PERIODS.keys.find do |yrange, countries|
yrange.include?(year) && countries.include?(country)
end
PERIODS[pkey] || century
end
3. 剩余的顾客业务(Customer 类)
(1) 顾客喜好排名
def rank(list)
list.uniq.sort_by do |a|
list.select {|b| a == b }.size
end.reverse
end
def composer_rankings
rank(edition_history.map {|ed| ed.composers }.flatten)
end
def instrument_rankings
rank(work_history.map {|work| work.instruments }.flatten)
end
(2)计算订购的副本的数目
def copies_of(edition)
orders.find(:all, :conditions => "edition_id = #{edition.id}").size
end
(3)未偿余额
方法一:
def balance
acc = 0
open_orders.each do |order|
acc += order.edition.price
end
"%.2f" % acc
end
方法二(递增的累计计算可以使用 inject 方法自动完成):
def balance
"%.2f" % open_orders.inject(0) do |acc,order|
acc + order.edition.price
end
end
(4)顾客结帐
方法一:
def check_out
orders.each do |order|
order.status = "paid"
order.update
end
end
方法二:
def check_out
orders.each do |order|
order.update_attribute(:status, "paid")
end
end
四、用类方法扩展模型功能
1. 确定一组作品的所有版本(app/models/edition.rb)def Edition.of_works(works)
works.map {|work| work.editions }.flatten.uniq
end
2. 获取库存作品的所有时代(app/models/work.rb)
def Work.all_periods
find(:all).map {|c| c.period }.flatten.uniq.sort
end
3. 确定作品的销售排名(app/models/work.rb)
def Work.sales_rankings
r = Hash.new(0)
find(:all).each do |work|
work.editions.each do |ed|
r[work.id] += ed.orders.size
end
end
r
end
散列是无序的,要使用这个散列,还需要对它进行排序:
rankings = Work.sales_rankings
r_sorted = rankings.sort_by {|key,value| value }
4. 确定作曲者的销售排名(app/models/composer.rb)
def Composer.sales_rankings
r = Hash.new(0)
Work.sales_rankings.map do |work,sales|
r[work.composer.id] += sales
end
r
end
相关文章推荐
- Ruby for Rails 最佳实践Ⅳ
- Ruby for Rails 最佳实践Ⅸ
- Ruby for Rails 最佳实践Ⅵ
- Ruby for Rails 最佳实践Ⅹ
- Ruby for Rails 最佳实践Ⅺ
- Ruby for Rails 最佳实践七
- Ruby for Rails 最佳实践Ⅻ
- Ruby for Rails 最佳实践八
- Ruby for Rails 最佳实践Ⅻ
- Ruby for Rails 最佳实践九
- Ruby for Rails 最佳实践十
- Ruby for Rails 最佳实践ⅩⅢ
- Ruby for Rails 最佳实践十一
- Ruby for Rails 最佳实践Ⅰ
- Ruby for Rails 最佳实践十二
- Ruby for Rails 最佳实践Ⅱ
- Ruby for Rails 最佳实践ⅩⅤ
- Ruby for Rails 最佳实践十三
- Ruby for Rails 最佳实践Ⅲ
- Ruby for Rails 最佳实践ⅩⅥ