<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>Woody</title>
    <description></description>
    <link>http://woody-420420.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>Rails中如何更加优雅的处理文件上传</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/216582" style="color:red;">http://woody-420420.javaeye.com/blog/216582</a>&nbsp;
          发表时间: 2008年07月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; 通常，在rails中处理文件上传，我们会这么做，在view中生成相应html tag：</p>
<pre name="code" class="html">&lt;input name="my_uploaded_file" type="file"&gt;</pre>
<p>&nbsp; 然后，在controller中，我们可以通过params[:my_uploaded_file]得到上传文件，进行相应处理。<br />&nbsp; 假如，现在作为controller的开发人员，我不知道view开发人员将input的name设置成什么？那应该如何处理呢？可能这个例子有些极端，绝大部分时候不存在这个问题。那再假如，现在我们要利用rails实现一个api，该api的功能是实现文件上传，rails后台得到该文件，并进行处理。由于这是一个开放api，用户可能通过jsp，rails，php，甚至桌面程序等来访问我们的api，这个时候又该如何处理呢？（当然，你可以在api specification中定死，说，我的地盘我做主，你们要使用我的api上传文件，form-data的name必须是XXX！）<br />&nbsp; 在asp.net中倒是可以很简单的解决这个问题，我记得asp.net中的form提供了一个参数，开发人员可以直接访问该参数得到此form中所有上传的文件，而不必管name是什么。但是在rails中，似乎没有这一机制（如果是我疏忽，望知情同学告知！）。不过，现在稍加修改，rails也同样能象asp.net那样处理上传文件。<br />&nbsp; 首先，我先贴出解决方案，为方便起见，在environment.rb中加入如下代码：</p>
<pre name="code" class="ruby">module ActionController
  class AbstractRequest
    class &lt;&lt; self
      alias_method :_original_read_multipart_, :read_multipart
      
      def read_multipart(body, boundary, content_length, env)
        params = _original_read_multipart_(body, boundary, content_length, env)
        file_data = params.values.dup.flatten.select do |form_data| 
          form_data.respond_to?(:original_path) &amp;&amp; !form_data.original_path.blank?
        end
        params.merge!({"file_data[]" =&gt; file_data})
      end
      
      private :_original_read_multipart_, :read_multipart
    end
  end
end</pre>
&nbsp;
<p>&nbsp; 我先大致解释下这段代码：在view中的某个form中，当你加入{:multipart =&gt; true}参数，rails会调用AbstractRequest中的私有类方法read_multipart，得到form中所有的参数。所以，我的解决方案就是截获此方法，调用原始版本后，遍历form中的所有值，将所有上传的文件放到一个file_data中去，塞回params中。这样，在controller中，就可以通过params[:file_data]得到form中所有上传文件进行处理，而并不用理会input的name到底是什么。这样做，我们甚至可以处理这种情况：</p>
<pre name="code" class="html">&lt;input name="same_name" type="file"&gt;
&lt;input name="same_name" type="file"&gt;</pre>
&nbsp;
<p>&nbsp; 就算这两个input的名字相同，通过file_data，仍然可以得到两个文件。这样，controller得到上传文件的代码便独立了，和view没有任何关系，你爱咋写name都行。另外，这行代码有两个地方值得注意：</p>
<pre name="code" class="ruby">params.merge!({"file_data[]" =&gt; file_data})</pre>
<p>&nbsp; 1. 关于key，不能想当然的用:file_data[]。因为rails内部处理参数的时候，调用了key.include?，所以，使用Symbol必然出错。<br />&nbsp; 2. [] 是必须的，否则rails不能将所有的上传文件塞到一个数组中去，这样，在controller中，你将只能通过file_data得到其中一个文件。<br />&nbsp; 另外，上述代码是基于rails 2.0.2。加入你目前正在使用rails 1.*。则可以使用如下代码实现相同的功能：</p>
<pre name="code" class="ruby">class CGI #:nodoc:
  module QueryExtension
    
    alias_method :_original_read_multipart_, :read_multipart
    def read_multipart(boundary, content_length)
      params = _original_read_multipart_(boundary, content_length)
      file_data = params.values.dup.flatten.select do |form_data| 
        form_data.respond_to?(:original_path) &amp;&amp; !form_data.original_path.blank?
      end
      params.merge!({"file_data[]" =&gt; file_data})
    end

    private :_original_read_multipart_, :read_multipart
  end
end</pre>
&nbsp;
<p>&nbsp; 2008.7.19&nbsp; 22:22&nbsp; 星期六</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/216582#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 19 Jul 2008 22:23:01 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/216582</link>
        <guid>http://woody-420420.javaeye.com/blog/216582</guid>
      </item>
      <item>
        <title>慎用typo(theme_support)的换肤机制</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/215970" style="color:red;">http://woody-420420.javaeye.com/blog/215970</a>&nbsp;
          发表时间: 2008年07月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>前言</h1>
</li>
</ul>
<p>&nbsp; 本文提到的<a href="https://rubyforge.org/projects/typo/" target="_blank">typo</a>版本是目前最新的5.0.3.98.1，<a href="https://rubyforge.org/projects/theme-generator/" target="_blank">theme_support</a>版本是1.3.0。在typo中，我们看到了很好很花哨的换肤机制，而theme_support则是从typo中抽取出来的一个plugin，以供其他程序进行换肤操作。<br />&nbsp; 先简单介绍下typo换肤的使用。<a href="http://typogarden.org/" target="_blank">typogarden</a>提供了typo十分丰富的皮肤，我们只需要下载喜欢的皮肤，解压，放在typo程序根目录的theme目录下即可，大致的结构图如下所示：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18020/49c22e18-d99f-3853-a474-235e6f62169a.png" height="467" alt="" width="255" /><br />&nbsp; 然后，就可以在admin界面选择自己的皮肤。的确十分方便。但是，使用这种机制，会存在一个严重的性能问题，下面将详细分析问题的原理及其我目前所知的解决方案。</p>
<ul>
<li>
<h1>Typo的换肤原理</h1>
</li>
</ul>
<p>&nbsp; 通常，我们会将程序的图片，css等与皮肤相关的文件放在网站的public目录下，在view中直接引用即可。但是从上图我们可以看到，typo将新皮肤的所有相关文件都存放在theme目录下的各个子目录下。那么typo是如何引用这些文件的呢？下面，我们随便打开某一个皮肤下面的layout文件，例如theme/typographic/layouts下面的default.html.erb文件，可以看到如下代码：</p>
<pre name="code" class="html">&lt;%= stylesheet_link_tag '/stylesheets/theme/style.css', :media =&gt; 'all' %&gt;</pre>
<p>&nbsp; 你仔细搜查一下程序，绝对发现不了/stylesheets/theme目录，乱引用？当然不是，我们可以在routes.rb中发现如下的route信息：</p>
<pre name="code" class="ruby">    get.with_options(:controller =&gt; 'theme', :filename =&gt; /.*/, :conditions =&gt; {:method =&gt; :get}) do |theme|
      theme.connect 'stylesheets/theme/:filename', :action =&gt; 'stylesheets'
      theme.connect 'javascripts/theme/:filename', :action =&gt; 'javascript'
      theme.connect 'images/theme/:filename',      :action =&gt; 'images'
    end</pre>
&nbsp;
<p>&nbsp; 很经典的route配置，原来在typo中，所有对css，javascript，image的引用，不是通过直接引用public目录下的文件，而是通过一个传统的controller：ThemeController来完成的。至于ThemeContorller具体代码，这里不详细谈，因为不是本文的重点，无非就是根据当前选择的皮肤（在admin界面选择的），到相应的皮肤目录（比如：theme/typographic）下，将各个文件找到，然后通过send_file的方式发送到客户端浏览器。那执行完一次action后，controller到底应该执行怎么样的render操作呢？（通过上图，我们看到不同的view文件，在各自的theme子目录下）<br />&nbsp; 在ApplicationController中，有如下代码：</p>
<pre name="code" class="ruby">class ApplicationController &lt; ActionController::Base
......
  def setup_themer
    # Ick!
    self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/themes/#{this_blog.theme}/views")
  end
end</pre>
<p>&nbsp;&nbsp; 大致讲下，setup_themer方法的作用是根据目前皮肤配置（this_blog.theme），将controller的view_paths设置为某一个皮肤的目录（比如：theme/typographic/views），这样，在执行render操作的时候，将使用皮肤目录下的view,layout等等（还记得前面提到的layout中引用css的方法么？重要！）。并且，将setup_themer方法设置为需要换肤的contorller的before_filter。于是，当我们执行程序的时候，就可以达到动态换肤的目的。<br />&nbsp; 大致原理如是。。。</p>
<ul>
<li>
<h1>性能问题的由来</h1>
</li>
</ul>
<p>&nbsp; 前面我讲了，这种机制会存在一个严重的性能问题，是怎么来的呢？在传统方式下，我们将css，images，js都放在public目录下，view进行引用的时候，在web服务器层面，就完成了文件的引用，发送操作。但是在typo这种机制下，每引用一个css，图片，js，web服务器会将请求route到rails，通过rails的ThemeController来处理请求，返回文件。这都还算小事，在一次request，response周期，这样一个额外操作往往占用不了多大的百分比。但是，我们知道ThemeController是rails中一个普通的Controller，它也继承自ApplicationController。通常，我们会将很多程序通用逻辑放在ApplicationController中来做，比如：验证用户合法性，处理本地化等等，而这些操作，大部分都是访问数据库。也就是说，我们通过ThemeController，仅仅想得到一个css或者图片文件，但是ApplicationController仍然会初始化，执行相应的操作（重复，无用的操作）。这就是性能问题的根源。口说无凭，下面的数据揭示了一切：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18016/72a6fba1-dd98-3036-8ace-4e6bb1b22866.png" height="362" alt="" width="281" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img src="http://woody-420420.javaeye.com/upload/picture/pic/18014/72522c09-5551-3f59-bd0a-f82663c2be1a.png" height="369" alt="" width="279" /><br />&nbsp; 上面两个性能测试是我随机访问一次typo首页得到的，我们可以看到，对css，image，js的请求，耗费了数十毫秒的操作。为什么请求一个css会耗费如此多时间呢？从下面的图中，我们可以看到原因：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18012/df4d2e29-8e08-3940-9dba-15a1d43054f0.png" height="562" alt="" width="587" /><br />&nbsp; Oh~My god！我出来打个酱油而已，咋搞出个db访问占用了这么长的周期？原因就是我前面提到的，访问ThemeController的时候，ApplicationContorller偷偷摸摸的作祟着（执行了一些业务逻辑的操作，用户验证等）。具体执行的多余db操作如下图所示：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18010/c0ffc657-4b56-359e-8338-460d4541dfa3.png" height="120" alt="" width="800" /><br />&nbsp; 什么show tables，select blogs，select triggers之类操作啊~我请求一个css，何必呢？<br />&nbsp; 性能问题的原因分析完毕！不要忘了，我举例的这些简单的皮肤中，只是简单的十几个css，js，images等。如果你真的使用这种机制在自己的程序中，我想，一个稍微复杂点的皮肤不止十个图片文件。加入我们要在皮肤中引用数十个此类文件，那么性能问题将是十分严重的。</p>
<ul>
<li>
<h1>解决方案</h1>
</li>
</ul>
<p>&nbsp; 目前，我找到三个解决方案处理这个性能问题。</p>
<p>&nbsp; 1. 在ApplicationController中，执行某些业务逻辑的时候，判断一下controller，如果是ThemeController，则跳过。这样做的好处是不用变动typo中换肤方法的使用，仍然将皮肤放在theme目录下即可。但是，这样做似乎&ldquo;侵入性&rdquo;太强，仅仅为了一个皮肤，修改业务逻辑，实在有些得不偿失，因此，这种方案不是一个很好的方案。</p>
<p>&nbsp; 2. 要想不影响已有的业务逻辑解决这个性能问题，我们则需要手动做一些修改。将某一个皮肤放到theme目录下以后，我们收到将css，image，js拷贝到public的相应目录下。比如：将theme/typographic/stylesheets下的所有css文件拷贝到public/stylesheets/typographic目录下，然后，手动将皮肤中所有对css的引用修改为引用public下面相应的目录，比如：我将前面提到的default.html.erb中对css的引用修改为如下所示：</p>
<pre name="code" class="html">&lt;%= stylesheet_link_tag 'typographic/style.css', :media =&gt; 'all' %&gt;</pre>
<p>&nbsp; 然后，将关于theme的roues信息全部删除，让web server为我们完成这个工作。这样，既能解决性能问题，还不影响业务逻辑。只不过，需要不少的手动工作（要将皮肤中所有关于css，js，images的引用修改到public目录下）。</p>
<p>&nbsp; 3. 一劳永逸，按照第二种方法重写theme_support plugin。。。<br />&nbsp; 这里，我按照第二种方法修改了typo的theme机制，主观上来讲，页面访问速度有不小的改进，不像原来，进度条象个蚂蚁一样一耸一耸的~最后，客观起见，还是来一张修改后的性能测试结果：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18018/8a7a650b-a03d-3bfd-b99a-919b2039174c.png" height="119" alt="" width="255" /><br />&nbsp; 整个世界清静了不少~不是吗？：）</p>
<p>&nbsp;</p>
<p>&nbsp; 2008.7.17&nbsp; 23:30&nbsp; 星期四</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/215970#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 17 Jul 2008 23:29:15 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/215970</link>
        <guid>http://woody-420420.javaeye.com/blog/215970</guid>
      </item>
      <item>
        <title>Ruby中&amp;&amp;操作符的妙用(旁门左道)</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/213240" style="color:red;">http://woody-420420.javaeye.com/blog/213240</a>&nbsp;
          发表时间: 2008年07月09日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; 几乎所有的现代编程语言都提供了&amp;&amp;操作符，ruby也不例外。我想每个人都知道&amp;&amp;的用法。但是在ruby中，利用&amp;&amp;可以实现一些&ldquo;诡异&rdquo;的用法，如下例子：</p>
<p>&nbsp; 1. 基于这样一个事实：几乎ruby中的所有expression都有返回值（甚至if，case等等），例如：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">a = 10 + 20 # =&gt; 30
some_bool = true # =&gt; true</pre>
<p>&nbsp;&nbsp; 利用这样的事实，怎么和&amp;&amp;一起&ldquo;妙用&rdquo;呢？现在假设有一个数组，比如[1,2,3,"woody",4,"woody"]，我们要写一段程序，在遍历数组的时候，如果碰到元素"woody"，则将其打印出来，但是只打印一次；如果是其他元素，则执行其他操作。通常，在其他语言中，我们会使用一个flag来完成，比如：<br /></p>
<pre name="code" class="ruby">flag = false
arr = [1,2,3,"woody",4,5,"woody"]
for e in arr
  if e == "woody" &amp;&amp; !flag
    p e
    flag = true
  end
end</pre>
<p>&nbsp;&nbsp; 在ruby中，我们可以利用上面提到的原理，如下编码：<br /></p>
<pre name="code" class="ruby">flag = false
arr = [1,2,3,"woody",4,5,"woody"]
for e in arr
  p e if e == "woody" &amp;&amp; !flag &amp;&amp; (flag = true)
end</pre>
<p>&nbsp;&nbsp; if？flag=true？通常其他程序员看到这样的代码会坚信是错误的，C#在编译过程就会报错。但是，在ruby中，你确实可以使用这样的&ldquo;九阴真经&rdquo;。尽管，这可能不是一个好的编程习惯，但是，在一些情况下，这样做确实可以使你的ruby代码简洁很多。<br />&nbsp; （这里，Enumerable中的方法我们就先不考虑了。或许这个例子举得不是很好~）</p>
<p>&nbsp;</p>
<p>&nbsp; 2. 同样还是基于expression的返回值，考虑如下代码：<br /></p>
<pre name="code" class="ruby">(1+2)&amp;&amp;(3+4) #=&gt; 7</pre>
<p>&nbsp;&nbsp; ruby会对&amp;&amp;左右的expression进行计算，至于返回值，当然如果左端操作不为nil或者false的话，就返回右端计算结果。这里，如果结合Array的构造函数，我们可以用如下代码，十分简洁的生成斐波那契数列：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">x,y = 0,1
Array.new(10) {|i| [0,1].include?(i) ? 1 : (x,y = y,x+y)&amp;&amp;(x+y) }
#=&gt;[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]</pre>
<p>&nbsp;&nbsp; 反正我是想了半天，没想到在C，C++或者C#中使用什么高级方法能写出更简洁的代码<img src="../../../images/smiles/icon_biggrin.gif" alt="" /></p>
<p>&nbsp; 3...</p>
<p>&nbsp; 4...</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/213240#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 09 Jul 2008 22:30:20 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/213240</link>
        <guid>http://woody-420420.javaeye.com/blog/213240</guid>
      </item>
      <item>
        <title>Ruby生成斐波拉契数列</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/213060" style="color:red;">http://woody-420420.javaeye.com/blog/213060</a>&nbsp;
          发表时间: 2008年07月09日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; 不管你是用c，c++，c#，java。。。不管你是用循环，递归，lambdas。。。我保证，你写的斐波拉契数列生成算法，没有用ruby写来得简洁：</p>
<pre name="code" class="ruby">x,y = 0,1
Array.new(10) {|i| [0,1].include?(i) ? 1 : (x,y = y,x+y)&amp;&amp;(x+y) }
#=&gt;[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]</pre>
<p>&nbsp; 不懂的语言不敢保证~呵呵<img src="../../images/smiles/icon_biggrin.gif" alt="" />
 &nbsp; </p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/213060#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 09 Jul 2008 13:52:11 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/213060</link>
        <guid>http://woody-420420.javaeye.com/blog/213060</guid>
      </item>
      <item>
        <title>使用jquery动态修改dom元素属性在IE下的问题</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/209758" style="color:red;">http://woody-420420.javaeye.com/blog/209758</a>&nbsp;
          发表时间: 2008年06月30日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; 当我们使用jquery的时候，如果要动态修改某一元素的属性，比如一个button的onclick属性，我们会很容易的写出如下代码：</p>
<p>&nbsp;</p>
<pre name="code" class="js">$(“#some_element”).attr('onclick',&quot;//some new operation&quot;);</pre>
<p>&nbsp; 但是这段代码在FireFox下会按我们的意图正确执行，但是在IE下什么动静都没有。关于问题的描述，可以参考：</p>
<p>&nbsp; http://www.nabble.com/onClick-prepend-td15194791s27240.html</p>
<p>&nbsp; http://ajaxian.com/archives/evaling-with-ies-windowexecscript</p>
<p>&nbsp; 解决的方法也很简单：</p>
<pre name="code" class="js">$(“#some_element”).unbind('click').removeAttr('onclick').click(function(){ 
//new operation
});</pre>
<p>&nbsp;&nbsp; 当然，这里还有个值得注意的地方就是，jquery使用click来存储客户端的onclick。</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/209758#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 30 Jun 2008 14:34:06 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/209758</link>
        <guid>http://woody-420420.javaeye.com/blog/209758</guid>
      </item>
      <item>
        <title>Linux下的几款svn gui工具</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/204924" style="color:red;">http://woody-420420.javaeye.com/blog/204924</a>&nbsp;
          发表时间: 2008年06月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; 近日，由于git-svn不支持remote branch的合并（没想到啊~没想到！），便试用了几款linux下的svn gui工具，打算专门用来做branch之间的合并用。目前，试用了如下几种：rapidsvn,kdesvn,esvn。</p>
<p>&nbsp;</p>
<p>&nbsp; 1. rapidsvn</p>
<p>&nbsp; 本认为rapidsvn是最好用的，但是玩了半天觉得不尽人意。首先，ubuntu的源里面尽然只有0.9.4-3这个版本，似乎是06年的release，小小郁闷了一把，为了图方便，懒得去下最新版本来自己make，就凑合着用这个版本。</p>
<p>&nbsp; 缺点：在ubuntu8.0.4下面，last changed列居然是乱码，或许是字符集的问题，没深入研究，还不影响具体功能；不支持直接打开一个已存在的svn目录；每一个操作没有svn执行的详细命令；速度一般。</p>
<p>&nbsp; 优点：界面比较简洁，操作使用还算方便。</p>
<p>&nbsp;</p>
<p>&nbsp; 2.kdesvn</p>
<p>&nbsp; 缺点：速度似乎其慢。我查看一个代码库的log信息，居然弹出一个窗口，显示***.*字节传输，如此之类；操作不是很简单，刚安装好，我还晕乎了半天。</p>
<p>&nbsp; 优点：个人觉得界面挺专业。</p>
<p>&nbsp;</p>
<p>&nbsp; 3.esvn</p>
<p>&nbsp; 这是我最终选定的。</p>
<p>&nbsp; 缺点：速度一般，update整个库，查看log较多的目录，时间较长。</p>
<p>&nbsp; 优点：界面很简单，操作也一目了然，刚使用发现神似当年的vss，呵呵；支持通过open working directory直接打开一个已存在的svn目录；可以方便的看到每一个操作的svn命令，不熟悉svn命令的同学在享受gui方便的同时，也可以熟悉svn命令；</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/204924#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 17 Jun 2008 22:59:04 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/204924</link>
        <guid>http://woody-420420.javaeye.com/blog/204924</guid>
      </item>
      <item>
        <title>遭遇mocha中的两个小陷阱</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/201082" style="color:red;">http://woody-420420.javaeye.com/blog/201082</a>&nbsp;
          发表时间: 2008年06月06日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp; mocha是ruby下的一个mock框架。</p>
<p>&nbsp; 关于mock object的相关信息，请参考：<a href="http://www.mockobjects.com/" target="_blank">http://www.mockobjects.com/</a> </p>
<p>&nbsp; 关于mocha的相关信息，请参考：<a href="http://mocha.rubyforge.org/" target="_blank">http://mocha.rubyforge.org/</a> </p>
<p>&nbsp; 基本概念及使用方法这里不谈，就谈谈最近在mocha碰到的两个小陷阱。</p>
<p>&nbsp;</p>
<p>&nbsp; 1.情况是这么个情况，完成了测试代码与功能代码后，我开始着手进行测试代码的重构（需要吗？不需要吗？）。首先，我盯上了刚完成测试代码时，就看着不爽的一段代码，这里整理如下：</p>
<pre name="code" class="ruby">def generate_some_mock_object(return1,return2)
  mo=mock()
  mo.stubs(:mock_method1).returns(return1)
  mo.stubs(:mock_method2).returns(return2)
  mo
end</pre>
<p>&nbsp;&nbsp; 这是我测试代码里的一个helper方法，用于生成一个mock对象，并stub了两个方法。味道是不怎么好，不是吗？于是，我决定重构她。扫了眼mocha的文档，发现mocha给出了一个mock对象生成的例子：</p>
<pre name="code" class="ruby">product = mock('ipod_product', :manufacturer =&gt; 'ipod', :price =&gt; 100)</pre>
<p>&nbsp;&nbsp; 哇哈哈，一个hash就搞定，而且还不用定义一个额外的局部变量，happy，动手将我的helper方法改为如下形式：</p>
<pre name="code" class="ruby">def generate_some_mock_object(return1,return2)
  mock(:mock_method1 =&gt; return1,:mock_method2 =&gt; return2)
end</pre>
<p>&nbsp;&nbsp; 我happy的重新跑了一把测试，一串&ldquo;F&rdquo;出现在我面前，提示了一些诸如&ldquo;expected call 1, not call 0&rdquo;之类的错误。我就纳闷了，不都是生成一个mock对象，mock了两个stud方法么？why？仔细查看mocha文档和源代码，才发现通过mock(...)方法生成mock对象的时候，如果传递了一串excepted方法的hash时，mocha内部不用通过stubs(...)方法，生成stub方法，而是通过expects(...)方法。而expects(...)生成的方法在测试的时候必须进行调用，才认为测试通过，否则测试被认为失败的。而我的功能代码正好是只调用了{:mock_method1,:mock_method2}中的一个，所以造成测试失败。下面是mock(...)方法的源代码：</p>
<pre name="code" class="ruby">def mock(*arguments, &amp;block)
  name = arguments.shift if arguments.first.is_a?(String)
  expectations = arguments.shift || {}
  mock = name ? Mock.named(name, &amp;block) : Mock.unnamed(&amp;block)
  mock.expects(expectations)
  mocks &lt;&lt; mock
  mock
end</pre>
<p>问题就出在<span style="color: #ff0000;"><strong>mock.expects(expectations)</strong> </span>这句代码上。<br />&nbsp;&nbsp; 难道没办法完成这次重构吗？当然不是，也怪我看文档不仔细，其实mocha为我们提供了另外一个生成mock对象的方法：stub(...) （注意：不要和stubs方法混淆），这里，使用stub方法，可以很轻松的完成我的重构，结果如下：</p>
<pre name="code" class="ruby">def generate_some_mock_object(return1,return2)
  stub(:mock_method1 =&gt; return1,:mock_method2 =&gt; return2)
end</pre>
<p>&nbsp;&nbsp; 重新跑一遍测试，全是"..."，那个happy啊。。。stub方法的源代码如下：</p>
<pre name="code" class="ruby">def stub(*arguments, &amp;block)
  name = arguments.shift if arguments.first.is_a?(String)
  expectations = arguments.shift || {}
  stub = name ? Mock.named(name, &amp;block) : Mock.unnamed(&amp;block)
  stub.stubs(expectations)
  mocks &lt;&lt; stub
  stub
end </pre>
<p>&nbsp; 发现关键所在的<span style="color: #ff0000;"><strong>stub.stubs(expectations)</strong> </span>代码了吧？：）</p>
<p>&nbsp;&nbsp; PS：我发现mocha内部也有臭的迹象，mock和stub~呵呵</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp; 2.事情是这么个事情，后来，我盯上了这么一段测试代码，整理如下：</p>
<pre name="code" class="ruby">def generate_some_stub_method(return1,return2)
  @my_object.stubs(:method1).returns(return1)
  @my_object.stubs(:method2).returns(return2)
end</pre>
<p>&nbsp;&nbsp; 这里，@my_object不是一个mock对象，而是我实在的一个测试用的业务对象，这个helper方法无非就是为该对象生成两个stub方法，以供测试用。我仍然看她不怎么爽，同样，mocha的文档给了一个例子如下：</p>
<pre name="code" class="ruby">object.stubs(:method1 =&gt; :result1, :method2 =&gt; :result2)</pre>
<p>&nbsp;&nbsp; 继续happy，又可以合二为一了。于是，我将这个helper方法重构如下：</p>
<pre name="code" class="ruby">def generate_some_stub_method(return1,return2)
  @my_object.stubs(:method1 =&gt;return1,:method2 =&gt; return2)
end</pre>
<p>&nbsp;&nbsp; 我又happy着重新跑了一遍测试，居然提示我"{:method1 =&gt;return1,:method2 =&gt; return2} is not a Symbol"。这个。。。老大，我承认，他绝对不是一个Symbol，但是你的示意代码和文档明明标明了stubs明明可以接受一个hash的方法集合啊？我有文档作证：</p>
<p>&nbsp;<em> </em><span class="method-signature"><span class="method-name"><em>stubs(method_name) &rarr; expectation<br />&nbsp; stubs(method_names) &rarr; last expectation</em> <br />&nbsp; 并且，我查看源代码，她明明可以处理hash的啊。bug？但是我又错了。。。后来又仔细查看了mocha的源代码，发现，在mocha/object.rb文件中，有如下定义：<br /></span></span></p>
<pre name="code" class="ruby">class Object
  ......
  def stubs(symbol) 
    method = stubba_method.new(stubba_object, symbol)
    $stubba.stub(method)
    mocha.stubs(symbol, caller)
  end
  ......
end</pre>
<p>&nbsp;&nbsp; 哦！问题出在这里！我前面说了，@my_object对象不是一个mock对象，而是一个普通的oject，我只是在这个普通的object上面搞了两个stub方法上去，自然，调用的是Object#stubs方法了，而不是Mock#stubs方法。原来如此~看来，这个重构完不成了，不过还好，不算太臭，就这么着贝~<br /><br />&nbsp; OK！继续接下来的测试吧~：）</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/201082#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 06 Jun 2008 23:21:32 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/201082</link>
        <guid>http://woody-420420.javaeye.com/blog/201082</guid>
      </item>
      <item>
        <title>Ruby On Rails-2.0.2源代码分析（4）-寻找Controller</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/175556" style="color:red;">http://woody-420420.javaeye.com/blog/175556</a>&nbsp;
          发表时间: 2008年03月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>前言</h1>
</li>
</ul>
<p>&nbsp; 
经过一番试验和考虑...一，我尝试了一些思维导图工具（MindMapper，FREEMIND），但我始终没有找到一种好的方式将自己学习Rails源代码的思路表述出来，就此作罢（顺便问问，有研究思维导图的同学么？能否推荐两个自己觉得用起来比较顺手的工具）。二，不再打算整理代码运行顺序图，对不熟悉Rails源代码的同学们来说，这个图可能的确没什么帮助，甚至会把人搞晕。我现在打算从Rails源代码功能点的角度出发，根据具体功能点，结合Rails源代码进行学习，整理，总结。如果某些源代码比较复杂，牵涉类比较繁多，我仍然打算整理一个类图，从一个高的层次了解系统内部对象的关系。<br />
&nbsp; 
前面三篇文章，我们看到了Rails启动的大致功能和流程，包括初始化多种环境变量，初始化Route表，启动Web服务器开始侦听客户端请求。。。那么接下来，当然是开门迎客，等待客户端（浏览器）的请求，并进行处理，最终将结果返回客户端（浏览器）呈现。那么熟悉Rails的同学都知道，首先，Rails必须根据客户端的一个请求，决定将要执行哪个Controller的哪个Action，这也是本文的主要目的。</p>
<ul>
<li>
<h1>寻找Controller</h1>
</li>
</ul>
<p> 首先，我们先来看一看Rails通过客户端请求，查找Controller的大致流程图<br />
<img src="../../../upload/picture/pic/16907/1f87ec9a-761b-3a7f-9532-9ca564c647b9.jpg" height="643" alt="" width="406" />
<br />
&nbsp;<strong> （一）生成DisapatchServlet实例，开始服务吧</strong>
<br />
&nbsp;<span style="color: #0000ff;"> 
源代码：gems/rails-2.0.2/lib/webrick_server.rb</span>
<br />
&nbsp; 
在第一篇文章，讲解Rails的启动时，我提到webrick_server.rb中定义了DisapatchServlet类，此类启动了WEBrick，开始侦听客户端请求。当有客户端请求到达时，会生成一个DispatchServlet实例，具体代码如下：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">class DispatchServlet &lt; WEBrick::HTTPServlet::AbstractServlet

  def initialize(server, options) #:nodoc:
    @server_options = options
    @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])
    # Change to the RAILS_ROOT, since Webrick::Daemon.start does a Dir::cwd(&quot;/&quot;)
    # OPTIONS['working_directory'] is an absolute path of the RAILS_ROOT, set in railties/lib/commands/servers/webrick.rb
    Dir.chdir(OPTIONS['working_directory']) if defined?(OPTIONS) &amp;&amp; File.directory?(OPTIONS['working_directory'])
    super
  end
  ...
end</pre>
<p><br />
&nbsp; 
初始化参数server是web服务器的类型，当然，在我的环境中是WEBRick::HTTPServer。option是一个hash，包含了一些列的环境参数，这里，我将一些比较重要的参数罗列出来：</p>
<table cellspacing="0" border="1" width="449" cellpadding="2">
<tbody>
<tr>
<td valign="top" width="132"><strong>名称</strong>
</td>
<td valign="top" width="133"><strong>类型</strong>
</td>
<td valign="top" width="182"><strong>参考值</strong>
</td>
</tr>
<tr>
<td valign="top" width="132">port</td>
<td valign="top" width="133">Fixnum</td>
<td valign="top" width="182">3000</td>
</tr>
<tr>
<td valign="top" width="132">ip</td>
<td valign="top" width="133">String</td>
<td valign="top" width="182">0.0.0.0(因为我是本机操作)</td>
</tr>
<tr>
<td valign="top" width="132">environment</td>
<td valign="top" width="133">String</td>
<td valign="top" width="182">development</td>
</tr>
<tr>
<td valign="top" width="132">charset</td>
<td valign="top" width="133">String</td>
<td valign="top" width="182">UTF-8</td>
</tr>
<tr>
<td valign="top" width="132">working_directory</td>
<td valign="top" width="133">String</td>
<td valign="top" width="182">D:\Project\Ruby\blog</td>
</tr>
</tbody>
</table>
<p>&nbsp; 
初始化中，首先将option参数赋DispatchServlet的@server_options变量，然后生成一个FileHandler对象，这个对象的具体作用马上会提到。紧接着将Rails的工作目录设置为&ldquo;working_directory&rdquo;，也就是前面文章提到过的RAIL_ROOT。至此，DsipatchServlet的初始化工作完成了。WEBRick会执行此Servlet的service方法。<br />
<br />
&nbsp; 
<strong>（二）是否存在相应html</strong>
<br />
&nbsp; <span style="color: #0000ff;">源代码：gems/rails-2.0.2/lib/webrick_server.rb<br />
</span>
&nbsp; 
第一步生成了Servlet实例，并且，开始执行service方法，我们先来看看service方法的具体内容：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">class DispatchServlet &lt; WEBrick::HTTPServlet::AbstractServlet

  def service(req, res) #:nodoc:
    unless handle_file(req, res)
      begin
        REQUEST_MUTEX.lock unless ActionController::Base.allow_concurrency
        unless handle_dispatch(req, res)
          raise WEBrick::HTTPStatus::NotFound, &quot;`#{req.path}' not found.&quot;
        end
      ensure
        unless ActionController::Base.allow_concurrency
          REQUEST_MUTEX.unlock if REQUEST_MUTEX.locked?
        end
      end
    end
  end
  ...
end</pre>
<p><br />
&nbsp; 
此方法算是处理一个Request的最高层次描述，首先是方法handle_file。这个方法会使用初始化生成的FileHandler对象，查找针对客户端请求的path，在RAILS_ROOT/public目录下是否存在相应的html。例如客户端的请求是<a href="http://localhost:3000/posts">http://localhost:3000/posts</a>
，那么首先Rails就使用FileHandler查找在public根目录下面是否存在posts.html，如果存在的话，则直接向客户端呈现这个html，如果不存在，OK，开始寻找Controller吧。<br />
&nbsp; 
（这里，值得一提是并发控制，默认情况下，Rails只允许一次dispatch一个request，当然，我们可以通过在程序配置文件中设置ActionController::Base.allow_concurrency来改变这个默认的行为。）<br />
&nbsp; 
（我想你应该知道很多Rails书籍提到过，如果你在routes.rb中通过map.root 
:controller=&gt;'posts'的方式，使得当用户通过<a href="http://www.yoursite.com/">http://www.yoursite.com</a>
访问站点时，显示相应的功能页面。但是你必须把public下的index.html删除掉，就是这个原因。）<br />
&nbsp; 
（handle_file源代码不列出，因为他十分简单，只是调用FileHandler的相应方法，而WEBRick暂不在研究范围内。）<br />
<br />
&nbsp;<strong> 
（三）开始Dispatch吧<br />
</strong>
&nbsp; <span style="color: #0000ff;">源代码：gems/rails-2.0.2/lib/webrick_server.rb<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
gems/actionpack-2.0.2/lib/action_controller/dispatcher.rb</span>
<br />
&nbsp; 
第二步说了，如果没有相应的html存在的话，Rails将执行Dispatch过程。我们先来看一看handle_dispatch方法： 
</p>
<pre name="code" class="ruby">def handle_dispatch(req, res, origin = nil) #:nodoc:
  data = StringIO.new
  Dispatcher.dispatch(
    CGI.new(&quot;query&quot;, create_env_table(req, origin), StringIO.new(req.body || &quot;&quot;)),
    ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
    data
  )
  ...
end</pre>
<p><br />
&nbsp; 
这里，可以看到Dispatch的主角Dispatcher对象开始登场了。要执行dispatch，首先生成一个CGI对象（默认CGI类型是&ldquo;query&rdquo;，并且将环境配置传递给CGI对象，包括：主机名称，查询字符串，字符集，Path信息...等，以及默认的Session管理方式），其中的data表示对用户的返回数据（StringIO请参考相应的API）。然后执行Dispatcher的类方法dispatch。此方法内容如下： 
</p>
<pre name="code" class="ruby">class Dispatcher
  class &lt;&lt; self
    # Backward-compatible class method takes CGI-specific args. Deprecated
    # in favor of Dispatcher.new(output, request, response).dispatch.
    def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
      new(output).dispatch_cgi(cgi, session_options)
    end
  ...
end</pre>
<p><br />
&nbsp; 
此类方法将生成一个Dispatcher实例，并调用其dispatch_cgi实例方法（从前面的方法调用，我想不难看出每一个参数是什么）。我们继续接着看dispatch_cgi方法： 
</p>
<pre name="code" class="ruby">def dispatch_cgi(cgi, session_options)
  if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
    @request = CgiRequest.new(cgi, session_options)
    @response = CgiResponse.new(cgi)
    dispatch
  end
rescue Exception =&gt; exception
  failsafe_rescue exception
end</pre>
<p><br />
&nbsp; 
前面也有request和reponse，这里又生成了一个request和response。我是这样理解的，前面handle_dispatch接收的req和res是&ldquo;原生&rdquo;的对象----WEBRick::HTTPRequest和WEBRick::HTTPResponse（是WEBRick和Rails的通讯方式），而这里的request和response是CgiRequest和CgiResponse对象，是针对Dispatch的通讯（CgiRequest和CgiResponse的细节这里先略过，我们看看主流程）。有了request和response对象，真正的dispatch过程开始了： 
</p>
<pre name="code" class="ruby">def dispatch
  run_callbacks :before
  handle_request
rescue Exception =&gt; exception
  failsafe_rescue exception
ensure</pre>
<p><br />
&nbsp; 
先来看一看run_callbacks： 
</p>
<pre name="code" class="ruby">def run_callbacks(kind, enumerator = :each)
  callbacks[kind].send!(enumerator) do |callback|
    case callback
    when Proc; callback.call(self)
    when String, Symbol; send!(callback)
    when Array; callback[1].call(self)
    else raise ArgumentError, &quot;Unrecognized callback #{callback.inspect}&quot;
    end
  end
end</pre>
<p><br />
&nbsp; 
其中的callbacks（hash）是Dispatcher的类属性，用来执行一些dispatch前，后，准备的工作。这里，我们直接看看他的值<br />
&nbsp; 
{:before=&gt;[:reload_application,:prepare_application],:after=&gt;[:flush_logger,:cleanup_application],:prepare=&gt;[:activerecord_instantiate_observers,&quot;Proc&quot;]}。就这里而言，我们要执行所有:before的callback，不一一列出，只看其中一个： 
</p>
<pre name="code" class="ruby">def reload_application
  if Dependencies.load?
    Routing::Routes.reload
    self.unprepared = true
  end
end</pre>
<p><br />
&nbsp; 
这段代码揭示了在程序运行时，我们改动了routes.rb中的路由信息后，下一次request马上就能生效的原理。另外prepare_application的功能是require我们熟悉的Controller/application.rb，并且验证ActiveRecord的数据库连接是否正常（当然，你需要使用AR框架的话）。好了，这里稍微偏离了主线，接下来，让我们回到dispatch方法中，看看下面的调用handle_request： 
</p>
<pre name="code" class="ruby">def handle_request
  @controller = Routing::Routes.recognize(@request)
  @controller.process(@request, @response).out(@output)
end</pre>
<p><br />
&nbsp; 
上面的代码非常直观，首先通过Routing系统，根据客户端的request找到相应的controller，然后执行并且将返回数据写入到@output中（这也是我前面提到的那个StringIO对象）。至于如何具体找到controller的，进入下一步吧。<br />
<br />
<strong>&nbsp; 
（四）寻找controller</strong>
<br />
&nbsp; <span style="color: #0000ff;">源代码：/actionpack-2.0.2/lib/action_controller/routing.rb</span>
<br />
&nbsp; 
从前面的方法调用中，我们看出寻找controller的入口是RouteSet对象的recognize方法（还记得Routing::Routes是一个RouteSet的对象实例吗？要理解Rails中的Routing子系统，我在第二篇文章中整理的那张类图十分重要！）。下面看看此方法的具体内容： 
</p>
<pre name="code" class="ruby">def recognize(request)
  params = recognize_path(request.path, extract_request_environment(request))
  request.path_parameters = params.with_indifferent_access
  &quot;#{params[:controller].camelize}Controller&quot;.constantize
end</pre>
<p><br />
&nbsp; 
首先调用recognize_path方法，其中request.path是客户端请求的路径（比如：如果客户端访问地址是<a href="http://localhost:3000/posts">http://localhost:3000/posts</a>
，那么此参数就是/posts，extract_request_environment(request)方法只是得到请求的http方法（get,post,put,delete），当然，此方法返回的结果params便是我们的Controller和Action。我们知道，Routing系统通过path和http 
verb就可以确定应该使用哪个Controller的哪个Action，下面看看他是怎么做到的吧： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def recognize_path(path, environment={})
  routes.each do |route|
    result = route.recognize(path, environment) and return result
  end

  allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method =&gt; verb) } }

  if environment[:method] &amp;&amp; !HTTP_METHODS.include?(environment[:method])
    raise NotImplemented.new(*allows)
  elsif !allows.empty?
    raise MethodNotAllowed.new(*allows)
  else
    raise RoutingError, &quot;No route matches #{path.inspect} with #{environment.inspect}&quot;
  end
end</pre>
<p><br />
&nbsp; 
如果你看过我的第二，三篇文章，我想你应该知道这里的routes数组就是Routing系统中庞大的路由表，routes数组的元素是Route对象，里面记录了相应的path 
pattern对应于哪个Controller的哪个Action方法。这里，通过path，和environment（http 
verb）参数，调用每一个Route对象的recognize方法，如果找到相应的Controller，则返回；如果未找到，则进行接下来的错误处理，这里我们可以看到很熟悉的&ldquo;No 
route 
matches...&rdquo;。那我们再来看看Route对象是如何通过Path和environment来识别Controller的，先来看看Route类的recognize方法： 
</p>
<pre name="code" class="ruby">def recognize(path, environment={})
  write_recognition
  recognize path, environment
end</pre>
<p><br />
&nbsp; 
在recognize方法中调用recognize方法？传说中的死循环？呵呵。当然不是了，看完write_recognition你就知道是怎么回事了： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def write_recognition
  # Create an if structure to extract the params from a match if it occurs.
  body = &quot;params = parameter_shell.dup\n#{recognition_extraction * &quot;\n&quot;}\nparams&quot;
  body = &quot;if #{recognition_conditions.join(&quot; &amp;&amp; &quot;)}\n#{body}\nend&quot;

  # Build the method declaration and compile it
  method_decl = &quot;def recognize(path, env={})\n#{body}\nend&quot;
  instance_eval method_decl, &quot;generated code (#{__FILE__}:#{__LINE__})&quot;
  method_decl
end</pre>
<p><br />
&nbsp; 
这里，Rails利用instance_eval重写了该对象（此Route对象）的recognize方法（可不是override哦）。完成重写后，将再次调用recognize方法，此时，这个方法已经是动态生成的了。那么现在我们来看看这个动态方法长什么样，这里，我假设客户端访问url为<a href="http://localhost:3000/posts">http://localhost:3000/posts</a>
，并且，在routes.rb中，我们已经通过map.resources 
:posts建立了一系列针对posts的Route，其中当然包括&ldquo;Get 
/posts/&rdquo;（对应的Controller是posts，对应的Action是index）。那么在调用这个Route对象的write_recognition方法时，将会动态生成如下代码： 
</p>
<pre name="code" class="ruby">def recognize(path, env={})
  if (match = /\A\/posts\/?\Z/.match(path)) &amp;&amp; conditions[:method] === env[:method]
    params = parameter_shell.dup 
    params
  end
end</pre>
<p><br />
&nbsp; 
逻辑很简单，只是通过正则表达式来判断此Route的pattern是否与客户端请求的path一致，并且http 
verb也匹配，如果是的话，则将parameter_shell方法的结果dup出来，并且返回。<br />
&nbsp; 
（注意，这里if子句的条件是通过recognition_conditions方法根据不同Route的不同condition动态生成的。因此针对每个Route，此条件都不同。另外recognition_extraction方法我一直没搞懂他干什么用的-_-!）<br />
&nbsp; 
这里，我们还是看一看parameter_shell方法： 
</p>
<pre name="code" class="ruby">def parameter_shell
  @parameter_shell ||= returning({}) do |shell|
    requirements.each do |key, requirement|
      shell[key] = requirement unless requirement.is_a? Regexp
    end
  end
end</pre>
<p><br />
&nbsp; 
无非就是将requirements（包括controller和action）塞到shell数组，然后返回。<br />
&nbsp; 
好啦，针对路由表中的每一个Route，调用其recognize方法，知道找到匹配的Route，然后将结果（controller和action数组）返回（如未找到匹配的，则进行错误处理），接下来，我们的思路得回到RouteSet对象的recognize方法，最终，使用#{params[:controller].camelize}Controller&quot;.constantize，将controller参数转换为首字符大写的形式，并且加上&ldquo;Controller&rdquo;字符串，最终将整个字符串（&ldquo;PostsController&rdquo;），转换为一个常量（PostsController，表示控制器对象），并且，调用此Controller的process方法（此方法其实是ActionController::Base的类方法），接下来的事，后续文章会继续分析。<br />
&nbsp; （这个过程也揭示了Rails中的&ldquo;约定甚于配置&rdquo;的精髓）<br />
&nbsp; 
（就目前而言，我们都在Rails的主线上游走，我觉得下篇文章，应该暂停一下，来看一些细节的东西，已达到更深入了解Rails的目的）</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/175556#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 24 Mar 2008 20:25:28 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/175556</link>
        <guid>http://woody-420420.javaeye.com/blog/175556</guid>
      </item>
      <item>
        <title>Ruby On Rails-2.0.2源代码分析（3）-named route和resource</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/174352" style="color:red;">http://woody-420420.javaeye.com/blog/174352</a>&nbsp;
          发表时间: 2008年03月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>前言</h1>
</li>
</ul>
<p>&nbsp; 在《Routing的载入》中，我大致介绍了一下Rails中最简单的route是如何加载的。这篇文章，我将来讲一讲Rails系统中更为复杂的named 
route和与RESTful相关的resource是如何被加载的。为了不重复太多的笔墨，这篇文章将在前文的基础上进行，如果发现单独看此文时，有少许云里雾里，建议先看一看我的前篇文章：<a href="../../blog/172796">Ruby 
On Rails-2.0.2源代码分析（2）-Routing的载入</a>
</p>
<ul>
<li>
<h1>进化的routing-named route</h1>
</li>
</ul>
<p>&nbsp; 首先，named route的载入全部发生在routing.rb中。其实named 
route一点也不比普通的route高深些什么，Rails内部最终也是将named 
route解析为一个普通的route保存在RouseSet类的routes数组中（还记得这家伙么？最好牢牢记住他，因为，他还会在后续文章中继续登台发挥重要作用），之所以我称他进化，是因为named 
route既然提供了name，在Rails内部，将会生成一系列的helper方法，当我们在controller或者view中使用link_to，redirect_to等方法时，不需要指定相应的controller和action，从而简化我们的代码，不用多了，先来看一看我们所熟悉的routes.rb</p>
<pre name="code" class="ruby">ActionController::Routing::Routes.draw do |map|
  map.purchase 'products/:id/purchase', :controller =&gt; 'catalog', :action =&gt; 'purchase'
  ...
end</pre>
<p><br />
&nbsp; 这里，我定义了一个purchase的named 
route（当然，你完全可以使用connect方法定义普通的route）。前一篇文章提到过，block中的map对象是Mapper类的实例，其实你可以想象到，其实Mapper类并没有定义purchase方法（天知道你要给你的named 
route起啥名？翠花？旺财？）。所有的一切，都是通过Mapper类的method_missing方法处理的。具体代码如下： 
</p>
<pre name="code" class="ruby">def method_missing(route_name, *args, &amp;proc) #:nodoc:
  super unless args.length &gt;= 1 &amp;&amp; proc.nil?
  @set.add_named_route(route_name, *args)
end</pre>
<p><br />
&nbsp; 
如果你记性还好，应该还记得@set对象是一个RouteSet类的实例，所以这里Mapper类将这个route的名称，还有所有的参数都传递到了RouteSet类的add_named_route方法。 
</p>
<pre name="code" class="ruby">def add_named_route(name, path, options = {})
  # TODO - is options EVER used?
  name = options[:name_prefix] + name.to_s if options[:name_prefix]
  named_routes[name.to_sym] = add_route(path, options)
end</pre>
<p><br />
&nbsp; 
这里，看到我们前面已经熟悉过了的add_route方法了吧？对此方法不用再过多解释，Rails将生成一个普通的route，保存在RouteSet的routes数组中，并将这个route返回，赋给named_routes对象，此对象是NamedRouteCollection类的一个实例。在NamedRouteCollection中有如下定义： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def add(name, route)
  routes[name.to_sym] = route
  define_named_route_methods(name, route)
end

def get(name)
  routes[name.to_sym]
end

alias []=   add
alias []    get</pre>
<p><br />
&nbsp; 
所以，接下来似乎应该关心下add方法了。这里，首先将此普通的route保存在NamedRouteCollection类的routes哈希中（注意和RouteSet的routes数组区分开来）。然后，named 
route开始其&ldquo;进化&rdquo;了----通过define_named_route_methods方法生成自己的一系列helper方法。 
</p>
<pre name="code" class="ruby">def define_named_route_methods(name, route)
  {:url =&gt; {:only_path =&gt; false}, :path =&gt; {:only_path =&gt; true}}.each do |kind, opts|
    hash = route.defaults.merge(:use_route =&gt; name).merge(opts)
    define_hash_access route, name, kind, hash
    define_url_helper route, name, kind, hash
  end
end</pre>
<p><br />
&nbsp; 或者你已经知道了，named 
route有name_url，和name_path两类helper方法，从上面这段代码中，我们能看到真正的实现。Rails这里为url和path分别生成hash 
access和url helper方法，其实现都利用了ruby强大的动态特性。具体实现分别如下： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby"> def define_hash_access(route, name, kind, options)
      selector = hash_access_name(name, kind)
      @module.module_eval &lt;&lt;-end_eval # We use module_eval to avoid leaks
        def #{selector}(options = nil)
          options ? #{options.inspect}.merge(options) : #{options.inspect}
        end
        protected :#{selector}
      end_eval
      helpers &lt;&lt; selector
    end

    def define_url_helper(route, name, kind, options)
      selector = url_helper_name(name, kind)
      # The segment keys used for positional paramters

      hash_access_method = hash_access_name(name, kind)
      @module.module_eval &lt;&lt;-end_eval # We use module_eval to avoid leaks
        def #{selector}(*args)
          #{generate_optimisation_block(route, kind)}

          opts = if args.empty? || Hash === args.first
            args.first || {}
          else
            options = args.last.is_a?(Hash) ? args.pop : {}
            args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
              h[k] = v
              h
            end
            options.merge(args)
          end

          url_for(#{hash_access_method}(opts))
        end
        protected :#{selector}
      end_eval
      helpers &lt;&lt; selector
    end</pre>
<p><br />
&nbsp; 
其中@module是NamedRouteCollection类中的一个匿名module，他通过module_eval方法动态的增加了这一系列的helper方法，并且将方法名保存在helpers数组当中，以供其后controller或者view的使用（link_to, 
redirect_to）。至此，named route的加载就全部完成了。十分简单，不是吗？ 
</p>
<ul>
<li>
<h1>RESTful的化身----resource</h1>
</li>
</ul>
<p>&nbsp; 
当然，光把RESTful和resource扯到一起似乎相当狭义，在Rails中，ActionController::Resources抽象了REST中的Resource，这里，我不谈REST的相关概念，网上资料一大坨。我们就来看看Rails中是如何通过Resource来轻松，简便的完成RESTful应用的吧。</p>
<h2>&nbsp; resources.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：/actionpack-2.0.2/lib/action_controller/resources.rb<br />
</span>
&nbsp; 
首先，我们也不需要将resource看得多么的高深，你可以把他理解为，当你在routes.rb中定义如下的resource的时候：</p>
<p>map.resources :products<br />
&nbsp; Rails会自动为我们生成众多的named route，这些route通过http 
verb和相应的controller中的action对应起来，当然了，众多的helper方法也随即产生。如下表所示：</p>
<table cellspacing="0" border="1" width="361" cellpadding="2">
<tbody>
<tr>
<td valign="top" width="58"><strong>Named Route</strong>
</td>
<td valign="top" width="301"><strong>Helpers</strong>
</td>
</tr>
<tr>
<td valign="top" width="63">product</td>
<td valign="top" width="301">
<pre>product_url, hash_for_product_url,
product_path, hash_for_product_path</pre>
</td>
</tr>
<tr>
<td valign="top" width="68">
<pre>new_product</pre>
</td>
<td valign="top" width="301">
<pre>new_product_url, hash_for_new_product_url,
new_product_path, hash_for_new_product_path</pre>
</td>
</tr>
<tr>
<td valign="top" width="72">
<pre>edit_product</pre>
</td>
<td valign="top" width="301">
<pre>edit_product_url, hash_for_edit_product_url,
edit_product_path, hash_for_edit_product_path</pre>
</td>
</tr>
<tr>
<td valign="top" width="80">...</td>
<td valign="top" width="301">...</td>
</tr>
</tbody>
</table>
<p>&nbsp; 
从这个角度来想，你可以把resource想成是众多相关named 
route的一个马甲。我们先从宏观的角度来看一看Rails是如何完成Resource到route转换的。<br />
<img src="../../../upload/picture/pic/16905/e9f44288-c611-33f6-9f28-d6793713e4fb.jpg" height="660" alt="" width="444" />
&nbsp;<br />
&nbsp; 
整个流程比较的直观，Rails通过resource按部就班的完成各种route的生成，接下来我们看一看核心代码是如何完成这些功能的。首先，还是在routes.rb中，可能会定义如下的resource：</p>
<pre name="code" class="ruby">ActionController::Routing::Routes.draw do |map|

  map.resources :products
  ...
end</pre>
<p><br />
&nbsp; 
resources方法定义在ActionController::Resources这个module中，然后通过mixin进入到Mapper类的。那我们首先来看一看这个方法：</p>
<pre name="code" class="ruby">def resources(*entities, &amp;block)
  options = entities.extract_options!
  entities.each { |entity| map_resource(entity, options.dup, &amp;block) }
end</pre>
<p><br />
&nbsp; 
很简单，将entities和options从参数中分离开来，然后针对每一个entity执行map_resource操作。我们继续进行，看看map_resource方法的真面目： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def map_resource(entities, options = {}, &amp;block)
  resource = Resource.new(entities, options)

  with_options :controller =&gt; resource.controller do |map|
    map_collection_actions(map, resource)
    map_default_collection_actions(map, resource)
    map_new_actions(map, resource)
    map_member_actions(map, resource)

    map_associations(resource, options)

    if block_given?
      with_options(:path_prefix =&gt; resource.nesting_path_prefix, :name_prefix =&gt; resource.nesting_name_prefix, :namespace =&gt; options[:namespace], &amp;block)
    end
  end
end</pre>
<p><br />
&nbsp; 
有了entity和options，还等什么呢？马上生成我们的Resource对象，Resource对象封装了和此resource相关的collection 
method，member method，new method，path prefix，name 
prefix，单/复数表示，还有option。生成这个Resource对象无非就是将此对象的相应属性从options中解析出来，保存起来，代码比较简单，这里就不再贴出。<br />
&nbsp; 
现在，Resource对象有了，从上面代码我们就可以看出来，接下来，就该处理和此resource相关named 
route了。具体的处理逻辑都类似，这里将map_member_actions(map, 
resource)拿出来作为示意，感兴趣的同学们可以自己查看相关的源代码。 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def map_member_actions(map, resource)
  resource.member_methods.each do |method, actions|
    actions.each do |action|
      action_options = action_options_for(action, resource, method)
      map.named_route(&quot;#{action}_#{resource.name_prefix}#{resource.singular}&quot;, &quot;#{resource.member_path}#{resource.action_separator}#{action}&quot;, action_options)
      map.named_route(&quot;formatted_#{action}_#{resource.name_prefix}#{resource.singular}&quot;, &quot;#{resource.member_path}#{resource.action_separator}#{action}.:format&quot;,action_options)
    end
  end

  show_action_options = action_options_for(&quot;show&quot;, resource)
  map.named_route(&quot;#{resource.name_prefix}#{resource.singular}&quot;, resource.member_path, show_action_options)
  map.named_route(&quot;formatted_#{resource.name_prefix}#{resource.singular}&quot;, &quot;#{resource.member_path}.:format&quot;, show_action_options)

  update_action_options = action_options_for(&quot;update&quot;, resource)
  map.connect(resource.member_path, update_action_options)
  map.connect(&quot;#{resource.member_path}.:format&quot;, update_action_options)

  destroy_action_options = action_options_for(&quot;destroy&quot;, resource)
  map.connect(resource.member_path, destroy_action_options)
  map.connect(&quot;#{resource.member_path}.:format&quot;, destroy_action_options)
end</pre>
<p><br />
&nbsp; 
这里，我们可以很直观的看到，Rails为resource的member相关方法生成了众多的route，我们可以看到Controller中熟悉的show，update，destroy 
action。是的，在这里，Rails就为url到controller的action生成了相应的route。完成了这些基本的route生成外，Rails还会处理嵌套，关联关系（存在block的处理和map_associations）。当然，基本逻辑都和上面的逻辑类似。<br />
&nbsp; 
如此云云。。。针对Resource的一张庞大的route表就生成完成了。 
</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/174352#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 21 Mar 2008 00:28:38 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/174352</link>
        <guid>http://woody-420420.javaeye.com/blog/174352</guid>
      </item>
      <item>
        <title>netbean调试ActiveSupport::OptionMerger需注意的一个问题</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/173407" style="color:red;">http://woody-420420.javaeye.com/blog/173407</a>&nbsp;
          发表时间: 2008年03月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这两天，在调试Rails的时候，碰到一个极度奇怪的问题，困扰了我足足两天，实在让我头晕脑胀，耳晕目眩。。。。。。具体情况描述如下：<br />  Rails框架的某一个地方使用了ActiveSupport::OptionMerger类，比如：ActiveSupport::OptionMerger.new(self, options)。但是，只要我通过单步进入到OptionMerger类initialize方法的时候，进程突然中止了，netbean的debug窗口提示一个错误：“can't dup NilClass”。但是，如果我不调试，直接F6运行程序的话，却跟啥事都没有一样通过了。调试和运行行为不一致？这个问题我在COM确实碰到过，但是现在Ruby里面，提示我这样的错误，我实在纳闷。。。试验了很多次，调试了很多次，未果~~灰头土脸，无计可施的时候，当代码运行到ActiveSupport::OptionMerger.new(self, options)时，我将netbean调试界面中的“局部变量”窗口切换到了“监视”窗口，再一F7~居然成功进入到了OptionMerger类的initialize方法，没有任何错误，程序顺利的运行了下去！线索！经过分析，得出了如下结论：<br />  首先，我们先来看一看OptionMerger的全部代码<br /><pre name="code" class="ruby">
module ActiveSupport
  class OptionMerger #:nodoc:
    instance_methods.each do |method|
      undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
    end

    def initialize(context, options)
      @context, @options = context, options
    end

    private
      def method_missing(method, *arguments, &block)
        merge_argument_options! arguments
        @context.send!(method, *arguments, &block)
      end

      def merge_argument_options!(arguments)
        arguments &lt;&lt; if arguments.last.respond_to? :to_hash
          @options.merge(arguments.pop)
        else
          @options.dup
        end
      end
  end
end
</pre><br />  其中，关键问题所在是定义了此类后，马上执行了很多次undef_method操作，通过代码我们可以看出来，最终，OptionMerger类只剩下如下的实例方法：__id__，__send__，object_id，instance_eval，class。<br />  后来，在创建一个OptionMerger类实例的时候，如果你想单步进入此类，而这个时候，恰好netbean的bebug窗口在“局部变量”的时候~嘿嘿~由于“局部变量”窗口有一个默认的self变量，要显示其类型和值，显示值的时候，我想netbean是默认调用self.to_s方法，但是前面讲了，to_s方法已经被咔嚓了，所以，这时将触发method_missing，而method_missing又调用了merge_argument_options方法，而更巧的是，这个时候，options参数正好又是nil，所以，这个时候@options.dup这句话，让netbean歇菜了~这也是每次我想调试进入此类时，就over了的原因。<br />  不知道这应该算是netbean的Bug还是Rails的Bug。。。不管怎么样，记得，在这种情况下，不要让调试窗口保持在“局部变量”窗口就是了，并且，“监视”窗口一定不能监视self变量，不然就会像我一样郁闷的~
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/173407#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 18 Mar 2008 15:08:47 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/173407</link>
        <guid>http://woody-420420.javaeye.com/blog/173407</guid>
      </item>
      <item>
        <title>Ruby On Rails-2.0.2源代码分析（2）-Routing的载入</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/172796" style="color:red;">http://woody-420420.javaeye.com/blog/172796</a>&nbsp;
          发表时间: 2008年03月16日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>&nbsp; 前言</h1>
</li>
</ul>
<p>&nbsp; 
在前一篇文章中，我大致的讲解了一下Rails的启动过程，并罗列了个人觉得比较核心的源代码进行分析，算是管中窥豹吧~在分析initializer.rb代码的时候，我说过&ldquo;initializer.rb的介绍暂时结束&rdquo;，因为我特意略过了初始化过程中一个十分相当非常重要的过程--Routing的载入。这里，我专门用这篇文章来讲解一下。<br />
&nbsp; 
Routing之于Rails就如同waiter（waitress）之于饭店。当你怀揣着这个月辛辛苦苦写软件得来的工资，来到一个上档次的饭店，如果没有门口的门生引领你到空闲的饭桌，上菜谱，点菜。。。恐怕哥们你只能一进门就甩着手中的票子大吼：给老子上两斤牛肉，一斤烧酒：）是的。当你通过浏览器指定到某一个Rails程序的URL时，Routing就跳了出来，通过分析，按照Rails内部的机制，指定相应的Controller，并执行上面相应的Action，于是，你得到服务了（小费就免了）~<br />
&nbsp; 
（关于Routing系统的具体机制，请参见《Agile Web Development with Rails 2nd》或者《The Rails 
Way》，里面有大把好的资料）</p>
<ul>
<li>
<h1>&nbsp; Routing的载入</h1>
</li>
</ul>
<p>&nbsp; 
好了，言归正传，在平日的开发中，我们很会很熟练的在/config/routes.rb中写类似如下的一些Routing信息（当然一些可以自动生成）：</p>
<pre name="code" class="ruby">ActionController::Routing::Routes.draw do |map|
  map.resources :comments

  map.root :controller=&gt;'posts'
  map.resources :posts
  map.resources :posts, :has_many =&gt; :comments
  map.namespace :admin do |admin| 
    admin.resources :posts 
  end 
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
  ...
end
</pre>
<p> 
下面，我将讲讲Routing这个如此重要的东东是如何被载入的。按照自己学习的方法，我仍然还是喜欢先整理一张关于Routing载入所执行源代码的执行顺序图。如下所示：<br />
<img src="../../../upload/picture/pic/16895/cd5a1f4a-85c5-30cd-a9d3-e00575cb8d00.jpg" height="434" alt="" width="443" />
<br />
&nbsp; 
请注意，由于Routing的载入仍然属于Rails启动阶段的工作，所以，我直接在上篇文章所用的代码执行顺序图的基础上进行了扩充（也删减了一些无关痛痒的部分），图中红色剪头表示Routing载入的相关执行代码。好了，下面，让我们稍微深入一点，详细看看每个代码文件到底都做了一些什么事情，来完成Routing的载入工作。</p>
<h2>&nbsp; environment.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：RAILS_ROOT/config/environment.rb<br />
</span>
&nbsp; 
此代码文件内容比较少，且十分容易理解，我先把代码整理贴出来：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION

# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  ...
end</pre>
<p><br />
&nbsp; 第一行设置Rails 
Gem的版本，用于加载相应版本的Rails，第二行执行的代码通过载入boot文件达到启动rails的目的（此时Rails还未初始化）。好了，到了Rails::Initializer.run方法了，还记得我前篇文章提到的此方法吗？：）让我们再深入的看一看吧。<br /></p>
<h2>&nbsp; initializer.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：gems/rails-2.0.2/lib/initializer.rb<br />
</span>
&nbsp; 
这次我们直接点，再来看看Initializer类的类方法run长什么样子：</p>
<pre name="code" class="ruby">def self.run(command = :process, configuration = Configuration.new)
  yield configuration if block_given?
  initializer = new configuration
  initializer.send(command)
  initializer
end</pre>
<p><br />
&nbsp; 
在environment.rb中对run方法的调用，没有带任何参数，所以command是默认的:process（重要），configuration当然是重新生成一个新的配置类。当然，顺便提一下，这次的调用带了一个block，因此，第一行代码将Configuration实例yield出去，用户可以在environment.rb中对Rails的任何配置进行修改（尽管如此，我们需要修改的配置不会太多）。接下来，很直观的事，向initializer实例发送:process消息（调用process实例方法）。<br />
&nbsp; 
在initializer的process实例方法中，会做很多事情，我先用下表罗列出来，并附上简单描述。</p>
<table cellspacing="0" border="1" width="654" cellpadding="2">
<tbody>
<tr>
<td valign="top" width="199">方法名</td>
<td valign="top" width="450">描述</td>
</tr>
<tr>
<td valign="top" width="199">check_ruby_version</td>
<td valign="top" width="450">检测ruby的版本（最低版本为1.8.2。注意：不支持1.8.3）</td>
</tr>
<tr>
<td valign="top" width="199">set_load_path</td>
<td valign="top" width="450">设置装载路径</td>
</tr>
<tr>
<td valign="top" width="199">require_frameworks</td>
<td valign="top" width="450">载入Rails的其他组件（AR，AV...）</td>
</tr>
<tr>
<td valign="top" width="199">set_autoload_paths</td>
<td valign="top" width="450">设置Rails自动加载源代码文件的路径（包括load_once）</td>
</tr>
<tr>
<td valign="top" width="199">add_plugin_load_paths</td>
<td valign="top" width="450">设置插件的load_path</td>
</tr>
<tr>
<td valign="top" width="199">load_environment</td>
<td valign="top" width="450">载入环境（根据目前运行环境development/test/production，载入正确的*.rb）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_encoding</td>
<td valign="top" width="450">初始化编码（默认UTF-8）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_database</td>
<td valign="top" width="450">初始化数据库（给AR传递数据库配置）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_logger</td>
<td valign="top" width="450">初始化日志记录程序</td>
</tr>
<tr>
<td valign="top" width="199">initialize_framework_logging</td>
<td valign="top" width="450">初始化各个组件（AR，AC，AM...）的日志记录程序</td>
</tr>
<tr>
<td valign="top" width="199">initialize_framework_views</td>
<td valign="top" width="450">分别初始化AM和AC的template根目录和view路径</td>
</tr>
<tr>
<td valign="top" width="199">initialize_dependency_mechanism</td>
<td valign="top" width="450">初始化依赖载入机制（这个以后将会详细谈到）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_whiny_nils</td>
<td valign="top" width="450">初始化警告系统（在nil上调用某一个方法）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_temporary_directories</td>
<td valign="top" width="450">初始化临时目录（为session，cache准备的目录）</td>
</tr>
<tr>
<td valign="top" width="199">initialize_framework_settings</td>
<td valign="top" width="450">初始化各个组件（通过send调用各个组件的setting方法）</td>
</tr>
<tr>
<td valign="top" width="199">add_support_load_paths</td>
<td valign="top" width="450">暂时未使用</td>
</tr>
<tr>
<td valign="top" width="199">load_plugins</td>
<td valign="top" width="450">载入插件</td>
</tr>
<tr>
<td valign="top" width="199">load_observers</td>
<td valign="top" width="450">（以后有深入了解再记录）</td>
</tr>
<tr>
<td valign="top" width="199"><strong>initialize_routing</strong>
</td>
<td valign="top" width="450"><strong>初始化Routing</strong>
</td>
</tr>
<tr>
<td valign="top" width="199">after_initialize</td>
<td valign="top" width="450">调用用户提供的block（完成初始化后执行的block）</td>
</tr>
<tr>
<td valign="top" width="199">load_application_initializers</td>
<td valign="top" width="450">载入/config/initializers/目录下的所有.rb文件</td>
</tr>
</tbody>
</table>
<p> 
当然，现在我主要关心表中黑体标出的initialize_routing方法，至于其他方法，感兴趣的同学们可以自己查看源代码，都十分简单易懂。下面，先来看看initialize_routing的内容</p>
<pre name="code" class="ruby">def initialize_routing
  return unless configuration.frameworks.include?(:action_controller)
  ActionController::Routing.controller_paths = configuration.controller_paths
  ActionController::Routing::Routes.reload
end</pre>
<p><br />
&nbsp; 
第一，没有哪位同学用Rails不使用action_controller组件吧？呵呵。这里，我们暂时只用记住第三行执行代码Routes.reload，并且暂时先记住Routes是routing.rb中定义的一个RouteSet实例。那么。。。还等什么，到routing.rb里面去转转吧。</p>
<h2>&nbsp; routing.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：/actionpack-2.0.2/lib/action_controller/routing.rb 
（actionpack？Agile Web Development with Rails 2nd！嘿嘿）</span>
<br />
&nbsp; 
主角总算登场了，Routing可以算得上Rails当中最复杂的功能系统之一。此代码文件内容相当的多，首先，我先按照如何将大象放进冰箱的方法学，从一个很高的角度看一看，Rails是如何将Routing载入的（明显会比装大象的步骤多）。下面是一张很高层次的流程图：<br />
<img src="../../../upload/picture/pic/16899/2e86d597-7be9-3dbb-a4db-d0d1f32dc1ce.jpg" height="640" alt="" width="397" />
 <br />
&nbsp; 
从这个层次来看，Routing的载入流程比较清晰，所做的工作无非就是首先判断是否需要载入，如需载入，则清空原来保存的所有信息，然后重新从config/routes.rb中载入所有Routing信息。现在，先将这幅大流程图保留在我们的脑海中，接下来，深入细节看看。routing.rb中内容较多，包含的类也较多，先来看一看此代码文件中包含类的类图：<br />
<img src="http://www.agilelabs.cn/photos/e5b7a5e4bd9c/images/4354/original.aspx" alt="" />
 
</p>
<p><img src="../../../upload/picture/pic/16903/900e3232-3e5f-3e14-8cdb-a6a8fec5ee86.jpg" height="520" alt="" width="528" />
<br />
&nbsp; （为了直观起见，我只将个人认为核心的类描绘出来，并且只将与载入Routing相关的方法列了出来；从简洁的角度出发，我还省略了方法参数）<br />
&nbsp; 
<strong>Route类<br />
</strong>
&nbsp; 
此类代表一个普通的Routing信息，例如&ldquo;'test/:controller/show/:id/*spec',:action=&gt;&quot;show&quot;, 
:requirements =&gt; { :id =&gt;/\d+/}, :conditions =&gt; { :method =&gt; :get 
}&rdquo;。<br />
&nbsp; 
segments----代表一个routing信息中path（'test/:controller/show/:id/*spec'）的&ldquo;片段&rdquo;数组。前面那个示意routing信息的&ldquo;片段&rdquo;数组有类似如下信息：[&quot;/&quot;,&quot;test&quot;,&quot;/&quot;,&quot;:controller&quot;,&quot;/&quot;,&quot;show&quot;,&quot;/&quot;,&quot;:id&quot;,&quot;spec&quot;]<br />
&nbsp; 
requirements----代表一个routing信息中的:requirements {:id =&gt; /\d+/}<br />
&nbsp; 
conditions----代表一个routing信息中的:conditions 
{:method=&gt;:get}。注意，目前版本的conditions只支持:method。<br />
&nbsp; 
（此类还包括很多非常重要的实例方法，比如：识别一个请求应该调用哪个Controller和Action；如何通过link_to,redirect_to等生成一个URL，但是这里我主要讲Routing的加载，所以这部分内容留到以后再讲。）<br />
<br />
&nbsp; 
<strong>NamedRouteCollection类</strong>
<br />
&nbsp; 次类代表一个named 
routes集合。有些类似一个Hash，一个name对应着一组普通的route。<br />
<br />
&nbsp; 
<strong>RouteSet类<br />
</strong>
&nbsp; 
还记得initializer.rb中的ActionController::Routing::Routes么？前面说了，他是一个RouteSet的实例。从类名就可以看出来，此类是一个Route的集合，他包括一个普通route的数组，还有一个named 
route的集合。就Rails框架而言，此类是Routing系统的对外接口，Rails框架的其他部分会通过他来载入route信息，识别请求路径，生成URL等等。此类有两个内部类Mapper和NamedRouteCollection。<br />
<br />
&nbsp;<strong> 
Mapper类<br />
</strong>
&nbsp; 
就用户而言，此类是Routing系统的对外接口，我们会在config/routes.rb用如下代码增加routes信息：</p>
<pre name="code" class="ruby">ActionController::Routing::Routes.draw do |map|
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end</pre>
<p><br />
&nbsp; 
block中的参数map就是一个Mapper类的实例，我们就是通过调用他的connect等实例方法增加route信息。Mapper类包含一个RouteSet类实例，其实Mapper类只是一个Wrapper而已，讲所有的调用操作都转发到了RouteSet类实例。<br />
<br />
&nbsp; 
<strong>RouteBuilder类<br />
</strong>
&nbsp; 
此类是Rails内部使用的类，确切的说，是给RouteSet实例使用的类。此类用于将用户提供的所有route信息Build成一个个普通的Route对象（然后当然是存放在RouteSet的普通route数组中或者named 
routes集合中）。<br />
<br />
&nbsp; <strong>Segment类<br />
</strong>
&nbsp; 
在讲Route类的时候，我提到了&ldquo;片段&rdquo;，这就是Segment类所抽象的东西。他是所有&ldquo;片段&rdquo;对象的父类。<br />
&nbsp; 
<strong>DynamicSegment类<br />
</strong>
&nbsp; 
代表一个动态Segment，有两个子类，PathSegment和ControllerSegment。PathSegment表示Route 
globbing，ControllerSegment表示代表controller的片段。<br />
&nbsp; 
<strong>StaticSegment类<br />
</strong>
&nbsp; 
代表一个静态Segment，有一个子类，DividerSegment，表示分隔符片段。<br />
&nbsp; 
相当抽象是吗？好了，那我们来看一个实际例子，你就会完全理解Segment了。例如，现在我们在config/routes.rb中定义了一个如下的route信息：<br />
map.connect 
'test/:controller/show/:id/*spec',:action=&gt;&quot;show&quot;, :requirements =&gt; { :id 
=&gt;/\d+/}, :conditions =&gt; { :method =&gt; :get }<br />
&nbsp; 
当Rails框架通过map对象的connect方法调用后，会在内部将这个route分解为如下的Segment：</p>
<table cellspacing="0" border="1" width="400" cellpadding="2">
<tbody>
<tr>
<td valign="top" width="200"><strong>Segment类型</strong>
</td>
<td valign="top" width="200"><strong>值</strong>
</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
<tr>
<td valign="top" width="200">StaticSegment</td>
<td valign="top" width="200">test</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
<tr>
<td valign="top" width="200">ControllerSegment</td>
<td valign="top" width="200">:controller</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
<tr>
<td valign="top" width="200">StaticSegment</td>
<td valign="top" width="200">show</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
<tr>
<td valign="top" width="200">DynamicSegment</td>
<td valign="top" width="200">:id</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
<tr>
<td valign="top" width="200">PathSegment</td>
<td valign="top" width="200">:spec</td>
</tr>
<tr>
<td valign="top" width="200">DividerSegment</td>
<td valign="top" width="200">/</td>
</tr>
</tbody>
</table>
<p>（注意，第一个和最后一个&quot;/&quot;，尽管我们提供path的时候是没有的，但是Rails的Routing系统内部会自动加上的。）<br />
&nbsp; 
通过这个具体的例子，我想对Segment已经不用再多做解释了，我想大家应该很了解各种Segment的用途了。为什么要将我们提供的route信息中的path分成Segment？当然是用于分析request和生成url用的了。<br />
<br />
&nbsp; 
好了，我费了很多唾沫在介绍Routing高层面的东西上。我想如果你看了以上的东西，可能还不是很明了整个过程（如果你看完就明白了，我只能说我太有才了~~^0^你更有才！），下面，我们就来看一看一些关键的代码片段，希望能起到一个融会贯通的作用。<br />
&nbsp; 
首先，我们还是先看看RouteSet类中的入口相关方法：</p>
<pre name="code" class="ruby">def load!
  Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
  clear!
  load_routes!
  install_helpers
end

def reload
  if @routes_last_modified &amp;&amp; defined?(RAILS_ROOT)
    mtime = File.stat(&quot;#{RAILS_ROOT}/config/routes.rb&quot;).mtime
    # if it hasn't been changed, then just return
    return if mtime == @routes_last_modified
    # if it has changed then record the new time and fall to the load! below
    @routes_last_modified = mtime
  end
  load!
end

def load_routes!
  if defined?(RAILS_ROOT) &amp;&amp; defined?(::ActionController::Routing::Routes) &amp;&amp; self == ::ActionController::Routing::Routes
    load File.join(&quot;#{RAILS_ROOT}/config/routes.rb&quot;)
    @routes_last_modified = File.stat(&quot;#{RAILS_ROOT}/config/routes.rb&quot;).mtime
  else
    add_route &quot;:controller/:action/:id&quot;
  end
end</pre>
<p><br />
&nbsp; 
如果你脑中现在还有那张装大象方法学的流程图，我想你对上面的代码会理解得十分透彻，不用过多解释。下面再看看和用户（config/route.rb）打交道的Mapper类长什么样： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">class Mapper #:doc:
  def initialize(set) #:nodoc:
    @set = set
  end

  # Create an unnamed route with the provided +path+ and +options+. See
  # ActionController::Routing for an introduction to routes.
  def connect(path, options = {})
    @set.add_route(path, options)
  end
  ...
end</pre>
<p><br />
&nbsp; 
其中实例变量@set是一个RouteSet对象实例，Mapper所做的工作基本都是转发消息到RouteSet实例。接下来，当然应该顺藤摸瓜的看一看RouteSet类的实例方法add_route都干了些什么了：</p>
<pre name="code" class="ruby">def add_route(path, options = {})
  route = builder.build(path, options)
  routes &lt;&lt; route
  route
end</pre>
<p><br />
&nbsp; 
很简单，只是调用builder对象（RouteBuilder类）的build方法返回一个Route对象实例，然后存放在数组routes中。path和options？我想你应该知道他们是什么了吧？<br />
&nbsp; 
path ： 'test/:controller/show/:id/*spec'<br />
&nbsp; options ： {:action=&gt;&quot;show&quot;, 
:requirements =&gt; { :id =&gt;/\d+/}, :conditions =&gt; { :method =&gt; :get } 
}<br />
&nbsp; 看这样子，真正干活的还是我们的Builder兄弟了，那让我们来看看他RouteBuilder对象的build实例方法都干了些什么： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">  def build(path, options)
    # Wrap the path with slashes
    path = &quot;/#{path}&quot; unless path[0] == ?/
    path = &quot;#{path}/&quot; unless path[-1] == ?/

    path = &quot;/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}&quot; if options[:path_prefix]

    segments = segments_for_route_path(path)
    defaults, requirements, conditions = divide_route_options(segments, options)
    requirements = assign_route_options(segments, defaults, requirements)

    route = Route.new

    route.segments = segments
    route.requirements = requirements
    route.conditions = conditions
    ...

    route
  end
end</pre>
<p><br />
&nbsp; 
我删减了一些代码，只将核心流程留了下来。首先，为path加两个保护罩（前后各加一个&ldquo;/&rdquo;），当然，如果不存在的话。接着，将path五马分尸，拆解成一个segments数组（具体规则？可以稍微看一下前面的内容）。接下来，会得到option对象中包含的requirement，conditions等信息。大功告成，最后生成一个Route实例，将相应属性赋给他，返回。Builder使命结束。RoutesSet的add_route得到这个route实例，保存在普通routes数组中。<br />
&nbsp; 
keep going。。。循环处理完所有config/routes.rb中的route信息，就完成了整个路由表的载入！<br />
&nbsp; <br />
&nbsp; 
（PS：关于Routing的载入暂时先告一个段落。你可能会发现，我没有提到named 
route的载入，并且我也完全忽视了代码执行顺序图中resource.rb的存在，因为，我觉得可以把named route单独开一个话题来讲）<br />
&nbsp; 
（再PS：本文可能不足以让你完全理解Routing载入的每一个细节，但是希望本文能起到一块转头的作用----当然不是拍人用----而是可以引来很多玉） 
</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/172796#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 16 Mar 2008 22:58:12 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/172796</link>
        <guid>http://woody-420420.javaeye.com/blog/172796</guid>
      </item>
      <item>
        <title>Ruby On Rails-2.0.2源代码分析（1）-Rails的启动</title>
        <author>woody_420420</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://woody-420420.javaeye.com/blog/170683" style="color:red;">http://woody-420420.javaeye.com/blog/170683</a>&nbsp;
          发表时间: 2008年03月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>前言</h1>
</li>
</ul>
<p>&nbsp; 本文主要是针对Ruby On Rails 2.0.2的源代码进行分析，学习与研究。所使用的工具是NetBean 6.1 
Beta，WEBRick，SciTE，ruby-debug-base（0.10.0），ruby-debug-ide（0.1.10）。Ruby版本为1.8.6。<br />
&nbsp; 
应该怎么分析总结，是开始最令人头痛的事，Ruby是面向对象的语言，从对象的层次记录吧，似乎一切都不那么直观，一个庞大的系统摆在眼前，整理一个类图，继承关系图。。。有点牛啃南瓜，无从下口的感觉。最后，决定打算从Ruby的本质-解释语言下手，从解释器的角度出发，跟着解释器的步伐，从细微入手，一步一步深入Rails，以达到从局部到整体，了解学习的目的。所以，最终，我决定从源代码执行顺序的角度去分析Rails。<br />
&nbsp; 
为方便起见，我直接使用NetBean的调试环境，使用Ruby自带的WEBRick，从接触Rails最基本的ruby 
script/server开始，首先来看Rails是怎么启动起来的。</p>
<ul>
<li>
<h1>Rails的启动</h1>
</li>
</ul>
<p>&nbsp; ruby 
script/server，应该是搞rails的同学们耳熟能详的命令了。server脚本主要执行两个的过程：1.启动Rails；2.启动web服务器（当然，我这里是启动WEBRick了）。我们就从这里入手，看看Rails是怎么样被启动起来的。<br />
&nbsp; 
前面我说过，我将从源代码执行顺序的角度去分析，所以，让我们先来看一看Rails启动时，核心源代码的执行顺序，具体见下图（为了使得分析简单明了，抓住关键本质所在，我只把个人认为与启动有关的源代码列出来，执行过程中，其他类似关于ActiveSupport中关于core的extension之类的代码就不列出）：<br />
<img src="../../../upload/picture/pic/16895/cd5a1f4a-85c5-30cd-a9d3-e00575cb8d00.jpg" height="434" alt="" width="443" /></p>
<h2>&nbsp; boot.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：RAILS_ROOT/config/boot.rb<br />
</span>
&nbsp; 
这个代码文件是Rails的启动入口，完成的功能是：首先判断Rails是否启动，如果未启动则先执行一个&ldquo;预初始化&rdquo;（preinitialize）过程，然后选择一种启动方式（Vendor/Gem），执行相应类上的run方法。主方法boot!代码如下：</p>
<pre name="code" class="ruby">def boot!
  unless booted?
    preinitialize
    pick_boot.run
  end
end</pre>
<p>&nbsp; 
其中，与初始化过程是执行RAILS_ROOT/config目录下面的preinitializer.rb（如果存在的话）。这个过程的目的是在加载environment.rb文件执行执行一些初始化工作。参见：<a href="http://yudionrails.com/2008/1/7/what-s-new-in-edge-rails-pre-environment-load-hook" title="http://yudionrails.com/2008/1/7/what-s-new-in-edge-rails-pre-environment-load-hook">http://yudionrails.com/2008/1/7/what-s-new-in-edge-rails-pre-environment-load-hook</a>
。此源代码中包含一个module 
Rails，此模块下面包括三个类：VendorBoot，GemBoot，他们都继承自Boot类，分别代表是通过Vendor还是Gem的方式启动Rails（如果RAILS_ROOT/vender/下面存在名为rails的目录，则以Vendor方式启动Rails，否则，从Gem启动Rails）。当使用Gem方式启动Rails的话，还有一个重要的功能就是判断加载哪个版本的Rails，当然，正如我们所知，environment.rb中的RAILS_GEM_VERSION起了作用。总得来说，boot代码逻辑较简单，没有什么费解的东西，下面给出这个文件的整个执行逻辑流程图：</p>
<p><br />
<img src="../../../upload/picture/pic/16895/cd5a1f4a-85c5-30cd-a9d3-e00575cb8d00.jpg" height="434" alt="" width="443" />
</p>
<h2>initialize.rb </h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：gems/rails-2.0.2/lib/initializer.rb 
(Gem方式启动）<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
RAILS_ROOT/vendor/rails/railties/lib/initializer.rb（Vendor方式启动）</span>
<br />
&nbsp; 
虽然两种不同启动方式执行的源代码不同，但是他们完成的功能都大同小异，都对Rails执行必要的配置以及初始化。我们先来看看上一步--执行boot.rb代码的最后一步（还记得pick_boot.run么？），具体代码如下：</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">class Boot
  def run
    load_initializer
    Rails::Initializer.run(:set_load_path)
  end
end
</pre>
<p>当完成了选择一个boot方式后，会执行相应Boot对象的run方法，那么run方法首先载入初始化器，VendorRoot通过如下方式载入：</p>
<pre name="code" class="ruby">class VendorBoot &lt; Boot
  def load_initializer
    require &quot;#{RAILS_ROOT}/vendor/rails/railties/lib/initializer&quot;
  end
end
</pre>
<p> 
GemRoot通过如下方式载入：</p>
<pre name="code" class="ruby">class GemBoot &lt; Boot
  def load_initializer
    self.class.load_rubygems
    load_rails_gem
    require 'initializer'
  end
  ...
end
</pre>
<p> 
OK。初始化器载入完成，Boot的run方法立即执行初始化器对象的类方法run，注意这里的run方法参数是:set_load_path符号。好了，下面，我们可以深入到initialize.rb里面去看个究竟了。&nbsp; 
initialize.rb中定义了一个module 
Rails，其中包括了此代码文件中最重要的两个类：Configuration和Initializer。从类名我们就可以很清晰的了解到，Initializer类完成Rails的初始化工作，当然这个过程需要各种各样的配置，参数，这些则由Configuration提供。那么首先来看看Configuration提供了Rails所需的哪些配置参数，详见下表：</p>
<table cellspacing="0" border="1" width="668" cellpadding="2">
<tbody>
<tr>
<td valign="top" width="189"><strong>配置名（accessor名）</strong>
</td>
<td valign="top" width="477"><strong>具体描述</strong>
</td>
</tr>
<tr>
<td valign="top" width="191">frameworks</td>
<td valign="top" width="477">会被载入的Rails框架组件列表，会包括action_controller，action_view等</td>
</tr>
<tr>
<td valign="top" width="193">load_paths</td>
<td valign="top" width="477">附加的load路径列表，app/controller;app/models等Rails项目下的目录</td>
</tr>
<tr>
<td valign="top" width="195">load_once_paths</td>
<td valign="top" width="477">Rails只会load一次的目录，似乎目前版本的Rails未用到这个参数</td>
</tr>
<tr>
<td valign="top" width="196">log_path</td>
<td valign="top" width="477">日志文件的路径，根据目前的环境（development，test，production）决定</td>
</tr>
<tr>
<td valign="top" width="197">log_level</td>
<td valign="top" width="477">Rails日志器的日志级别（info，debug）</td>
</tr>
<tr>
<td valign="top" width="198">view_path</td>
<td valign="top" width="477">view的目录路径，默认路径是app/view了</td>
</tr>
<tr>
<td valign="top" width="198">controller_paths</td>
<td valign="top" width="477">controller的目录路径，默认路径是app/controller</td>
</tr>
<tr>
<td valign="top" width="198">cache_classes</td>
<td valign="top" width="477">是否对类进行缓存。目前未使用（一直是false）</td>
</tr>
<tr>
<td valign="top" width="198">whiny_nils</td>
<td valign="top" width="477">true/false，当设置为true的话，当你在Rails中调用一个nil方法的时候，将会得到警告</td>
</tr>
<tr>
<td valign="top" width="198">plugins</td>
<td valign="top" width="477">载入的插件列表，默认为空</td>
</tr>
<tr>
<td valign="top" width="198">plugin_paths</td>
<td valign="top" width="477">插件路径，默认是RAILS_ROOT/vendor/plugins目录</td>
</tr>
<tr>
<td valign="top" width="198">plugin_locators</td>
<td valign="top" width="477">插件的定位器，默认是Plugin::FileSystemLocator</td>
</tr>
<tr>
<td valign="top" width="198">plugin_loader</td>
<td valign="top" width="477">插件的载入器，默认是Plugin::Loader</td>
</tr>
<tr>
<td valign="top" width="198">database_configuration_file</td>
<td valign="top" width="477">数据库配置文件，默认位于RAILS_ROOT/config/database.yml</td>
</tr>
</tbody>
</table>
<p>&nbsp; 
绕了一圈，现在让我们回到Initializer类的run方法（由boot.rb调用：Rails::Initializer.run(:set_load_path)），十分简单：</p>
<pre name="code" class="ruby">def self.run(command = :process, configuration = Configuration.new)
  yield configuration if block_given?
  initializer = new configuration
  initializer.send(command)
  initializer
end
</pre>
<p> 
现在我们姑且不管block（boot.rb调用他的时候确实也没有关联一个block），接下来的工作是生成一个新的Configuration对象，并赋给Initializer的构造函数(然后由Initializer对象保存该配置对象)，然后执行initializer上的command方法，默认情况是执行process方法，这里通过boot.rb的调用，将执行set_load_path方法。在这里值得注意的是，新生成的Configuration对象的所有配置参数都是默认值，例如：frameworks参数通过如下方法得到默认值：</p>
<pre name="code" class="ruby">def default_frameworks
  [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ]
end
</pre>
<p> 
controller_path参数通过如下方法得到默认值： 
</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def default_controller_paths
  paths = [File.join(root_path, 'app', 'controllers')]
  paths.concat builtin_directories
  paths
end
</pre>
<p> 
其实，所有的这些配置都不是定死的，我们可以在enviroment.rb中重新定义他们，象下面这样：</p>
<pre name="code" class="ruby">Rails::Initializer.run do |config|
  config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
  config.plugins = [ :exception_notification, :ssl_requirement, :all ]
  ...
end
</pre>
<p> 
到这里，initializer.rb的介绍暂时结束，只是简单的执行了set_load_path方法设置load路径。接下来，执行流程回到了script/server:</p>
<pre name="code" class="ruby">require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'
</pre>
<p> 该执行第二句了，下面轮到server.rb出场了。 
</p>
<h2>&nbsp; server.rb</h2>
<p> <span style="color: #0000ff;">源代码路径：gems/rails-2.0.2/lib/commands/server.rb<br />
</span>
&nbsp; 
server.rb主要完成两个功能：1.加载active_support；2.加载web服务器。<br />
&nbsp; 
加载active_support十分简单，只是通过源代码开始的第一句：</p>
<pre name="code" class="ruby">require 'active_support'</pre>
<p> 
加载web服务器相比复杂一些。首先，Rails会试图加载FastCGI，然后会试图加载mongrel，如下代码所示：</p>
<pre name="code" class="ruby">begin
  require_library_or_gem 'fcgi'
rescue Exception
  # FCGI not available
end

begin
  require_library_or_gem 'mongrel'
rescue Exception
  # Mongrel not available
end
</pre>
<p> 
最终，会通过defined?(Mongrel)和defined?(FCGI)来决定使用哪种服务器。当然，本文前面提到了将使用WEBRick作为web服务器，这里最终加载的服务器当然是webrick。server.rb的最后一句代码：&nbsp; 
</p>
<pre name="code" class="ruby">require &quot;commands/servers/#{server}&quot; 
</pre>
<p> 
在这里#{server}当然是webrick了，所以，接下来执行的将是webrick.rb 
</p>
<h2>&nbsp; webrick.rb</h2>
<p> <span style="color: #0000ff;">源代码路径：gems/rails-2.0.2/lib/commands/servers/webrick.rb</span>
<br />
&nbsp; 
webrick.rb完成如下几个主要功能：1.加载Ruby自带的webrick库；2.加载environment.rb；3.加载webrick_server.rb；4.执行DispatchServlet（在webrick_server.rb中定义）的类方法dispatch。整个过程都是顺序完成的，所以，示意的源代码可以如下所示：</p>
<pre name="code" class="ruby">  require 'webrick'
  require RAILS_ROOT + &quot;/config/environment&quot;
  require 'webrick_server'
  DispatchServlet.dispatch(OPTIONS)</pre>
<h2>&nbsp; environment.rb</h2>
<p>&nbsp; <span style="color: #0000ff;">源代码路径：RAILS_ROOT/config/environment.rb<br />
</span>
<span style="color: #0000ff;">&nbsp; <span style="color: #000000;">回到了我们熟悉的environment.rb中，在这里我们可以对Rails运行的环境进行配置，这里不做过多阐述，可以参与相关Rails文档。</span>
</span>
</p>
<h2><span style="color: #0000ff;"><span style="color: #000000;">&nbsp; 
webrick_server.rb</span>
</span>
</h2>
<p> <span style="color: #0000ff;">源代码路径：gems/rails-2.0.2/lib/webrick_server.rb<br />
</span>
&nbsp; 
这是Rails启动所执行的最后一个源代码文件。我前面提到了一点，此源代码文件中定义了DispatchServlet，这是一个自定义的dispatch 
servlet，用于将浏览器的请求dispatch到相应的controller，action上。因为这里我只打算介绍Rails的启动，所以，我们只用关注如下的代码即可：</p>
<pre name="code" class="ruby">class DispatchServlet &lt; WEBrick::HTTPServlet::AbstractServlet

  # Start the WEBrick server with the given options, mounting the
  # DispatchServlet at &lt;tt&gt;/&lt;/tt&gt;.
  def self.dispatch(options = {})
    Socket.do_not_reverse_lookup = true # patch for OS X

    params = { :Port        =&gt; options[:port].to_i,
               :ServerType  =&gt; options[:server_type],
               :BindAddress =&gt; options[:ip] }
    params[:MimeTypes] = options[:mime_types] if options[:mime_types]

    server = WEBrick::HTTPServer.new(params)
    server.mount('/', DispatchServlet, options)

   trap(&quot;INT&quot;) { server.shutdown }
    server.start
  end
end
</pre>
<p> 
这是一个经典的启动WEBRick服务器的方式，不用过多阐述，可以参考webrick的相关文档。当然，webrick_server.rb文件中还有一个相当重要的类CGI，并且，DispatchServlet类中还有一些重要的方法，我们暂时可以将他们搁在一旁。以后进一步分析Rails的时候再讲解。&nbsp; 
OK。至此，Rails就正式上马了，web服务器也启动起来了，接下来的事情，当然是等着web服务器将request转发给Rails进行处理了，按照国际惯例，这当然是下文分解的事了~ 
</p>
          <br/>
          <span style="color:red;">
            <a href="http://woody-420420.javaeye.com/blog/170683#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 12 Mar 2008 23:32:59 +0800</pubDate>
        <link>http://woody-420420.javaeye.com/blog/170683</link>
        <guid>http://woody-420420.javaeye.com/blog/170683</guid>
      </item>
  </channel>
</rss>