12.4 Helper Methods

我们可以在example group中定义helper方法,这样定义的helper方法可以在example group中的每个code example里使用。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe Thing do
  it "should do something when ok" do
    thing = Thing.new
    thing.set_status('ok')
    thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
    ...
  end

  it "should do something else when not so good" do
    thing = Thing.new
    thing.set_status('not so good')
    thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
    ...
  end
end

可以把thing = Thing.newthing.set_status('ok')部分提取出来,改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe Thing do
  def create_thing(options)
    thing = Thing.new
    thing.set_status(options[:status])
    thing
  end

  it "should do something when ok" do
    thing = create_thing(:status => 'ok')
    thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
    ...
  end

  it "should do something else when not so good" do
    thing = create_thing(:status => 'not so good')
    thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
    ...
  end
end

还可以进一步提取为(但是个人感觉这么做意义不大, 纯属是为了使其更符合英语语法结构):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe Thing do
  def given_thing_with(options)
    yield Thing.new do |thing|
      thing.set_status(options[:status])
    end
  end

  it "should do something when ok" do
    given_thing_with(:status => 'ok') do |thing|
      thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
      ...
    end
  end

  it "should do something else when not so good" do
    given_thing_with(:status => 'not so good') do |thing|
      thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
      ...
  end
end

共享Helper Methods

为了能够在example groups之间共享Helper Methods, 我们可以将Helper Methods定义在module里
并在需要使用这些方法的时候, 在example groups里面include相应的module.

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module UserExampleHelpers
  def create_valid_user
    User.new(:email => 'email@example.com', :password => 'shhhhh')
  end

  def create_invalid_user
    User.new(:password => 'shhhhh')
  end
end

describe User do
  include UserExampleHelpers

  it "does something when it is valid" do
    user = create_valid_user
    # do stuff
  end

  it "does something when it is not valid" do
    user = create_invalid_user
    # do stuff
  end

如果某个module中的helper methods, 在所有的example group里都用得上
还可以把这个module通过配置全局加载

1
2
3
RSpec.configure do |config|
  config.include(UserExampleHelpers)
end

12.5 Shared Examples

有时我们想要某个class的多个不同实例表现出相同的行为
这是我么可以采用shared example group将其行为描述出来
然后在需要的地方include这个example group

这里我们会用到2个方法:

shared_examples_for 用来用来声明shared example group

1
2
3
4
5
shared_examples_for "any pizza" do
  it "tastes really good" do
    @pizza.should taste_really_good
  end
end

it_behaves_like 用来include声明过的shared example group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe "New York style thin crust pizza" do
  before(:each) do
    @pizza = Pizza.new(:region => 'New York', :style => 'thin crust')
  end

  it_behaves_like "any pizza"

  it "has a really great sauce" do
    @pizza.should have_a_really_great_sauce
  end
end

describe "Chicago style stuffed pizza" do
  before(:each) do
    @pizza = Pizza.new(:region => 'Chicago', :style => 'stuffed')
  end

  it_behaves_like "any pizza"

  it "has a ton of cheese" do
    @pizza.should have_a_ton_of_cheese
  end
end

it_hehaves_like方法会生成一个名为behaves like xxx的嵌套group(nested example group)
传入it_hehaves_like的代码块会在这个嵌套group里执行

于是, 上述代码运行时会得到以下输出内容:

1
2
3
4
5
6
7
8
9
10
11
New York style thin crust pizza
  has a really great sauce
  behaves like any pizza
    tastes really good
    is available by the slice

Chicago style stuffed pizza
  has a ton of cheese
  behaves like any pizza
    tastes really good
    is available by the slice

12.6 Nested Example Groups

inner group可以看做是outer group的一个子类
所以在outer group里声明的任何helper method, before/after
在inner group里都依然可用

nested example group里的代码执行顺序:

  1. Outer before
  2. Inner before
  3. Example
  4. Inner after
  5. Outer after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ChatTeamTopic < ActiveRecord::Base
  acts_as_paranoid

  ...

  class << self
    alias_method :orig_delete_all, :delete_all

    def delete_all(conditions = nil)
      clear_notifications(conditions) if conditions.present?
      orig_delete_all(conditions)
    end

    def clear_notifications(conditions)
      ...
    end
  end

  ...

end

前段时间为正在着手的项目添加了基于faye的即时聊天功能
开发时参照了RailsCasts的教程, 还算比较顺利
但是部署到测试服务器时候, 发生了些问题

测试服务器使用了https server, 所有请求都走在https下
但是我的faye server跑在http下, 导致faye.js无法加载, faye client也无法创建

几经折腾, 最后采用apache添加了reverse proxy办法, 把https下的对faye的请求代理到http下faye server实际位置
以下为具体操作

为apache添加反向代理

加载proxy和proxy_http module
1
2
$ sudo a2enmod proxy
$ sudo a2enmod proxy_http

修改apache.conf

apache.conf
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
<VirtualHost *:80>
    ServerName myapp.example.com
    RewriteEngine On
    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent]
</VirtualHost>


# Provide an HTTPS entry point as well but one that will not spin up a second rails instance
# but rather redirect traffic accordingly
<VirtualHost *:443>
    ServerName myapp.example.com
    DocumentRoot /home/hanbing/work/svn/college/trunk/public
    <Directory /home/hanbing/work/svn/college/trunk/public>
      Require all granted
      Order allow,deny
      AllowOverride all
      Allow from all
      # MultiViews must be turned off.
      #Options -MultiViews
    </Directory>

    SSLEngine on
    SSLOptions +StrictRequire
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCertificateFile /etc/apache2/certs/myapp.example.com.crt
    SSLCertificateKeyFile /etc/apache2/certs/myapp.example.com.key

    ProxyPass /faye.js http://127.0.0.1:9292/faye.js
    ProxyPassReverse /faye.js http://127.0.0.1:9292/faye.js

    ProxyPass /faye http://127.0.0.1:9292/faye
    ProxyPassReverse /faye http://127.0.0.1:9292/faye
</VirtualHost>

在controller里添加

1
2
3
4
5
@faye_server =  if request.ssl?
                  request.protocol << request.host_with_port
                else
                  request.protocol << request.host << ":9292"
                end

创建faye client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%= javascript_include_tag @faye_server << "/faye.js" %>
<script language="javascript" type="text/javascript">
  var faye = new Faye.Client("<%= @faye_server %>/faye");

  faye.subscribe('/chat/new', function (data) {
    $("chat_list").insert({bottom: data["chat_log"].toString()});

    var post = $("chat_list").childElements().last();

    if(data["user_id"].toString() != "<%= current_user.id %>"){
      post.removeClassName("my-post");
      post.addClassName("member-post")
    }
    var objDiv = $("chat_list");
    objDiv.scrollTop = objDiv.scrollHeight;    
    new Effect.Highlight(post.id);
    $("errors").hide();

  });

</script>