The Kai Way

Pragmaticly hacking

Web Resources Help You Fight With Older IEs

| Comments

Recently I have to fight with older version IE, that’s really a nightmare.

And then search some resources to fight with it.

Carrierwave-upyun配置多个不同buckets

| Comments

背景

carrierwave是RubyOnRails社区中比较 流行的文件上传插件,carrierwave-upyun 是集成upyun服务的插件。

把不同类型的文件存放到upyun不同的bucket上

使用upyun时有个常见的需求就是把不同类型的图片分开放到不同的bucket当中,但在 carreirwave-upyun的文档当中并没有提到这点,只是给出了怎么配置全局参数的例子:

1
2
3
4
5
6
7
CarrierWave.configure do |config|
  config.storage = :upyun
  config.upyun_username = "xxxxxx"
  config.upyun_password = 'xxxxxx'
  config.upyun_bucket = "my_bucket"
  config.upyun_bucket_domain = "my_bucket.files.example.com"
end

解决

而实际上,可以这么去做:

1
2
3
4
5
6
7
8
class CoverUploader < CarrierWave::Uploader::Base
  storage :upyun
  self.upyun_bucket = "my-covers"
end
class AttachementUploader < CarrierWave::Uploader::Base
  storage :upyun
  self.upyun_bucket = "my-attachements"
end

这样简单地在Uploader里assign一下就可以解决问题。

为什么以上的解决方法行得通?

Carreirwave的各种Configuration都是通过这里的add_config方法加入的。代码可以看以下的链接

https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/uploader/configuration.rb#L75-L94

add_config为每个Uploader实例添加了直接访问Class variable的方法,Uploader中 的各种Configuration 项(比如这里的upyun_bucket)都是存储在Uploader的Class 中。

而所有默认的Configuration项都是存储在CarrierWave::Uploader::Base,所以在我们 自定义的Uploader可以通过add_config为我们加入的self.#{config_item}= 去修改Configuration项。

也就是说Carrierwave一早就实现了这样的机制让不同的Uploader天生可以具有配置能力。

Use Weekly to Fetch Tech Updates

| Comments

我现在获取技术更新的来源,从订阅各种RSS改为订阅各类Weekly Mail。

比起RSS,Weekly的优点不少:

  • 更少的噪音,订阅某个Blog或者News site的RSS往往附带很多噪音
  • 更少的信息量,Weekly中已经是人工筛选过,更重要的信息
  • 减少阅读时间,每周读几封Weekly比时不时就去看RSS花更少的时间
  • 清理RSS,大大减少RSS的未阅读数量

现在RSS里剩下的内容,只有名人和大牛的博客(如韩寒,云风等),或者没有相关Weekly的内容。

以下是我订阅的几个Weekly

以上全部Weekly Mail都是免费的,值得一提的是前3份都是大牛 Peter Cooper 搞的

另外我还订一个Hacker News的monthly,是将HN上热门的内容制作成精美的ebook,有PDF,iPad版的PDF,epub和mobi。订阅地址是,http://hackermonthly.com/。这个是付费,一年$29。

Play a HTTP Toy Server With EventMachine

| Comments

EventMachine

EventMachine是Ruby社区的Reactor模式的实现。

所谓Reactor模式,通过运行一个事件循环,将输入分发给对应的处理器,处理过程全权交给处理器,从而实现同时处理多个输入,是实现高并发的利器。几乎每个语言都有对应的实现,比如Pythong的Twisted,最近很火的Node.js

这次我们通过实现一个简单的HTTP File server来探索EventMachine。

通过Rubygems可以安装它:

1
$ gem install eventmachine

Beginning Sample

我们先从一个简单的例子入手,以下代码实现了这样的一个服务器,打印发过来数据,并返回Yike

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'eventmachine'

module TcpSample
  def receive_data(data)
    puts "[#{Time.now}] receive #{data}"
    send_data "Yike\n"
  end
end

EM.run do
  %w{INT TERM}.each{ |s| Signal.trap s, &proc{EM.stop_event_loop} }
  port = ENV["PORT"] || 8001
  host = ENV["HOST"] || "127.0.0.1"
  EM.start_server host, port, TcpSample
  puts "Server start on #{host}:#{port}"
end

来解释几个EventMachine的API:

  • EM.run 这个方法初始化并启动一个事件循环。
  • EM.stop_event_loop 这个方法顾名思义就是停止事件循环。在这段代码中我们注册了两个Signal,INT和TERM,用来在命令行用Ctrl-C停止程序。
  • EM.start_server 启动一个TCP服务器并监听传入参数的host和port,最后一个传入的参数是具体的行为逻辑实现,可以是Module或者是Class。

代码中TcpSample module就是具体的Connection逻辑实现,只要实现几个由EventMachine Connection约定的方法,比如收发数据的receive_datasend_data。EventMachine会在运行过程事件被触发时回调Connection里的方法。具体关于EventMachine::Connection的文档请点击这里

我们可以用telnet来测试它:

1
2
3
4
5
6
$ telnet 127.0.0.1 8001
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hey
Yike

第一个简单的例子就这样演示完成了,继续下一步。

Toy File Server

接着步入正题,实现HTTP File Server。一句话来解释HTTP服务器做的事情,就是解析来自客户的Request,然后依照请求生成Response。这里的演示代码如题目所示,只是个Toy,按照请求返回静态文件。

首先需要接受并解析Request。EventMachine已经附带了好几种Protocol的解析,其中包括实现了HTTP的HeaderAndContentProtocol。注意这里的各种Protocol实现都是继承第一个例子中讲到EventMachine::Connection,并为各自的协议包装了一个receive_xxx的回调方法,HeaderAndContentProtocol的回调方法名为receive_request。我们的HTTP Toy要做的就是继承HeaderAndContentProtocol,在receive_request方法中实现File Server的逻辑。

1
2
3
4
5
class HTTPToy < EM::P::HeaderAndContentProtocol
  def receive_request headers, content
    #TODO ...
  end
end

先来完成HTTP Headers的解析:

1
2
3
4
5
6
7
8
9
REGEX = /\A(?<request_method>\w+) (?<full_path>\S+) HTTP\/(?<version>[\d.]+)\Z/
def parse_request(data)
  {}.tap do |req|
    matched = REGEX.match(headers.shift)
    req["request_method"] = matched[:request_method]
    req["full_path"] = matched[:full_path]
    req["http_version"] = matched[:version]
  end
end

接着实现Callback方法receive_request,主要的逻辑是查找文件和拼装Response并返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def receive_request headers, content
  @request = parse_headers(headers)
  filename = "." + @request["full_path"]
  if @request["full_path"] == "/"
    filename = "./index.html"
  end
  if File.exists?(filename) && File.file?(filename)
    serve_file filename
  else
    respond_not_found
  end
ensure
  close_connection_after_writing
end

def serve_file(filename)
  extname = File.extname(filename)
  send_headers "Content-Type" => {
                 "html" => "text/html",
                 "js" => "application/x-javascript",
                 "css" => "text/css"
               }(extname)
  send_data File.read(filename)
end

def send_headers(more = {})
  request["status"] = more.delete('status') || "200 OK"
  headers = "HTTP/1.1 #{request['status']}\r\n"
  more = {
    # the magic string "+8000" means my life is at HARD MODE
    "Date" => Time.now.strftime("%a, %d %b %Y %H:%m:%S +8000"),
    "Server" => "my-http-toy"
  }.merge(more)
  if more.any?
    more.each{ |k, v| headers << "#{k}: #{v}\r\n" }
  end
  send_data headers + "\r\n"
end

以上代码的close_connection_after_writing方法也是EventMachine的API之一,文档在这里。这个方法会等待send_data的完成再把与客户端之间的连接关闭。上述大段代码的作用就是读文件并用send_data返回。

把它跑起来并通过浏览器可以测试一下它:

screenshot

查看完整的实现代码请点击这里

Benchmark

最后来对比异步IO和同步的IO效率相差有多大,和本文实现的简单http file server对比的是Rack。用Rack来做对比是因为它几乎是最小最快的HTTP File server实现。代码如下:

1
2
# file: config.ru
run Rack::File.new(Dir.pwd)

压力测试用的是HTTPerf,作为测试Fixture的是一个名为index.html的小文件(47B)。

Rack的File middleware在并发超过350的情况就歇菜了:

1
2
3
4
5
6
7
8
9
10
11
12
$ rackup ./config.ru -p 8002
$ httperf --rate=350 --timeout=5 --num-conns=350 --port=8002 --uri=/index.html

Total: connections 350 requests 350 replies 347 test-duration 1.978 s

Connection rate: 177.0 conn/s (5.7 ms/conn, <=26 concurrent connections)
Connection time [ms]: min 2.1 avg 68.5 max 1272.6 median 13.5 stddev 245.5
Connection time [ms]: connect 63.6
Connection length [replies/conn]: 1.000

Request rate: 177.0 req/s (5.7 ms/req)
Request size [B]: 72.0

350个并发的请求用了接近2秒的时间,速度是177个连接每秒。接着再来测试我们的HTTPToy:

1
2
3
4
5
6
7
8
9
10
11
12
$ ruby ./httptoy.rb 8001
$ httperf --rate=600 --timeout=5 --num-conns=600 --port=8001 --uri=/index.html

Total: connections 600 requests 600 replies 600 test-duration 1.000 s

Connection rate: 600.1 conn/s (1.7 ms/conn, <=17 concurrent connections)
Connection time [ms]: min 0.4 avg 2.8 max 25.9 median 0.5 stddev 3.9
Connection time [ms]: connect 0.2
Connection length [replies/conn]: 1.000

Request rate: 600.1 req/s (1.7 ms/req)
Request size [B]: 72.0

完胜,和前面的差距不是一星半点,一秒内响应600个连接。如果继续提高并发数,到了700以上我们的HTTPToy也会出现不稳定的情况(崩溃或连接失败)。

Conclusion

EventMachine应用的场景和Node.js基本一样,IO密集的高并发场景,比如

在生产环境中大量使用EventMachine公司就是PostRank,这个公司基于EventMachine开发了大量的框架和库,有兴趣可以点击igrigorikpostrank-labs的Github帐号。

最后谈下EventMachine缺点,和其它的Reactor模式实现一样,对付CPU密集的应用不行,而且使用的库全部都必须是异步,不然会把Main Event Loop阻塞(其后果是处理速度大大降低)。而像Node.js程序里出现了大量Callback的情况,在EventMachine上会好一点。

本文中的运行环境是Mac OSX Lion,Ruby 1.9.3-p0。

如何阅读

| Comments

阅读

结构化的知识大都是通过阅读书籍得到的,所以阅读是学习的重要途径之一。

阅读的结果

获得以下四个问题的答案:

  • _这本书讲了什么?_
  • _作者详细讲述了什么?怎么讲?_
  • _这本书讲得有道理吗?是全部都有道理还是只有部分?_
  • _为什么要读这本书?获得了什么信息,得到了什么启发?_

做笔记

  • _划线_
  • _作记号_
  • _记下当时的问题_
  • _记下感想_

理解的技巧

  • _通过书名对书本做分类_
  • _把握内容的结构_
  • _找到重点的字词句_
  • _判断作者的主旨_
  • _评判该书_
  • _赞同或反对作者_

快速阅读的技巧

过慢的阅读速度并不一定能提高理解能力,但一定会影响精神状态。

更少的定焦能提高阅读速度,比如阅读一行文字仅用定位三次,可以通过指示物加快定焦(包括换行)

通过高速阅读练习(限制的眼睛定焦次数和时间{3,2,1分钟内}),实现高速效应,提高平时正常阅读速度。

记忆

有时需要在阅读后记忆一些事情,这需要理解人是怎么遗忘信息的。快速的多遍阅读记忆效果比一遍阅读强很多。

通过在遗忘曲线上的几个拐点复习来巩固记忆的效果,这几个拐点分别是5分钟,30分钟,12小时,1天,2天和5天。

后记

本篇文章是我在读完文末列出的几本书之后写下的笔记。这几本是很有趣的书,让你在阅读中学习如何阅读的书。

Refs

新起点

| Comments

这个月开始我从ThePlant离职,加入Intridea

ThePlant是我毕业后第一个公司。算上在毕业前半年的时间,我已经为ThePlant工作了两年。在这两年里我学到了很多,和同事们一起也很开心,学到很多宝贵的经验,在此感谢ThePlant里的所有人。

经过这两年工作,我有了更多的思考,思考自己能做什么,需要去做什么,所以便有了换工作的理由。就是去和更多不同的优秀的开发者一起工作,理解更多解决问题的思路,验证在之前得到想法和经验是否正确,参与更多不同类型项目的开发,获得更多经验。

理由很简单是吗?真的就这么简单,我想进步想有更多提升。

新的工作是Remote work的形式,远程的沟通协作和更多自我管理。

新的发型 http://instagr.am/p/Bxu71 和新的起点 http://intridea.com/about/people/kai

Learning Tinyrb 1

Tinyrb,一个Ruby的实现, 作者Marc-André Cournoyer计划实现参照Lua和Potion 实现来做出一个轻量级快速的Ruby实现, 这是一个对作者关于Tinyrb想法的采访。目前还不能直接运行mspec,或者是我不知道怎么运行mspec。

其中用了一些外挂:

  • GC, Boehm-Demers-Weiser conservative garbage collector
  • 语法解析(Lexical Parse), peg/leg
  • 命令行选项解析(Command-Line Option Parse), Free Getopt
  • 正则表达式解析(Regular Parse), PCRE (Perl-compatible regular expression library)

Tinyrb到目前为止是个非常清晰简洁的实现,整个实现,VM+Ruby Library就200K多一点。编译运行非常简单,在Github上clone下来后make就行了。跑下Fib测试:

~/ws/tinyrb > time ruby bench/bm_app_fib.rb
real    0m10.866s
user    0m9.393s
sys 0m1.170s
~/ws/tinyrb > time ruby-1.9 bench/bm_app_fib.rb
real    0m1.827s
user    0m1.423s
sys 0m0.013s
~/ws/tinyrb > time jruby bench/bm_app_fib.rb
real    0m2.718s
user    0m2.447s
sys 0m0.097s
~/ws/tinyrb > time tinyrb bench/bm_app_fib.rb
real    0m1.884s
user    0m1.680s
sys 0m0.007s

运行环境:Archlinux,kernal26-2.26.29,Core T8100,2G Mem。或许Tinyrb目前实现还不完全,启动速度比其它的Ruby实现就快了不少。

现在来剖析一下源码,我们从Tinyrb解析器启动入手,整个解析器的启动是在vm/tr.c文件中定义的:

/* file: vm/tr.c */
int main (int argc, char *argv[]) {
  int opt;
  TrVM *vm = TrVM_new();
  while((opt = getopt(argc, argv, "e:vdh")) != -1) {
    switch(opt) {
      /* 参数解析 */
      ...
    }
  }
  /* 参数处理 */
  if (argc > 0) {
    TR_FAILSAFE(TrVM_load(vm, argv[argc-1]));
    return 0;
  }
  TrVM_destroy(vm);
  return usage();
}

可以看到整个VM的启动由TrVM_new()开始

/* file: vm/vm.c */
/* GC初始化 */
GC_INIT();
/* [A]VM分配空间并初始化 */
TrVM *vm = TR_ALLOC(TrVM);
vm->symbols = kh_init(str);
vm->globals = kh_init(OBJ);
vm->consts = kh_init(OBJ);
vm->debug = 0;
/* [B]初始化几个核心类Method,Symbol,Class,Object,Module */
TrMethod_init(vm);
TrSymbol_init(vm);
TrModule_init(vm);
TrClass_init(vm);
TrObject_preinit(vm);
TrClass *symbolc = (TrClass*)TR_CORE_CLASS(Symbol);
TrClass *modulec = (TrClass*)TR_CORE_CLASS(Module);
TrClass *classc = (TrClass*)TR_CORE_CLASS(Class);
TrClass *methodc = (TrClass*)TR_CORE_CLASS(Method);
TrClass *objectc = (TrClass*)TR_CORE_CLASS(Object);
/* [C]设置核心类的继承体系 */
symbolc->super = modulec->super = methodc->super = (OBJ)objectc;
classc->super = (OBJ)modulec;
/* [D]设置核心类的MetaClass */
symbolc->class = TrMetaClass_new(vm, objectc->class);
modulec->class = TrMetaClass_new(vm, objectc->class);
classc->class = TrMetaClass_new(vm, objectc->class);
methodc->class = TrMetaClass_new(vm, objectc->class);
objectc->class = TrMetaClass_new(vm, objectc->class);
/* [E]确保所有在Object之前创建的的Symbol的类得到初始化 */
TR_KH_EACH(vm->symbols, i, sym, {
  TR_COBJECT(sym)->class = (OBJ)symbolc;
});
/* [F]装入各种核心类 */
...
/* [G] */
vm->self = TrObject_alloc(vm, 0);
vm->cf = -1;
/* [H]缓存几个常用的值 */
vm->sADD = tr_intern("+");
vm->sSUB = tr_intern("-");
vm->sLT = tr_intern("<");
vm->sNEG = tr_intern("@-");
vm->sNOT = tr_intern("!");
/* 装载Ruby Library(在lib/目录下的文件) */
TR_FAILSAFE(TrVM_load(vm, "lib/boot.rb"));

在GC完成初始化之后,代码中[A]部分完成了维护整个Tinyrb运行环境的虚拟机对象的空间分配和初始化,VM是个宏:

#define VM TrVM *vm

TrVM这个VM的结构包括了什么:

/* file: vm/tr.h */
typedef struct TrVM {
  khash_t(str) *symbols; /* 全局的符号表 */
  khash_t(OBJ) *globals; /* 全局对象 */
  khash_t(OBJ) *consts;  /* 全局常量 */
  OBJ classes[TR_T_MAX]; /* 虚拟机维护的核心类 */
  TrFrame *top_frame; /* 最顶层的栈幀(运行时栈的栈顶) */
  TrFrame *frame; /* 当前的栈幀 */
  int cf;   /* 栈幀的数量 count of frames */
  OBJ self; /* 根对象 */
  /* 调试和错误符号,还有一堆异常 */
  ...
  /* 几个缓存的对象 */
  OBJ sADD;
  OBJ sSUB;
  OBJ sLT;
  OBJ sNEG;
  OBJ sNOT;
};

在前一块代码中设置的symbols,globals,consts就是保存运行时(Runtime)环境的各种信息,这几个变量都是Hash。接着下面的是Tinyrb的几个核心类列表,这是一个枚举值。然后是运行环境必不可少的栈帧,作用域调用就是靠这个维护的。栈幀由对象栈幀,栈顶,栈幀数这几个变量维护。还有一个虚拟机环境的根对象,这个对象就是在整个运行环境最外层作用域的对象,Ruby能做到不像Java那样写个什么都要包覆在一个对象中就是靠这个对象实现的,这个对象混入了Kernel模块,后面会看到每个栈帧(TrFrame)中都会有一个这样对象存在。最后是调试标记和异常信息,暂时略过。最后的几个常用的对象,可以看到在TrVM_new()中的[H]处进行初始化。

在VM的初始化中,中间的大段代码就是复杂完成Tinyrb的对象体系的构建,由Method开始初始化:

/* file:vm/class.c */
void TrMethod_init(VM) {
  OBJ c = TR_INIT_CORE_CLASS(Method, Object);
  tr_def(c, "name", TrMethod_name, 0);
  tr_def(c, "arity", TrMethod_arity, 0);
  tr_def(c, "dump", TrMethod_dump, 0);
}

TR_INIT_CORE_CLASS这个宏会引发一连串复杂的调用:

/* file:vm/tr.h */
#define TR_INIT_CORE_OBJECT(T) ({ \
  Tr##T *o = TR_ALLOC(Tr##T); \
  o->type  = TR_T_##T; \
  o->class = vm->classes[TR_T_##T]; \
  o->ivars = kh_init(OBJ); \
  o; \
})
#define TR_CORE_CLASS(T)     vm->classes[TR_T_##T]
#define TR_INIT_CORE_CLASS(T,S) \
  TR_CORE_CLASS(T) = TrObject_const_set(vm, vm->self, tr_intern(#T), \
                                   TrClass_new(vm, tr_intern(#T), TR_CORE_CLASS(S)))
/* vm/class.c */
OBJ TrClass_new(VM, OBJ name, OBJ super) {
  TrClass *c = TR_INIT_CORE_OBJECT(Class);
  TR_INIT_MODULE(c);
  /* if VM is booting, those might not be set */
  if (super && TR_CCLASS(super)->class) c->class = TrMetaClass_new(vm, TR_CCLASS(super)->class);
  c->super = super;
  return (OBJ)c;
}
#define TR_INIT_MODULE(M) \
  (M)->name = name; \
  (M)->methods = kh_init(OBJ); \
  (M)->meta = 0

TR_INIT_CORE_CLASS(Method, Object);展开如下

TrClass *obj = TR_ALLOC(TrClass);
obj->type  = TR_T_Class;
obj->class = vm->classes[TR_T_Class];/* 实际上这句将其赋值为0,因为VM内部还没有创建Class类型 */
obj->ivars = kh_init(OBJ);
obj->name = tr_intern(Method);
obj->methods = kh_init(OBJ);
obj->meta = 0;
obj->super = vm->classes[TR_T_Object]; /* 实际上这句将其赋值为0,因为VM内部还没有创建Object类型 */
OBJ const_class = TrObject_const_set(vm, vm->self, tr_intern(Method), (OBJ)obj);
vm->classes[TR_T_Method]=const_class;

这样很清楚看到Method这个类初始化的过程,首先分配一个类(TrClass)的内存空间,接着设置所有的属性,之后把这个Method类设置到VM的常量列表,最后将这个Method类的对象地址保存到虚拟机的类型列表(Classes List)。注意这里其实Method类的class和super都是为0的。

顺便提一下,Tinyrb内部的对象类型(Object type)就是这个枚举值。

/* file: vm/tr.h */
typedef enum {
  /*  0 */ TR_T_Object, TR_T_Module, TR_T_Class, TR_T_Method, TR_T_Binding,
  /*  5 */ TR_T_Symbol, TR_T_String, TR_T_Fixnum, TR_T_Range, TR_T_Regexp,
  /* 10 */ TR_T_NilClass, TR_T_TrueClass, TR_T_FalseClass,
  /* 12 */ TR_T_Array, TR_T_Hash,
  /* 14 */ TR_T_Node,
  TR_T_MAX /* keep last */
} TR_T;

当Method的属性设置完成之后接着就开始为其添加方法name(),arity(), dump()。添加方法是通过之前已经设置好的Method类,实例化三个Method Object,并把这三个对象添加到Method类的方法列表中,下面是tr_def(c, “name”, TrMethod_name, 0) 的展开:

TrMethod *method = TR_INIT_CORE_OBJECT(Method); /* 初始化一个Method对象并返回 */
method->func = TrMethod_name;
method->data = TR_NIL;
method->arity = 0;
TrClass *m =  (TrClass*)E(vm->classes[TR_T_Method])
TR_KH_SET(m->methods, name, method);
method->name = name;

接着其它几个类型也类似的过程进行初始化:Symbol,Module,Class和Object。

到此为止VM的Const List已经有了这几个类Method,Symbol,Module,Class,Object,并且它们的第一个实例也已经保存到Classes List中。接着这些类的继承体系和剩下的核心类怎样初始化呢?请听下回分解。

Runtime Error Words File Not Found of DataMapper

| Comments

最近运行测试时在跑到用dm-sweatshop创建fixtures时总是报RuntimeError words file not found,而出错的行包含/\w+/.gen,然后就开始折腾…

最初看到/\w+/.gen这种语法时觉得十分的新奇,还以为是dm-sweatshop的创意,原来是dm-sweatshop依赖的randexp库的魔法,而这背后原来是从系统的words文件中随机挑选出一个单词来实现的。words file in Wikipedia

如果系统中没有words文件那在使用刀sweatshop的/\w+/.gen时就一直会报RuntimeError words file not found。而就是这个没有明确说明的依赖,让我折腾了好多天,因为系统中不一定一开始就有这个words文件,像在我用的ArchLinux上是由 words包提供的(貌似Ubuntu中是wbritish包)。

一开始我以为是DM又抽风了,就删了dm,然后又把整个rubygems删了。然后开始用grep和find搜索各种源码,先是在dm-more中找不到,接着去找ParseTree(sweatshop依赖它来完成unique {/\w+/.gen})语法。最后读了一下sweatshop依赖中看到randexp,接着在randexp中搜索到了”words file not found”这个RuntimeError。

以上的做法比较S13,如果在使用sweatshop之前RTFM,就能看到它依赖于Randexp,然后如果再去看看源码,就不用折腾那么久XD

不过仔细读了一下sweatshop的源码,知道其中的猫腻,sweatshop为DataMapper::Model模块加入了几个方法:

fixtures:定义fixtures,和FactoryGirl的define差不多,可以用个Symbol指定名字,否则就为:defualt。方法缩写fix。 generate:使用之前fixtures方法中定义的属性值创建模型实例,调用的是模型的create方法。缩写gen。 generate!:同上,不过故名思义创建实例调用的方法是create!。缩写gen!。 generate_attributes:返回在fixtures方法中定义的属性Hash。类似FactoryGirl的attribute_for。 make:调用new创建实例,即不保存。 pick:返回一个由上述创建实例的方法创建的实例。一般用于关联,要将已创建的模型实例作为关联对象时使用。 sweatshop中维护了两个Hash,维护模型定义的model_map和维护实例(包括保存与未保存)的record_map。上述方法中 fixtures将模型定义加入model_map,gen/gen!/make方法则在创建实例后将其加入record_map中,pick方法就在 record_map中找出现存实例。

如果想要在FactoryGirl中使用/\w+/.gen就require ‘rendexp’这句就行了。

最后有个疑问,Rendexp这个包在win平台能用吗?