<?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>LetRails &#187; 指南</title>
	<atom:link href="http://www.letrails.cn/archives/category/%e6%8c%87%e5%8d%97/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.letrails.cn</link>
	<description>为Ruby on Rails在中文社区的枝繁叶茂贡献点滴</description>
	<lastBuildDate>Sat, 04 Sep 2010 08:24:57 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>升级到Rails 3 Beta</title>
		<link>http://www.letrails.cn/archives/upgrade-to-rails-3-beta/</link>
		<comments>http://www.letrails.cn/archives/upgrade-to-rails-3-beta/#comments</comments>
		<pubDate>Fri, 26 Feb 2010 03:54:35 +0000</pubDate>
		<dc:creator>yuanyi</dc:creator>
				<category><![CDATA[指南]]></category>
		<category><![CDATA[rails3]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/?p=184</guid>
		<description><![CDATA[安装 gem install tzinfo builder memcache-client rack rack-test rack-mount erubis mail text-format thor bundler i18n gem install rails --pre 这里会遇到一个rack-mount依赖问题，可以通过手动安装0.4.0版的rack-mount解决： sudo gem install rack-mount -v=0.4.0 升级应用 rails_upgrade(http://github.com/rails/rails_upgrade)是Rails官方的Rails3升级辅助插件，它提供了几个Rake任务帮助你轻松完成升级。 1. 执行 rails:upgrade:backup 备份会被Rails 3覆盖的文件，比如：application_controller.rb会被重命名为application_controller.rb.rails2 2. 执行 rake rails:upgrade:routes 生成Rails 3格式的routes.rb，以及rake rails:upgrade:gems生成新的Gemfile 3. 在你的程序目录执行: rails . 注意不要覆盖routes.rb和Gemfile 接下来要做的就是将备份的rails2文件合并到新生成的文件中，再次启动应用，如果幸运的话，你的应用现在已经运行在Rails 3 beta上了，注意检查Log中的Deprecated Warning，并将其替换为Rails 3的方式，关于Rails 3的新特性请参看Release Notes：http://guides.rails.info/3_0_release_notes.html ActionView Rails 3重写了View Helper，并作废了以前的link_to_function, [...]]]></description>
			<content:encoded><![CDATA[<p>安装</p>
<p class="code"><code class="ruby"><br />
gem install tzinfo builder memcache-client rack rack-test rack-mount erubis mail text-format thor bundler i18n<br />
gem install rails --pre<br />
</code></p>
<p>这里会遇到一个rack-mount依赖问题，可以通过手动安装0.4.0版的rack-mount解决：</p>
<p>sudo gem install rack-mount -v=0.4.0</p>
<p>升级应用</p>
<p><a href="http://github.com/rails/rails_upgrade">rails_upgrade(http://github.com/rails/rails_upgrade)</a>是Rails官方的Rails3升级辅助插件，它提供了几个Rake任务帮助你轻松完成升级。</p>
<p>1. 执行 rails:upgrade:backup 备份会被Rails 3覆盖的文件，比如：application_controller.rb会被重命名为application_controller.rb.rails2<br />
2. 执行 rake rails:upgrade:routes 生成Rails 3格式的routes.rb，以及rake rails:upgrade:gems生成新的Gemfile<br />
3. 在你的程序目录执行: rails .  注意不要覆盖routes.rb和Gemfile</p>
<p>接下来要做的就是将备份的rails2文件合并到新生成的文件中，再次启动应用，如果幸运的话，你的应用现在已经运行在Rails 3 beta上了，注意检查Log中的Deprecated Warning，并将其替换为Rails 3的方式，关于Rails 3的新特性请参看Release Notes：<a href="http://guides.rails.info/3_0_release_notes.html">http://guides.rails.info/3_0_release_notes.html</a></p>
<p>ActionView</p>
<p>Rails 3重写了View Helper，并作废了以前的link_to_function, link_to_remote, remote_form_for等AJAX Helper，而改为为link_to, form_for增加:remote => true来实现。</p>
<p>link_to_function(text, function) => link_to(text, &#8216;#&#8217;, function)<br />
link_to_remote(text, {:url => url}) => link_to(text, url, :remote => true)<br />
remote_form_for(object) => form_for(object, :remote => true)</p>
<p>Passenger</p>
<p>Passenger从2.2.9版本开始支持Rails 3，如果你的版本小于2.2.9，请先升级，另外需要注意的是，Rails 3会在你的应用目录下创建一个config.ru文件，如果需要以development模式运行你的应用，需要做点改变：</p>
<p>1. 在你的apache配置中加入： RackEnv development<br />
2. 删除config.ru，这样passenger会继续使用RailsEnv</p>
<p>jQuery</p>
<p>Rails 3默认是使用Prototype的，如果要使用jQuery，<a href="http://github.com/rails/jquery-ujs.git">jquery-ujs（http://github.com/rails/jquery-ujs.git）</a>项目提供了jQuery版本的rails.js，下载并替换默认的rails.js，然后在 application.html.erb的head部分增加:</p>
<p class="code"><code class="ruby"><br />
&lt;meta name="csrf-token" content="&lt;%= form_authenticity_token %&gt;" /&gt;<br />
&lt;meta name="csrf-param" content="authenticity_token" /&gt;<br />
</code></p>
<p>Paperclip</p>
<p>Paperclip目前还不支持Rails 3，不过Github上已经有了一个Rails 3的分支，我们可以直接使用这个分支：</p>
<p class="code"><code class="ruby"><br />
git submodule add -b rails3 git://github.com/thoughtbot/paperclip.git vendor/plugins/paperclip<br />
git submodule init<br />
</code></p>
<p>但是这个分支目前工作也不正常，还需要打一个补丁：</p>
<p class="code"><code class="ruby"><br />
# in lib/paperclip/attachment.rb at line 293<br />
def callback which #:nodoc:<br />
&nbsp;&nbsp;# replace this line...<br />
&nbsp;&nbsp;# instance.run_callbacks(which, @queued_for_write){|result,obj| result == false }<br />
&nbsp;&nbsp;# with this:<br />
&nbsp;&nbsp;instance.run_callbacks(which, @queued_for_write)<br />
end<br />
</code></p>
<p>此处参考：<a href="http://jameswilding.net/2010/02/07/paperclip-on-rails-3-beta/">http://jameswilding.net/2010/02/07/paperclip-on-rails-3-beta/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/upgrade-to-rails-3-beta/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Rails 2.2 国际化(i18n) 简明指南</title>
		<link>http://www.letrails.cn/archives/rails-2-2-i18n-tutorials/</link>
		<comments>http://www.letrails.cn/archives/rails-2-2-i18n-tutorials/#comments</comments>
		<pubDate>Thu, 08 Jan 2009 13:59:45 +0000</pubDate>
		<dc:creator>yuanyi</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/rails-2-2-i18n-tutorials</guid>
		<description><![CDATA[Rails 2.2出来有段时间了，前段时间需要个Wiki程序，于是试着将Instiki（Ruby on Rails官方网站的Wiki程序）移植到了2.2（http://github.com/yzhang/instiki_i18n/tree/master），同时加上了i18n支持，需要建Wiki的朋友可以试试，不过中间发现网上的指南都讲的不太全，所以我决定把看过的几个帖子整理下，合并出一个稍微完整的指南。 基本用法 基本用法想必大家都知道了，Rails默认的Locale文件夹在config/locales下，假设你要支持中文和英语，那么你需要在这个文件夹下放置en.yml和zh.yml。 # zh.yml zh: &#160;&#160;submit: '提交' &#160;create: '创建' #en.yml en: &#160;&#160;submit: 'Submit' &#160;&#160;create: 'Create' Rails 会自动加载config/locales目录下的locale文件，如果你的locale文件放在别的地方，那么你可以通过修改I18n.load_path来加载它： # in config/initializer/locale.rb I18n.load_path += Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ] 使用很简单，你可以进入Console进行测试： > I18n.t 'submit' => "Submit" > I18n.locale = 'zh' => "zh" > I18n.t('submit') => "提交" 试图中更加简单，你可以直接调用t方法： &#60;%= t 'submit' %&#62; 传递变量 有些时候，我们的字符串中可能需要包含变量，只需要将其放在两个大括号内就可以了： [...]]]></description>
			<content:encoded><![CDATA[<p>Rails 2.2出来有段时间了，前段时间需要个Wiki程序，于是试着将Instiki（Ruby on Rails官方网站的Wiki程序）移植到了2.2（<a href="http://github.com/yzhang/instiki_i18n/tree/master">http://github.com/yzhang/instiki_i18n/tree/master</a>），同时加上了i18n支持，需要建Wiki的朋友可以试试，不过中间发现网上的指南都讲的不太全，所以我决定把看过的几个帖子整理下，合并出一个稍微完整的指南。</p>
<h2>基本用法</h2>
<p>基本用法想必大家都知道了，Rails默认的Locale文件夹在config/locales下，假设你要支持中文和英语，那么你需要在这个文件夹下放置en.yml和zh.yml。</p>
<p class="code"><code class="ruby"><br /> <br />
# zh.yml<br /> <br />
zh:<br /> <br />
&nbsp;&nbsp;submit: '提交'<br /> <br />
&nbsp;create: '创建'<br /> <br />
#en.yml<br /> <br />
en:<br /> <br />
&nbsp;&nbsp;submit: 'Submit'<br /> <br />
&nbsp;&nbsp;create: 'Create'<br /> <br />
</code></p>
<p>Rails 会自动加载config/locales目录下的locale文件，如果你的locale文件放在别的地方，那么你可以通过修改I18n.load_path来加载它：</p>
<p class="code"><code class="ruby"><br /> <br />
# in config/initializer/locale.rb<br /> <br />
I18n.load_path += Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ]<br /> <br />
</code></p>
<p>使用很简单，你可以进入Console进行测试：</p>
<p class="code"><code class="ruby"><br /> <br />
> I18n.t 'submit'<br /> <br />
=> "Submit"<br /> <br />
> I18n.locale = 'zh'<br /> <br />
=> "zh"<br /> <br />
> I18n.t('submit')<br /> <br />
=> "提交"<br /> <br />
</code></p>
<p>试图中更加简单，你可以直接调用t方法：</p>
<p class="code"><code class="ruby"><br /> <br />
&lt;%= t 'submit' %&gt;</code></p>
<h2>传递变量</h2>
<p>有些时候，我们的字符串中可能需要包含变量，只需要将其放在两个大括号内就可以了：</p>
<p># zh.yml</p>
<p class="code"><code class="ruby"><br /> <br />
zh:<br /> <br />
&nbsp;&nbsp;hello: "你好, {{name}}"<br /> <br />
</code></p>
<p>打开console：</p>
<p class="code"><code class="ruby"><br /> <br />
> I18n.t 'hello', :name => 'Rails'<br /> <br />
=> "你好，Rails!"<br /> <br />
</code></p>
<h2>单复数处理</h2>
<p>实际上，中文不存在这个问题，这个问题主要存在于字母语言，解决方法是：</p>
<p class="code"><code class="ruby"><br /> <br />
# en.yml<br /> <br />
en:<br /> <br />
&nbsp;&nbsp;post:<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;one: '1 post'<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;other: '{{count}} posts'<br /> <br />
</code></p>
<p>然后在console中测试：</p>
<p class="code"><code class="ruby"><br /> <br />
> I18n.t 'post', :count => 1<br /> <br />
=> "1 post"<br /> <br />
> I18n.t 'post', :count => 5<br /> <br />
=> "5 posts"<br /> <br />
</code></p>
<h2>时间和日期</h2>
<p>时间和日期的翻译稍微复杂，需要用到<a href="http://github.com/svenfuchs/rails-i18n/tree/master">rails-i18n</a>项目下rails/locale文件夹下的zh-CN.yml文件，由于文件太大，就不贴出来了，有了这个文件，现在你就可以：</p>
<p class="code"><code class="ruby"><br />
> I18n.l Date.today, :format => 'long'<br /> <br />
=> "2009年1月08日"<br /> <br />
> I18n.l Time.now, :format => 'default'<br /> <br />
=> "2009年1月08日 星期四 20:37:58 CST"<br /> <br />
> I18n.time_ago_in_words(Time.now)<br /> <br />
=> "一分钟内"<br /> <br />
> I18n.time_ago_in_words(48.minutes.ago)<br /> <br />
=> "大约一小时"<br />
</code></p>
<h2>数字</h2>
<p>假设你已经有了上面的那个文件:</p>
<p class="code"><code class="ruby"><br /> <br />
> number_to_currency(100)<br /> <br />
=> "$100.00"<br /> <br />
> I18n.locale = 'zh'<br /> <br />
=> "zh"<br /> <br />
> number_to_currency(100)<br /> <br />
=> "CNY 100.00"<br /> <br />
</code></p>
<p>你只需要将CNY换成人民币的符号就可以了。</p>
<h2>ActiveRecord</h2>
<p>ActiveRecord也很简单，假设你有一个user model，它有两个属性login和email，那么需要在zh.yml中定义：</p>
<p class="code"><code class="ruby"><br /> <br />
zh:<br /> <br />
&nbsp;&nbsp;activerecord:<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;models:<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user: "用户"<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;attributes:<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user:<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;login:  "用户名"<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;email:  "电邮"<br /> <br />
</code></p>
<p>这样就OK了:</p>
<p class="code"><code class="ruby"><br /> <br />
> u = User.create<br /> <br />
false<br /> <br />
> u.errors.full_messages<br /> <br />
['用户名不能为空字符', '电邮不能为空字符']<br /> <br />
</code></p>
<h2>根据请求设定语言</h2>
<p>要设置应用的默认语言，可以通过</p>
<p>config.i18n.default_locale = :zh</p>
<p>如果要根据用户浏览器的设置选择语言，需要在application.rb中加一个before_filter:</p>
<p class="code"><code class="ruby"><br /> <br />
class ApplicationController<br /> <br />
&nbsp;&nbsp;before_fiter :set_language<br /> <br />
&nbsp;&nbsp;def set_language<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;request_language = request.env['HTTP_ACCEPT_LANGUAGE']<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;request_language = request_language.nil? ? nil : request_language[/[^,;]+/]<br /> <br />
&nbsp;&nbsp;&nbsp;&nbsp;I18n.locale = request_language if request_language &#038;&#038; File.exist?("#{RAILS_ROOT}/config/locales/#{request_language}.yml")<br /> <br />
&nbsp;&nbsp;end<br /> <br />
end</code></p>
<p>参考：</p>
<ul>
<li><a href="http://rails-i18n.org/wiki/pages/i18n-rails-guide">http://rails-i18n.org/wiki/pages/i18n-rails-guide</a></li>
<li><a href="http://media.railscasts.com/videos/138_i18n.mov">http://media.railscasts.com/videos/138_i18n.mov</a></li>
<li><a href="http://blog.ashchan.com/archive/2008/11/24/rails-i18n-activerecord-model-human-name-made-easy/">http://blog.ashchan.com/archive/2008/11/24/rails-i18n-activerecord-model-human-name-made-easy/</a></li>
<li><a href="http://i18n-demo.phusion.nl/">http://i18n-demo.phusion.nl/</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/rails-2-2-i18n-tutorials/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
<enclosure url="http://media.railscasts.com/videos/138_i18n.mov" length="22224368" type="video/quicktime" />
		</item>
		<item>
		<title>Rails 2.1 时区简介</title>
		<link>http://www.letrails.cn/archives/rails-2-1-timezone-tutorials/</link>
		<comments>http://www.letrails.cn/archives/rails-2-1-timezone-tutorials/#comments</comments>
		<pubDate>Tue, 03 Jun 2008 08:59:52 +0000</pubDate>
		<dc:creator>yuanyi</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/rails-2-1-timezone-tutorials</guid>
		<description><![CDATA[今年的儿童节不止属于孩子们，它也属于所有Rails的开发者，因为DHH给大家送上了最好的儿童节礼物：Rails 2.1。因为最近正在做一个需要时区支持的项目，2.1的时区功能绝对是雪中送炭，下面让我们看看如何使用2.1中的时区功能： 先升级到2.1，然后建一个新应用 $ sudo gem update rails $ rails demo210 $ cd demo210 然后建一个post model： $ script/generate model post title:string body:text published_at:datetime $ rake db:migrate 现在我本机的时间是：2008-06-03 15:55:00，然后让我们进入Console，创建一个新的Post： >> post = Post.create(:published_at => Time.now) >> post.published_at => Tue, 03 Jun 2008 07:55:00 UTC +00:00 变成了7:55，没错，因为我本地的时区设置是UTC，而我们比UTC快了8小时，现在让我们将时区修改到上海，然后再来看看published_at： >> Time.zone.name => &#8220;UTC&#8221; >> Time.zone = &#8216;Asia/Shanghai&#8217; => &#8220;Asia/Shanghai&#8221; [...]]]></description>
			<content:encoded><![CDATA[<p>今年的儿童节不止属于孩子们，它也属于所有Rails的开发者，因为DHH给大家送上了最好的儿童节礼物：<a href="http://weblog.rubyonrails.org/2008/6/1/rails-2-1-time-zones-dirty-caching-gem-dependencies-caching-etc">Rails 2.1</a>。因为最近正在做一个需要时区支持的项目，2.1的时区功能绝对是雪中送炭，下面让我们看看如何使用2.1中的时区功能：</p>
<p>先升级到2.1，然后建一个新应用</p>
<p>$ sudo gem update rails<br />
$ rails demo210<br />
$ cd demo210</p>
<p>然后建一个post model：</p>
<p>$ script/generate model post title:string body:text published_at:datetime<br />
$ rake db:migrate</p>
<p>现在我本机的时间是：2008-06-03 15:55:00，然后让我们进入Console，创建一个新的Post：</p>
<p>>> post = Post.create(:published_at => Time.now)<br />
>> post.published_at<br />
=> Tue, 03 Jun 2008 07:55:00 UTC +00:00</p>
<p>变成了7:55，没错，因为我本地的时区设置是UTC，而我们比UTC快了8小时，现在让我们将时区修改到上海，然后再来看看published_at：</p>
<p>>> Time.zone.name<br />
=> &#8220;UTC&#8221;<br />
>> Time.zone = &#8216;Asia/Shanghai&#8217;<br />
=> &#8220;Asia/Shanghai&#8221;<br />
>> post.reload<br />
>> post.published_at<br />
=> Tue, 03 Jun 2008 15:55:00 CST +08:00</p>
<p>可以看到，现在时间已经变成了东八区，这是怎么实现的呢？实际上Rails在数据库中保存的依然是UTC时间，我们可以通过column_before_type_cast来查看它:</p>
<p>>> post.published_at_before_type_cast<br />
=> &#8220;2008-06-03 07:55:00&#8243;</p>
<p>也就是说published_at实际上是根据原始的UTC时间加上当前时区的设置重新计算出来的。</p>
<p>关于Rails 2.1的其它特性，可以参看官方Blog给出的介绍链接，基本上只看代码就能明白怎么回事。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/rails-2-1-timezone-tutorials/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>RSpec Story简介</title>
		<link>http://www.letrails.cn/archives/rspec-story-tutorials/</link>
		<comments>http://www.letrails.cn/archives/rspec-story-tutorials/#comments</comments>
		<pubDate>Fri, 28 Mar 2008 15:11:34 +0000</pubDate>
		<dc:creator>yuanyi</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/rspec-story-tutorials</guid>
		<description><![CDATA[Story是RSpec的新功能，目前还处于试验阶段，在讲解RSpec Story之前，先让我们来看看什么是Story？ 什么是Story？ Story是BDD中的概念，我们可以简单的将它看作是对应用特性的描述（feature），举个例子，比如我们现在要设计一个提款机的程序，那么以下两个story是必不可少的： 用户能够取出钱来 银行职员可以放钱进去 如何定义Story？ 但是我们该如何描述一个Story呢？BDD对Story的结构进行了规范，下面给出Story的定义模板，这也是RSpec支持的格式： Title (one line describing the story) Narrative: As a [role] I want [feature] So that [benefit] Acceptance Criteria: (presented as Scenarios) Scenario 1: Title Given [context] And [some more context]… When [event] Then [outcome] And [another outcome]… Scenario 2: … 我们可以看到，一个Story由2个部分组成：定义及场景，Story的定义包含3个部分：role（角色），feature（特性）以及benefit（结果），为了对结果进行度量，Story包含了许多的Scenario（场景），每个Scenario包含上下文，时间以及输出，有些抽象？让我们来看个例子，还以上面的取款机程序为例子，那么”用户能够取出钱来”的Story就可以这样定义： Story: 允许用户取钱 作为一个用户 我想要从ATM取款 这样当银行下班之后我也可以得到现金 Scenario: [...]]]></description>
			<content:encoded><![CDATA[<p>Story是<a href="http://rspec.info/">RSpec</a>的新功能，目前还处于试验阶段，在讲解<a href="http://rspec.info/documentation/stories.html">RSpec Story</a>之前，先让我们来看看什么是Story？</p>
<p><strong>什么是Story？</strong></p>
<p>Story是<a href="http://dannorth.net/introducing-bdd">BDD</a>中的概念，我们可以简单的将它看作是对应用特性的描述（feature），举个例子，比如我们现在要设计一个提款机的程序，那么以下两个story是必不可少的：</p>
<ol>
<li>用户能够取出钱来</li>
<li>银行职员可以放钱进去</li>
</ol>
<p><strong>如何定义Story？</strong></p>
<p>但是我们该如何描述一个Story呢？BDD对Story的结构进行了规范，下面给出Story的定义模板，这也是RSpec支持的格式：</p>
<p>Title (one line describing the story)</p>
<p>Narrative:<br />
As a [role]<br />
I want [feature]<br />
So that [benefit]</p>
<p>Acceptance Criteria: (presented as Scenarios)</p>
<p>Scenario 1: Title<br />
Given [context]<br />
  And [some more context]…<br />
When  [event]<br />
Then  [outcome]<br />
  And [another outcome]…</p>
<p>Scenario 2: …</p>
<p>我们可以看到，一个Story由2个部分组成：定义及场景，Story的定义包含3个部分：role（角色），feature（特性）以及benefit（结果），为了对结果进行度量，Story包含了许多的Scenario（场景），每个Scenario包含上下文，时间以及输出，有些抽象？让我们来看个例子，还以上面的取款机程序为例子，那么”用户能够取出钱来”的Story就可以这样定义：</p>
<p>Story: 允许用户取钱</p>
<p>作为一个用户<br />
我想要从ATM取款<br />
这样当银行下班之后我也可以得到现金</p>
<p>Scenario: 账户有足够的余额<br />
Given 一张余额为 1000 块的卡<br />
 And 卡是有效的<br />
 And ATM机有足够现金<br />
When 用户请求取款 100 块<br />
Then ATM应该吐出 100 块现金<br />
 And 用户账户应该被扣除 100 块<br />
 And 卡应该被吐出</p>
<p>Scenario: 账户余额不足<br />
Given 一张余额为50块的卡<br />
 And 卡是有效的<br />
 And ATM机有足够现金<br />
When 用户请求取款100块<br />
Then 提示用户余额不足<br />
 And ATM不应吐出钞票<br />
 And 用户账户余额应该为50<br />
 And 卡应该被吐出</p>
<p><strong>应用RSpec测试Story</strong></p>
<p>那么我们该如何使用RSpec来测试这个Story呢？假设我们将上面的Story保存为一个名为atm的文件，那么现在只需要定义一个atm.rb文件，并将他们保存在一个目录下，我们就可以通过atm.rb来运行这个Story了，atm.rb文件如下：</p>
<p>require &#8216;rubygems&#8217;<br />
require &#8216;spec/story&#8217;</p>
<p>steps_for(:atm) do</p>
<p>  # Given the string PeepCode<br />
  Given(&#8220;一张余额为 $credit 块的卡&#8221;) do |credit|<br />
    @origal = @credit = credit.to_i<br />
  end</p>
<p>  Given(&#8220;卡是有效的&#8221;) do<br />
  end</p>
<p>  Given(&#8220;ATM机有足够现金&#8221;) do<br />
  end</p>
<p>  # When the string is reversed<br />
  When(&#8220;用户请求取款 $taken 块&#8221;) do |taken|<br />
    @credit = @credit &#8211; taken.to_i<br />
  end</p>
<p>  # Then the result should be edoCpeeP<br />
  Then(&#8220;用户账户应该被扣除 $taken 块&#8221;) do |taken|<br />
    @credit.should == (@origal &#8211; taken.to_i)<br />
  end</p>
<p>  Then(&#8220;ATM应该吐出 100 块现金&#8221;) do<br />
  end</p>
<p>  Then(&#8220;卡应该被吐出&#8221;) do<br />
  end<br />
end</p>
<p>with_steps_for(:atm) do<br />
  run &#8220;atm&#8221;<br />
end</p>
<p>运行结果：</p>
<p>$ ruby atm.rb<br />
Running 2 scenarios</p>
<p>Story: 允许用户取钱</p>
<p>  作为一个用户<br />
  我想要从ATM取款<br />
  这样当银行下班之后我也可以得到现金</p>
<p>  Scenario: 账户有足够的余额</p>
<p>    Given 一张余额为 1000 块的卡<br />
    And 卡是有效的<br />
    And ATM机有足够现金</p>
<p>    When 用户请求取款 100 块</p>
<p>    Then ATM应该吐出 100 块现金<br />
    And 用户账户应该被扣除 100 块<br />
    And 卡应该被吐出</p>
<p>  Scenario: 账户余额不足</p>
<p>    Given 一张余额为50块的卡 (PENDING)<br />
    And 卡是有效的<br />
    And ATM机有足够现金</p>
<p>    When 用户请求取款100块 (PENDING)</p>
<p>    Then 提示用户余额不足 (PENDING)<br />
    And ATM不应吐出钞票 (PENDING)<br />
    And 用户账户余额应该为50 (PENDING)<br />
    And 卡应该被吐出</p>
<p>2 scenarios: 1 succeeded, 0 failed, 1 pending</p>
<p>Pending Steps:<br />
1) 允许用户取钱 (账户余额不足): 一张余额为50块的卡<br />
2) 允许用户取钱 (账户余额不足): 用户请求取款100块<br />
3) 允许用户取钱 (账户余额不足): 提示用户余额不足<br />
4) 允许用户取钱 (账户余额不足): ATM不应吐出钞票<br />
5) 允许用户取钱 (账户余额不足): 用户账户余额应该为50</p>
<p>我们可以看到，第一个场景的测试获得了通过，而第二个场景则处于挂起状态，现在想必你已经对RSpec Story有了一个大概的了解，不过RSpec Story目前还处于试验阶段，因此不推荐应用到真实项目中，不过作为一种新的开发方式，保持一定的关注度还是有必要的。</p>
<p>参考：</p>
<ul>
<li><a href="http://dannorth.net/whats-in-a-story">What&#8217;s in a story?</a></li>
<li><a href="http://blog.davidchelimsky.net/articles/tag/stories">David Chelimsky（RSpec作者）的Blog</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/rspec-story-tutorials/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ActiveResource指南</title>
		<link>http://www.letrails.cn/archives/activeresource-tutorials/</link>
		<comments>http://www.letrails.cn/archives/activeresource-tutorials/#comments</comments>
		<pubDate>Wed, 09 Jan 2008 14:56:06 +0000</pubDate>
		<dc:creator>yuanyi</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/activeresource%e6%8c%87%e5%8d%97</guid>
		<description><![CDATA[尽管ActiveResource（以下简称ARes）在Rails2.0庞大的ChangeLog中只占了短短的几行，但我们不应该因此就认为这只是一个小改动，实际上，ARes可以算作是Rails1.2中引入的Resource概念的一个后续，利用Resource，你可以轻松构建基于REST风格的Web Service，而ARes则让你可以像使用ActiveRecord对象那样简单的使用这些Web Service。 理论总是过于抽象，让我们通过一个例子来看看ARes究竟是如何工作的，假设我们要开发一个类似于豆瓣那样的书评网站（我们为它申请了一个 shuping.com的域名），但为了不在一开始就让事情变得不可收拾，在第一个迭代周期内，我们只打算让用户做两件事情：提交/编辑书目，提交/编辑 /删除评论，也就是说我们只需要创建2个Resource：book和comment： map.resources :books do &#124;book&#124; &#160;&#160;book.resources :comments end 现在，我们已经具有了一组REST风格的URI，用户可以通过/books/new来创建新书目，或者通过/books/: book_id/comments/new来提交对书目的新评论，这对于使用浏览器的普通用户来说没什么问题，只要我们有一位足够优秀的UI设计师，用户应该不会在这一过程中感到任何的不快。但我们的目标并不局限于此，我们希望我们的数据能够被应用到更广泛的领域，同时，我们也希望用户能够通过更多的方式来向我们提交数据，而不仅仅只是通过我们自己的网站，因此，我们向第三方开发者开放了我们的REST API，他们可以通过： GET /books，获取所有书目 POST /books，创建新书目 GET /books/1.xml，获取编号为1的书目 PUT /books/1.xml，修改编号为1的书目 DELETE /books/1.xml，删除编号为1的书目 但是作为应用的开发者，他们应该如何来使用我们的API呢，要使用我们的API，他们需要： 创建一条到我们服务器的HTTP连接 构造一个请求报文 发送请求报文到我们的REST URI 解析得到的响应，处理可能的错误 不过幸运的是，有了ARes，他们不需要真的这么做，现在让我们将角色切换到为我们的书评网站开发应用的第三方开发者，假设我们要使用书评网的数据，那么，我们首先需要定义一个ARes类： class Book < ActiveResource::Base &#160;&#160;self.site = "http://shuping" end 接下来，我们就可以像ActiveRecord那样来使用我们的ActiveResource类了。 获取书目信息 要从书评网获取书目信息，可以使用find方法： books = Book.find(:all) 现在，我们已经得到了书评网最新的书目信息，那么ARes是如何帮助我们做到这些的呢？实际上它替我们向书评网发送了一条GET请求： GET http://shuping.com/books.xml &#60;books type="array"&#62; &#160;&#160;&#60;book&#62; &#160;&#160;&#160;&#160;&#60;title&#62;Agile Web [...]]]></description>
			<content:encoded><![CDATA[<p>尽管ActiveResource（以下简称ARes）在Rails2.0庞大的ChangeLog中只占了短短的几行，但我们不应该因此就认为这只是一个小改动，实际上，ARes可以算作是Rails1.2中引入的Resource概念的一个后续，利用Resource，你可以轻松构建基于REST风格的Web Service，而ARes则让你可以像使用ActiveRecord对象那样简单的使用这些Web Service。</p>
<p>理论总是过于抽象，让我们通过一个例子来看看ARes究竟是如何工作的，假设我们要开发一个类似于豆瓣那样的书评网站（我们为它申请了一个 shuping.com的域名），但为了不在一开始就让事情变得不可收拾，在第一个迭代周期内，我们只打算让用户做两件事情：提交/编辑书目，提交/编辑 /删除评论，也就是说我们只需要创建2个Resource：book和comment：</p>
<p class="code"><code class="ruby">map.resources :books do |book|<br />
&nbsp;&nbsp;book.resources :comments<br />
end</code></p>
<p>现在，我们已经具有了一组REST风格的URI，用户可以通过/books/new来创建新书目，或者通过/books/: book_id/comments/new来提交对书目的新评论，这对于使用浏览器的普通用户来说没什么问题，只要我们有一位足够优秀的UI设计师，用户应该不会在这一过程中感到任何的不快。但我们的目标并不局限于此，我们希望我们的数据能够被应用到更广泛的领域，同时，我们也希望用户能够通过更多的方式来向我们提交数据，而不仅仅只是通过我们自己的网站，因此，我们向第三方开发者开放了我们的REST API，他们可以通过：</p>
<ul>
<li>GET /books，获取所有书目</li>
<li>POST /books，创建新书目</li>
<li>GET /books/1.xml，获取编号为1的书目</li>
<li>PUT /books/1.xml，修改编号为1的书目</li>
<li>DELETE /books/1.xml，删除编号为1的书目</li>
</ul>
<p>但是作为应用的开发者，他们应该如何来使用我们的API呢，要使用我们的API，他们需要：</p>
<ul>
<li>创建一条到我们服务器的HTTP连接</li>
<li>构造一个请求报文</li>
<li>发送请求报文到我们的REST URI</li>
<li>解析得到的响应，处理可能的错误</li>
</ul>
<p>不过幸运的是，有了ARes，他们不需要真的这么做，现在让我们将角色切换到为我们的书评网站开发应用的第三方开发者，假设我们要使用书评网的数据，那么，我们首先需要定义一个ARes类：</p>
<p class="code"><code class="ruby">class Book < ActiveResource::Base<br />
&nbsp;&nbsp;self.site = "http://shuping"<br />
end</code></p>
<p>接下来，我们就可以像ActiveRecord那样来使用我们的ActiveResource类了。</p>
<p><strong>获取书目信息</strong></p>
<p>要从书评网获取书目信息，可以使用find方法：</p>
<p class="code"><code class="ruby">books = Book.find(:all)</code></p>
<p>现在，我们已经得到了书评网最新的书目信息，那么ARes是如何帮助我们做到这些的呢？实际上它替我们向书评网发送了一条GET请求：</p>
<p>GET http://shuping.com/books.xml</p>
<p class="code"><code class="ruby">&lt;books type="array"&gt;<br />
&nbsp;&nbsp;&lt;book&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&lt;title&gt;Agile Web Development with Rails&lt;/title&gt;<br />
&nbsp;&nbsp;&lt;/book&gt;<br />
&lt;/books&gt;</code></p>
<p>并将得到的XML报文重新组装为本地的Book对象。</p>
<p>但要注意，ARes没有提供ActiveRecord的find_by函数，不过我们可以通过向find传递:params来实现同样的功能，下面是一些复杂的find操作：</p>
<p class="code"><code class="ruby">Book.find(:first, :params => {:title => 'TDD'})<br />
# GET http://shuping/books.xml?title=TDD<br />
Book.find(:first, :from => :popular)<br />
# GET http://shuping/books/popular.xml<br />
Book.find(:first, :from => '/yzhang/books.xml')<br />
# GET http://shuping/yzhang/books.xml</code></p>
<p>同样的，我们也可以使用save方法创建一条新书目：</p>
<p class="code"><code class="ruby">book = Book.new(:title => "Programming Ruby")<br />
book.new? # true<br />
book.save # true<br />
book.id # 2</code></p>
<p>这相当于向书评网发送了一条 POST请求：</p>
<p>POST http://shuping.com/books</p>
<p>POST的内容就是我们新建的书目的XML表示，而返回的XML响应则通过Location字段指示了新资源在远端服务器上的位置：</p>
<p>Location: http://shuping.com/books/3.xml</p>
<p>ARes会根据返回的响应自动为我们更新新书目的id字段，因此，我们可以看到，在保存成功后，新书目的id变为了2，除了find和save，我们也可以更新一个已存在的书目或者删除错误添加的书目：</p>
<p class="code"><code class="ruby">book = Book.find(2)<br />
book.authors = "Dave Thomas, Andrew Hunter"<br />
book.new? # false<br />
book.save # PUT http://shuping.com/books/1.xml<br />
book.destroy # DELETE http://shuping.com/books/1.xml<br />
book.exist? # false<br />
Book.exist?(2) # false</code></p>
<p><strong>错误处理</strong></p>
<p>上面的代码，我们假设了所有的数据都是正确的，但在现实世界中，错误往往是难以避免的，因此，一个稳固的应用必须考虑到可能的错误，首先我们来看看如果我们试图保存一个没有名称的新书目会发生什么情况：</p>
<p class="code"><code class="ruby">book = Book.new<br />
book.save # false<br />
book.errors.full_messages # => ["Title can't be blank"]<br />
book.errors.on :title # => Title can't be blank</code></p>
<p>就像我们所想的，保存失败了，并且同ActiveRecord一样，我们可以通过errors来向告诉用户失败的原因，但除了Validation失败，我们还需要面对其它可能出现的错误，比如请求的资源不存在，我们将会遭遇一个404响应，因此，要正确的处理这些错误，我们必须先了解它们，ARes可能产生的错误包括：</p>
<ul>
<li>200 - 399，有效的响应，没有异常产生</li>
<li>404，请求的资源不存在，引发ActiveResource::ResourceNotFound异常</li>
<li>409，资源存在冲突，引发ActiveResource::ResourceConflict异常</li>
<li>422，无效的资源（如Validation失败），引发ActiveResource::ResourceInvalid异常</li>
<li>401 - 499，其它客户端错误，引发ActiveResource::ClientError异常</li>
<li>500 - 599，其它服务端错误，引发ActiveResource::ServerError异常</li>
</ul>
<p>通常情况下，我们只需要考虑404，409，422错误即可，因此安全的代码看起来应该是下面这个样子：</p>
<p class="code"><code class="ruby">begin<br />
&nbsp;&nbsp;book = Book.find(1)<br />
rescue ActiveResource::ResourceNotFound<br />
&nbsp;&nbsp;redirect_to :action => 'not_found'<br />
rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid<br />
&nbsp;&nbsp;redirect_to :action => 'new'<br />
end</code></p>
<p><strong>资源嵌套</strong></p>
<p>大家一定已经注意到了，我们的书评网定义了两个资源，但是前面的例子都只演示了如何操作Book资源，而没有涉及嵌套在Book中的comment资源，实际上，这相当的简单，我们只需这样定义comment.rb：</p>
<p class="code"><code class="ruby">class Comment < ActiveResource::Base<br />
&nbsp;&nbsp;self.site = "http://shuping/books/:book_id"<br />
end</code></p>
<p>大家应该注意到，在Site URI中包含了一个:book_id的symbol，ARes会负责将它替换为真实的user_id，当然我们需要将它传递给ARes：</p>
<p class="code"><code class="ruby">comment = Comment.new(:body => 'test', :book_id => 1)<br />
comment.save #true, POST http://shuping/books/1/comments<br />
comment.id # 1<br />
# GET http://shuping/books/1/comments/1<br />
comment = Comment.find(1, :params => {:book_id => 1})<br />
comment.body # test</code></p>
<p><strong>自定义方法</strong></p>
<p>现在，让我们再次回到我们的书评网站，假设我们的网站大受欢迎（虽然这不太可能），那么，相信我们很快就需要面临这样一个问题：垃圾评论。为了应对垃圾评论，我们决定采用最为稳妥的方式，人工审核，因此我们需要为comment controller扩展一个moderate方法：</p>
<p class="code"><code class="ruby">book.resources :comments :member => {:moderate => :put}</code></p>
<p>现在，我们可以通过PUT /books/1/comments/1/moderate来人工批准一条评论，但问题随之也就来了，毕竟我们自己的人手有限，因此我们计划让我们的联盟网站来和我们一起审核评论，但是它们应该如何通过ARes来调用moderate方法呢，其实很简单：</p>
<p class="code"><code class="ruby">comment = Comment.find(1, :params => {:book_id => 1})<br />
comment.put(:moderate) # PUT http://shuping/books/1/comments/1/moderate<br />
# true</code></p>
<p>最后，我们的书评网站必然是需要认证的，ARes提供了简单的方式来支持HTTP认证：</p>
<p class="code"><code class="ruby">class Book < ActiveResource::Base<br />
&nbsp;&nbsp;self.site = "http://yzhang:password@shuping/"<br />
end</code></p>
<p>参考：<a href="http://ryandaigle.com/articles/2007/3/14/rest-activeresource">REST &#038; ActiveResource</a> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/activeresource-tutorials/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>REST on Rails之自定义路由</title>
		<link>http://www.letrails.cn/archives/64/</link>
		<comments>http://www.letrails.cn/archives/64/#comments</comments>
		<pubDate>Wed, 26 Sep 2007 13:46:33 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/64</guid>
		<description><![CDATA[要扩展你的REST路由，可以通过设置map.resource的:collection，:member或者:new选项来实现： map.resources :projects, :collection =&#62; {:rss =&#62; 'get'} map.resources :projects, :member =&#62; {:close =&#62; 'post'} map.resources :projects, :new =&#62; {:validate =&#62; 'post'} :collection 扩展针对资源集合（collection）的路由，它指向一个形如 #{action} =&#62; #{method}的Hash，其中method可以是get/post/put/delete，而action就是你要扩展的方法，产生的路由为 /projects/rss（Edge Rails，Rails1.2.3为/projects;rss），因此GET /projects/rss将被影射到ProjectsController#rss，同时还有一个URL Helper：rss_projects_path。 :member同:collection类似，不过它扩展针对单个资源的路由，因此它产生的路由为/projects/1/close（Edge Rails，Rails1.2.3为/projects/1;close），它也有一个URL Helper：close_project_path。 :new也差不多，不过它针对的是那些已创建但尚未被保存的资源。 参考： Rails API文档 RESTful Rails Development PDF]]></description>
			<content:encoded><![CDATA[<p>要扩展你的REST路由，可以通过设置map.resource的:collection，:member或者:new选项来实现：</p>
<p class="code"><code class="ruby">map.resources :projects, :collection =&gt; {:rss =&gt; 'get'}<br />
map.resources :projects, :member =&gt; {:close =&gt; 'post'}<br />
map.resources :projects, :new =&gt; {:validate =&gt; 'post'}</code></p>
<p>:collection 扩展针对资源集合（collection）的路由，它指向一个形如 #{action} =&gt; #{method}的Hash，其中method可以是get/post/put/delete，而action就是你要扩展的方法，产生的路由为 /projects/rss（Edge Rails，Rails1.2.3为/projects;rss），因此GET /projects/rss将被影射到ProjectsController#rss，同时还有一个URL Helper：rss_projects_path。</p>
<p>:member同:collection类似，不过它扩展针对单个资源的路由，因此它产生的路由为/projects/1/close（Edge Rails，Rails1.2.3为/projects/1;close），它也有一个URL Helper：close_project_path。</p>
<p>:new也差不多，不过它针对的是那些已创建但尚未被保存的资源。</p>
<p>参考：</p>
<ul>
<li><a href="http://docs.google.com/map.resources%20:messages,%20:path_prefix%20=%3E%20%22/thread/:thread_id%22" title="Rails API文档" id="icpe">Rails API文档</a></li>
<li><a href="http://www.b-simple.de/documents" title="RESTful Rails Development PDF" id="ki-l">RESTful Rails Development PDF</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/64/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>REST on Rails之资源嵌套</title>
		<link>http://www.letrails.cn/archives/63/</link>
		<comments>http://www.letrails.cn/archives/63/#comments</comments>
		<pubDate>Mon, 17 Sep 2007 06:17:06 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/63</guid>
		<description><![CDATA[REST认为一切都是资源（Resource），但并不是所有资源都是平行对等的，资源之间也有从属关系，要建立这种资源的层次关系，就必须将资源进行嵌套（nested resource）。 以Blog系统为例，假设每个用户拥有一个Profile，以及多个Blog，那么我们可以这样建立资源的嵌套关系，修改route.rb: &#160; map.resources :users do &#124;user&#124; &#160;&#160;user.resource :profile &#160;&#160;user.resources :blogs end 这样我们将获得如下的路由映射： # profile GET /users/1/profile = ProfilesController#show GET /users/1/profile/new = ProfilesController#new GET /users/1/profile/edit = ProfilesController#edit POST /users/1/profile = ProfilesController#create PUT /users/1/profile = ProfilesController#update DELETE /users/1/profile = ProfilesController#destroy # Blogs GET /users/1/blogs = BlogsController#index GET /users/1/blogs/1 = BlogsController#show GET /users/1/blogs/1/edit = BlogsController#edit GET [...]]]></description>
			<content:encoded><![CDATA[<p>REST认为一切都是资源（Resource），但并不是所有资源都是平行对等的，资源之间也有从属关系，要建立这种资源的层次关系，就必须将资源进行嵌套（nested resource）。</p>
<p>以Blog系统为例，假设每个用户拥有一个Profile，以及多个Blog，那么我们可以这样建立资源的嵌套关系，修改route.rb:</p>
<p class="code">&nbsp;</p>
<p class="code"><code class="ruby>map.resources :users do |user|<br />
&nbsp;&nbsp;user.resource :profile<br />
&nbsp;&nbsp;user.resources :blogs<br />
end</pre>
</p>
<p>这样我们将获得如下的路由映射：</p>
<p class="code"><code># profile<br />
GET /users/1/profile = ProfilesController#show<br />
GET /users/1/profile/new = ProfilesController#new<br />
GET /users/1/profile/edit = ProfilesController#edit<br />
POST /users/1/profile = ProfilesController#create<br />
PUT /users/1/profile = ProfilesController#update<br />
DELETE /users/1/profile = ProfilesController#destroy<br />
# Blogs<br />
GET /users/1/blogs = BlogsController#index<br />
GET /users/1/blogs/1 = BlogsController#show<br />
GET /users/1/blogs/1/edit = BlogsController#edit<br />
GET /users/1/blogs/new = BlogsController#new<br />
POST /users/1/blogs = BlogsController#create<br />
PUT /users/1/blogs/1 = BlogsController#update<br />
DELETE /users/1/blogs/1 = BlogsController#destroy</code></p>
<p>同时URL的Helper也需要进行相应的更改：</p>
<p class="code"><code># profile<br />
user_profile_path(1)             /users/1/profile<br />
new_user_profile_path(1)         /users/1/profile/new<br />
edit_user_profile_path(1)       /users/1/profile/edit<br />
# blogs<br />
user_blogs_path(1)           /users/1/blogs<br />
user_blog_path(1, 1)  /users/1/blogs/1<br />
edit_user_blog_path(1, 1)  /users/1/blogs/1/edit<br />
new_user_blog_path(1) /users/1/blogs/new</code></p>
<p>另外如果你使用Edge Rails，那么针对user_blogs_path，你还可以简单的使用redirect_to:</p>
<p class="code"><code class="ruby">redirect_to ([user, blog]) =  /users/1/blogs/1</code></p>
<p>除了以上这些，new.html.erb和edit.html.erb中的form_for也必须做相应的修改：</p>
<p class="code"><code class="ruby">#edit.html.erb<br />
&lt;%= form_for(:profile, {:html=&gt;{:method =&gt;:put}, :url=&gt;user_profile_path(current_user)}) {|f| %&gt;</code></p>
<p>这将生成：</p>
<p class="code"><code class="ruby">&lt;form action="/users/4/profile" method="post"&gt;&lt;div style="margin:0;padding:0"&gt;&lt;input name="_method" type="hidden" value="put" /&gt;&lt;/div&gt;</code></p>
<p>注意这里的method必须是put，否则就会提交给ProfilesController#create，new.html.erb比较简单：</p>
<p class="code"><code class="ruby">#new.html.erb<br />
&lt;% form_for(:profile, :url =&gt; user_profile_path(current_user)) do |f| %&gt;</code></p>
<p>Blogs的new.html.erb和edit.html.erb可以依此类推，基本就这些了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/63/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>swfchart: 我的第一个插件</title>
		<link>http://www.letrails.cn/archives/60/</link>
		<comments>http://www.letrails.cn/archives/60/#comments</comments>
		<pubDate>Thu, 06 Sep 2007 01:54:24 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[指南]]></category>
		<category><![CDATA[插件]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/60</guid>
		<description><![CDATA[简介 Swfchart能够让你很轻松的将maani.us的SWF/XML Chart绘图工具集成到你的Rails应用中。 安装 首先你需要去maani.us下载SWF/XML Chart,目前经过测试的版本是4.6版，然后将其解压缩到你的public目录下： public &#124;-- charts.swf `-- charts_library &#160;&#160;&#160;&#160;&#124;-- arno.swf &#160;&#160;&#160;&#160;&#124;-- arst.swf &#160;&#160;&#160;&#160;&#124;........... &#160;&#160;&#160;&#160;`-- scno.swf 然后安装swfchart generator： $ cd $RAILS_ROOT/vendor/plugins $ svn co https://svn.elctech.com/svn/public/plugins/swfchart swfchart 使用 1. 生成文件 $ ./script/generate swfchart create lib/swfchart_helper.rb create app/controllers/swfcharts_controller.rb create app/models/swfchart.rb create test/unit/swfchart_test.rb create test/functional/swfcharts_controller_test.rb 2. 添加路由，你需要在route.rb中为swfchart添加一条路由： map.swfcharts 'swfcharts/:name', :controller =&#62; 'swfcharts', :action =&#62; 'show' 3. [...]]]></description>
			<content:encoded><![CDATA[<p><strong>简介</strong></p>
<p>Swfchart能够让你很轻松的将<a href="http://www.maani.us/xml_charts/">maani.us的SWF/XML Chart绘图工具</a>集成到你的Rails应用中。</p>
<p><strong>安装</strong></p>
<p>首先你需要去<a href="http://www.maani.us/xml_charts/index.php?menu=Download">maani.us下载SWF/XML Chart</a>,目前经过测试的版本是4.6版，然后将其解压缩到你的public目录下：</p>
<p class="code"><code>public<br />
|-- charts.swf<br />
`-- charts_library<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- arno.swf<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- arst.swf<br />
&nbsp;&nbsp;&nbsp;&nbsp;|...........<br />
&nbsp;&nbsp;&nbsp;&nbsp;`-- scno.swf</code></p>
<p>然后安装swfchart generator：</p>
<p class="code"><code>$ cd $RAILS_ROOT/vendor/plugins<br />
$ svn co https://svn.elctech.com/svn/public/plugins/swfchart swfchart</code></p>
<p><strong>使用</strong></p>
<p>1. 生成文件</p>
<p class="code"><code>$ ./script/generate swfchart<br />
create  lib/swfchart_helper.rb<br />
create  app/controllers/swfcharts_controller.rb<br />
create  app/models/swfchart.rb<br />
create  test/unit/swfchart_test.rb<br />
create  test/functional/swfcharts_controller_test.rb</code></p>
<p>2. 添加路由，你需要在route.rb中为swfchart添加一条路由：</p>
<p class="code"><code class="ruby">map.swfcharts 'swfcharts/:name', :controller =&gt; 'swfcharts', :action =&gt; 'show'</code></p>
<p>3. 修改environment.rb，在启动时加载swfchart_helper.rb:</p>
<p class="code"><code class="ruby">require 'swfchart_helper.rb'</code></p>
<p>4. 在需要绘制图表的view中调用swf_chart helper：</p>
<p class="code"><code class="ruby">&lt;%= swf_chart {|c|<br />
&nbsp;&nbsp;c.data_source = swfcharts_url(:name =&gt; "sample_line")<br />
&nbsp;&nbsp;c.width = "400"<br />
&nbsp;&nbsp;c.height = "320"<br />
} %&gt;</code></p>
<p>这将会在你的view中绘制一个400×320的图标控件，下面对图表的各个属性进行说明：</p>
<ul>
<li>data_source，图表的数据来源，根据第二步添加的路由，上面的例子生成的URL为/swfcharts/sample_line，这是swfchart内置的数据源，它将会渲染一个线型图。</li>
<li>width，图表控件的宽度</li>
<li>height，图表控件的高度</li>
<li>bg_color，图表的背景颜色，比如&#8221;000000&#8243;代表黑色</li>
<li>transparent，是否透明，true表示透明</li>
<li>license，SWF/XML Chart免费版只提供有限的功能，因此，如果你需要使用增强功能，最好购买一个License</li>
</ul>
<p>5. 如果你想要构造自己的数据源，可以参考sample_line，它是swfchart model的一个class method：</p>
<p class="code"><code class="ruby">def self.sample_line<br />
&nbsp;&nbsp;swf = Swfchart.new('sample swf', 'line')<br />
&nbsp;&nbsp;swf.data_array = []<br />
&nbsp;&nbsp;swf.data_array[0] = ['a',        1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14]<br />
&nbsp;&nbsp;swf.data_array[2] = ['Region B', 1,  1,  2,  3,  4,  6,  8,  8,  5,  7,  9,  4,  8,  4 ]<br />
&nbsp;&nbsp;swf.data_array[1] = ['Region A', 53, 52, 52, 52, 50, 44, 43, 34, 32, 28, 25, 20, 15, 10]<br />
&nbsp;&nbsp;swf</p>
<p>end</code></p>
<p>Swfchart.new的第一个参数指明图表的名称，第二个参数是图表的类型，这里我们创建一个线性图，事实上还有第三个参数，它指名横轴坐标的显示间隔，默认为2。</p>
<p>data_array存储真正的数据源，第一列存储横轴的坐标，显示间隔为2意味着，横轴只会显示1,4,7,10,13这几个数值，后面各列是真正的数据，每列数据的第一个元素为这个数据的名称。</p>
<p>6. 就这么简单，当然如果你需要更复杂的控制，swfchart还提供以下属性可供修改：</p>
<ul>
<li>axis_ticks</li>
<li>axis_value</li>
<li>chart_border</li>
<li>chart_grid_h</li>
<li>chart_grid_v</li>
<li>chart_pref</li>
<li>chart_rect</li>
<li>chart_transition</li>
<li>chart_value_text</li>
<li>chart_value</li>
<li>draw</li>
<li>legend_label</li>
<li>legend_rect</li>
<li>legend_transition</li>
<li>link</li>
<li>link_data</li>
<li>live_update</li>
<li>series_color</li>
<li>series_explode</li>
<li>series_gap</li>
<li>series_switch</li>
</ul>
<p>关于这些属性的含义及用法请<a href="http://www.maani.us/xml_charts/index.php?menu=Reference">参看maani.us的相关文档</a>，如果需要修改这些属性，则只需要将XML attributes转换为Ruby hash即可，比如：</p>
<p class="code"><code class="ruby">swf.chart_rect = {'x'=&gt;4, 'y'=&gt;4, 'width'=&gt;72, 'height'=&gt;52, 'positive_color'=&gt;"ffffff", 'negative_color'=&gt;"ffffff", 'positive_alpha'=&gt;100, 'negative_alpha'=&gt;100}</code></p>
<p>将覆盖原有的chart_rect属性。</p>
<p>7. 最后给出上面的line chart截图，有一些难看：</p>
<p><a href="http://lilac.greatweb.cn/rails/wp-content/uploads/2007/09/chart.png" title="chart.png"><img src="http://lilac.greatweb.cn/rails/wp-content/uploads/2007/09/chart.png" alt="chart.png" border="0"/></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/60/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>创建你自己的Generator</title>
		<link>http://www.letrails.cn/archives/58/</link>
		<comments>http://www.letrails.cn/archives/58/#comments</comments>
		<pubDate>Wed, 05 Sep 2007 12:25:29 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/58</guid>
		<description><![CDATA[今天写了个简单的generator，发现比想像的要简单许多，参照的这篇贴，顺便翻译下。 以basic_auth为例，首先创建如下目录结构： basic_auth &#124;-- USAGE &#124;-- basic_auth_generator.rb `-- templates &#160;&#160;&#160;&#160;&#124;-- INSTALL &#160;&#160;&#160;&#160;&#124;-- controllers &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- user_controller.rb &#160;&#160;&#160;&#160;&#124;-- lib &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- basic_auth.rb &#160;&#160;&#160;&#160;&#124;-- migrate &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- create_users.rb &#160;&#160;&#160;&#160;&#124;-- models &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#124;-- notifications.rb &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- user.rb &#160;&#160;&#160;&#160;&#124;-- test &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#124;-- fixtures &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- users.yml &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#124;-- functional &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- user_controller_test.rb &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- unit &#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;`-- user_test.rb &#160;&#160;&#160;&#160;`-- views &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#124;-- notifications &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#124;&#160;&#160;&#160;&#160;`-- forgot_password.rhtml &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;`-- user &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#124;-- change_password.rhtml &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#124;-- forgot_password.rhtml [...]]]></description>
			<content:encoded><![CDATA[<p>今天写了个简单的generator，发现比想像的要简单许多，参照的<a href="http://www.aidanf.net/node/33" title="这篇贴" id="rgbd">这篇贴</a>，顺便翻译下。</p>
<p>以basic_auth为例，首先创建如下目录结构：</p>
<p class="code"><code>basic_auth<br />
|-- USAGE<br />
|-- basic_auth_generator.rb<br />
`-- templates<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- INSTALL<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- controllers<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- user_controller.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- lib<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- basic_auth.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- migrate<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- create_users.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- models<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|-- notifications.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- user.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|-- test<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|-- fixtures<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- users.yml<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|-- functional<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- user_controller_test.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- unit<br />
&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`-- user_test.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;`-- views<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- notifications<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;`-- forgot_password.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`-- user<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- change_password.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- forgot_password.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- hidden.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- login.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|-- signup.rhtml<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`-- welcome.rhtml</code></p>
<p>可以看到generator主要由两部分组成：</p>
<ul>
<li>一个Templates目录，保存generator的模板文件</li>
<li>一个以generator结尾的rb文件，它负责拷贝模板文件到用户的Rails应用中，并在必要时对模板进行修改</li>
</ul>
<p>要能够通过generate/destroy脚本调用到你的generator，basic_auth_generator.rb必须对Rails::Generator::Base进行扩展，并实现manifest方法：</p>
<p class="code"><code class="ruby">class BasicAuthGenerator &lt; Rails::Generator::Base<br />
&nbsp;&nbsp;def manifest<br />
&nbsp;&nbsp;&nbsp;&nbsp;record do |m|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Controller<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "lib/basic_auth.rb", "lib/basic_auth.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "controllers/user_controller.rb",  “app/controllers/user_controller.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Models<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "models/user.rb", "app/models/user.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "models/notifications.rb", "app/models/notifications.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Tests<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "test/unit/user_test.rb", "test/unit/user_test.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "test/functional/user_controller_test.rb", "test/functional/user_controller_test.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "test/fixtures/users.yml", "test/fixtures/users.yml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Views.<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.directory "app/views/notifications"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.directory "app/views/user"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/login.rhtml", "app/views/user/login.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/signup.rhtml", "app/views/user/signup.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/change_password.rhtml", "app/views/user/change_password.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/forgot_password.rhtml", "app/views/user/forgot_password.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/hidden.rhtml", "app/views/user/hidden.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/user/hidden.rhtml", "app/views/user/welcome.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.file "views/notifications/forgot_password.rhtml", "app/views/notifications/forgot_password.rhtml"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.migration_template "migrate/create_users.rb", "db/migrate"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m.readme "INSTALL"<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;end<br />
end</code></p>
<p>这段代码很直观，基本不需要解释，不过如果你的generator除了增加文件，还需要用户手动修改文件，那么你可能想在generate脚本的最后给出用户一些提示信息，下面是Restful Auth的做法：</p>
<p class="code"><code class="ruby">def manifest<br />
&nbsp;&nbsp;recorded_session = record do |m|<br />
&nbsp;&nbsp;&nbsp;&nbsp;......<br />
&nbsp;&nbsp;end<br />
&nbsp;&nbsp;action = nil<br />
&nbsp;&nbsp;action = $0.split("/")[1]<br />
&nbsp;&nbsp;case action<br />
&nbsp;&nbsp;&nbsp;&nbsp;when "generate"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts ("-" * 70)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "Don't forget to:"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "  - add restful routes in config/routes.rb"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "    map.resources :#{model_controller_file_name}"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "    map.resource  :#{controller_singular_name.singularize}"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts ("-" * 70)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;when "destroy"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts ("-" * 70)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "Thanks for using restful_authentication"<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts ("-" * 70)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;&nbsp;&nbsp;else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts<br />
&nbsp;&nbsp;end<br />
&nbsp;&nbsp;recorded_session<br />
end</code></p>
<p>这将在用户调用generate/destroy脚本是分别给出一段提示。</p>
<p>如果你的generator需要根据用户参数动态生成代码及文件名称，那么你需要使用Rails::Generator::NamedBase来代替 Rails::Generator::Base，这会强制用户输入一个名称参数，你可以通过class_name（返回&#8221;Account&#8221;)和file_name(返回&#8221;account&#8221;)来得到用户希望创建的类名和文件名，并使用m.template来拷贝文件，m.template会在拷贝之前先使用erb来对文件进行解析，因此现在你的模板文件可能看起来是这个样子：</p>
<p class="code"><code class="ruby">class &lt;%= class_name %&gt;Controller &lt; ApplicationController</code></p>
<p>拷贝语句则是这个样子：</p>
<p class="code"><code class="ruby">m.template "controllers/user_controller.rb", "app/controllers/#{file_name}_controller.rb"</code></p>
<p>如果以上这些还不能满足你的需求，那么建议你去现有的generator代码中学习，对于Rails来说，代码就是最好的文档。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/58/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用ruby-prof获取rails应用的profile</title>
		<link>http://www.letrails.cn/archives/36/</link>
		<comments>http://www.letrails.cn/archives/36/#comments</comments>
		<pubDate>Sat, 14 Jul 2007 14:36:14 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[性能]]></category>
		<category><![CDATA[指南]]></category>

		<guid isPermaLink="false">http://www.letrails.cn/archives/36</guid>
		<description><![CDATA[ruby-prof最新的0.5版本开始支持Rails，但是文档并没有相应更新，这是来在Charlie Savage的一篇关于如何在Rails中使用ruby-prof的指南，希望对你有所帮助。 假设你发现你的Rails应用对某个请求的处理花费了太多的时间，并且你已经排除了网络，Web Server，客户端浏览器或者是数据库的嫌疑，那么ruby-prof可以帮你轻松定位出到底是那段代码消耗了太多的时间： gem install ruby-prof windows平台用户应该选择针对win32平台的预编译版本，安装完之后，你需要从ruby-prof的安装目录下拷贝plugin到你的Rails应用的vendor/plugins目录: cd /your/ruby/gems/path/ruby-prof-0.5.0/lib/ cp ruby-prof/rails_plugin/ruby_prof rails_app/vendor/plugins 然后在你的environment.rb中增加： config.cache_classes = true 如果不设置这一项的话，那么Rails会在处理每个请求时重新将所有的model和controller都加载一遍，这会消耗大量的时间，从而使你的action的花费看起来微不足道。 完成以上操作以后，如果现在你通过浏览器向Rails发送一个请求，那么在请求处理完成后，这个请求的profile已经被写入到了Log里面，它看起来是这个样子： Completed in 0.71900 (1 reqs/sec) Rendering: 0.37600 (52%) &#124; DB: 0.18700 (26%) 200 OK [http://localhost/] Thread ID: 175635930 Total: 0.719 %self total self wait child calls name 26.15 0.19 0.19 0.00 0.00 23 PGconn#exec 26.01 0.19 [...]]]></description>
			<content:encoded><![CDATA[<p>ruby-prof最新的0.5版本开始支持Rails，但是文档并没有相应更新，这是来在<a href="http://cfis.savagexi.com/">Charlie Savage</a>的一篇关于<a href="http://cfis.savagexi.com/articles/2007/07/10/how-to-profile-your-rails-application">如何在Rails中使用ruby-prof的指南</a>，希望对你有所帮助。</p>
<p>假设你发现你的Rails应用对某个请求的处理花费了太多的时间，并且你已经排除了网络，Web Server，客户端浏览器或者是数据库的嫌疑，那么ruby-prof可以帮你轻松定位出到底是那段代码消耗了太多的时间：</p>
<p class="code"><code>gem install ruby-prof</code></p>
<p>windows平台用户应该选择针对win32平台的预编译版本，安装完之后，你需要从ruby-prof的安装目录下拷贝plugin到你的Rails应用的vendor/plugins目录:</p>
<p class="code"><code>cd /your/ruby/gems/path/ruby-prof-0.5.0/lib/<br />
cp ruby-prof/rails_plugin/ruby_prof rails_app/vendor/plugins</code></p>
<p>然后在你的environment.rb中增加：</p>
<p class="code"><code>config.cache_classes = true</code></p>
<p>如果不设置这一项的话，那么Rails会在处理每个请求时重新将所有的model和controller都加载一遍，这会消耗大量的时间，从而使你的action的花费看起来微不足道。</p>
<p>完成以上操作以后，如果现在你通过浏览器向Rails发送一个请求，那么在请求处理完成后，这个请求的profile已经被写入到了Log里面，它看起来是这个样子：</p>
<p class="code"><code>Completed in 0.71900 (1 reqs/sec)<br />
Rendering: 0.37600 (52%) | DB: 0.18700 (26%)<br />
200 OK [http://localhost/]<br />
Thread ID: 175635930<br />
Total: 0.719<br />
%self     total     self     wait    child    calls  name<br />
26.15      0.19     0.19     0.00     0.00       23  PGconn#exec<br />
26.01      0.19     0.19     0.00     0.00       27  PGresult#each<br />
4.45      0.03     0.03     0.00     0.00     1403  String#match<br />
4.45      0.03     0.03     0.00     0.00      171  IO#write<br />
2.23      0.02     0.02     0.00     0.00      872  String#to_s<br />
2.23      0.02     0.02     0.00     0.00      467  String#gsub<br />
2.23      0.03     0.02     0.00     0.02       76  ManyParser#_parse<br />
2.23      0.05     0.02     0.00     0.03       60  MonitorMixin#synchronize<br />
2.23      0.02     0.02     0.00     0.00       60  Parsers#sequence<br />
2.23      0.02     0.02     0.00     0.00     1640  ParseContext#eof<br />
2.23      0.02     0.02     0.00     0.00       31  ActionView::Partials#add_object_to_local_assigns!<br />
2.23      0.02     0.02     0.00     0.00       11  ActionView::Helpers::UrlHelper#link_to<br />
2.23      0.02     0.02     0.00     0.00      243  Object#add_error<br />
2.23      0.02     0.02     0.00     0.00      175  Kernel#clone</code></p>
<p>第一行显示了Rails处理这个请求的总花费，接下来就是ruby-prof的plat格式的输出，按照花费总时间由高到低排列，这里只列出了花费大于2%的部分。</p>
<p>从上面的结果可以看出，花费时间最多的是PGconn#exec，这是个连接数据库和执行查询的C扩展，在Rails这段并没有多少优化余地，但是在数据 库段还是存在优化空间的，接下来就是PGresult#each，这是处理数据库查询结果的，它的花费明显太多了，但是它后面的String#match 被执行了1403次，这也不太正常。</p>
<p>如果你觉得总是去Rails log中查看profile结果实在是不方便，那么可以通过Hack ruby-prof来获取图形化的输出，修改vendor/ruby-prof/lib/profiling.rb：</p>
<p class="code"><code class="ruby"> module ActionController #:nodoc:<br />
&nbsp;&nbsp; module Profiling #:nodoc:<br />
&nbsp;&nbsp;&nbsp;&nbsp; def perform_action_with_profiling<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if RubyProf.running? or<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # Example for Graph html printer<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printer = RubyProf::GraphHtmlPrinter.new(result)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File.open('request.html', 'w') do |file|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printer.print(file, {:min_percent =&gt; 1,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :print_file =&gt; true})<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;end<br />
end</code></p>
<p>现在再次执行上面的那个请求，ruby-prof已经创建了一个request.html的文件（windows下位于c:/temp/目录下):</p>
<p><img src="http://docs.google.com/File?id=ahxzccrw5xq_120ctwf3qgc" height="347" width="481" /></p>
<p>如果你以前没有使用过ruby-prof的graph格式，那么上面的输出可能会让你感到迷惑，不要紧，实际上，我们只需要关心粗体的那一行就够了，在它 上面是调用了它的函数的调用栈，下面则是它调用的函数，至于Array#each, Array#each-1, Array#each-2，它们后面的数字表示存在嵌套调用，数字用于指明嵌套的层次（也就是Array#each调用了Array#each-1， Array#each-1又调用了Array#each-2）。</p>
<p>从上图可以看出，Array#each-4调用了String#match最多次（380），因此我们下一步要做的就是追踪Array#each-4，看看到底谁调用了它。</p>
<p>如果你觉得上面的图对于追踪函数调用关系还是不够直观的话，还有办法，ruby-prof0.5提供了call tree格式的输出：</p>
<p class="code"><code class="ruby">module ActionController #:nodoc:<br />
&nbsp;&nbsp;module Profiling #:nodoc:<br />
&nbsp;&nbsp;&nbsp;&nbsp;def perform_action_with_profiling<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if RubyProf.running? or<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Example for Graph html printer<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printer = RubyProf::CallTreePrinter.new(result)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File.open('callgrind.out', 'w') do |file|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printer.print(file, {:min_percent =&gt; 1,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; :print_file =&gt; true})<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;&nbsp;&nbsp;end<br />
&nbsp;&nbsp;end<br />
end</code></p>
<p>现在再次执行同样的请求，你将得到一个callgrind.out的文件，可以使用 KCachegrind (目前只支持Linux，Windows下的版本叫做WinCacheGrind)打开它：</p>
<p><a href="http://docs.google.com/File?id=ahxzccrw5xq_119f457z9fq"><img src="http://docs.google.com/File?id=ahxzccrw5xq_119f457z9fq" height="476" width="429" /></a></p>
<p>不过，需要注意的是，大多数情况下你并不需要一开始就使用ruby-prof，你可以先从web server log， rails log或者是database log入手，如果确定了问题确实是由于你的rails代码引起的，那么ruby-prof就该登场亮相了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.letrails.cn/archives/36/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
