您的位置:首页 > 编程语言 > Ruby

Ruby for Rails 最佳实践ⅩⅤ

2012-01-05 13:40 417 查看

第十五章 通过编程改进 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: