<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>里克的自习室 &#187; AWDWR</title>
	<atom:link href="http://railser.cn/index.php/blog/tag/agile-web-development-with-rails/feed" rel="self" type="application/rss+xml" />
	<link>http://railser.cn</link>
	<description>关注Ruby和Rails的学习与开发</description>
	<lastBuildDate>Thu, 05 Aug 2010 03:15:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>重读AWDWR笔记</title>
		<link>http://railser.cn/index.php/blog/awdwr-4</link>
		<comments>http://railser.cn/index.php/blog/awdwr-4#comments</comments>
		<pubDate>Sat, 29 Nov 2008 17:44:20 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[AWDWR]]></category>

		<guid isPermaLink="false">http://railser.cn/?p=385</guid>
		<description><![CDATA[加载路径 标准的环境配置回自动将下列目录纳入应用程序的加载路径。 test/mocks/environment 由于该目录位于加载路径的第一位，在这里定义的类回自动覆盖真正的实现类，这样你就可以在测试阶段使用这些替代品。 app/controllers 目录及其子目录。 app/models 和 components 目录下所有以下划线或者小写字母开头的目录。 app, app/models, app/controllers, app/helps, app/services, app/apis, components, config, lib 以及 vendor 目录 检查 vendor/rails 目录，如果存在就从这里加载 Rails 库。 命名约定 变量名全部小写，单词之间以下划线分割。 类和模块的名称没有下划线，短语中每个单词的首字母大写。 表应该像变量一样，采用小写字母，并且是复数形式。 文件名全部小写，以下划线分割。 controller 用名词，action 用动词。当你创建了一个 action 的名字是 动词_名词 这样的格式，那么有必要再创建一个新的controller了 。]]></description>
			<content:encoded><![CDATA[<p><strong>加载路径</strong><br />
标准的环境配置回自动将下列目录纳入应用程序的加载路径。</p>
<ul>
<li>test/mocks/environment 由于该目录位于加载路径的第一位，在这里定义的类回自动覆盖真正的实现类，这样你就可以在测试阶段使用这些替代品。</li>
<li>app/controllers 目录及其子目录。</li>
<li>app/models 和 components 目录下所有以下划线或者小写字母开头的目录。</li>
<li>app, app/models, app/controllers, app/helps, app/services, app/apis, components, config, lib 以及 vendor 目录</li>
<li>检查 vendor/rails 目录，如果存在就从这里加载 Rails 库。</li>
</ul>
<p><strong>命名约定</strong></p>
<ul>
<li>变量名全部小写，单词之间以下划线分割。</li>
<li>类和模块的名称没有下划线，短语中每个单词的首字母大写。</li>
<li>表应该像变量一样，采用小写字母，并且是复数形式。</li>
<li>文件名全部小写，以下划线分割。</li>
<li>controller 用名词，action 用动词。当你创建了一个 action 的名字是 动词_名词 这样的格式，那么有必要再创建一个新的controller了 。</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://railser.cn/index.php/blog/awdwr-4/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>重读AWDWR笔记</title>
		<link>http://railser.cn/index.php/blog/awdwr-3</link>
		<comments>http://railser.cn/index.php/blog/awdwr-3#comments</comments>
		<pubDate>Sat, 29 Nov 2008 06:49:31 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[AWDWR]]></category>

		<guid isPermaLink="false">http://railser.cn/?p=382</guid>
		<description><![CDATA[第9章 使用局部模板。 render :partial => "cart", bject => @cart render :partial => "cart_item", :collection => @cart.items 辅助方法的一个示例。 # app/views/layout/stroe.rhtml "cart") %> "cart", bject => @cart %> &#60;div&#62; # 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"], [...]]]></description>
			<content:encoded><![CDATA[<p>第9章</p>
<ul>
<li>使用局部模板。</li>
</ul>
<pre name="code" class="ruby">
render :partial => "cart", <img src='http://railser.cn/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> bject => @cart
render :partial => "cart_item", :collection => @cart.items
</pre>
<ul>
<li>辅助方法的一个示例。</li>
</ul>
<pre name="code" class="ruby">
# app/views/layout/stroe.rhtml
<%= hidden_div_if(@cart.items.empty?, :id => "cart") %>
  <%= render :partial => "cart", <img src='http://railser.cn/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> bject => @cart %>
&lt;div&gt;

# app/controllers/store_controller.rb
def hidden_div_if(condition, attributes = {})
  if condition
    attributes["style"] = "display: none;"
  end
  attrs = tag_options(attributes.stringify_keys)
  "
<div #{attrs}>"
end
</pre>
<p>第10章</p>
<ul>
<li>validates_inclusion_of 方法验证某属性，在指定的列表中存在。防止别人构造不存在的支付方法逃避支付。</li>
</ul>
<pre name="code" class="ruby">
PAYMENT_TYPES = [
    ["Check", "check"],
    ["Credit Card", "cc"],
    ["Purchase Order", "po"]
  ]

validates_inclusion_of :pay_type, :in => PAYMENT_TYPES.map {|disp, value| value}
</pre>
<ul>
<li>一个向 Order 中填充 Cart 里面订购项目的过程。</li>
</ul>
<pre name="code" class="ruby">
# 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
</pre>
]]></content:encoded>
			<wfw:commentRss>http://railser.cn/index.php/blog/awdwr-3/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>重读AWDWR笔记</title>
		<link>http://railser.cn/index.php/blog/awdwr-2</link>
		<comments>http://railser.cn/index.php/blog/awdwr-2#comments</comments>
		<pubDate>Sat, 29 Nov 2008 02:14:39 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[AWDWR]]></category>

		<guid isPermaLink="false">http://railser.cn/?p=371</guid>
		<description><![CDATA[第12章 使用 :through 声明，可以通过间接关联来联系两张表。 可以使用 curl 或者 wget 工具来模拟请求xml。 class Product < ActiveRecord::Base has_many rders, :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? [...]]]></description>
			<content:encoded><![CDATA[<p>第12章</p>
<ul>
<li>使用 :through 声明，可以通过间接关联来联系两张表。</li>
<li>可以使用 curl 或者 wget 工具来模拟请求xml。</li>
</ul>
<pre name="code" class="ruby">
class Product < ActiveRecord::Base
  has_many <img src='http://railser.cn/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> rders, :through => :line_items
end

class Order < ActiveRecord::Base
  has_many :line_itemss
end

class LineItem < ActiveRecord::Base
  belongs_to <img src='http://railser.cn/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> rders
  belongs_to :product
end
</pre>
<pre name="code" class="ruby">
curl http://localhost:3000/info/who_bought/1
</pre>
<p>第11章</p>
<ul>
<li>after_destroy 钩子方法于 delete 同在一个事务中，因此只要该方法里抛出异常，整个事务会回滚。after_destroy 会在 delete 语句执行之后被调用。</li>
<li>这里关键概念是，用异常来表示删除用户的过程中出现了错误。这里的异常同时承担两个任务。首先，在事务内部，异常会导致自动回滚；如果在删除用户之后 user 表为空，抛出异常可以撤销删除操作，恢复最后一个用户。</li>
<li>其次，异常可以把错误信息带回给控制器。</li>
</ul>
<pre name="code" class="ruby">
# 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
</pre>
<ul>
<li>before_filter 前置过滤器进行访问控制。</li>
<li>利用 session 存储登录前的 uri 做出更有友好的登录系统。</li>
</ul>
<pre name="code" class="ruby">
# 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
</pre>
<ul>
<li>登录的一个典型做法。</li>
</ul>
<pre name="code" class="ruby">
# 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
</pre>
<ul>
<li>看一下完整的 user model。</li>
</ul>
<pre name="code" class="ruby">
# 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
</pre>
]]></content:encoded>
			<wfw:commentRss>http://railser.cn/index.php/blog/awdwr-2/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>重读AWDWR笔记</title>
		<link>http://railser.cn/index.php/blog/awdwr-1</link>
		<comments>http://railser.cn/index.php/blog/awdwr-1#comments</comments>
		<pubDate>Fri, 28 Nov 2008 06:57:05 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[tech]]></category>
		<category><![CDATA[AWDWR]]></category>

		<guid isPermaLink="false">http://railser.cn/?p=353</guid>
		<description><![CDATA[感恩节放假，抓紧时间重读一下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? &#124;&#124; price < 0.01 end errors.add() 方法第一个参数是字段名称，第二个参数是出错信息的正文。 在将价格和 0.01 比较之前，先检查它是不是 nil。试图将 nil 和数字比较会引发异常。 下面代码演示了如何用正则表达式验证模型属性。 validates_format_of :image_url, :with => %r{\.(gif&#124;jpg&#124;png)$}i, :message => "must be a URL for a GIF, JPG, or PNG image" [...]]]></description>
			<content:encoded><![CDATA[<p>感恩节放假，抓紧时间重读一下AWDWR把一些知识点记录如下。<br />
第6章</p>
<ul>
<li>迁移习惯 create 来创建表，add 给现有表增加字段。你可以会看到 002_add_price.rb 的迁移。</li>
<li>model 中验证方法设置为 protected 是因为该方法必须在特定的模型上下文中调用，不能在外部调用。</li>
</ul>
<pre name="code" class="ruby">
protected
def validate
  errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
end
</pre>
<ul>
<li>errors.add() 方法第一个参数是字段名称，第二个参数是出错信息的正文。</li>
<li>在将价格和 0.01 比较之前，先检查它是不是 nil。试图将 nil 和数字比较会引发异常。</li>
<li>下面代码演示了如何用正则表达式验证模型属性。</li>
</ul>
<pre name="code" class="ruby">
validates_format_of :image_url,
  :with => %r{\.(gif|jpg|png)$}i,
  :message => "must be a URL for a GIF, JPG, or PNG image"
</pre>
<ul>
<li>cycle 交替的设置两个属性。</li>
<li>h() 方法用于对内容进行格式化，去除其中的 HTML。</li>
<li>link_to 方法后面的 :confirm =>"Are you sure?"。</li>
<li>stylesheet_link_tag</li>
<li>关于渲染表格的方法。</li>
</ul>
<pre name="code" class="ruby">
&lt;table&gt;
	&lt;tr&gt;
	<% for column in Product.content_columns %>
		&lt;th&#038;gt<%= column.hunman_name %>&lt;/th&gt;
	<% end %>
	&lt;/tr&gt;
	&lt;tr&gt;
	<% for column in Product.content_columns %>
		&lt;td&#038;gt<%= h product.send(column.name) %>&lt;/td&gt;
	<% end %>
	&lt;/tr&gt;
&lt;/table&gt;
</pre>
<p>第7章</p>
<ul>
<li>number_to_currency(product.price) 格式化价格的方法。</li>
<li>如何使用类方法。</li>
</ul>
<pre name="code" class="ruby">
class StoreController < ApplicationController
  def inde
    @products = Product.find_products_sale
  end
end

class Product < ActiveRecord:Base
  def self.find_products_sale
    find(:all, <img src='http://railser.cn/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> rder => "title")
  end
end
</pre>
<p>第8章</p>
<ul>
<li>如何把 session 放在数据库中。</li>
<li>在 session 中保存尽可能简单的东西：字符串，数字，等等。应用层面的对象应该放在数据库，然后把它们的主键放入 session，需要时根据 session 中的主键来查找对象。</li>
</ul>
<pre name="code" class="ruby">
rake db:sessions:create
rake db:migrate
#environment.rb
config.action_controller.sesson_store = :active_recored_store
</pre>
<ul>
<li>下面是一段非常常见的购物代码。</li>
<li>注意下面的 controller 中演示了如何防止构造错误的传递参数。</li>
</ul>
<pre name="code" class="ruby">
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
</pre>
<ul>
<li>Mac 用户使用 Console.app，在 Application 的 Utilities 中，可以很方便的跟踪日志文件，只要使用 open 命令，并传入日志文件的名称即可。</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://railser.cn/index.php/blog/awdwr-1/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
