Today's the day

向软件大牛炫耀我会焊单片机,向硬件大牛炫耀我会写 Rails,向软硬件大牛炫耀我生物,向软硬件生物大牛炫耀我会折腾期货 -_-bbb

拆分数组

Ruby 中的 Array 类提供了非常实用的 select 和 reject 方法,可以方便的从数组中挑出或剔除符合条件的元素:

  1. a = [1,2,3,4,5,6,7,8]
  2. a.select {|x| x % 2 == 0}
  3. >> [2,4,6,8]

如果想要把数组拆分成两个集合,一个符合某个条件,一个不符合,要怎么办呢?

可以先写一个 select 然后再来个 reject:

  1. a = [1,2,3,4,5,6,7,8]
  2. even = a.select {|x| x % 2 == 0}
  3. odd = a.reject {|x| x % 2 == 0}
  4. >> even = [2,4,6,8]
  5. >> odd = [1,3,5,7]

这样的话,不仅条件要重复写两遍,数组中的元素也要遍历两遍

可以利用 Ruby 可以返回多个值的特性,来写个 separate 函数:

  1. class Array
  2.     def separate
  3.         yes, no = [], []
  4.  
  5.         self.each do |x|
  6.             yield(x) ? yes << x : no << x
  7.         end
  8.  
  9.         return yes, no
  10.     end
  11. end

这样,数组拆分一次就可以搞定了

  1. even, odd = a.separate {|x| x % 2 == 0}

return false 在 IE 下失效了?

link_to 有个 method 属性,经常拿来配合 confirm 属性做删除链接:

<%= link_to ' Delete', { :action => 'destroy'}, :confirm => "Delete this post?", :method => :post %>

其实就是在链接的 onclick 中创建了一个临时表单,提交请求,最后 return false,这样链接本身就不会被触发:

<a href="/admin/destroy/1234" onclick="...;return false;">Delete</a>

不过发现,在 IE 下这个 return false 并没有效果,onclick 中的表单提交了,链接也触发了,以上面这个为例,点这个链接,会向 /admin/destroy/1234 发送两次请求,一次 post 一次 get 

那么为什么原来没有发现这个问题呢,因为 Rails 生成的控制器代码中都会有这么一句:

  1. verify :method => :post, :only => [ :destroy, :create, :update ],
  2. :redirect_to => { :action => :list }

所以第二次错误的 get 请求就会被转到 list 方法上去,但是即使通过 post 删除成功后,大多数情况也会转到 list 上去,看起来是一样的汗,实际上被转了两次。

问题就在于 IE 忽略了 return false,google 了下,好像碰到这个问题的人不是很多汗,难道是我 rp 问题…… 最后搜到了这个:

onclick="return false;" don't work in IE

看来有人在 CakePHP 的 AjaxHelper 中也发现了这个问题,解决的方法就是在 return false 前面加上一句:

onclick="... event.returnValue = false; return false;"

这样问题就解决了,不过 Rails 链接的 onclick 都是自动生成的,要怎么改呢。

再次拜 Ruby 强大的 OpenClass 所赐:

  1. module ActionView
  2.  module Helpers
  3.   module UrlHelper
  4.    private
  5.    def convert_options_to_javascript!(html_options, url = '')
  6.     ...
  7.     when confirm && popup
  8.       "... ;event.returnValue = false; return false;"
  9.     when confirm && method
  10.       "... ;event.returnValue = false; return false;"
  11.     ...
  12.     when method
  13.       "... event.returnValue = false;return false;"
  14.     when popup
  15.       ... + 'event.returnValue = false;return false;'
  16.     ...    
  17.   end
  18.  end
  19. end

找到 conver_options_to_javascript 这个方法,copy 过来,再添加上高亮部分的代码即可。直接覆盖核心的 private 方法,这种事情干起来真爽……

自动闭合 html 标签简化版

Chito 现在用 Rubyful Soup 来解决 html 没有正确闭合的问题,其中的 BeautifulSoup 类的 prettify 方法非常强大,几乎可以修正 html 中的所有错误。

不过 Chito 只是拿来闭合标签,其他的功能完全用不到,Rubyful Soup 还要依赖 htmltools 这个库,为了个闭合标签就要多附带这么多库太不爽了,而且还有个 %nbsp 的 bug。

自己尝试写了个,close_html:

  1. def close_html(html)
  2.     stream = html.scan( /<\s*[^>]+>/ ).map {|x| x[ /<\s*([^>^\s]*)[^>]*>/ , 1]}
  3.     stack = []
  4.     stream.each do |x|
  5.         if x =~ /^\//
  6.             stack.pop if x[1..-1] == stack.last
  7.         else
  8.             stack.push x
  9.         end
  10.     end
  11.     stack.reverse.each {|x| html << "</#{x}>\n"}
  12.     html
  13. end

处理的方式罗嗦了一些,先把 html 文本中的标签提取出来,然后再去掉属性之类的,转换成一个 “标签流”,比如:

<div class="main><ul><li>test</li>

就转换成了:

["div","ul","li""/li"]

接着读取上面这个流,把开始标签压栈,遇到以 / 开头的结束标签,检查是否和栈顶的标签相匹配,匹配就弹出,走完标签流后,还留在栈里的标签就是需要闭合的标签了。

测试一下:

  1. test_html = <<END
  2. <div>
  3.     <ul>
  4.         <li> lalalalala </li>
  5.         <li><strong><a href="http://www.google.com"> Google
  6. END
  7.  
  8. puts close_html(test_html)

输出:

  1. <div>
  2.     <ul>
  3.         <li> lalalalala </li>
  4.         <li><strong><a href="http://www.google.com"> Google
  5. </a>
  6. </strong>
  7. </li>
  8. </ul>
  9. </div> 

效果不错,标签都正确闭合了~

这种方法还是有局限性的,因为只是个简单的栈操作,所以只适合修正那些正确的 html 被拦腰截断的情况,而不能修正类似 <h1></h2> 这样匹配错误的情况,<div><a></div> 这样中间格式错误的情况也不行,不过 Chito 的编辑器和 textile 都不会生成这些样子的 html,所以完全可以用这个小函数替代那个庞大的 Rubyful Soup 了~

原来有个 reset_session

之前在用户登出的时候,都手动小心的清除登录用户的 session,今天才发现有 reset_session 这个方法,一口气把当前 session 对象的内容全部清除,又方便又安全~

reset_session  -  Resets the session by clearing out all the objects stored within and initializing a new session object. 

不光用户登出时要清空 session,用户登入之前,也要清除 session,免除当前 session 数据,和恶意伪造 session 数据的影响:

  1. def login
  2.     ...
  3.     if user.try_to_login
  4.         reset_session
  5.         session[:user_id] = user.id
  6.     ...
  7.  
  8. def logout
  9.     reset_session
  10.     redirect_to 'xxx'
  11. end

 

 

Ruby 中获取目录大小

查了查,Dir 类没有 size 方法,也没找到现成的解决方案,自己动手吧~

当前 Chito 中统计目录大小完全是偷懒的方式

  1. class Dir
  2.     def self.size(dir)
  3.        `du -s #{dir} | awk '{print $1}'`.to_i
  4.     end
  5. end

直接调用 du 这个程序然后把结果转换成数值就可以了,非常方便~  不过要是运行在 Windows 下就不灵了,只好自己写代码统计下:

  1. class Dir
  2.     def self.size(dir)
  3.         sum = 0
  4.         Dir.foreach(dir) do |entry|
  5.            next if entry =~ /^\./
  6.            path = File.join(dir, entry)
  7.            FileTest.directory?(path) ? sum += Dir.size(path) : sum += File.size(path)
  8.         end
  9.         sum
  10.     end
  11. end

不过计算目录大小是个很慢的工作,尤其时文件夹嵌套很深,小文件又很多的时候,缓存一下是很必要的,当新文件上传或者删除设置个 dirty 标记,dirty 标记存在的时候再去重新获取。