十一 28
感恩节放假,抓紧时间重读一下AWDWR把一些知识点记录如下。
第6章
- 迁移习惯 create 来创建表,add 给现有表增加字段。你可以会看到 002_add_price.rb 的迁移。
- model 中验证方法设置为 protected 是因为该方法必须在特定的模型上下文中调用,不能在外部调用。
protected
def validate
errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
end
- errors.add() 方法第一个参数是字段名称,第二个参数是出错信息的正文。
- 在将价格和 0.01 比较之前,先检查它是不是 nil。试图将 nil 和数字比较会引发异常。
- 下面代码演示了如何用正则表达式验证模型属性。
validates_format_of :image_url,
:with => %r{\.(gif|jpg|png)$}i,
:message => "must be a URL for a GIF, JPG, or PNG image"
- cycle 交替的设置两个属性。
- h() 方法用于对内容进行格式化,去除其中的 HTML。
- link_to 方法后面的 :confirm =>"Are you sure?"。
- stylesheet_link_tag
- 关于渲染表格的方法。
<table>
<tr>
<% for column in Product.content_columns %>
<th><%= column.hunman_name %></th>
<% end %>
</tr>
<tr>
<% for column in Product.content_columns %>
<td><%= h product.send(column.name) %></td>
<% end %>
</tr>
</table>
第7章
- number_to_currency(product.price) 格式化价格的方法。
- 如何使用类方法。
class StoreController < ApplicationController
def inde
@products = Product.find_products_sale
end
end
class Product < ActiveRecord:Base
def self.find_products_sale
find(:all, :order => "title")
end
end
第8章
- 如何把 session 放在数据库中。
- 在 session 中保存尽可能简单的东西:字符串,数字,等等。应用层面的对象应该放在数据库,然后把它们的主键放入 session,需要时根据 session 中的主键来查找对象。
rake db:sessions:create
rake db:migrate
#environment.rb
config.action_controller.sesson_store = :active_recored_store
- 下面是一段非常常见的购物代码。
- 注意下面的 controller 中演示了如何防止构造错误的传递参数。
class StoreController < ApplicationController
before_filter :find_cart, :except => :empty_cart
def index
@products = Product.find_product_for_sale
end
def add_to_cart
begin
product = Product.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid product #{params[:id]}.")
redirect_to_index unless request.xhr?
end
end
def empty_cart
session[:cart] = nil
redirect_to_index
end
def checkout
if @cart.item.empty?
redirect_to_index("Your cart is empty.")
else
@order = Order.new
end
end
def save_order
@order = Order.new(params[:order])
@order.add_line_items_from_cart(@cart)
if @order.save
session[:cart] = nil
redirect_to_index("Thank you for your order.")
else
render :action => :checkout
end
end
private
def redirect_to_index(msg = nil)
flash[:notice] = msg if msg
redirect_to :action => :index
end
def find_cart
@cart = (session[:cart] ||= Cart.new)
end
end
class Cart
attr_reader :items
def initialize
@items = []
end
def add_product(product)
current_item = @items.find {|item| item.product == product}
if current_item
current_item.increment_quantity
else
current_item = CartItem.new(product)
@items << current_item
end
current_item
end
def total_items
@items.sum {|item| item.quantity}
end
def total_price
@items.sum {|item| item.price}
end
end
class CartItem
attr_reader :product, :quantity
def initialize(product)
@product = product
@quantity = 1
end
def increment_quantity
@quantity += 1
end
def title
@product.title
end
def price
@product.price * @quantity
end
end
- Mac 用户使用 Console.app,在 Application 的 Utilities 中,可以很方便的跟踪日志文件,只要使用 open 命令,并传入日志文件的名称即可。
十一 27
Continuations 是一种非常强大的控制流机制。一个 Continuation 代表了调用栈和词法变量的特定状态。它是 Ruby 代码执行过程中特定点的一个快照。不幸的是,在 Ruby1.8 的实现中 Continuations 是如此的慢以至于对于许多的应用来说根本无法使用。在即将到来的 Ruby1.9 虚拟机中情况可能会有所改善,但最好不要期望在使用了 Ruby1.8 的 Continuation 的情况下能够获得好的性能。尽管如此,Continuations 仍然是非常有用的概念,基于 Continuations 的 web 框架提供了 Rails 的一些有趣的选择,因此在这里我们会对它们做一下考察。
Continuations之所以非常强大有下面几个原因:
- Continuations其实就是一些对象;它们可以被传递在函数之间。
- 可以从任何位置调用Continuations。只要持有对Continuation的引用,就可以对其进行调用。
- Continuations是可重入的。可以使用Continuations从一个函数多次返回。
Continuations常常被描述为“结构化的GOTO“。因此,它们应该像任何类型的GOTO概念一样被谨慎的对待。在应用代码中不应该出现或者应该极少出现Continuations;它们通常应该被封装在库文件中。我不这么说是因为我认为开发人员应该被与它们隔离开来。
Continuations是如此普遍的一个概念,以至于通常来说在其之上建立抽象比直接使用它更合理。程序员在构建应用软件的时候应考虑使用”外部迭代(external iterator)“或者”协同程序(coroutine)“(两个都是在Continuations之上的抽象)而不是”continuation“。
SeaSide是一个基于Continuations的Smalltalk Web应用框架。Continuations 在SeaSide 中用于管理会话状态。每个用户的会话都对应一个服务器端的Continuation。当一个请求到达的时候Continuation将被调用而且更多的代码被执行。其结果是整个的事务能够被编写为单独的一段代码流,即使它们跨越了多个HTTP请求。这种能力是由于Smalltalk的Continuation能够被序列化;它们可以被写到一个数据库或者文件系统,然后被取出和反序列化,并在请求中重新调用。Ruby的Continuation不能被序列化。在Ruby中,Continuation只能被装载到内存中而不能被转换为一个字节流。
Borges(http://borges.rubyforge.org/)是直接移植到Ruby的Seaside 2实现。SeaSide与Borges的最根本的区别就是Borges必须将所有当前的Continuations保存在内存中,因为它们不能被序列化。这个巨大的限制很不幸地阻止了Borges在任何规模的Web应用中被成功使用。只要Continuation在任何一种Ruby实现中被支持,这个限制就可以被克服了。
Continuation的能力可以通过下面的Borges示例代码来见证,它用于渲染在线商店的一系列条目:
class SushiNet::StoreItemList < Borges::Component
def choose(item)
call SushiNet::StoreItemView.new(item)
end
def initialize(items)
@batcher = Borges::BatchedList.new items, 8
end
def render_content_on(r)
r.list_do @batcher.batch do |item|
r.anchor item.title do choose item end
end
r.render @batcher
end
end # class SushiNet::StoreItemList
Action的功能render_content_on方法中完成,使用了BatchedList(一个分页器)来渲染一组分页的产品链接。有趣的地方在于anchor方法的调用,它存储了choose方法,当相应的链接被点击时调用。
但是,关于Continuations在Web编程中有多大的用处仍然存在着很大争议,HTTP被设计为无状态的协议,使用Continuations来实现Web事务与无状态的想法正好相反。所有的Continuations都被存在服务器端,这需要占用额外的内存和硬盘空间。需要粘性会话(sticky session)来将一个用户始终导向同一台服务器。因此,如果一台服务器当机,它的所有会话都会丢失。最流行的基于Seaside的应用DabbleDB(http://dabbledb.com)事实上很少使用Continuations。
十一 27
if 关键字
if condition
# code here, executed if condition evaluates to true.
end
if x > 100 then puts x end
if x > 100; puts x end
if condition
# code executed if condition is true.
else
# code executed if conditions is false.
end
if condition1
# code executed if condition1 is true.
elsif condition2
# code executed if condition2 is true.
elsif condition3
# code executed if neither condition1
# nor condition2 is true, but condition3 is true.
end
print "Enter a integer: "
n = gets.to_i
if n > 0
puts "Your number is positive."
elsif n < 0
puts "Your number is negative."
else
puts "Your number is zero."
end
puts "Big numbe!" if x > 100
unless
if not (x == 1)
if !(x == 1)
unless x == 1
case 语句以一个表达式开始,然后处理列出得各种可能得匹配。每一个可能的匹配包含在一个 when 表达式中,该表达式由一个或多个可能的匹配和一段代码构成。
print "Exit the program? (yes or no): "
answer = gets.chomp
case answer
when "yes", "y"
puts "Good-bye"
exit
when "no", "n"
puts "OK, we'll continue"
else
puts "That's an unknow answer -- assuming you meant 'no'"
end
用 loop 方法实现无条件循环。loop 方法不带任何参数,但它可以带一个代码块。代码块包含想要循环执行的代码。代码块可以是大括号 {} 或者用关键字 do 和 end 。当然你也可以使用 break 来中断循环。或者使用 next 来跳到下一个循环。
n = 1
loop do
n = n + 1
if unless n == 7
break if n > 9
end
条件循环可以根据 while 和 until 实现。
n = 1
while n < 11
puts n
n = n + 1
end
puts "Done!"
n = 1
begin
puts n
n = n + 1
end while n < 11
puts "Done!"
n = 1
until n > 10
puts n
n = n + 1
end
n = 1
n = n + 1 until n == 10
puts "We've reached 10!"
基于值列表的循环
celsius = [0, 10, 20, 30, 40, 50, 60, 70]
puts "Celsius\tFahrenheit"
for c in celsius
puts "c\t#{Temperature.c2f(c)}"
end
十一 17
class, module, def 标志着切换到新的 self, main 是默认的 self 对象用来引用自己的专门术语。self 作为消息默认的接受者,如果消息接受者是 self ,可以省略接受者和圆点。
如果存在同名的方法名和变量,而且你使用裸词标识符,那么变量具有优先权。要强制 Ruby 将标识符当作方法名,你必须使用 self.talk 或者用参数列表为空的 talk() 来调用方法。
有一个场合,即使是发送消息给当前的 self,也必须使用完整的“对象 圆点 消息”记法,那就是在调用写方法的时候。因为 Ruby 总之将序列“裸词=值”解释为对局部变量的赋值。为了调用当前对象的 venue= 方法,必须显示的给出 self。
private 和 protected 的区别
私有方法意味着该方法不能使用显式的接收者来调用。Ruby 认为你想要发送消息给当前对象 self。所以仅当 self 是一个可以响应消息的对象时,该消息才有对象接收。那啥时候 self 才会是类的实例呢?当执行类的任何一个实例方法时。所以看如下代码
class Baker
def bake_cake
@batter = []
pour_flour
add_egg
return Cake.new
end
def pour_flour
@batter.push(Flour.new)
end
def add_egg
@batter.push(Egg.new)
end
private :pour_flour, :add_egg
end
总结,当 add_egg 标记为私有的,就是说Baker的实例对象可以将此消息发送给它自己,但是其它任何对象都不可以给该Baker实例对象发送该消息。Ruby 通过禁止对私有方法使用显示的接收者来获得此私有性。
保护方法的规则是:只要默认对象 self 和你想要调用的方法所属的对象是同一个类的实例,你就可以调用该保护方法。如果某个对象调用其所属的保护方法,而该对象所属的类和self所属的类相同,那么该调用是合法的。
class C
def initialize(n)
@n = n
end
def n
@n
end
def compare(c)
if c.n > n
puts "The other object's n is bigger."
else
puts "The other object's n is the same or smaller"
end
end
protected :n
end
c1 = C.new(100)
c2 = C.new(201)
c1.compare(c2)
十一 16
模块没有实例,模块被混含在类中。这样类的实例可以调用定义在模块中的实例方法。混含操作由 include 语句实现。require 或 load 时,加载的内容放在引号里。但是使用 include 时,不使用引号。
module Stacklike
attr_reader :stack
def initialize
@stack = Array.new
end
def add_to_stack(obj)
@stack.push(obj)
end
def take_from_stack
@stack.pop
end
end
require "stacklike"
class CargoHold
include Stacklike
def load_and_report(obj)
pust obj.object_id
add_to_stack(obj)
end
def unload
take_from_stack
end
end
使用名称作为类名,使用形容词作为模块的名。上面的例子演示了如何进一步发挥模块的作用。
当给对象发送它不理解的消息的时候,会触发内建方法 method_missing 调用。
class Bicycle
attr_reader :gears, :wheels, :seats
def initialize(gears = 1)
@wheels = 2
@seats = 1
@gears = gears
end
end
class Tandem < Bicycle
def initialize(gears)
super
@seats = 2
end
end
以super提升方法查找路径。以裸词的方式调用时,自动向前传递调用 super 的发给你发所获得的参数。这个是默认滴。用空参数表调用 super() 时,不给上一级方法传递任何参数,即使是当前方法的谙熟也不传递。用特定参数 super(a, b, c) 时,传递这些指定的参数。
十一 16
实例变量使得单个对象可以记忆状态。实例变量的名字以@开头。实例变量仅仅对于它所属的对象来说是可见的。在一个特定类的某一方法中初始化的实例变量,与同一个类的其他方法定义中引用的同名实例变量是同一个。
class c
def inst_var_init(value)
puts "Setting an instance variable..."
@ivar = value
end
def inst_var_report
puts "Inspection the value of the instance variable..."
puts @ivar
end
end
初始化对象状态
class c
def initialize(venue, date)
@venue = venue
@date = date
end
def venue
@venue
end
def date
@date
end
end
=号方法和语法糖衣,Ruby允许定义以等号结束的方法。另外当解释器看到一个裸词后面有等号的时候。它会自动忽略等号前面的空格。从而得到一条单独的消息,例如 price= 。
class Ticket
def price=(amount)
@price = amount
end
def price
@price
end
end
字符串内建的split方法的示例
month, day, year = date.split('/')
ActiveRecord自动生成与数据库表的字段名相对应的设置方法。通过 params 方法,ActiveRecord 收集了所有属于 customer 的值, 并将它们成批地传递到新生成的 Customer 对象中。
customer = Customer.new(params[:customer])
自动生成属性方法,attr_accessor, attr_reader, attr_writer
常量的名字是以一个大写字母开头。可以在类外引用常量,Ticker::VENUES
class Ticket
VENUES = ["Convention Center", "Fairgrounds", "Town Hall"]
end
每个类有自己的实例方法,可以继承链上面的类的实例方法。定义一个动作,就是给控制器类添加一个实例方法。
十一 14
写在前面:我忘了我je的密码了,就贴到自己博客吧。
帖子地址:http://galaxystar.javaeye.com/blog/210934
目前看官方可能不会在今年放出ruby的包了,不过从长远(非常长远啊)的角度看,这个ruby包还是有前途的,毕竟大量的网站可能会安装使用ucenter,与uc的互通是有必要的。
但是,ucenter的概念很简单,1-2个程序员完全可以用ruby来开发一套基于rails或其他框架的ucenter包来。
关键的关键,ucenter的理念对我个人影响很深,我在逐渐用这个方式来思考新的设计项目-------------松耦合,目标是:每一个部分可以单独安装。
从近来对ucenter的了解,uc包含如下几个关键功能:用户验证(注册,登陆),web应用管理,积分换算,feed,好友,站内短信,黑名单等。(从uc_client包内的client.php可以看到这些)。其中的要点是要解决好积分换算和feed,uc是为多个系统服务的,之间的积分,金币换算非常重要。feed是要告诉各个应用,当前应用的当前用户的操作是什么。
不过这里有个问题,就是uchome将我们带进了一个弯路。uchome是一个代表性的uc应用,可是在开发上,比如实名,用户资料扩展,就对uc的定义产生了影响。例如,实名在别的系统无法使用,因为它不存在uc中,后果是如果每个应用都有一个关键的用户资料扩充或feed扩充,将会无法实现(我只能hack了)。
另外:ucenter设计真的合理吗?
所以,我想在下一个Rails项目之前,了解下facebook的设计,如果我能快速理解的话。希望来的朋友多提供宝贵看法,谢谢。
再补充些观点:uchome1.5的推出,必将在江湖上产生无数的sns应用,这对开发者是个好机会,基于uch的应用必将收到欢迎。但是同时,sns概念也将从火热转为平淡,没有灵魂的sns也将死去。所以,基于sns,或者是摒弃传统sns的开发,将会给web应用带来生机。庆幸,下一个项目就是这样的。。。。。
ps:很看好web game。
十一 14
当你一次需要修改许多文件的时候,你可以选择 Edit -> Find -> Find in Project 快捷键 Shift+Apple+F 。虽然看起来略有不同,但是仍然和上一章的搜索对话框基本一样。
在输入搜索的信息之后,按一下回车键会触发搜索过程。TextMate会在整个项目中搜索匹配的文本,并把它们显示在搜索框的下面。你可以点选任意一个匹配记录,TextMate会自动跳转到该文件。
这里最好用的功能应该是替换。你可以使用 Replace All 按钮来替换全部匹配文本。或者你可以手动来一个一个的替换掉。用鼠标在下面显示搜索结果的地方点一条记录,你可以使用Shift或者Apple来选择或者删除多个匹配记录。当你选择了需要替换的记录之后,按一下 Replace Selected 按钮。就会替换掉刚才选择的那些记录。

搜索整个项目需要花很多时间。如果你有兴趣可以限制只搜索某些文件。你可以让TextMate创建一个新的临时项目。只包含你感兴趣想搜索的文件。搜索之后再销毁这个临时项目。利用 mate 命令你可以很容易的做到这些。 例如我想创建一个项目,仅包含 lib 和 test 文件夹下面的 ruby 文件,那么你需要在 Terminal 下面输入:
$ find {lib,test} -name '*.rb' -print0 | xargs -0 mate
你也可以用鼠标做一样的事。拖动一些文件或文件夹到Dock,并放于TextMate的图标上。这告诉 TextMate 创建一个新项目并且过滤掉你不希望存在的。
当项目打开的时候,我可以搜索项目然后关闭它。当关闭项目的时候搜索也停止。这可以让我只在需要的时候才搜索并且能提高不少速度。
十一 10
def all
@order = params[:order] || "number"
sort_proc = case @order
when "author" then lambda {|r| [r.user.name.downcase, r.number]}
when "status",
"title", then lambda {|r| [r.send(@order).downcase, r.number]}
when "number" then lambda {|r| -r.number}
end
@rcrs = Rcr.find(:all).sort_by &sort_proc
end
下面是一个 respond_to? 和 send 方法的例子
request = gets.chomp
if ticket.respond_to?(request)
puts ticket.send(request)
else
puts "No such information available"
end
参数的默认值
def m(a, b=1, *m)
end
m(1,2,3,4)
a = 1, b = 1, m = [3, 4]
十一 09
你可以通过Edit -> Find -> Find或者按Ctrl+F调出搜索功能。当你没有选择正则表达式功能的时候,这个对话框和其它Mac的应用程序使用方法一样。
你可以在Find字段输入一些想匹配的文本,并且在replace字段输入一些想用来替换的文本。因为Tab键盘会跳转到输入框的焦点而回车键会出发搜索动作。所以你不能输入这两个字符。当然如果你确实需要输入它们,你可以按Option+Tab键和Option+回车键来替代。当你想输入更多字符的时候,可以拖动对话框右下角的按钮。点一下淡蓝色的按钮可以开始搜索最近一个符合的条目了。按钮的名称是Next。
在你开始搜索之前可以用鼠标选择一下搜索选项或者使用快捷键,Ctrl+Apple+R, Ctrl+Apple+I, Ctrl+Apple+W。后面的章节会重点介绍正则表达式不过现在先让我们选择忽略大小写和循环搜索两个功能。
当你完成搜索定义,可以用回车键触发搜索功能。或者按Previous搜索前一个。我不太喜欢用Replace 或者 Replace
& Find 按钮。建议你用Σ按钮,它会告诉你Replace All一共发生了多少次替换。你可以用快捷键Ctrl+Apple+F触发Replace All功能。

需要注意下面这些常用的快捷键,都是你不需要打开搜索对话框就可以使用的。Apple+E 替换当前Find字段里面的文字。Apple+G,搜索下一个符合条件的单词。Option+Apple+F替换当前所选并定位到下一个符合条件的单词。Shift+Apple+E替换当前搜索字段。你可以用快捷键Shift+Ctrl+Apple+F触发Replace AlI In Selectionl功能。