Ruby for Rails 最佳实践十五

第十五章 通过编程改进 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值