生命的精彩不在于它的长度,而在于其厚度和宽度!

A MarkDown TextEditor blackanger.z Jun 28

Post a comment

网上找了半天没有找到相关的markdown texteditor, 于是自己写了一个gem, 方便与rails3 集成。

基于JQuery和Rails3的滑动验证码gem blackanger.z Jun 27

Post a comment

看见一个很有创意的滑动验证码jquery插件, 可惜是基于php的,于是修改了一下,就有了这个rails3的gem:

rails_qaptcha

欢迎fork。

Mongoid Counter Cache blackanger.z Jun 19

Post a comment

Support: Ruby1.9.2/Rails3/Mongoid2.0.2/MongoDB 1.8.1

Fork it

# 
#  lib/mongoid/counter_cache.rb
#  ruby
#  
#  Created by Zhang Alex on 2011-06-17.
#  Copyright 2011 __ZhangHanDong__. All rights reserved.
# 
# ===================================
# class Forum
#   references_many :topics
#   references_many :posts
# end
#
#
# class Topic
#   referenced_in :forum
#   include Mongoid::CounterCache
#   counter_cache name: :forum, inverse_of: :posts
# end
# ===================================

module Mongoid
  module CounterCache
    extend ActiveSupport::Concern

    module ClassMethods
      def counter_cache(metadata)
        counter_name = "#{metadata[:inverse_of]}_count"

        set_callback(:create, :after) do |document|
          relation = document.send(metadata[:name])
          if relation
            relation.inc(counter_name.to_sym, 1) if relation.class.fields.keys.include?(counter_name)
          end
        end

        set_callback(:destroy, :after) do |document|
          relation = document.send(metadata[:name])
          if relation && relation.class.fields.keys.include?(counter_name)
            relation.inc(counter_name.to_sym, -1) 
          end
        end

      end

    end #ClassMethods

  end #CounterCache
end #Mongoid

Rails 3和Ruby1.9.2 Yaml文件无法解析错误 blackanger.z Jun 12

Post a comment

项目运行在Ruby1.9.2下,使用了mongoid, 启动的时候,发现无法读取yml配置文件,老是报错,类似:Psych::SyntaxError

在google group里,发现了解决办法:

  #在config/boot.rb里添加
  require 'yaml'
  YAML::ENGINE.yamler= 'syck'

具体原因,可能是RubyGems引起的:

Rubygems 1.5.0 changes the yaml parsing default from syck 
to psych and psych doesn't like the ":<<" in yaml files. 

Pow的使用心得 blackanger.z Jun 09

Post a comment

  1. 官网: http://pow.cx

  2. Pow很不稳定,有时候会出问题,变得无法工作, 解决方法:

    1. 使用powder管理pow. 到项目根目录

       $>   powder restart 

    2. 如果还不行, 就log out系统。

    3. 如果再不行, 就重启系统。

  3. Powder的使用: 安装

    gem install powder
    $>    powder --help
    

  4. 如果你的pow抛出这样的错误: Error: unknown process error ... ...

则可能是你项目出了问题, 请你仔细检查,或者启用webrick或者其他服务器,查看日志。

因为pow不会抛出类似的日志。

rails中form_for的:prompt不工作 blackanger.z May 08

Post a comment

碰到个奇怪的问题:

 #views/
 =  f.input :typee, :collection => link_collect, :label => false, :prompt => "..."

#link_collect helper method

  def link_collect

     [['web', 1], ['wap', 2], ['iphone', 3], ['android', 4]]

  end

这个prompt无论如何都不工作 。。。

因为用了simple_form还以为是simple_form的问题呢,就跑github的simple_form提了一个issuse , 直到rafaelfranca解答,我才恍然大悟。

原来是这个字段有默认值,所以令prompt失效。

  private
    def add_options(option_tags, options, value = nil)
             ...
      if value.blank? && options[:prompt]
              ...
      end
      option_tags.html_safe
    end

看来有些问题,想偷懒还无法解决,只能去源代码中找答案。

DRY方式组织model层公用或类似代码 blackanger.z May 07

Post a comment

项目里经常会遇到多个model里有相似的方法,或者是公用的代码,我们一直遵循DRY的原则, 我们要发挥懒惰的积极一面 - 少写代码。 把这些公用,相似的代码都放到一个地方,一处修改,多处更新,便于维护。下面拿项目里碰的例子来说明:

下面这三个model是相似代码:

class AppRankDay < ActiveRecord::Base

   # 包含了一个通用的search方法
   include General::Models::AppRankGeneral
   define_search_and_sort_method_for_app_rank

end

class AppRankWeek < ActiveRecord::Base
  # 包含了一个通用的search方法
  include General::Models::AppRankGeneral
  define_search_and_sort_method_for_app_rank

end

class AppRankMonth < ActiveRecord::Base
  # 包含了一个通用的search方法
  include General::Models::AppRankGeneral
  define_search_and_sort_method_for_app_rank

end

它们都包含了相似的search方法和sort方法。

还有其他model, 只用到了缓存相关的代码,并没有search和sort方法,比如:

class App < ActiveRecord::Base
  include General::Models
end

class Subject < ActiveRecord::Base
  include General::Models
end

我们通过自定义一个module来实现上面的代码DRY:

#general.rb
module General
  module Models
    extend ActiveSupport::Concern

    included do
      acts_as_cached :ttl => 30.minutes
      after_save :reset_cache
      after_destroy :expire_cache
      paginates_per 30
    end
    module ClassMethods
      #TODO
    end#ClassMethods

    module InstanceMethods
      #TODO
    end#module InstanceMethods

    # module AppRankGeneral
    module AppRankGeneral
      extend ActiveSupport::Concern
      include ::General::Models
      included do
        paginates_per 200
      end

      module ClassMethods
        #TODO
        def define_search_and_sort_method_for_app_rank
          singleton_class.class_eval do #这里是动态定义类方法
            define_method("search") do |keywords|
              ...
            end

            define_method("sort") do |sort_ids|
              ...
            end
          end#singleton class end
        end
      end#module ClassMethod

      module InstanceMethods
        #TODO
      end#module InstanceMethods
    end#AppRankGeneral

  end#Models
end#General

说明: extend ActiveSupport::Concern是Rails3里实现的方法,相当于我们之前经常用的, 具体可以去看API:

def self.included(base)
  base.send :include, InstanceMethods
  base.send :extend, ClassMethods
end

Rails 3 Ajax Post&&AUTH_TOKEN blackanger.z Apr 26

Post a comment

Rails3 中使用Ajax POST请求, 不会自动加AUTH_TOKEN, 需要我们自己添加, 添加方法:

layout/application.html.haml

= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery?

application.js

jQuery.ajaxSetup({ 
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
})

jQuery(document).ready(function() {

  // UJS authenticity token fix: add the authenticy_token parameter
  // expected by any Rails POST request.
  jQuery(document).ajaxSend(function(event, request, settings) {
    // do nothing if this is a GET request. Rails doesn't need
    // the authenticity token, and IE converts the request method
    // to POST, just because - with love from redmond.
    if (settings.type == 'GET') return;
    if (typeof(AUTH_TOKEN) == "undefined") return;
    settings.data = settings.data || "";
    settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN);
  });
});

CoffeeScript blackanger.z Apr 17

Post a comment

介绍:

Rails 3.1 集成了CoffeeScript和JQuery, SASS. 所以CoffeeScript的学习是必要的。 CoffeeScript是纯Ruby实现的一种编程语言,通过编译生成相应的js文件。

安装:

需要安装nodeJsnpm

sudo npm install coffee-script

用法:

安装好CoffeeScript,你就可以直接使用coffee命令(需要设置环境变量 - /node_modules下面)来进入REPL来学习语法。

coffee命令接受下列参数:

-c, --compile 把一个.coffee文件编译为同名的.js文件
-i, --interactive 启动一个CoffeeScript会话交互界面,你可以执行一些代码片段。如果使用了rlwrap, 使用起来会更棒。
-o, --output [DIR] 输出指定目录的所有编译过的js文件,与--compile或--watch联合使用。
-j, --join 把所有要编译的脚本按顺序连在一起,然后再编译。这在构建一个大的projects时很有用。
-w, --watch 查看coffee-script脚本修改次数,重编译次数。
-p, --print 直接把JavaScript输出到终端而不生成js文件。
-l, --lint  如果安装了jsl(JavaScript Lint)命令, 用它来检查一个CoffeScript文件的编译。(和--watch联合使用)
译者注:在mac下安装jsl, 需要把jsl.conf置于(/node.js/lib/node_modules/npm/node_modules/coffee-script/lib/../extras/jsl.conf)
-s, --stdio 没看懂,保留原文
Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT. Good for use with processes written in other languages. An example: cat src/cake.coffee | coffee -sc
-e, --eval 直接在命令行编译和打印一些小代码片段,例如:
           coffee -e "puts num for num in [10..1]"
-r, --require 在编译和执行你脚本直接load一个lib库。能被用于编译器的hook(例如增加Growl消息通知)
-b, --bare 编译为没有顶级安全包装函数的JavaScript(一般用于CoffeeScript作为Node.js模块的时候)
-t, --tokens 词法分析,打印出一堆如下的token stream:
            [IDENTIFIER square] [ASSIGN =] [PARAM_START (] ...
-n, --nodes  比楼上的高级,词法分析然后解析为解析树
             Expressions
               Assign
                 Value "square"
                 Code "x"
                   Op *
                     Value "x"
                     Value "x"
--nodejs  不要和上面的混淆。
The node executable has some useful options you can set, such as
--debug and --max-stack-size. Use this flag to forward options directly to Node.js.

下面是一些例子:

  1. Compile a directory tree of .coffee files into a parallel tree of .js, in lib:

    coffee -o lib/ -c src/

  2. Watch a file for changes, and recompile it every time the file is saved:

    coffee --watch --compile experimental.coffee

  3. Concatenate a list of files into a single script:

coffee -o lib/ --join --compile src/*.coffee

  1. Print out the compiled JS from a one-liner:

coffee -bpe "alert i for i in [0..10]"

  1. Start the CoffeeScript REPL:
    coffee

语言参考:

Functions 函数是通过一个可选参数的一对括号,一个箭头,和一个函数体。例如:

#CoffeeScript:
  square = (x) -> x * x
  cube   = (x) -> square(x) * x

#JavaScript:
  var cube, square;
  square = function(x) {
    return x * x;
  };
  cube = function(x) {
    return square(x) * x;
  };

函数的参数也可以有默认值。

#CoffeeScript:
  fill = (container, liquid = "coffee") ->
    "Filling the #{container} with #{liquid}..."
JavaScript:
  var fill;
  fill = function(container, liquid) {
    if (liquid == null) {
      liquid = "coffee";
    }
    return "Filling the " + container + " with " + liquid + "...";
  };

(是不是感觉和Ruby用法差不多?)

Objects and Arrays CoffeeScript的对象和数组字面量和它的JavaScript表哥很相似。对象的创建和YAML类似:

#CoffeeScript:
  song = ["do", "re", "mi", "fa", "so"]
  singers = {Jagger: "Rock", Elvis: "Roll"}
  bitlist = [
    1, 0, 1
    0, 0, 1
    1, 1, 0
  ]

  kids =
    brother:
      name: "Max"
      age:  11
    sister:
      name: "Ida"
      age:  9

#JavaScript:
  var bitlist, kids, singers, song;
  song = ["do", "re", "mi", "fa", "so"];
  singers = {
    Jagger: "Rock",
    Elvis: "Roll"
  };
  bitlist = [1, 0, 1, 0, 0, 1, 1, 1, 0]; 
  kids = {
    brother: {
      name: "Max",
      age: 11
    },
    sister: {
      name: "Ida",
      age: 9
    }
  };

Lexical Scoping and Variable Safety

#CoffeeScript:
  outer = 1
  changeNumbers = ->
    inner = -1
    outer = 10
  inner = changeNumbers()

#JavaScript:
  var changeNumbers, inner, outer;
  outer = 1;
  changeNumbers = function() {
    var inner;
    inner = -1;
    return outer = 10; 
  };
  inner = changeNumbers();

未完 。。。

【转】张文钿的RSpec投影片 blackanger.z Mar 26

Post a comment

git submodule blackanger.z Mar 24

Post a comment

为项目增加submodule:

git submodule add git://github.com/ZhangHanDong/use_db.git vendor/plugin/use_db

删除submodule:

  git rm --cache vendor/plugin/use_db
  rm -rf vendor/plugin/use_db

使用:

  git submodule init
  git submodule update

注意: 要增加的submodule git链接的权限。

《Meta Programming》 读书笔记 blackanger.z Mar 22

Post a comment

豆瓣读书笔记, 点这里

基于二分算法实现的include? blackanger.z Mar 19

Post a comment

update info: 后经检测,此方法效率并不是很高。此文章意在算法,不在性能。

昨天,突然想知道Ruby 内建的include?方法是否是基于二分查找算法实现的,于是就找来了源码看了看:

# Ruby BuildIn include?实现:
/*
 *  call-seq:
 *     ary.include?(obj)   -> true or false
 *
 *  Returns <code>true</code> if the given object is present in
 *  +self+ (that is, if any object <code>==</code> <i>anObject</i>),
 *  <code>false</code> otherwise.
 *
 *     a = [ "a", "b", "c" ]
 *     a.include?("b")   #=> true
 *     a.include?("z")   #=> false
 */

VALUE
rb_ary_includes(VALUE ary, VALUE item)
{
    long i;

    for (i=0; i<RARRAY_LEN(ary); i++) {
        if (rb_equal(RARRAY_PTR(ary)[i], item)) {
            return Qtrue;
        }
    }
    return Qfalse;
}

然后发现, 内建的include?只是简单的循环查找(当然,优势在于支持任何类型元素的Array)。于是就用二分查找实现了一个类似的功能(局限:只能是可排序对象组成的数组 - 因为要求有可比性):

class Array
  def binary_search_index(e, l = 0, u = length - 1)
    return if  l>u
    m=(l+u)/2
    begin
      e.to_s < self[m].to_s ? u=m-1 : l=m+1
      e == self[m] ? m : binary_search_index(e,l,u)
    rescue
      nil
    end
  end


  def b_include? e
    begin
      temp = self.sort_by{|i| i.to_s}
      !temp.binary_search_index(e).nil?
    rescue
      return false
    end
  end
end

然后我们和Ruby内建的include?做一个性能比较:

arr = (1..100000000).to_a

Benchmark.bm do |x|
  x.report{  arr.include?(99999999) }
  x.report{  arr.b_include?(99999999) }
end

# 性能检测:
#                  user     system      total        real
# include?       7.640000   0.260000   7.900000 (  8.581378)
# b_include?     0.000000   0.000000   0.000000 (  0.000142)

我写了一个小的Ruby Array/Hash扩展gem, 里面包含了上述代码,安装就可以方便的使用了。

Ruby中的深层拷贝问题 blackanger.z Mar 13

Post a comment

背景

在项目开发中,有时候会碰到一些需求:比如,Rails项目中, 要对params做一些处理,但是不能影响到params本身的数据。这个时候,你也许会这样:

  params_clone = params
  params_clone.delete(:controller)

然后你拿着 params_clone 就去用了, 殊不知, 你拿的还是params这个对象,在你修改params_clone的同时,也修改了params.

Ruby 1.8 (包括Ruby1.9) 中, =赋值符, 只是把引用指向另一个变量:

   a = [1, [1, 1]]
   b = a

这里, a 和 b 实际指向的是同一个对象。

a.object_id == b.object_id   #=>  true

当你意识到这个问题的时候, 你可能会想, 那拷贝一个对象不就完了:

  b = a.dup    
  a.object_id == b.object_id   #=>  false

你会说,这次是2个对象了吧。但是,打住吧,请看:

 b[1][0]  =  2
 puts b   #=>   [1, [2, 1]]
 puts a   #=>   [1, [2, 1]]

a也跟着变, 纳闷不?

a[0]保存的是 1的地址, a[ 1 ]保存的是[1,1]的地址, 和 b[ 1 ]指向的是同一个对象, 而dup方法只拷贝了一层。所以不靠谱。

注:**dup vs clone**

都是浅拷贝。 dup只会复制内容, 而clone会连同单例方法一并复制.
ruby字面量(Fixnum,true,false,nil,Symbol)不可以调用dup和clone方法,会报TypeError。
对对象的state(taint,frozen)的改变:dup会把frozen的对象unfrozen,clone不会。

那么如何做比较靠谱呢:

Ruby提供了一个持久化库(Marshal), 提供了两个方法:一个是dump方法,可以导出对象实例obj的持久化数据;另一个是load方法,可以根据持久化的数据,重新还原出了一个对象实例。

于是,我们可以这样:

b = Marshal.load(Marshal.dump(a))  
b[1][0] = 2
puts b #=> [1, [2, 1]]
puts a #=> [1, [1, 1]]

a没受影响。

但是,Marshal的局限性是, 如果一个对象需要被还原,那么解释器必须能够找到这个对象的定义。同时,解释器找到的这个对象的类的定义必须和原来的定义相同,否则还原出的对象将不是原来保存的对象。

下面是深层拷贝的一些代码:

clone.rb 来自一位日本朋友

#!ruby

class Object
  def deep_clone
    _deep_clone({})
  end

  protected
  def _deep_clone(cloning_map)
    return cloning_map[self] if cloning_map.key? self
    cloning_obj = clone
    cloning_map[self] = cloning_obj
    cloning_obj.instance_variables.each do |var|
      val = cloning_obj.instance_variable_get(var)
      begin
        val = val._deep_clone(cloning_map)
      rescue TypeError
        next
      end
      cloning_obj.instance_variable_set(var, val)
    end
    cloning_map.delete(self)
  end
end

class Array
  protected
  def _deep_clone(cloning_map)
    return cloning_map[self] if cloning_map.key? self
    cloning_obj = super
    cloning_map[self] = cloning_obj
    cloning_obj.map! do |val|
      begin
        val = val._deep_clone(cloning_map)
      rescue TypeError
        #
      end
      val
    end
    cloning_map.delete(self)
  end
end

class Hash
  protected
  def _deep_clone(cloning_map)
    return cloning_map[self] if cloning_map.key? self
    cloning_obj = super
    cloning_map[self] = cloning_obj
    pairs = cloning_obj.to_a
    cloning_obj.clear
    pairs.each do |pair|
      pair.map! do |val|
        begin
          val = val._deep_clone(cloning_map)
        rescue TypeError
          #
        end
        val
      end
      cloning_obj[pair[0]] = pair[1]
    end
    cloning_map.delete(self)
  end
end

HTTP状态码思维导图 blackanger.z Mar 09

Post a comment

因为图片太大, 加个链接吧!HTTP状态码思维导图