重读AWDWR笔记
第9章
- 使用局部模板。
render :partial => "cart",bject => @cart render :partial => "cart_item", :collection => @cart.items
- 辅助方法的一个示例。
# app/views/layout/stroe.rhtml <%= hidden_div_if(@cart.items.empty?, :id => "cart") %> <%= render :partial => "cart",bject => @cart %> <div> # app/controllers/store_controller.rb def hidden_div_if(condition, attributes = {}) if condition attributes["style"] = "display: none;" end attrs = tag_options(attributes.stringify_keys) "
" end第10章
- validates_inclusion_of 方法验证某属性,在指定的列表中存在。防止别人构造不存在的支付方法逃避支付。
PAYMENT_TYPES = [ ["Check", "check"], ["Credit Card", "cc"], ["Purchase Order", "po"] ] validates_inclusion_of :pay_type, :in => PAYMENT_TYPES.map {|disp, value| value}
- 一个向 Order 中填充 Cart 里面订购项目的过程。
# app/controller/stroe_controller.rb def save_order @cart = find_cart @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 # app/models/order.rb def add_line_items_from_cart(cart) cart.items.each do |item| li = LineItem.from_cart_item(item) line_items << li end end # app/models/line_item.rb def self.from_cart_item(cart_item) li = self.new li.product = cart_item.product li.quantity = cart_item.quantity li.total_price = cart_item.price li end
重读AWDWR笔记
第12章
- 使用 :through 声明,可以通过间接关联来联系两张表。
- 可以使用 curl 或者 wget 工具来模拟请求xml。
class Product < ActiveRecord::Base has_manyrders, :through => :line_items end class Order < ActiveRecord::Base has_many :line_itemss end class LineItem < ActiveRecord::Base belongs_to
rders belongs_to :product end
curl http://localhost:3000/info/who_bought/1
第11章
- after_destroy 钩子方法于 delete 同在一个事务中,因此只要该方法里抛出异常,整个事务会回滚。after_destroy 会在 delete 语句执行之后被调用。
- 这里关键概念是,用异常来表示删除用户的过程中出现了错误。这里的异常同时承担两个任务。首先,在事务内部,异常会导致自动回滚;如果在删除用户之后 user 表为空,抛出异常可以撤销删除操作,恢复最后一个用户。
- 其次,异常可以把错误信息带回给控制器。
# app/model/user.rb
def after_destroy
if User.count.zero?
raise "Can't delete last user."
end
end
# app/controllers/login_controller.rb
def delete_user
if request.post?
user = User.find(params[:id])
begin
use.destroy
flash[:notice] = "User #{user.name} deleted"
rescue Exception => e
flash[:notice] = e.message
end
end
redirect_to :action => :list_users
end
- before_filter 前置过滤器进行访问控制。
- 利用 session 存储登录前的 uri 做出更有友好的登录系统。
# app/controllers/application.rb
private
def authorize
unless User.find_by_id(session[:user_id])
session[:original_uri] = request.request_uri
flash[:notice] = "Please log in."
redirect_to :action => :login, :controller => :login
end
end
# app/controllers/login_controller.rb
before_filter :authorize, :except => :login
- 登录的一个典型做法。
# app/controllers/login_controller.rb
def login
session[:user_id] = nil
if request.post?
user = User.authenticate(params[:name], params[:password])
if user
session[:user_id] = user.id
redirect_to :action => :index
else
flash[:notice] = "Invalid user/password combination"
end
end
end
- 看一下完整的 user model。
# app/models/user.rb
require "digest/sha1"
class User < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
attr_accessor :password_confirmation
validates_confirmation_of :password
def validate
errors.add_to_base("Missing password") if hashed_password.blank?
end
def self.authenticate(name, password)
user = self.find_by_name(name)
if user
expected_password = encrypted_password(password, user.salt)
if user.hashed_password != expected_password
user = nil
end
end
end
# 'password' is a virtual attribute
def password
@password
end
def password =(pwd)
@password = pwd
return if pwd.blank?
create_new_salt
self.hashed_password = User.encrypted_password(self.password, self.salt)
end
private
def self.encrypted_password(password, salt)
string_to_hash = password + "wibble" + salt
Digest::SHA1.hexdigest(string_to_hash)
end
def create_new_salt
self.salt = self.object_id.to_s + rand.to_s
end
end
带着restful_authentication运行rspec时,貌似cookies无效的问题
起因:在未做自己开发rspec的时候,只是调试下安装完 restful_authentication 的rspec和部分test时候,出现了几个错误:
1、
Spec::Mocks::MockExpectationError in ‘SessionsController Logging in by cookie fails cookie login with bad cookie’
fails cookie login with bad cookie(Spec::Rails::Example::ControllerExampleGroup::Subclass_1::Subclass_4) expected :cookies with (any args) once, but received it 0 times
./spec/controllers/authenticated_system_spec.rb:85:
2、
‘SessionsController Logging in by cookie logs in with cookie’ FAILED
expected true, got false
./spec/controllers/authenticated_system_spec.rb:81:
3、
‘AccessControlTestController requesting xml; I am logged in and Login is required succeeds’ FAILED
expected “<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n<hash>\n <success>xml</success>\n</hash>\n”, got “HTTP Basic: Access denied.\n”
./spec/controllers/access_control_spec.rb:65:
这是为什么呢?
经过N个小时的思考和google,还是没有发现问题,但是刚才检查 logged_in?.should be_true 一句时,发现我在application_helper.rb里 竟然定义了个同名的方法。就是这个同名方法,搞乱了正常的测试结果。so,改名后,一切都正常了。
经验:1、学习和使用TDD开发还是需要时间的,2、下次应该不会犯了吧。。。。
在windows和netbeans6.5上,准备Rspce开发
一、环境
ruby -v
>> ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]
rails -v
>> Rails 2.1.2
二、需要安装的gem和plugin
gem install ZenTest
因为我的Rails是2.1.2,所以在安装plugin时,用了下面的方法:
ruby script/plugin install git://github.com/dchelimsky/rspec.git ruby script/plugin install git://github.com/dchelimsky/rspec-rails.git ruby script/generate rspec
另外还有一些辅助的工具,不在此详述
三、出现的问题
1、netbeans加载autotest时候的环境变量
解决:在系统环境中,需要加入home=当前项目的根文件夹,这样nb就能正常启用autotest了
2、restful_authentication的rspec时,出现mysql的Mysql::Error: Incorrect datetime value错误
mysql配置文件my.ini中,注释掉 sql-mode=”STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION” 一行(我的在87行)
下面添加一行:sql-mode=”"
重启mysql
四:参考文章
The Basics of Creating Rails Plugins,创建Rails插件的基础教程
RSpec的很重要的中文文档:
http://www.letrails.cn/archives/20
http://www.letrails.cn/archives/advanced-rspec-tutorials-rspec-scaffold
http://www.letrails.cn/archives/11-02-advanced-rspec-tutorials-basics
http://www.letrails.cn/archives/11-07-advanced-rspec-tutorials-mocking
《The Rails Way》第18章:Rspec on Rails
这里提到的 set home=. 方法,是在cmd环境下可以,集成到netbeans中,还得用上面 三(1) 里的方法。(ps:blogspot又打不开了?)
好了,上面只是一篇准备开发Rspec on Rails 时候的笔记,更多的内容还会以笔记形式更新。
新站广告:一汽家园,一汽的生活圈 www.17jiayuan.com ,租房,在线游戏,二手交易,一汽交友圈。
目前状况:正在开发一个Rails项目,没想到和邀请我开发的人想法碰到一起,所以很高兴开发这个项目。项目的主旨是松耦合,高复用。这种程度到了应用的开发也要符合这个要求,这和研究半年多uchome得到的想法很一致。不多说了,等项目上线后,再整理心得吧。
由于个人原因耽误了些进度,在此表示歉意。
User Points,值得参考。
重读AWDWR笔记
感恩节放假,抓紧时间重读一下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,
rder => "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 命令,并传入日志文件的名称即可。
1.3.3 Continuations
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。
ruby for rails 摘录
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
ruby for rails 摘录
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)
ruby for rails 摘录
模块没有实例,模块被混含在类中。这样类的实例可以调用定义在模块中的实例方法。混含操作由 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) 时,传递这些指定的参数。
ruby for rails 摘录
实例变量使得单个对象可以记忆状态。实例变量的名字以@开头。实例变量仅仅对于它所属的对象来说是可见的。在一个特定类的某一方法中初始化的实例变量,与同一个类的其他方法定义中引用的同名实例变量是同一个。
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
每个类有自己的实例方法,可以继承链上面的类的实例方法。定义一个动作,就是给控制器类添加一个实例方法。
