<?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>Shining Ray &#187; ruby</title>
	<atom:link href="http://shiningray.cn/tag/ruby/feed" rel="self" type="application/rss+xml" />
	<link>http://shiningray.cn</link>
	<description>一缕阳光</description>
	<lastBuildDate>Mon, 21 Jun 2010 07:11:46 +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>Ruby的类成员作用域与self</title>
		<link>http://shiningray.cn/class-member-scope-and-self-in-ruby.html</link>
		<comments>http://shiningray.cn/class-member-scope-and-self-in-ruby.html#comments</comments>
		<pubDate>Tue, 17 Mar 2009 03:01:21 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[日记]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://shiningray.cn/?p=507</guid>
		<description><![CDATA[请考虑一下代码 class Test private def self.test puts 'arst' end def test puts 'arst' end class &#60;&#60; self def test2 puts 'arst' end private def test3 puts 'arst' end end end Test.test Test.test2 begin Test.test3 rescue StandardError =&#62; e p e end begin Test.new.test rescue StandardError =&#62; e p e end 结果为 arst arst #&#60;NoMethodError: private [...]]]></description>
			<content:encoded><![CDATA[<p>请考虑一下代码</p>
<pre><code>class Test
private
  def self.test
    puts 'arst'
  end
  def test
    puts 'arst'
  end
  class &lt;&lt; self
    def test2
      puts 'arst'
    end
    private
    def test3
      puts 'arst'
    end
  end
end

Test.test
Test.test2
begin
  Test.test3
rescue StandardError =&gt; e
  p e
end
begin
  Test.new.test
rescue StandardError =&gt; e
  p e
end
</code></pre>
<p>结果为</p>
<p><code>arst<br />
arst<br />
#&lt;NoMethodError: private method `test3' called for Test:Class&gt;<br />
#&lt;NoMethodError: private method `test' called for #&lt;Test:0x2c28060&gt;&gt;</code></p>
<p>类方法<code>Test.test</code>是不受作用域修饰符<code>private</code>影响的，可能原因在于<code>self.</code>的声明方式其实只是<code>class&lt;&lt;self</code>的语法糖。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/class-member-scope-and-self-in-ruby.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Rails SQL Session Store优化版</title>
		<link>http://shiningray.cn/rails-sql-session-store-optimized-version.html</link>
		<comments>http://shiningray.cn/rails-sql-session-store-optimized-version.html#comments</comments>
		<pubDate>Mon, 22 Dec 2008 13:58:27 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[翻译]]></category>
		<category><![CDATA[解决方案]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[scalability]]></category>
		<category><![CDATA[session]]></category>

		<guid isPermaLink="false">http://shiningray.cn/?p=387</guid>
		<description><![CDATA[问题根源 原始的ActiveRecord会话仓库很慢。对于低流量的网站而言没有什么问题，但是对于大一点的而言就慢了。首先，它的慢是因为ActiveRecord本身比较慢。虽然这是一个强大的ORM框架，但对于像会话管理这种简单的任务而言就是杀鸡用牛刀了。 还有其他的解决方案如cookie会话仓库（会话长度有限，不能在会话中存放敏感数据），memcached（无法持久化+难以实现高可用性方案）。 这就是为何要创建SqlSession仓库的原因。它直接操作mysql的数据库API，要比原始的AR会话仓库快很多。不过有时候它还是比较慢，因为： 每次访问都会创建、更新会话 &#8211; 任何机器人或者偶然的访客都会在数据库中创建一条会话记录，最后导致会话表里面会有成千上万的无用记录，但其中99%的访问其实是不需要任何会话更新的。 它使用32位字符串作为会话记录的键 &#8211; 所有RDBMS在处理字符串索引都要比整数慢很多，所以使用整数更好，然而我们的会话ID非常长，同时这些会话仓库都直接使用他们作为表索引。 使用了auto_increment 主键，对于MySQL 5.1.21之前的版本都会导致InnoDB使用表级锁。在不必要的插入上使用表级锁会给大型网站造成奇怪的问题。 解决方案 FastSessions Rails插件便是作为对于以上几个问题的解决方案而诞生的。 首先，我们从会话表中去掉了id字段，所以我们无须用到auto-increment锁。接下来为了让查找更快，我们使用了以下这些技术：不使用(session_id)作为查找索引，使用(CRC32(session_id), session_id)——双列键，可以帮助MySQL更快地找到会话记录因为键的基数更高（这样，mysql可以更快地找到记录而不用检查很多索引行）。我们测试过这种方法，在大会话表中显示了10-15%的性能提升。 最后，也是最强的优化是，对于空会话不创建数据库记录，同时如果数据没有在请求处理过程中没有更新过,则会话数据不会存回数据库。这个更改基本上可以减少50-90%的插入数量（根据应用程序的情况）。 那么，你肯定在想，用了这个插件之后会话究竟能快多少？这很难说。结果要看你是怎么操作会话的：如果你很少更改会话数据（如登录时储存用户id），那么可能会有90%的性能提升，但如果每次访问，你都要给用户会话写入些信息（比如最后一次访问时间last_visit_time），那么根据服务器的负载和会话表的大小，可能有5-15%的性能提升。 你只需要安装一个简单的插件，便可以自动实现上面这些更改。 我们解决了AUTO_INC锁的问题并去掉auto-increment键之后，又引入了一个新的问题，我在这里想说一下。这个问题是这样的。InnoDB会根据主键将所有数据分组。这意味着当我们使用自增长主键并向表插入记录时，会话记录会被组到一起，顺序地存储在磁盘上。然而如果使用比较随机的值（如一个随机会话id的crc32值）作为主键，那么每一个会话记录会被插入到属于它自己的不同的地方，那么会产生一些随机I/O，这对于I/O有限制的服务器不是很理想。所以，我们决定让用户自己选择在部署的时候使用何种主键，当你想在MySQL 5.1.22+上使用这个模块的话，可以设置 CGI::Session::ActiveRecordStore::FastSessions.use_auto_increment = true 这样在InnoDB中会连贯地插入数据。另一个情况当你的MySQL服务器的I/O有限制，不想因为随机主键而增加随机I/O，也可以这样设置。 如果你不想丢失使用AR会话插件创建的旧会话数据，你可以设置 CGI::Session::ActiveRecordStore::FastSessions.fallback_to_old_table = true 这样当某些session_id在新的会话表中找不到的时候，就会回去访问旧的会话表。旧会话表的名字可以使用CGI::Session::ActiveRecordStore::FastSessions.old_table_name 变量进行设置. 这个选项会使会话变慢所以我建议只要在升级到新会话表的时候才使用。在这种情况下，新的会话数据就会存入新表中，当到了会话超时期限的时候，就可以删除旧表了(我们用了两个月的期限，过了两个月之后，我们就可以删除旧表，并将此选项关闭。)。 安装 安装FastSessions插件十分简单，只需以下几个步骤： 将该插件代码从我们的SVN库中安装到vendor/plugins目录中（可以使用./script/plugin install安装，或者piston import命令进行安装——看你喜欢）例如： $ piston import http://rails-fast-sessions.googlecode.com/svn/trunk/ vendor/plugins/fast_sessions 在config/environment.rb文件中启用ActiveRecord会话仓库： Rails::Initializer.run do &#124;config&#124; ...... config.action_controller.session_store = :active_record_store ...... end 为新的会话表创建数据库迁移： [...]]]></description>
			<content:encoded><![CDATA[<h3>问题根源</h3>
<p>原始的<code>ActiveRecord</code>会话仓库很慢。对于低流量的网站而言没有什么问题，但是对于大一点的而言就慢了。首先，它的慢是因为<code>ActiveRecord</code>本身比较慢。虽然这是一个强大的ORM框架，但对于像会话管理这种简单的任务而言就是杀鸡用牛刀了。</p>
<p>还有其他的解决方案如cookie会话仓库（会话长度有限，不能在会话中存放敏感数据），memcached（无法持久化+难以实现高可用性方案）。</p>
<p>这就是为何要创建<code>SqlSession</code>仓库的原因。它直接操作mysql的数据库API，要比原始的AR会话仓库快很多。不过有时候它还是比较慢，因为：</p>
<ul>
<li><strong>每次访问都会创建、更新会话</strong> &#8211; 任何机器人或者偶然的访客都会在数据库中创建一条会话记录，最后导致会话表里面会有成千上万的无用记录，但其中99%的访问其实是不需要任何会话更新的。</li>
<li>它使用<strong>32位字符串</strong>作为会话记录的键 &#8211; 所有RDBMS在处理字符串索引都要比整数慢很多，所以使用整数更好，然而我们的会话ID非常长，同时这些会话仓库都直接使用他们作为表索引。</li>
<li>使用了<strong>auto_increment</strong> 主键，对于MySQL 5.1.21之前的版本都会导致InnoDB使用表级锁。在不必要的插入上使用表级锁会给大型网站造成奇怪的问题。</li>
</ul>
<h3>解决方案</h3>
<p><a onclick="javascript:pageTracker._trackPageview ('/outbound/code.google.com');" href="http://code.google.com/p/rails-fast-sessions/" target="_blank"><code>FastSessions</code> Rails插件</a>便是作为对于以上几个问题的解决方案而诞生的。</p>
<p>首先，我们从会话表中去掉了id字段，所以我们无须用到auto-increment锁。接下来为了让查找更快，我们使用了以下这些技术：不使用<code>(session_id)</code>作为查找索引，使用<code>(CRC32(session_id), session_id)</code>——双列键，可以帮助MySQL更快地找到会话记录因为键的基数更高（这样，mysql可以更快地找到记录而不用检查很多索引行）。我们测试过这种方法，在大会话表中显示了<strong>10-15%的性能提升</strong>。</p>
<p>最后，也是最强的优化是，对于空会话不创建数据库记录，同时如果数据没有在请求处理过程中没有更新过,则会话数据不会存回数据库。这个更改基本上可以<strong>减少50-90%的插入数量</strong>（根据应用程序的情况）。</p>
<p>那么，你肯定在想，用了这个插件之后会话究竟能快多少？这很难说。结果要看你是怎么操作会话的：如果你很少更改会话数据（如登录时储存用户id），那么可能会有90%的性能提升，但如果每次访问，你都要给用户会话写入些信息（比如最后一次访问时间last_visit_time），那么根据服务器的负载和会话表的大小，可能有5-15%的性能提升。</p>
<p>你只需要安装一个简单的插件，便可以自动实现上面这些更改。</p>
<p>我们解决了AUTO_INC锁的问题并去掉auto-increment键之后，又引入了一个新的问题，我在这里想说一下。这个问题是这样的。InnoDB会根据主键将所有数据分组。这意味着当我们使用自增长主键并向表插入记录时，会话记录会被组到一起，顺序地存储在磁盘上。然而如果使用比较随机的值（如一个随机会话id的crc32值）作为主键，那么每一个会话记录会被插入到属于它自己的不同的地方，那么会产生一些随机I/O，这对于I/O有限制的服务器不是很理想。所以，我们决定让用户自己选择在部署的时候使用何种主键，当你想在MySQL 5.1.22+上使用这个模块的话，可以设置<br />
<code><br />
CGI::Session::ActiveRecordStore::FastSessions.use_auto_increment = true<br />
</code><br />
这样在InnoDB中会连贯地插入数据。另一个情况当你的MySQL服务器的I/O有限制，不想因为随机主键而增加随机I/O，也可以这样设置。</p>
<p>如果你不想丢失使用AR会话插件创建的旧会话数据，你可以设置<br />
<code><br />
CGI::Session::ActiveRecordStore::FastSessions.fallback_to_old_table = true<br />
</code><br />
这样当某些session_id在新的会话表中找不到的时候，就会回去访问旧的会话表。旧会话表的名字可以使用<code>CGI::Session::ActiveRecordStore::FastSessions.old_table_name</code> 变量进行设置.</p>
<p>这个选项会使会话变慢所以我建议只要在升级到新会话表的时候才使用。在这种情况下，新的会话数据就会存入新表中，当到了会话超时期限的时候，就可以删除旧表了(我们用了两个月的期限，过了两个月之后，我们就可以删除旧表，并将此选项关闭。)。</p>
<h3>安装</h3>
<p>安装FastSessions插件十分简单，只需以下几个步骤：</p>
<ol>
<li>将该插件代码从我们的SVN库中安装到vendor/plugins目录中（可以使用./script/plugin install安装，或者piston import命令进行安装——看你喜欢）例如：<br />
<code><br />
$  piston import http://rails-fast-sessions.googlecode.com/svn/trunk/ vendor/plugins/fast_sessions<br />
</code></li>
<li>在config/environment.rb文件中启用ActiveRecord会话仓库：<br />
<code><br />
Rails::Initializer.run do |config|<br />
......<br />
config.action_controller.session_store = :active_record_store<br />
......<br />
end<br />
</code></li>
<li>为新的会话表创建数据库迁移：<br />
<code><br />
$ ./script/generate fast_session_migration AddFastSessions<br />
</code></li>
<li>如果需要，可以打开新创建的迁移脚本并更改表名table_name和插件use_auto_increment参数。</li>
<li>运行数据库迁移:<br />
<code><br />
$  rake db:migrate<br />
</code></li>
<li>启动应用程序并尝试进行一定会保存数据到会话的操作。然后检查fast_sessions会话表（如果你没有改名字的话）有没有这条记录。</li>
</ol>
<h3>下载</h3>
<p>该插件的最新版本可以在<a href="http://code.google.com/p/rails-fast-sessions/">它的项目网站</a>或在 <a href="http://rails-fast-sessions.googlecode.com/svn/trunk/">SVN仓库</a>中找到。该插件由<a href="http://kovyrin.info/">Alexey Kovyrin</a>（<a href="http://www.percona.com/">Percona</a>的MySQL性能专家）制作。开发由<a href="http://www.scribd.com/">Scribd.com</a>赞助。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/rails-sql-session-store-optimized-version.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>关于《浅析Rails部署》</title>
		<link>http://shiningray.cn/guan-yu-qian-xi-rails-bu-shu.html</link>
		<comments>http://shiningray.cn/guan-yu-qian-xi-rails-bu-shu.html#comments</comments>
		<pubDate>Fri, 11 Jul 2008 06:18:08 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[日记]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[部署]]></category>

		<guid isPermaLink="false">http://shiningray.cn/?p=184</guid>
		<description><![CDATA[我之前写的文章《浅析Ruby on Rails部署方案》受到不少同学的关注，在此首先感谢大家。 但是也有同学对此提出了一些疑问，我经过检查，发现文章确实存在很多漏洞和不足： Lighttpd作为负载均衡反向代理时，无论是链接FastCGI还是HTTP后端，KeepAlive链接默认都是关闭的 Nginx的FastCGI模式，默认也是关闭持久链接的 缺少了一些重要的前后端搭配的方式 对于Rails应用的内存占用也应该考虑在内 我会针对这一些问题，重新设计测试案例，并重写该文章。再次感谢大家的关注。]]></description>
			<content:encoded><![CDATA[<p>我之前写的文章《<a href="http://shiningray.cn/ruby-on-rails-deployment-schemes.html">浅析Ruby on Rails部署方案</a>》受到不少同学的关注，在此首先感谢大家。</p>
<p>但是也有同学对此提出了一些疑问，我经过检查，发现文章确实存在很多漏洞和不足：</p>
<ol>
<li>Lighttpd作为负载均衡反向代理时，无论是链接FastCGI还是HTTP后端，KeepAlive链接默认都是关闭的</li>
<li>Nginx的FastCGI模式，默认也是关闭持久链接的</li>
<li>缺少了一些重要的前后端搭配的方式</li>
<li>对于Rails应用的内存占用也应该考虑在内</li>
</ol>
<p>我会针对这一些问题，重新设计测试案例，并重写该文章。再次感谢大家的关注。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/guan-yu-qian-xi-rails-bu-shu.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Slapp: 简易聊天墙的Merb教程</title>
		<link>http://shiningray.cn/slapp-a-simple-chat-wall-merb-tutorial.html</link>
		<comments>http://shiningray.cn/slapp-a-simple-chat-wall-merb-tutorial.html#comments</comments>
		<pubDate>Sun, 29 Jun 2008 16:23:19 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[翻译]]></category>
		<category><![CDATA[ActiveRecord]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[Merb]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://shiningray.cn/?p=181</guid>
		<description><![CDATA[有问题或评价，请联系: socialface@gmail.com 程序截图: http://www.socialface.com/slapp/screenshot.jpg 简介 欢迎来到Slapp的教程。本文的主要目标是通过构建一个简易的聊天墙应用来介绍一下Merb微框架的主要组件。 本文其次的目标是成为最好的Merb开放教程并能不断更新。同时，我们希望本教程可以逐渐变得丰富来展现Merb框架的所有方面和开发方式。 许可 This tutorial is Copyright 2008 Social Corp. and is licensed under a Creative Commons Attribution-Noncommercial 3.0 United States License, available at: http://creativecommons.org/licenses/by-nc/3.0/us/ 相关的源代码以MIT形式的开发源代码许可: http://www.socialface.com/slapp/source 直接浏览代码： http://gitorious.org/projects/slapp/repos/mainline/trees/master 也可以直接下载： http://www.socialface.com/slapp/binary 参与人员名单 本教程最初由来自#merb的Slurry撰写。Slurry在撰写本文时主要参照了Merb的官方文档[^a]以及#merb IRC频道的内容。 在学习Merb的RSpec的过程中，还咨询了John Hornbeck的Blerb[^b]，参考了Tim Connor的“Isolate controller and view testing in merb”一文[^c]——虽然帮助很大，真正掌握Merb/Rspec还是得益于来自#merb的benburkert。 中文版由ShiningRay翻译 前期决定 设计上来说，Merb是一个ORM无关的框架。当然，这仅仅是表示有不同的模型层可以选择。对于Merb v0.9.2来说，包括：DataMapper、Sequel和ActiveRecord（来自Rails）。 尽管DataMapper和Sequel都是很好的选择，但现在关注Merb的主流人群基本上都是来自Rails背景；因此，我们在本教程中会继续使用ActiveRecord。 1.) 创建模型 [...]]]></description>
			<content:encoded><![CDATA[<p>有问题或评价，请联系: <a target="_blank" href="https://mail.google.com/mail?view=cm&amp;tf=0&amp;ui=1&amp;to=socialface@gmail.com">socialface@gmail.com</a></p>
<p>程序截图: <a href="http://www.socialface.com/slapp/screenshot.jpg">http://www.socialface.com/slapp/screenshot.jpg</a></p>
<h2>简介</h2>
<p>欢迎来到Slapp的教程。本文的主要目标是通过构建一个简易的聊天墙应用来介绍一下Merb微框架的主要组件。</p>
<p>本文其次的目标是成为最好的Merb开放教程并能不断更新。同时，我们希望本教程可以逐渐变得丰富来展现Merb框架的所有方面和开发方式。</p>
<h2>许可</h2>
<p>This tutorial is Copyright 2008 Social Corp. and is licensed under a Creative<br />
Commons Attribution-Noncommercial 3.0 United States License, available at:</p>
<ul>
<li><a href="http://creativecommons.org/licenses/by-nc/3.0/us/">http://creativecommons.org/licenses/by-nc/3.0/us/</a></li>
</ul>
<p>相关的源代码以MIT形式的开发源代码许可:</p>
<ul>
<li><a href="http://www.socialface.com/slapp/source">http://www.socialface.com/slapp/source</a></li>
</ul>
<p>直接浏览代码：</p>
<ul>
<li><a href="http://gitorious.org/projects/slapp/repos/mainline/trees/master">http://gitorious.org/projects/slapp/repos/mainline/trees/master</a></li>
</ul>
<p>也可以直接下载：</p>
<ul>
<li><a href="http://www.socialface.com/slapp/binary">http://www.socialface.com/slapp/binary</a></li>
</ul>
<h2>参与人员名单</h2>
<p>本教程最初由来自#merb的Slurry撰写。Slurry在撰写本文时主要参照了Merb的官方文档[^a]以及#merb IRC频道的内容。</p>
<p>在学习Merb的RSpec的过程中，还咨询了John Hornbeck的Blerb[^b]，参考了Tim Connor的“Isolate controller and view testing in merb”一文[^c]——虽然帮助很大，真正掌握Merb/Rspec还是得益于来自#merb的benburkert。</p>
<p>中文版由<a href="http://shiningray.cn/">ShiningRay</a>翻译</p>
<h2>前期决定</h2>
<p>设计上来说，Merb是一个ORM无关的框架。当然，这仅仅是表示有不同的模型层可以选择。对于Merb v0.9.2来说，包括：DataMapper、Sequel和ActiveRecord（来自Rails）。</p>
<p>尽管DataMapper和Sequel都是很好的选择，但现在关注Merb的主流人群基本上都是来自Rails背景；因此，我们在本教程中会继续使用ActiveRecord。</p>
<h2>1.) 创建模型</h2>
<p>在像Merb这样快速成长的社区中，一个问题可能只需更新或者重装Merb相关的包(gem)就能简单解决，所以让我们先花点时间这样准备一下：</p>
<pre><code># gem sources -a http://merbivore.com
# gem install merb activerecord merb_activerecord merb_helpers rspec merb_rspec

$ merb-gen app slapp
$ cd slapp
</code></pre>
<p>完成了前两行命令后，Merb、ActiveRecord以及Rspec应该已经安装好或已经更新了，同时后两行命令则应该创建了一个初始的空Merb应用。</p>
<p>从这里开始，我们就要告诉Merb：我们要怎样用ActiveRecord，要使用哪个测试框架，以及需要载入哪些确切的merb_helpers包（如表单相关的东西）</p>
<p>编辑：<code>slapp/config/init.rb</code> 并取消第<code>27</code>和<code>42</code>的注释，同时将：<code>dependency "merb_helpers"</code> 添加在<br />
<code>Merb::BootLoader.after_app_loads do</code> 这一行之前:</p>
<pre><code>use_orm :activerecord
...
use_test :rspec
...
dependency "merb_helpers"
...
Merb::BootLoader.after_app_loads do
  ### Add dependencies here that must load after the application loads:

  # dependency "magic_admin" # this gem uses the app's model classes
end
</code></pre>
<p>现在我们已经告诉Merb要使用ActiveRecord： <code>use_orm :activerecord</code>, 让我们来生成第一个模型：</p>
<pre><code>$ merb-gen model Post
</code></pre>
<p>靠，居然没效。不过等等，你应该还有一个全新的<code>slapp/config/database.yml.sample</code> 文件等待配置，对吧？[^1]</p>
<p>好，现在你要做的就是将这个文件名字改成<code>database.yml</code>并在其中插入合适的数据库链接信息，像这样：</p>
<pre><code>:development: &amp;defaults
  :adapter: mysql
  :database: slapp_development
  :username: slapp
  :password: SL@rrYin08
  :host: localhost
  :socket: /tmp/mysql.sock
  :encoding: utf8

:test:
  &lt;&lt;: *defaults
  :database: slapp_test
</code></pre>
<p>让我们重新运行上面的merb-gen命令，不过这次我们还要再加上一些Post的默认属性：</p>
<pre><code>$ merb-gen model Post body:string created_at:datetime
</code></pre>
<p>如果一切正常，那么第一个模型类就有了。让我们使用<code>rake</code>对数据库进行迁移，来加上表：</p>
<pre><code>$ rake db:migrate
</code></pre>
<p>现在，Slapp应该有一个有效可用的Post模型了。为了以防万一，我们将介绍RSpec[^2].</p>
<h2>2.) RSpec</h2>
<p>RSpec——你可能已经知道了——是Test::Unit的另一种替代测试方案。与RSpec交互主要通过rake任务和<code>spec</code>命令：</p>
<pre><code>$ rake spec
</code></pre>
<p>或者，更加详细的：</p>
<pre><code>$ spec spec/* --format specdoc -c
</code></pre>
<p><em>(重要：这里使用的rake任务来自Merb前沿代码，应该很快会发布为标准包。在那之前，可以使用以下同等任务：<br />
<code>rake specs</code> 以及 <code>rake spec TASK=controllers/pages</code>)</em></p>
<p>两个任务会运行<code>slapp/spec/*</code>中的任何代码，简洁起见，我们这里会使用rake。</p>
<p>让我们再次运行测试：</p>
<pre><code>$ rake spec
</code></pre>
<p>如你所见，Merb已经为我们创建了一个默认的测试，但是他还不能通过： </p>
<pre><code>Post
- should have specs (PENDING: Not Yet Implemented)
...
</code></pre>
<p>打开: <code>slapp/spec/models/post_spec.rb</code> 看看这个测试在哪里：</p>
<pre><code>describe Post do

  it "should have specs"

end
</code></pre>
<p>将该spec改成实用的代码，比如：</p>
<pre><code>describe Post do

  it "should be valid when new" do
      post = Post.new
      post.should be_valid
  end

end
</code></pre>
<p>该测试看上去好像不多，但是它确实可以验证Merb、RSpec、ActiveRecord和我们的数据库都已经安装成功并工作正常。</p>
<pre><code>$ rake spec
</code></pre>
<p>同时，如果我们在此运行新的spec，数据库又出现一个问题。确切地说，我们忘记了创建<code>slapp_test</code> 数据库并将<code>slapp_development</code>的结构复制过去。</p>
<pre><code>$ rake db:create:all
$ rake db:test:clone
</code></pre>
<p>重新运行spec:</p>
<pre><code>$ rake spec
...
Post
- should be valid
...
1 example, 0 failures
</code></pre>
<p>这时成功在向我们问候：<code>1 example, 0 failures</code>，这一行表示所有的spec都通过了。更重要的是，我们遇到并克服了实际使用RSpec的第一个问题。</p>
<h2>3.) 控制器</h2>
<p>虽然现在从技术上说我们没有控制器也能启动Merb，但是基本上作不了什么。事实上就是什么都作不了，我们还是先来创建一个控制器：</p>
<pre><code>$ merb-gen controller Posts
</code></pre>
<p>如你所见，Merb的控制器的命名方式是模型名（或者资源等）的名字复数化，且不使用<code>Controller</code>后缀。</p>
<p>看一下<code>slapp/app/controllers/posts.rb</code>，你应该看到我们新的Posts控制器里有一个默认的<code>#index</code>动作。另外，Merb还应该创建了一个新的spec: <code>slapp/spec/controllers/posts_spec.rb</code> ，里面的内容应该类似于：</p>
<pre><code>describe Posts, "index action" do
  before(:each) do
      dispatch_to(Posts, :index)
  end
end
</code></pre>
<p>让我们编辑：<code>slapp/spec/controllers/posts_spec.rb</code>，更改内容为：</p>
<pre><code>require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')

describe Posts, "#index" do

  it "should respond correctly" do
      dispatch_to(Posts, :index).should respond_successfully
  end

end
</code></pre>
<p>指定控制器是比较直观的，在上面的例子中，我们描述了Posts控制器（从<code>dispatch_to</code>中返回的）有一个<code>#index</code>动作并且它能被外部世界成功调用（即，它返回一个HTTP 20x 代码）[^3].</p>
<p>我们想再次运行spec，但是由于我们没有添加任何模型或视图测试，所以让我们执行更加明确的“仅控制器”的rake任务：</p>
<pre><code>$ rake spec:controller
</code></pre>
<p>成功的测试结果：</p>
<pre><code>Posts index action
- should respond correctly
...
1 example, 0 failures
</code></pre>
<p>漂亮。现在我们已经有了一个可以工作的控制器和一个有效的模型——我们只缺一些好的视图了。</p>
<h2>4.) The View from Above</h2>
<p>前面当我们创建Posts控制器时，Merb同时也为<code>#index</code>动作创建了一个草图。位于：<code>slapp/app/views/posts/index.html.erb</code>，你也许会看看里面有什么，然而，让我们先暂时忽略这个视图，并回到控制器。</p>
<p>在 <code>slapp/app/controllers/posts.rb</code> 中，我们执行一个简单的 ActiveRecord <code>#find</code><br />
并在<code>#index</code>动作中将结果存储为一个实例变量：</p>
<pre><code>class Posts &lt; Application

  def index
      @posts = Post.find(:all, :order =&gt; "created_at DESC")
      render
  end

end
</code></pre>
<p>然后我们确认该动作仍然是可以调用的：</p>
<pre><code>$ rake spec:controller
...
1 example, 0 failures
</code></pre>
<p>就和Rails（或者其他Web框架）中一样，在控制器中创建的实例变量可以在对应的视图中调用。</p>
<p>在本案中，我们可以回到：<code>slapp/app/views/posts/index.html.erb</code> 并将临时文本替换成显示<code>@posts</code>变量内容的代码。</p>
<p>为了能保持模块化，我们将使用Merb <code>#partial</code>[^4] 功能来达到这个目的。</p>
<p>将 <code>slapp/app/views/posts/index.html.erb</code> 的内容替换成：</p>
<pre><code>&lt;h1&gt;Welcome to Slapp&lt;/h1&gt;
&lt;h2&gt;A simple chat wall&lt;/h2&gt;

&lt;p&gt;Recent Posts:&lt;/p&gt;
&lt;div id="posts" class="container"&gt;
    &lt;%= partial("/shared/post", :with =&gt; @posts) %&gt;
&lt;/div&gt;
</code></pre>
<p>不看HTML，调用<code>#partial</code>应该还是比较容易理解的——<br />
我们想多次渲染<code>slapp/app/views/shared/_post.html.erb</code> 视图来呈现<code>@posts</code>的内容。</p>
<p>现在创建: <code>shared/</code> 目录以及: <code>_post.html.erb</code> 文件:</p>
<pre><code>$ mkdir app/views/shared
$ touch app/views/shared/_post.html.erb
</code></pre>
<p>并编辑 <code>slapp/app/views/shared/_post.html.erb</code> 的内容为：</p>
<pre><code>&lt;div id="post-&lt;%= post.id %&gt;" class="post"&gt;
    &lt;p class="body"&gt;&lt;%= h(post.body) %&gt;&lt;/p&gt;
    &lt;p class="created"&gt;&lt;%= relative_date(post.created_at) %&gt;&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>上面调用: <code>partial("/shared/post", :with =&gt; @posts)</code> 会反复传递一个单个<code>post</code>对象给视图<code>_post.html.erb</code>并渲染。</p>
<h2>5.) 启动Merb</h2>
<p>现在我们有了模型、控制器、以及一个视图，让我们启动Merb：</p>
<pre><code>$ merb
$ curl http://localhost:4000/
</code></pre>
<p>你应该看到了一个普通的欢迎页，再转到：</p>
<pre><code>$ curl http://localhost:4000/posts/index
</code></pre>
<p>这时你应该看到<code>slapp/app/views/posts/index.html.erb</code>的内容了。当然，由于我们还未创建任何帖子，所以应该看不到任何东西。</p>
<p>让我们使用交互Merb会话（其实就是一个在Merb应用的内容中启动的IRB）修正上面的问题：</p>
<pre><code>$ merb -i
~ Loaded DEVELOPMENT Environment...
...
&gt;&gt; Post.create(:body =&gt; "Memp went down")
</code></pre>
<p>使用典型的 ActiveRecord <code>#create</code> 方法，我们现在创建了一个Post。重新载入 <br />
<code>Posts#index</code> 页面:</p>
<pre><code>$ curl http://localhost:4000/posts/index
</code></pre>
<p>我们应该成功地看到了新的帖子。</p>
<h2>6.) 特殊的视图</h2>
<p>现在我们已经实现了视图，<strong>也许</strong>他们不会经常更改，让我们先描述他们。</p>
<p>首先，创建目录和spec文件：</p>
<pre><code>$ mkdir -p spec/views/posts/
$ touch spec/views/posts/index_spec.rb
</code></pre>
<p>然后将一下代码放入<code>slapp/spec/views/posts/index_spec.rb</code></p>
<pre><code>require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")

describe "posts/index" do

  before(:each) do
      @controller = Posts.new(fake_request)
      @posts = [Post.create(:body =&gt; "Merb", :created_at =&gt; Time.now), Post.create(:body =&gt; "Rocks!", :created_at =&gt; Time.now)]
      @controller.instance_variable_set(:@posts, @posts)
      @body = @controller.render(:index)
  end

  it "should have a containing div for the posts" do
      @body.should have_selector("div#posts.container")
  end

  it "should have a div for each individual post" do
      @posts.each do |post|
        @body.should have_selector("div#posts.container div#post-#{ post.id }.post")
      end
  end

  it "should have the contents of each post inside a div with an id and class" do
      @posts.each do |post|
        @body.should match_tag(:div, :id =&gt; "post-#{ post.id }", :class =&gt; "post", :content =&gt; post.body)
      end
  end

  after(:each) do
      Post.destroy_all
  end

end
</code></pre>
<p>当使用RSpec描述对象时，常常需要在运行测试的前后维护测试特定的内容。毫不奇怪，RSpec向我们提供了<code>#before</code>和<code>#after</code>块来实现这个目的。</p>
<p>回到代码中：我们在<code>#before</code>块中首先做的是从我们的 <code>Posts</code> 创建<code>@controller</code>实例。下面我们使用<code>fake_request</code>助手[^5]来模拟HTTP请求。</p>
<p>回想一下我们的视图：</p>
<pre><code>...
&lt;p&gt;Recent Posts:&lt;/p&gt;
&lt;div id="posts" class="container"&gt;
    &lt;%= partial("/shared/post", :with =&gt; @posts) %&gt;
&lt;/div&gt;
</code></pre>
<p>我们知道我们还需要一组帖子来进行测试，碰巧，这就是<code>#before</code>块的第二和第三行所做的事情：</p>
<pre><code>...
@posts = [Post.create(:body =&gt; "Merb", :created_at =&gt; Time.now), Post.create(:body =&gt; "Rocks!", :created_at =&gt; Time.now)]
</code></pre>
<p>@controller.instance<em>variable</em>set(:@posts, @posts)<br />
    &#8230;</p>
<p>这里，我们仅仅将插入了一组记录并保存为<code>@controller</code>的<code>@posts</code>实例变量。记住，调用Posts控制器的<code>#index</code>动作和我们之前所做的没有什么区别，除了我们使用了一个ActiveRecord的<code>#find</code>，而不是手工使用<code>#new</code>创建帖子：</p>
<p>前面的Posts控制器Our Posts controller from earlier:</p>
<pre><code>class Posts &lt; Application

  def index
      @posts = Post.find(:all, :order =&gt; "created_at DESC")
      render
  end

end
</code></pre>
<p>最后，<code>#before</code>的第四和最后一行渲染了视图并将响应的主体（本案中是HTML）放入了<code>@body</code> 实例变量。</p>
<pre><code>...
@body = @controller.render(:index)
...
</code></pre>
<p>(本质上来说，我们是使用fake_request来“查看”Posts#index动作。)</p>
<p>现在，我们的控制器已经设置好了，同时视图也渲染了，我们就开始列出我们对视图中应该有什么的预期。[^6]</p>
<p>首先，我们描述HTML里应该最外面有一个div来放每一个单独的帖子的div：</p>
<pre><code>...
it "should have a containing div for the posts" do
    @body.should have_selector("div#posts.container")
end
...
</code></pre>
<p>下面，我们断定容器div确实包含着帖子的div：</p>
<pre><code>...
it "should have a div for each individual post" do
  @posts.each do |post|
      @body.should have_selector("div#posts.container div#post-#{ post.id }.post")
  end
end
...
</code></pre>
<p>然后，我们进入每个帖子的div来验证内容准确地匹配对应的Post：</p>
<pre><code>...
it "should have the contents of each post inside a div with an id and class" do
  @posts.each do |post|
      @body.should match_tag(:div, :id =&gt; "post-#{ post.id }", :class =&gt; "post", :content =&gt; post.body)
  end
end
...
</code></pre>
<p>最后，我们使用<code>#after</code>块来删除在#before块中创建的帖子：</p>
<pre><code>...
after(:each) do
  Post.destroy_all
end
...
</code></pre>
<p>尽管我们还没真正完成，我们先来验证一下整个应用：</p>
<pre><code>$ rake spec
...
Posts#index
- should respond correctly

Post
- should be valid

posts/index.html.erb
- should have a containing div for the posts
- should have a div with an id and class for each individual post
- should have the contents of each post inside a div with an id and class

Finished in 0.226266 seconds

5 examples, 0 failures
</code></pre>
<h2>7.) 表单创建</h2>
<p>有了浏览帖子的能力之后，现在就可以实现同样重要的创建帖子的功能了。</p>
<p>打开<code>slapp/app/views/posts/index.html.erb</code>并在帖子列表下面添加该表单[^7]：</p>
<pre><code>...
&lt;p&gt;Post Something:&lt;/p&gt;
&lt;% form_tag(:action =&gt; url( :controller =&gt; "posts", :action =&gt; "create") ) do %&gt;
    &lt;%= text_field(:name =&gt; "body", :size =&gt; 40) %&gt;
    &lt;%= submit_button("Post Message!") %&gt;
&lt;% end %&gt;
</code></pre>
<p>几乎不言自明，我们是要构建一个简单的表单，有一个文本输入框和一个提交按钮。</p>
<p>你应该已经注意到我们已经将表单设置为递交到<code>Posts</code>控制器的<code>#create</code>动作。我们需要实现这个动作，不过在我们继续之前，我们先快速描述一下这个表单。</p>
<p>编辑: <code>slapp/spec/views/posts/index_spec.rb</code>并在<code>#after</code> 块上面添加一下内容:</p>
<pre><code>...
it "should have a form to create new posts with a single input and submit button" do
  @body.should match_selector("form[@action=/posts/create]")
  @body.should match_selector("form[@action=/posts/create] input[@name=body]")
  @body.should match_selector("form[@action=/posts/create] button[@type=submit]")
end
</code></pre>
<p>和前面一样，我们使用<code>#match_selector</code> 来断言表单、正文输入框以及提交按钮的存在。唯一不同的是我们使用了一个基于HTML属性的选择器[^8]<code>form[@action=/posts/create]</code></p>
<p>Run the specs:</p>
<pre><code>$ rake spec:view
...
4 examples, 0 failures
</code></pre>
<p>我们可以再次启动Merb来亲眼检验一下新的表单了。不过，因为我们已经使用了RSpec，这步不是非常必须的，我们可以立刻继续往下。</p>
<p>说道RSpec，这次，当我们在要去完成<code>#create</code>动作的时候，我们应该在写代码之前先写出该步骤的spec。</p>
<p>将以下内容复制到 <code>slapp/spec/controllers/posts_spec.rb</code>：</p>
<pre><code>require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')

describe Posts, "#index" do

  it "should respond correctly" do
      dispatch_to(Posts, :index).should respond_successfully
  end

end

describe Posts, "#create" do

  before(:each) do
      @params = { :body =&gt; "It was a good game though" }
  end

  it "should redirect to #index after successfully creating a Post" do
      lambda {
        dispatch_to(Posts, :create, @params).should redirect_to("/posts/index")
      }.should change(Post, :count)
  end

end
</code></pre>
<p>这里，在<code>#before</code>块中，我们准备了一个只有一个<code>:body</code>键的<code>@params</code>表以便开始描述<code>#create</code>动作 。下面，我们列出了第一个预期：create动作在它成功创建一个帖子之后应该重定向到<code>#index</code>动作。</p>
<p>(这就是如果一个浏览器通过表单提交了某些信息，在我们的应用中能看到的情况。)</p>
<p>我们用一个lambda表达式检验一个帖子是不是被创建了，其中RSpec会调用两次：先执行一次，然后等相关的<code>{...}</code>块执行完之后再执行另一次。</p>
<p>这两次中，RSpec都会调用<code>Post.count</code>，如果两次调用返回不同的值，那么我们就能确信Post被创建了，那么这个块（该动作）就是成功的。</p>
<p>因为我们还没有编码<code>#create</code>，所以spec显然会失败：</p>
<pre><code>$ rake spec:controller
...
Posts#create
- should redirect to #index after successfully creating a Post (FAILED - 1)

1)
'Posts#create should redirect to #index after successfully creating a Post' FAILED
count should have changed, but is still 0
...
</code></pre>
<p>切换到: <code>slapp/app/controllers/posts.rb</code>并再次复制以下内容：</p>
<pre><code>class Posts &lt; Application

  def index
      @posts = Post.find(:all, :order =&gt; "created_at DESC")
      render
  end

  def create
      Post.create!(:body =&gt; params[:body])
      redirect url(:action =&gt; "index")
  end

end
</code></pre>
<p>现在我们定义了<code>#create</code>，现在回到spec文件，应该就可以通过了：</p>
<pre><code>$ rake spec:controller
...
2 examples, 0 failures
</code></pre>
<p>通过了这些spec，我们就有了一个可以正常工作的聊天墙了。</p>
<p>听起来是个好消息，我们也几乎就要完成了。我们还需要做得就是检验创建一个新的帖子需要一定的文本，否则则会出现一个异常。</p>
<h2>8.) 收尾</h2>
<p>现在，任何都可以点击 &#8220;Post Message!&#8221; 然后创建一个新的帖子。因为我们并不想让一堆空白的帖子占据聊天墙，所以我们应该在创建新帖子之前校验至少有一些文本被提交了。</p>
<p>因为本文是一个教程，我们不打算将所有东西都仔细进行合适的处理，所以这里我们直接使用ActiveRecord的<code>validates_length_of</code>过滤器。 ;-)</p>
<p>打开: <code>slapp/spec/models/post_spec.rb</code> 并观察我们现有的spec：</p>
<pre><code>...
it "should be valid when new" do
  post = Post.new
  post.should be_valid
end
</code></pre>
<p>因为我们要校验正文文本的存在，这个spe已经不再有效，我们需要如下的内容来替代：</p>
<pre><code>describe Post do

  it "should NOT be valid when new" do
      post = Post.new
      post.should_not be_valid
  end

  it "should require at least two body characters to be valid" do
      post = Post.new
      post.should_not be_valid
      post.errors.on(:body).should include("is too short (minimum is 2 characters)")
  end

end
</code></pre>
<p>和平时一样，我们首先运行失败的spec来建立我们对于特定行为的预期：</p>
<pre><code>...
Post
- should NOT be valid when new (FAILED - 1)
- should require at least two body characters to be valid (FAILED - 2)

1)
'Post should NOT be valid when new' FAILED
expected valid? to return false, got true
./spec/models/post_spec.rb:7:

2)
'Post should require at least two body characters to be valid' FAILED
expected valid? to return false, got true
./spec/models/post_spec.rb:12:
...
</code></pre>
<p>然后实现上面提出的修正，在本案中，则是在<code>slapp/app/models/post.rb</code>中加入一样:</p>
<pre><code>class Post &lt; ActiveRecord::Base

  validates_length_of :body, :minimum =&gt; 2

end
</code></pre>
<p>再次运行spec来检验我们的修正是否有效：</p>
<pre><code>$ rake spec:model
...
2 examples, 0 failures
</code></pre>
<p>这时我们的模型完成了。让我们继续给Posts控制器添加一个spec——<br />
我们需要描写当提交一个帖子没有包含正文的时候，我们没有真正去处理这个错误而已直接返回由我们的ORM抛出的异常。</p>
<p>编辑<code>slapp/spec/controllers/posts_spec.rb</code>并加入下面这个spec：</p>
<pre><code>...
it "should raise an exception when insufficient body text is submitted" do
  lambda {
      dispatch_to(Posts, :create).should redirect_to("/posts/index")
  }.should raise_error(ActiveRecord::RecordInvalid)
end
</code></pre>
<p>如你所见，我们仅仅是不带<code>@params</code>表来调用<code>#create</code>。这创建一个没有正文的空Post，这样就会导致ActiveRecord的校验失败，并抛出我们预期的<code>RecordInvalid</code>异常。</p>
<p>有了这个，我们的第一个聊天墙的版本就完成了。就和前面一样，你可以通过启动Merb并浏览<code>Posts#index</code>动作来试试程序：</p>
<pre><code>$ merb
$ curl http://localhost:4000/posts/index
</code></pre>
<h2>最后的思考</h2>
<p>从这里开始，你可能还有很多东西想添加，例如：分页、动态发布/更新、SPAM过滤器、文本格式化等等。</p>
<p>这些对于任何优秀的聊天程序都是很基本的，你都可以利用Merb来实现。</p>
<p>同时，别忘了浏览官方的项目首页看看有没有最新的更新、看看别人的版本甚至创建属于你自己的：</p>
<ul>
<li><a href="http://www.socialface.com/slapp/source">http://www.socialface.com/slapp/source</a></li>
</ul>
<p>&#8230;</p>
<p>有任何问题/评价，请致电<a target="_blank" href="https://mail.google.com/mail?view=cm&amp;tf=0&amp;ui=1&amp;to=socialface@gmail.com">socialface@gmail.com</a>或者在#merb找 Slurry</p>
<h2>脚注</h2>
<p>[^1]: The observant may have also just found their first &#8220;Merb&#8221; bug.. the<br />
generator claimed to have made a &#8220;database.sample.yml&#8221; file, although the file<br />
is really named &#8220;database.yml.sample&#8221;. :-)</p>
<p>[^2]: RSpec links in order of approximate handyness to the beginner:</p>
<ul>
<li><a href="http://rspec.info/documentation/expectations.html">http://rspec.info/documentation/expectations.html</a></li>
<li><a href="http://rspec.info/documentation/test_unit.html">http://rspec.info/documentation/test_unit.html</a></li>
<li><a href="http://rspec.info/documentation/before_and_after.html">http://rspec.info/documentation/before_and_after.html</a></li>
<li><a href="http://rspec.info/rdoc/classes/Spec/Matchers.html">http://rspec.info/rdoc/classes/Spec/Matchers.html</a></li>
<li><a href="http://rspec.info/rdoc/index.html">http://rspec.info/rdoc/index.html</a></li>
</ul>
<p>[^3]: Merb RSpec controller matchers: </p>
<ul>
<li><a href="http://www.merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000089&amp;name=Rspec#">http://www.merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000089&amp;name=Rspec#</a></li>
</ul>
<p>[^4]: Merb partials: </p>
<ul>
<li><a href="http://www.merbivore.com/documentation/merb-core/0.9.2/index.html?a=M000328&amp;name=partial">http://www.merbivore.com/documentation/merb-core/0.9.2/index.html?a=M000328&amp;name=partial</a></li>
</ul>
<p>[^5]: Merb fake_request helper: </p>
<ul>
<li><a href="http://merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000116&amp;name=FakeRequest">http://merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000116&amp;name=FakeRequest</a></li>
</ul>
<p>[^6]: Merb RSpec view matchers: </p>
<ul>
<li><a href="http://merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000089&amp;name=Rspec#">http://merbivore.com/documentation/merb-core/0.9.2/index.html?a=C00000089&amp;name=Rspec#</a></li>
</ul>
<p>[^7]: Merb Form Helpers: </p>
<ul>
<li><a href="http://merbivore.com/documentation/merb-plugins/head/merb_helpers/index.html?a=M000046&amp;name=form_tag">http://merbivore.com/documentation/merb-plugins/head/merb_helpers/index.html?a=M000046&amp;name=form_tag</a></li>
</ul>
<p>[^8]: Hpricot CSS Selectors: </p>
<ul>
<li><a href="http://code.whytheluckystiff.net/hpricot/wiki/SupportedCssSelectors">http://code.whytheluckystiff.net/hpricot/wiki/SupportedCssSelectors</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/slapp-a-simple-chat-wall-merb-tutorial.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>浅析Ruby on Rails部署方案</title>
		<link>http://shiningray.cn/ruby-on-rails-deployment-schemes.html</link>
		<comments>http://shiningray.cn/ruby-on-rails-deployment-schemes.html#comments</comments>
		<pubDate>Wed, 30 Apr 2008 06:24:34 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[解决方案]]></category>
		<category><![CDATA[论文]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[部署]]></category>

		<guid isPermaLink="false">http://shiningray.cn/?p=170</guid>
		<description><![CDATA[2006初，我接到了公司分配的一个遗留项目，让我负责一个基于C/S的系统的服务器端。其实是系统是基于HTTP协议的，因为负责客户端的同事对于服务器端编程不甚了解，虽然使用PHP对熟悉C＋＋的他来说是驾轻就熟，但是在进一步实现更多的功能和更高的性能上就捉襟见肘了。项目是在非常突然的情况下交给我的，因为该同事在客户端上有更多的事情要做。我在分析了他的数据库结构和PHP源代码之后，决定按照与客户端的通讯协议重写他的服务器端。为了能应付老板苛刻的时间限制，我打算使用正在学习的Ruby on Rails。后来，项目在功能上非常顺利地交付了。 两年过去了，随着客户端数量的不断增加、客户端功能的增加、与服务器端交互数据的增加、老板对功能的要求不断增加，我在这个项目上走了不少弯路，尤其是在部署——或者说是架构——方面。 我遇到的最大的问题就在于并发链接数上。服务器与客户端的每次交互的数据量并不大，但内容无法缓存。起初用的是Nginx/Apache+Mongrel 的部署方式，但当遇到大量并发请求时，常常会遇到Mongrel进程死掉的情况。而客户端的用户在无法登录客户端的时候，经常会反复尝试，加重了服务器的负担、导致最后所有的Mongrel进程都挂掉。 最后，经过不懈努力，在现有的3台低端服务器上，可以满足每天500万次的请求。在这里，我将我的一些心得和研究成果总结出来，与大家分享。 文档地址：http://docs.google.com/Doc?id=ddcvzh74_28f9xppqfh 文章发布在Google Docs上，欢迎大家在其上做批注，有意见和建议也可以在此留言。文章按照知识共享“署名 3.0 中国大陆”许可协议发布，欢迎转载。]]></description>
			<content:encoded><![CDATA[<p>2006初，我接到了公司分配的一个遗留项目，让我负责一个基于C/S的系统的服务器端。其实是系统是基于HTTP协议的，因为负责客户端的同事对于服务器端编程不甚了解，虽然使用PHP对熟悉C＋＋的他来说是驾轻就熟，但是在进一步实现更多的功能和更高的性能上就捉襟见肘了。项目是在非常突然的情况下交给我的，因为该同事在客户端上有更多的事情要做。我在分析了他的数据库结构和PHP源代码之后，决定按照与客户端的通讯协议重写他的服务器端。为了能应付老板苛刻的时间限制，我打算使用正在学习的Ruby on Rails。后来，项目在功能上非常顺利地交付了。</p>
<p>两年过去了，随着客户端数量的不断增加、客户端功能的增加、与服务器端交互数据的增加、老板对功能的要求不断增加，我在这个项目上走了不少弯路，尤其是在部署——或者说是架构——方面。</p>
<p>我遇到的最大的问题就在于并发链接数上。服务器与客户端的每次交互的数据量并不大，但内容无法缓存。起初用的是Nginx/Apache+Mongrel 的部署方式，但当遇到大量并发请求时，常常会遇到Mongrel进程死掉的情况。而客户端的用户在无法登录客户端的时候，经常会反复尝试，加重了服务器的负担、导致最后所有的Mongrel进程都挂掉。</p>
<p>最后，经过不懈努力，在现有的3台低端服务器上，可以满足每天500万次的请求。在这里，我将我的一些心得和研究成果总结出来，与大家分享。 </p>
<hr />
<p>文档地址：<a href="http://docs.google.com/Doc?id=ddcvzh74_28f9xppqfh">http://docs.google.com/Doc?id=ddcvzh74_28f9xppqfh</a></p>
<p>文章发布在Google Docs上，欢迎大家在其上做批注，有意见和建议也可以在此留言。文章按照<a href="http://creativecommons.org/licenses/by/3.0/deed.zh">知识共享“署名 3.0 中国大陆”许可协议</a>发布，欢迎转载。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/ruby-on-rails-deployment-schemes.html/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Redmine</title>
		<link>http://shiningray.cn/redmine.html</link>
		<comments>http://shiningray.cn/redmine.html#comments</comments>
		<pubDate>Wed, 16 Jan 2008 08:23:10 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[介绍]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[trac]]></category>

		<guid isPermaLink="false">http://shiningray.cn/redmine.html</guid>
		<description><![CDATA[Redmine是基于Ruby/Rails的一个项目管理软件。比较类似的则是基于Python的Trac，相比之下，Redmine有很多优势： 简单的安装、配置和部署Redmine利用rake、rails的db migration安装很方便，Trac则要用到命令行的trac-admin进行配置，以及每个项目有单独的ini配置文件 方便的用户和权限管理Redmine支持多LDAP认证、还支持用户自己注册，然后通过邮件激活。Trac依然需要使用trac-admin来配置，而且默认的用户登录方式是HTTP验证，基于cookie的还需通过插件实现 基于Web的多项目管理Trac创建和配置新项目需要使用trac-admin AjaxRedmine通过Ajax在某些方面提供了更好的用户体验，如代码仓库的浏览 多语言包括简体中文 多种SCM包括SVN、CVS、Darcs、Mercurial、Bazaar，是通过调用它们的可执行文件来实现的。 trac的很多功能都需要通过trac-admin在命令行方式下进行配置，不易上手，这方面Redmine则十分方便。 其实功能方面，Redmine更多地是模仿Sourceforge的功能，比如新闻、文档、下载等，目的是建立这种适合团队协作的平台。我估计以后Rubyforge可能会迁移到Redmine上去。]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.redmine.org">Redmine</a>是基于Ruby/Rails的一个项目管理软件。比较类似的则是基于Python的<a href="http://trac.edgewall.org/">Trac</a>，相比之下，Redmine有很多优势：</p>
<ul>
<li><strong>简单的安装、配置和部署</strong><br />Redmine利用rake、rails的db migration安装很方便，Trac则要用到命令行的trac-admin进行配置，以及每个项目有单独的ini配置文件</li>
<li><strong>方便的用户和权限管理</strong><br />Redmine支持多LDAP认证、还支持用户自己注册，然后通过邮件激活。Trac依然需要使用trac-admin来配置，而且默认的用户登录方式是HTTP验证，基于cookie的还需通过插件实现</li>
<li><strong>基于Web的多项目管理</strong><br />Trac创建和配置新项目需要使用trac-admin</li>
<li><strong>Ajax</strong><br />Redmine通过Ajax在某些方面提供了更好的用户体验，如代码仓库的浏览</li>
<li><strong>多语言</strong><br />包括简体中文</li>
<li><strong>多种SCM</strong><br />包括SVN、CVS、Darcs、Mercurial、Bazaar，是通过调用它们的可执行文件来实现的。</li>
</ul>
<p>trac的很多功能都需要通过trac-admin在命令行方式下进行配置，不易上手，这方面Redmine则十分方便。</p>
<p>其实功能方面，Redmine更多地是模仿<a href="http://www.sourceforge.net">Sourceforge</a>的功能，比如新闻、文档、下载等，目的是建立这种适合团队协作的平台。我估计以后<a href="http://www.rubyforge.org">Rubyforge</a>可能会迁移到Redmine上去。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/redmine.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>升级到RubyGems 1.0</title>
		<link>http://shiningray.cn/sheng-ji-dao-rubygems-10.html</link>
		<comments>http://shiningray.cn/sheng-ji-dao-rubygems-10.html#comments</comments>
		<pubDate>Tue, 08 Jan 2008 12:31:27 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[备忘]]></category>
		<category><![CDATA[gem]]></category>
		<category><![CDATA[mongrel]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://shiningray.cn/sheng-ji-dao-rubygems-10.html</guid>
		<description><![CDATA[虽然前一段时间Ruby社区推出了很多重大的更新，如RubyGems发布了1.0，Rails发布了2.0，Ruby发布了1.9测试版。但是却潜藏了很多危险。比如在Windows平台下，升级了RubyGems之后，会导致我现有的Mongrel无法启动，原因在于RubyGems1.0修改了系统的识别，将mswin32改成了x86-mswin32，从而导致gem_plugin无法正确定位相应gem。 修复的方法也很简单，只需要先删除Mongrel再重新安装： ::先升级RubyGems到1.0 gem update --system ::卸载再重装Mongrel gem uninstall mongrel gem i mongrel 另外还在一些人的BLOG中发现Rails2.0刚发布时，是要求使用RubyGems0.9.5，也会出现一些问题。幸而我暂时没有迁移到Rails2.0上，也还没有遇到问题。 从这一个事件上也可以看出Ruby社区相对于Python/Perl的社区来说，还不是十分的成熟。]]></description>
			<content:encoded><![CDATA[<p>虽然前一段时间Ruby社区推出了很多重大的更新，如RubyGems发布了1.0，Rails发布了2.0，Ruby发布了1.9测试版。但是却潜藏了很多危险。比如在Windows平台下，升级了RubyGems之后，会导致我现有的Mongrel无法启动，原因在于RubyGems1.0修改了系统的识别，将mswin32改成了x86-mswin32，从而导致gem_plugin无法正确定位相应gem。</p>
<p>修复的方法也很简单，只需要先删除Mongrel再重新安装：</p>
<pre>
::先升级RubyGems到1.0
gem update --system
::卸载再重装Mongrel
gem uninstall mongrel
gem i mongrel</pre>
<p>另外还在一些人的BLOG中发现Rails2.0刚发布时，是要求使用RubyGems0.9.5，也会出现一些问题。幸而我暂时没有迁移到Rails2.0上，也还没有遇到问题。</p>
<p>从这一个事件上也可以看出Ruby社区相对于Python/Perl的社区来说，还不是十分的成熟。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/sheng-ji-dao-rubygems-10.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ruby 1.9.0 发布</title>
		<link>http://shiningray.cn/64.html</link>
		<comments>http://shiningray.cn/64.html#comments</comments>
		<pubDate>Wed, 26 Dec 2007 05:04:38 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[介绍]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://shiningray.cn/2007/12/26/64/</guid>
		<description><![CDATA[来自Ruby官方网站的最新消息，Matz已经发布了1.9的开发版本作为献给大家的圣诞礼物。 1.9的性能较1.8有大幅提升（请参考相关测试结果），并且添加了大量新的语言的特性。 这无疑对于Ruby爱好者是一个福音，但前不久刚发布Rails 2.0还不能完全正常运行于Ruby 1.9之上，包括诸如Mongrel之类的其他组件，所以在很长时间内Ruby 1.8将仍然是主流的平台。不过相信Ruby社区和相关社区的朋友们会一起努力，使这个迁移快速而顺利地完成（就像当年Python 2.5发布的时候）]]></description>
			<content:encoded><![CDATA[<p>来自<a href="http://www.ruby-lang.org/en/news/2007/12/25/ruby-1-9-0-released/">Ruby官方网站的最新消息</a>，Matz已经发布了1.9的开发版本作为献给大家的圣诞礼物。</p>
<p>1.9的性能较1.8有大幅提升（请参考<a href="http://rubychan.de/share/yarv_speedups.html">相关测试结果</a>），并且添加了大量<a href="http://eigenclass.org/hiki/Changes+in+Ruby+1.9">新的语言的特性</a>。</p>
<p>这无疑对于Ruby爱好者是一个福音，但前不久刚发布Rails 2.0还不能完全正常运行于Ruby 1.9之上，包括诸如Mongrel之类的其他组件，所以在很长时间内Ruby 1.8将仍然是主流的平台。不过相信Ruby社区和相关社区的朋友们会一起努力，使这个迁移快速而顺利地完成（就像当年Python 2.5发布的时候）</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/64.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Rails + Memcached = Undefined Class/Module?</title>
		<link>http://shiningray.cn/rails-memcached-undefined-classmodule.html</link>
		<comments>http://shiningray.cn/rails-memcached-undefined-classmodule.html#comments</comments>
		<pubDate>Mon, 10 Dec 2007 08:28:49 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[备忘]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://shiningray.cn/2007/12/10/rails-memcached-undefined-classmodule/</guid>
		<description><![CDATA[问题描述： 当使用memcached并将Model对象保存在其中时，若要取出这个缓存对象时，可能会找不到Model的类，并抛出“Undefined Class/Module SomeClass”的错误。 例如： if not (genres = Cache.get(key)) genres = Genre.find(:all, :condition =&#62; "platform_id = 1") Cache.put(key, genres, 60*60*24) # cache for 1 day end 解决方案 要解决这个问题，可以在引用到该对象之间，先引用其类。比如，在前面的代码前面加入要引用的类Genre： Genre if not (genres = Cache.get(key)) genres = Genre.find(:all, :condition =&#62; "platform_id = 1") Cache.put(key, genres, 60*60*24) # cache for 1 day end 还有更好地方法是在Controller的before_filter中加载所依赖的所有Model： before_filter :preload_models [...]]]></description>
			<content:encoded><![CDATA[<p><strong>问题描述：</strong><br />
当使用memcached并将Model对象保存在其中时，若要取出这个缓存对象时，可能会找不到Model的类，并抛出“Undefined Class/Module SomeClass”的错误。<br />
例如：</p>
<pre class="code" lang="ruby">if not (genres = Cache.get(key))
  genres = Genre.find(:all, :condition =&gt; "platform_id = 1")
  Cache.put(key, genres, 60*60*24) # cache for 1 day
end</pre>
<p><strong>解决方案</strong><br />
要解决这个问题，可以在引用到该对象之间，先引用其类。比如，在前面的代码前面加入要引用的类Genre：</p>
<pre class="code" lang="ruby">
Genre
if not (genres = Cache.get(key))
  genres = Genre.find(:all, :condition =&gt; "platform_id = 1")
  Cache.put(key, genres, 60*60*24) # cache for 1 day
end</pre>
<p>还有更好地方法是在Controller的before_filter中加载所依赖的所有Model：</p>
<pre class="code" lang="ruby">before_filter  :preload_models
def preload_models
  Model1
  Model2
  ...
  ...
  ...
  Model9
end</pre>
<p><strong>结论</strong><br />
这个问题应该是Rails的一个Bug，不知道在2.0中有没有解决。我猜测是因为Rails中很多类信息是lazy load的，而从memcached中取出时没有附带类信息，也不知道如何加载，而通过上面的方法预先加载所需的类和其相关的类的信息，便解决了这个问题。</p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/rails-memcached-undefined-classmodule.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Rails 2.0: It&#8217;s done!</title>
		<link>http://shiningray.cn/rails-20-its-done.html</link>
		<comments>http://shiningray.cn/rails-20-its-done.html#comments</comments>
		<pubDate>Fri, 07 Dec 2007 17:58:00 +0000</pubDate>
		<dc:creator>ShiningRay</dc:creator>
				<category><![CDATA[介绍]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://shiningray.cn/2007/12/07/rails-20-its-done/</guid>
		<description><![CDATA[原文：http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done 但是，目前仍应该将所有的必须的gems（除了Mongrel）freeze到正在维护的Rails应用中。请运行： rake rals:freeze:gems]]></description>
			<content:encoded><![CDATA[<p>原文：<a href="http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done">http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done</a></p>
<p>但是，目前仍应该将所有的必须的gems（除了Mongrel）freeze到正在维护的Rails应用中。请运行：</p>
<blockquote><pre>rake rals:freeze:gems</pre>
</blockquote>
<p><a href="http://weblog.rubyonrails.org/2007/12/7/rails-2-0-it-s-done"></a></p>
]]></content:encoded>
			<wfw:commentRss>http://shiningray.cn/rails-20-its-done.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
