RSpec best practices and tips

After a year using RSpec, I’m happy to share “(My) RSpec Best Practices and Tips”. Let’s make your specs easier to maintain, less verbose, more structured and covering more cases!

Use shortcuts specify {}, it {} and subject {}

You think RSpec is verbose? In case your code doesn’t need any description, use a specify block!

it "should be valid" do
@user.should be_valid
end

can be replaced with

specify { @user.should be_valid }

RSpec will generate a nice description text for you when running this expectation. Even better, you can use the it block!

describe User do
it { should validate_presence_of :name }
it { should have_one :address }
end

In case the subject is the not the class described, just set it with the subject method:

subject { @user.address }
it { should be_valid }

Start context with ‘when’/’with’ and methods description with ‘#’

Have you ever get a failed test with an incomprehensible error message like:

User non confirmed confirm email wrong token should not be valid

Start your contexts with when and get nice messages like:

User when non confirmed when #confirm_email with wrong token should not be valid

Use RSpec matchers to get meaningful messages

In case of failure

specify { user.valid?.should == true }

displays:

'User should == true' FAILED
expected: true,
got: false (using ==)

While

specify { user.should be_valid }

displays:

'User should be valid' FAILED
expected valid? to return true, got false

Nice eh?

Only one expectation per it block

I often see specs where it blocks contain several expectations. This makes your tests harder to read and maintain.

So instead of that…

describe DemoMan do
it "should have expected attributes" do
demo_man = DemoMan.new
demo_man.should respond_to :name
demo_man.should respond_to :gender
demo_man.should respond_to :age
end
end

… do this:

describe DemoMan do
before(:all) do
@demo_man = DemoMan.new
end
 
subject { @demo_man }
 
it { should respond_to :name }
it { should respond_to :gender }
it { should respond_to :age }
end

(Over)use describe and context

Big specs can be a joy to play with as long as they are ordered and DRY. Use nested describe and context blocks as much as you can, each level adding its own specificity in the before block.
To check your specs are well organized, run them in ‘nested’ mode (spec spec/my_spec.rb -cf nested).
Using before(:each) in each context and describe blocks will help you set up the environment without repeating yourself. It also enables you to use it {} blocks.

Bad:

describe User do
 
it "should save when name is not empty" do
User.new(:name => 'Alex').save.should == true
end
 
it "should not save when name is empty" do
User.new.save.should == false
end
 
it "should not be valid when name is empty" do
User.new.should_not be_valid
end
 
it "should be valid when name is not empty" do
User.new(:name => 'Alex').should be_valid
end
 
it "should give the user a flower when gender is W" do
User.new(:gender => 'W').present.should be_a Flower
end
 
it "should give the user a iMac when gender is M" do
User.new(:gender => 'M').present.should be_an IMac
end
end

Good:

describe User do
before { @user = User.new }
 
subject { @user }
 
context "when name empty" do
it { should not be_valid }
specify { @user.save.should == false }
end
 
context "when name not empty" do
before { @user.name = 'Sam' }
 
it { should be_valid }
specify { @user.save.should == true }
end
 
describe :present do
subject { @user.present }
 
context "when user is a W" do
before { @user.gender = 'W' }
 
it { should be_a Flower }
end
 
context "when user is a M" do
before { @user.gender = 'M' }
 
it { should be_an IMac }
end
end
end

Test Valid, Edge and Invalid cases

This is called Boundary value analysis, it’s simple and it will help you to cover the most important cases. Just split-up method’s input or object’s attributes into valid and invalid partitions and test both of them and there boundaries. A method specification might look like that:

describe "#month_in_english(month_id)" do
context "when valid" do
it "should return 'January' for 1" # lower boundary
it "should return 'March' for 3"
it "should return 'December' for 12" # upper boundary
context "when invalid" do
it "should return nil for 0"
it "should return nil for 13"
end
end

I hope this will help you improve your specs. Let me know if I missed anything! :)

You could also be interested in (My) Cucumber best practices and tips or rspec-set a little gem that helps you speeding up your model specs.

Posted in Uncategorized

使用 RSpec 进行行为驱动测试

Bruce Tate, CTO, WellGood LLC

在过去十年中,软件开发人员对测试的热情日渐低迷。同一时期出现的动态语言并没有提供编译程序来捕捉最基本的错误,这使得测试变得更加重要。随着测试社区的成长,开发人员开始注意到,除了捕获 bug 等最基本的优点外,测试还具有以下优势:

  • 测试能够改进您的设计。进行测试的每个目标对象必须具备至少两个客户机:生产代码和测试用例。这些客户机强制您对代码进行解耦。测试还鼓励开发人员使用更小、更简单的方法。
  • 测试减少了不必要的代码。在编写测试用例时,您养成了很好的测试习惯,即只编写运行测试用例所需的最少代码。您抵制住了对功能进行编码的诱惑,因为您目前还不需要它。
  • 推动了测试优先开发。您编写的每个测试用例会确定一个小问题。使用代码解决这个问题非常有用并且可以推动开发。当我进行测试驱动开发时,时间过得飞快。
  • 测试提供了更多的自主权。在使用测试用例捕获可能的错误时,您会发现自己非常愿意对代码进行改进。

测试驱动的开发和 RSpec

有关测试的优点无需赘述,我将向您介绍一个简单的使用 RSpec 的测试驱动开发示例。RSpec 工具是一个 Ruby 软件包,可以用它构建有关您的软件的规范。该规范实际上是一个描述系统行为的测试。使用 RSpec 的开发流程如下:

  • 编写一个测试。该测试描述系统中某个较小元素的行为。
  • 运行测试。由于尚没有为系统中的相应部分构建代码,测试失败。这一重要步骤将测试您的测试用例,检验测试用例是否在应当失败的时候失败。
  • 编写足够的代码,使测试通过。
  • 运行测试,检验测试是否成功。

实质上,RSpec 开发人员所做的工作就是将失败的测试用例调试为成功的测试用例。这是一个主动的过程。本文中,我将介绍 RSpec 的基本用法。

首先,假设您已安装了 Ruby 和 gems。您还需要安装 RSpec。输入下面的内容:

gem install rspec

 

 

使用示例

接下来,我将逐步构建一个状态机。我将遵循 TDD 规则。首先编写自己的测试用例,并且直到测试用例需要时才编写代码。Rake 的创建者 Jim Weirich 认为这有助于角色扮演。在编写实际的生产代码时,您希望充当一回 jerk 开发人员的角色,只完成最少量的工作来使测试通过。在编写测试时,您则扮演测试人员的角色,试图为开发人员提供一些有益的帮助。

以下的示例展示了如何构建一个状态机。如果您以前从未接触过状态机,请查阅 参考资料。状态机具有多种状态。每种状态支持可以转换状态机状态的事件。测试驱动开发入门的关键就是从零入手,尽量少地使用假设条件。针对测试进行程序设计。

使用清单 1 的内容创建名为 machine_spec.rb 的文件。该文件就是您的规范。您还不了解 machine.rb 文件的作用,目前先创建一个空文件。

清单 1. 最初的 machine_spec.rb 文件 

  require 'machine'

 

接下来,需要运行测试。始终通过输入 spec machine_spec.rb 运行测试。清单 2 展示了预料之中的测试失败:
清单 2. 运行空的规范

~/rspec batate$ spec machine_spec.rb
/opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require':
 no such file to load -- machine (LoadError)
        from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
        from ./state_machine_spec.rb:1
        from ...

 

在测试驱动开发中,您需要进行增量开发,因此在进行下一次开发前,需要先解决此次测试出现的问题。现在,我将扮演 jerk 开发人员的角色,即只完成满足应用程序运行所需的最少工作量。我将创建一个名为 machine.rb 的空文件,使测试通过。我现在可以以逸待劳,测试通过而我几乎没做任何事情。

继续角色扮演。我现在扮演一个烦躁的测试人员,促使 jerk 开发人员做些实际的工作。我将编码以下规范,需要使用 Machine 类,如清单 3 所示:
清单 3. 初始规范

require 'machine'

describe Machine do
  before :each do
    @machine = Machine
  end
end

 

该规范描述了目前尚不存在的 Machine 类。describe 方法提供了 RSpec 描述,您将传入测试类的名称和包含实际规范的代码块。通常,测试用例需要执行一定数量的设置工作。在 RSpec 中,将由 before 方法完成这些设置工作。您向 before 方法传递一个可选的标志和一个代码块。代码块中包含设置工作。标志确定 RSpec 执行代码块的频率。默认的标志为 :each,表示 RSpec 将在每次测试之前调用 set up 代码块。您也可以指定 :all,表示 RSpec 在执行所有测试之前只调用一次 before 代码块。您应该始终使用:each,使各个测试彼此独立。

输入 spec 运行测试,如清单 4 所示:
清单 4. 存在性测试失败

~/rspec batate$ spec machine_spec.rb

./machine_spec.rb:3: uninitialized constant Machine (NameError)

 

现在,烦躁的测试人员要促使 jerk 开发人员做点什么了 — jerk 开发人员现在需要创建某个类。对我来说,就是修复测试出现的错误。在 machine.rb 中,我输入最少量的代码,如清单 5 所示:
清单 5. 创建初始 Machine 类

class Machine
end

 

保存文件,然后运行测试。毫无疑问,清单 6 显示的测试报告没有出现错误:
清单 6. 测试 Machine 是否存在

~/rspec batate$ spec machine_spec.rb

Finished in 5.0e-06 seconds

0 examples, 0 failures

 

 

编写行为

现在,我可以开始实现更多的行为。我知道,所有状态机必须在某些初始状态下启动。目前我还不是很清楚如何设计这个行为,因此我先编写一个非常简单的测试,首先假设 state 方法会返回 :initial 标志。我对 machine_spec.rb 进行修改并运行测试,如清单 7 所示:
清单 7. 实现初始状态并运行测试

require 'machine'

describe Machine do
  before :each do
    @machine = Machine.new
  end

  it "should initially have a state of :initial" do
    @machine.state.should == :initial
  end

end

~/rspec batate$ spec machine_spec.rb

F

1)
NoMethodError in 'Machine should initially have a state of :initial'
undefined method `state' for #<Machine:0x10c7f8c>
./machine_spec.rb:9:

Finished in 0.005577 seconds

1 example, 1 failure

 

注意这条规则: it "should initially have a state of :initial" do @machine.state.should == :initial end。首先注意到这条规则读起来像是一个英文句子。删除标点,将得到 it should initially have a state of initial。然后会注意到这条规则并不像是典型的面向对象代码。它确实不是。您现在有一个方法,称为 it。该方法具有一个使用引号括起来的字符串参数和一个代码块。字符串应该描述测试需求。最后,do 和 end 之间的代码块包含测试用例的代码。

可以看到,测试进度划分得很细。这些微小的步骤产生的收益却很大。它们使我能够改进测试密度,提供时间供我思考期望的行为以及实现行为所需的 API。这些步骤还能使我在开发期间跟踪代码覆盖情况,从而构建更加丰富的规范。

这种风格的测试具有双重作用:测试实现并在测试的同时构建需求设计文档。稍后,我将通过测试用例构建一个需求列表。

我使用最简单的方式修复了测试,返回 :initial,如清单 8 所示:
清单 8. 指定初始状态

class Machine

  def state
    :initial
  end
end

 

当查看实现时,您可能会放声大笑或感觉受到了愚弄。对于测试驱动开发,您必须稍微改变一下思考方式。您的目标并不是编写最终的生产代码,至少现在不是。您的目标是使测试通过。当掌握以这种方式工作时,您可能会发现新的实现,并且编写的代码要远远少于采用 TDD 时编写的代码。

下一步是运行代码,查看它是否通过测试:
清单 9. 运行初始状态测试

~/rspec batate$ spec machine_spec.rb

.

Finished in 0.005364 seconds

1 example, 0 failures

 

花些时间思考一下这个通过测试的迭代。如果查看代码的话,您可能会觉得气馁。因为并没有取得什么进展。如果查看整个迭代,将看到更多内容:您捕获了一个重要需求并编写测试用例实现需求。作为一名程序员,我的第一个行为测试帮助我明确了开发过程。因为实现细节随着测试的进行越来越清晰。

现在,我可以实现一个更健壮的状态实现。具体来讲,我需要处理状态机的多个状态。我需要创建一个新的规则获取有效状态列表。像以前一样,我将运行测试并查看是否通过。
清单 10. 实现有效状态规范

 it "should remember a list of valid states" do
    @machine.states = [:shopping, :checking_out]
    @machine.states.should = [:shopping, :checking_out]
  end

run test(note: failing first verifies test)

~/rspec batate$ spec machine_spec.rb

.F

1)
NoMethodError in 'Machine should remember a list of valid states'
undefined method `states=' for #<Machine:0x10c7154>
./machine_spec.rb:13:

Finished in 0.005923 seconds

2 examples, 1 failure

 

在清单 10 中,出现了一个 RSpec 形式的断言。该断言从 should 方法开始,然后添加了一些比较关系。should 方法对应用程序进行某种观察。工作中的应用程序应该以某种方式运行。should 方法很好地捕获了这种需求。在本例中,我的状态机应该记忆两种不同的状态。

现在,应该添加一个实例变量来实际记忆状态。像以往一样,我在修改代码后运行测试用例,并观察测试是否成功。
清单 11. 创建一个属性以记忆状态

class Machine
  attr_accessor :states

  def state
    :initial
  end
end

~/rspec batate$ spec machine_spec.rb

..

Finished in 0.00606 seconds

2 examples, 0 failures

 

 

驱动重构

此时,我并不想决定将 :initial 状态称为状态机的第一个状态。相反,我更希望第一个状态是状态数组中的第一个元素。我对状态机的理解在不断演变。这种现象并不少见。测试驱动开发经常迫使我重新考虑之前的假设。由于我已经通过测试用例捕获了早期需求,我可以轻松地对代码进行重构。在本例中,重构就是对代码进行调整,使其更好地工作。

修改第一个测试,使其如清单 12 所示,并运行测试:
清单 12. 初始状态应该为指定的第一个状态

it "should initially have a state of the first state" do
  @machine.states = [:shopping, :checking_out]
  @machine.state.should == :shopping
end

~/rspec batate$ spec machine_spec.rb

F.

1)
'Machine should initially have a state of the first state' FAILED
expected :shopping, got :initial (using ==)
./machine_spec.rb:10:

Finished in 0.005846 seconds

2 examples, 1 failure

 

可以这样说,测试用例起到作用了,因为它运行失败,因此我现在需要修改代码以使其工作。显而易见,我的任务就是使测试通过。我喜欢这种测试目的,因为我的测试用例正在驱动我进行设计。我将把初始状态传递给 new 方法。我将对实现稍作修改,以符合修改后的规范,如清单 13 所示。
清单 13. 指定初始状态

start to fix it
class Machine
  attr_accessor :states
  attr_reader :state

  def initialize(states)
    @states = states
    @state = @states[0]
  end
end

~/rspec batate$ spec machine_spec.rb

1)
ArgumentError in 'Machine should initially have a state of the first state'
wrong number of arguments (0 for 1)
./machine_spec.rb:5:in `initialize'
./machine_spec.rb:5:in `new'
./machine_spec.rb:5:

2)
ArgumentError in 'Machine should remember a list of valid states'
wrong number of arguments (0 for 1)
./machine_spec.rb:5:in `initialize'
./machine_spec.rb:5:in `new'
./machine_spec.rb:5:

Finished in 0.006391 seconds

2 examples, 2 failures

 

现在,测试出现了一些错误。我找到了实现中的一些 bug。测试用例不再使用正确的接口,因为我没有把初始状态传递给状态机。可以看到,测试用例已经起到了保护作用。我进行了较大的更改,测试就发现了 bug。我们需要对测试进行重构以匹配新的接口,将初始状态列表传递给 new 方法。在这里我并没有重复初始化代码,而是将其放置在 before 方法中,如清单 14 所示:
清单 14. 在 “before” 中初始化状态机 

require 'machine'

describe Machine do
  before :each do
    @machine = Machine.new([:shopping, :checking_out])
  end

  it "should initially have a state of the first state" do
    @machine.state.should == :shopping
  end

  it "should remember a list of valid states" do
    @machine.states.should == [:shopping, :checking_out]
  end

end

~/rspec batate$ spec machine_spec.rb

..

Finished in 0.005542 seconds

2 examples, 0 failures

 

状态机开始逐渐成型。代码仍然有一些问题,但是正在向良好的方向演化。我将开始对状态机进行一些转换。这些转换将促使代码实际记忆当前状态。

测试用例促使我全面地思考 API 的设计。我需要知道如何表示事件和转换。首先,我将使用一个散列表表示转换,而没有使用成熟的面向对象实现。随后,测试需求可能会要求我修改假设条件,但是目前,我仍然保持这种简单性。清单 15 显示了修改后的代码:
清单 15. 添加事件和转换

remember events... change before conditions

require 'machine'

describe Machine do
  before :each do
    @machine = Machine.new([:shopping, :checking_out])
    @machine.events = {:checkout =>
                               {:from => :shopping, :to => :checking_out}}
  end

  it "should initially have a state of the first state" do
    @machine.state.should == :shopping
  end

  it "should remember a list of valid states" do
    @machine.states.should == [:shopping, :checking_out]
  end

  it "should remember a list of events with transitions" do
    @machine.events.should == {:checkout =>
                               {:from => :shopping, :to => :checking_out}}
  end

end

~/rspec batate$ spec machine_spec.rb

FFF

1)
NoMethodError in 'Machine should initially have a state of the first state'
undefined method `events=' for #<Machine:0x10c6f38>
./machine_spec.rb:6:

2)
NoMethodError in 'Machine should remember a list of valid states'
undefined method `events=' for #z7lt;Machine:0x10c5afc>
./machine_spec.rb:6:

3)
NoMethodError in 'Machine should remember a list of events with transitions'
undefined method `events=' for #<Machine:0x10c4a58>
./machine_spec.rb:6:

Finished in 0.006597 seconds

3 examples, 3 failures

 

由于新的测试代码位于 before 中,将我的三个测试分解开来。尽管如此,清单 16 中展示的测试非常容易修复。我将添加另一个访问程序:
清单 16. 记忆事件

class Machine
  attr_accessor :states, :events
  attr_reader :state

  def initialize(states)
    @states = states
    @state = @states[0]
  end
end

~/rspec batate$ spec machine_spec.rb

...

Finished in 0.00652 seconds

3 examples, 0 failures

test

 

测试全部通过。我得到了一个能正常运行的状态机。接下来的几个测试将使它更加完善。

 

接近真实的应用程序

目前为止,我所做的不过是触发了一次状态转换,但是我已经做好了所有基础工作。我得到了一组需求。我还构建了一组测试。我的代码可以为状态机提供使用的数据。此时,管理单个状态机转换仅表示一次简单的转换,因此我将添加如清单 17 所示的测试:
清单 17. 构建状态机的状态转换

it "should transition to :checking_out upon #trigger(:checkout) event " do
  @machine.trigger(:checkout)
  @machine.state.should == :checking_out
end

~/rspec batate$ spec machine_spec.rb

...F

1)
NoMethodError in 'Machine should transition to :checking_out upon
#trigger(:checkout) event '
undefined method `trigger' for #<Machine:0x10c4d00>
./machine_spec.rb:24:

Finished in 0.006153 seconds

4 examples, 1 failure

 

我需要抵制快速构建大量功能的诱惑。我应该只编写少量代码,只要使测试通过即可。清单 18 展示的迭代将表示 API 和需求。这就足够了:
清单 18. 定义 trigger 方法

def trigger(event)
  @state = :checking_out
end

~/rspec batate$ spec machine_spec.rb

....

Finished in 0.005959 seconds

4 examples, 0 failures

 

这里出现了一个有趣的边注。在编写代码时,我两次都弄错了这个简单的方法。第一次我返回了 :checkout;第二次我将状态设置为:checkout 而不是 :checking_out。在测试中使用较小的步骤可以为我节省大量时间,因为测试用例为我捕获的这些错误在将来的开发中很难捕获到。本文的最后一个步骤是实际执行一次状态机转换。在第一个示例中,我并不关心实际的机器状态是什么样子的。我仅仅是根据事件进行盲目转换,而不考虑状态。

两节点的状态机无法执行这个操作,我需要在第三个节点中构建。我没有使用已有的 before 方法,只是在新状态中添加另外的状态。我将在测试用例中进行两次转换,以确保状态机能够正确地执行转换,如清单 19 所示:
清单 19. 实现第一次转换

it "should transition to :success upon #trigger(:accept_card)" do
    @machine.events = {
       :checkout => {:from => :shopping, :to => :checking_out},
       :accept_card => {:from => :checking_out, :to => :success}
    }

    @machine.trigger(:checkout)
    @machine.state.should == :checking_out
    @machine.trigger(:accept_card)
    @machine.state.should == :success
  end

~/rspec batate$ spec machine_spec.rb
....F

1)
'Machine should transition to :success upon #trigger(:accept_card)' FAILED
expected :success, got :checking_out (using ==)
./machine_spec.rb:37:

Finished in 0.007564 seconds

5 examples, 1 failure

 

这个测试将使用 :checkout 和 :accept_card 事件建立新的状态机。在处理签出时,我选择使用两个事件而不是一个,这样可以防止发生双命令。签出代码可以确保状态机在签出之前处于 shopping 状态。第一次签出首先将状态机从 shopping 转换为 checking_out。测试用例通过触发 checkout 和 accept_card 事件实现两个转换,并在调用事件之后检验事件状态是否正确。与预期一样,测试用例失败 — 我并没有编写处理多个转换的触发器方法。代码修正包含一行非常重要的代码。清单 20 展示了状态机的核心:
清单 20. 状态机的核心

def trigger(event)
    @state = events[event][:to]
  end

~/rspec batate$ spec machine_spec.rb
.....

Finished in 0.006511 seconds

5 examples, 0 failures

 

测试可以运行。这些粗糙的代码第一次演变为真正可以称之为状态机的东西。但是这还远远不够。目前,状态机缺乏严密性。不管状态如何,状态机都会触发事件。例如,当处于 shopping 状态时,触发 :accept_card 并不会转换为 :success 状态。您只能够从:checking_out 状态触发 :accept_card。在编程术语中,trigger 方法的范围应针对事件。我将编写一个测试来解决问题,然后修复 bug。我将编写一个负测试(negative test),即断言一个不应该出现的行为,如清单 21 所示:
清单 21: 负测试

it "should not transition from :shopping to :success upon :accept_card" do
    @machine.events = {
       :checkout => {:from => :shopping, :to => :checking_out},
       :accept_card => {:from => :checking_out, :to => :success}
    }

    @machine.trigger(:accept_card)
    @machine.state.should_not == :success
  end

rspec batate$ spec machine_spec.rb
.....F

1)
'Machine should not transition from :shopping to :success upon :accept_card' FAILED
expected not == :success, got :success
./machine_spec.rb:47:

Finished in 0.006582 seconds

6 examples, 1 failure

 

现在可以再次运行测试,其中一个测试如预期一样运行失败。修复代码同样只有一行,如清单 22 所示:
清单 22. 修复 trigger 中的范围问题 

def trigger(event)
    @state = events[event][:to] if state == events[event][:from]
  end

rspec batate$ spec machine_spec.rb
......

Finished in 0.006873 seconds

6 examples, 0 failures

 

 

组合代码

现在,我具有一个可简单运行的状态机。无论从哪方面来说,它都不是一个完美的程序。它还具有下面这些问题:

  • 状态散列实际上不具备任何功能。我应该根据状态对事件及其转换进行验证,或者将所有状态集中起来。后续需求很可能会要求这样做。
  • 某个既定事件只能存在于一个状态中。这种限制并不合理。例如,submit 和 cancel 事件可能需要处于多个状态。
  • 代码并不具备明显的面向对象特征。为使配置保持简单,我将大量数据置入散列中。后续的迭代会进一步驱动设计,使其朝面向对象设计方向发展。

但是,您还可以看到,这个状态机已经能够满足一些需求了。我还具备一个描述系统行为的文档,这是进行一系列测试的好起点。每个测试用例都支持系统的一个基本需求。事实上,通过运行 spec machine_spec.rb --format specdoc,您可以查看由系统规范组成的基本报告,如清单 23 所示:
清单 23. 查看规范

spec machine_spec.rb --format specdoc

Machine
- should initially have a state of the first state
- should remember a list of valid states
- should remember a list of events with transitions
- should transition to :checking_out upon #trigger(:checkout) event
- should transition to :success upon #trigger(:accept_card)
- should not transition from :shopping to :success upon :accept_card

Finished in 0.006868 seconds

 

测试驱动方法并不适合所有人,但是越来越多的人开始使用这种技术,使用它构建具有灵活性和适应性的高质量代码,并且根据测试从头构建代码。当然,您也可以通过其他框架(如 test_unit)获得相同的优点。RSpec 还提供了优秀的实现方法。这种新测试框架的一大亮点就是代码的表示。新手尤其可以从这种行为驱动的测试方法中受益。请尝试使用该框架并告诉我您的感受。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
  • RSpec 是一种行为驱动框架,可增强 Ruby 中的测试驱动开发。
  • Martin Fowler 有关 Test Driven Development 的访谈将进一步使您了解这种技术如此强大和高效的原因。
  • Ruby 主页 提供了优秀的 Ruby 编程语言入门资料。
  • From Java to Ruby 一书也由本文作者编写。Bruce Tate 提供了一份管理人员指南,解释了为什么 Ruby 语言对于某些业务问题来说非常重要。
  • 有限状态机 是一种软件概念,它将问题分为有限的状态,以及由事件触发的状态之间的转换。状态机构成了本文的基本内容。
  • 查看所有 developerWorks 的 重要试用下载。 
  • 订阅 developerWorks Web 开发新闻。 
  • 从 Web 开发专区的技术库 获得更多 howto 文章。 

讨论

Posted in Uncategorized

rails 3.1.0 ActionView::Template::Errror (application.css isn't precompiled)

I made a basic rails app with a simple pages controller with an index function and when I load the page I get:

ActionView::Template::Error (application.css isn’t precompiled):
app/views/layouts/application.html.erb:5:in `_app_views_layouts_application_html

By default Rails assumes that you have your files precompiled in the production environment, if you want use live compiling (compile your assets during runtime) in production you must set the config.assets.compile to true.

 Demo

# config/environments/production.rb

config.assets.compile = true

You can use this option to fallback to Sprockets when you are using precompiled assets but there are any missing precompiled files.

If config.assets.compile option is set to false and there are missing precompiled files you will get an “AssetNoPrecompiledError” indicating the name of the missing file.

You will get better performance in production if you set config.assets.compile to false in production.rb and precompile your assets. You can precompile with this rake task:

bundle exec rake assets:precompile

Posted in Uncategorized

ubuntu下查看进程端口

# 查看所有打开的端口及服务名(注意这里显示的服务名只是标准端口对应的服务名,可能并不准确)

nmap localhost

# 查看哪些进程打开了指定端口port(对于守护进程必须以root用户执行才能查看到)

lsof -i:port +sudo比较好

# 查看哪些进程打开了指定端口port,最后一列是进程ID(此方法对于守护进程作用不大)

netstat -nap|grep port

# 查看端口号对应的系统服务名称

cat /etc/services

# 启动|停止|重启系统服务

sudo /etc/init.d/service start|stop|restart

常见端口详细说明

服务及对应端口          服务及对应端口   
Echo(7)                 FTP(21)
Ssh(22)                 Telnet(23)
SMTP(25)                 DNS(53)
HTTP(80)                 MTA-X.400 over TCP/IP(102)
pop3(110)                NETBIOS Name Service(137、
138、139)
IMAP v2(143)               SNMP(161)
LDAP、ILS(389)              Https(443)
IMAP(993) SQL(1433)
NetMeeting T.120(1503) NetMeeting(1720)
NetMeeting Audio Call Control(1731) 超级终端(3389)
QQ客户端(4000) pcAnywere(5631)
RealAudio(6970) Sygate (7323)
OICQ(8000) Wingate(8010)
代理端口(8080)
1、端口:7
服务:Echo
说明:能看到许多人搜索Fraggle放大器时,发送到X.X.X.0和X.X.X.255的信息。
2、端口:21
服务:FTP
说明:FTP服务器所开放的端口,用于上传、下载。最常见的攻击者用于寻找打开anonymous的FTP服务器的方法。这些服务器带有可读写的目录。木马Doly Trojan、Fore、Invisible FTP、WebEx、WinCrash和Blade Runner所开放的端口。
3、端口:22
服务:Ssh
说明:PcAnywhere建立的TCP和这一端口的连接可能是为了寻找ssh。这一服务有许多弱点,如果配置成特定的模式,许多使用RSAREF库的版本就会有不少的漏洞
存在。
4、端口:23
服务:Telnet
说明:远程登录,入侵者在搜索远程登录UNIX的服务。大多数情况下扫描这一端口是为了找到机器运行的操作系统。还有使用其他技术,入侵者也会找到密码。木马Tiny Telnet Server就开放这个端口。
5、端口:25
服务:SMTP
说明:SMTP服务器所开放的端口,用于发送邮件。入侵者寻找SMTP服务器是为了传递他们的SPAM。入侵者的帐户被关闭,他们需要连接到高带宽的E-MAIL服务器上,将简单的信息传递到不同的地址。木马Antigen、Email Password Sender、Haebu Coceda、Shtrilitz Stealth、WinPC、WinSpy都开放这个端口。
6、端口:53
服务:Domain Name Server(DNS)
说明:DNS服务器所开放的端口,入侵者可能是试图进行区域传递(TCP),欺骗DNS(UDP)或隐藏其他的通信。因此防火墙常常过滤或记录此端口。
7、端口:80
服务:HTTP
说明:用于网页浏览。木马Executor开放此端口。
8、端口:102
服务:Message transfer agent(MTA)-X.400 over TCP/IP
说明:消息传输代理。
9、端口:110
服务:pop3
说明:POP3(Post Office Protocol 3)服务器开放此端口,用于接收邮件,客户端访问服务器端的邮件服务。POP3服务有许多公认的弱点。关于用户名和密码交换缓冲区溢出的弱点至少有20个,这意味着入侵者可以在真正登陆前进入系统。成功登陆后还有其他缓冲区溢出错误。
10、端口:137、138、139
服务:NETBIOS Name Service
说明:其中137、138是UDP端口,当通过网上邻居传输文件时用这个端口。而139端口:通过这个端口进入的连接试图获得NetBIOS/SMB服务。这个协议被用于windows文件和打印机共享和SAMBA。还有WINS Regisrtation也用它。
11、端口:143
服务:Interim Mail Access Protocol v2
说明:和POP3的安全问题一样,许多IMAP服务器存在有缓冲区溢出漏洞。记住:
一种LINUX蠕虫(admv0rm)会通过这个端口繁殖,因此许多这个端口的扫描来自不知情的已经被感染的用户。当REDHAT在他们的LINUX发布版本中默认允许IMAP后,这些漏洞变的很流行。这一端口还被用于IMAP2,但并不流行。
12、端口:161
服务:SNMP
说明:SNMP允许远程管理设备。所有配置和运行信息的储存在数据库中,通过SNMP可获得这些信息。许多管理员的错误配置将被暴露在Internet。Cackers将试图使用默认的密码public、private访问系统。他们可能会试验所有可能的组合。
SNMP包可能会被错误的指向用户的网络。
13、端口:389
服务:LDAP、ILS
说明:轻型目录访问协议和NetMeeting Internet Locator Server共用这一端口 。
14、端口:443
服务:Https
说明:网页浏览端口,能提供加密和通过安全端口传输的另一种HTTP。
15、端口:993
服务:IMAP
说明:SSL(Secure Sockets layer)
16、端口:1433
服务:SQL
说明:Microsoft的SQL服务开放的端口。
17、端口:1503
服务:NetMeeting T.120
说明:NetMeeting T.120
18、端口:1720
服务:NetMeeting
说明:NetMeeting H.233 call Setup。
19、端口:1731
服务:NetMeeting Audio Call Control
说明:NetMeeting音频调用控制。
20、端口:3389
服务:超级终端
说明:WINDOWS 2000终端开放此端口。
21、端口:4000
服务:QQ客户端
说明:腾讯QQ客户端开放此端口。
22、端口:5631
服务:pcAnywere
说明:有时会看到很多这个端口的扫描,这依赖于用户所在的位置。当用户打开pcAnywere时,它会自动扫描局域网C类网以寻找可能的代理(这里的代理是指agent而不是proxy)。入侵者也会寻找开放这种服务的计算机。,所以应该查看这种扫描的源地址。一些搜寻pcAnywere的扫描包常含端口22的UDP数据包。
23、端口:6970
服务:RealAudio
说明:RealAudio客户将从服务器的6970-7170的UDP端口接收音频数据流。这是由TCP-7070端口外向控制连接设置的。
24、端口:7323
服务:[NULL]
说明:Sygate服务器端。
25、端口:8000
服务:OICQ
说明:腾讯QQ服务器端开放此端口。
26、端口:8010
服务:Wingate
说明:Wingate代理开放此端口。
27、端口:8080
服务:代理端口
说明:WWW代理开放此端口

Posted in Uncategorized

rails学习笔记: rake 相关命令

命令行
rake db:*****
script/generate model task name:string priority:integer
script/generate migration add_description_to_task description:string
script/generate migration remove_description_from_task description:string

类似的概念里有叫做“迁移 (migration)”的东西,Rails的世界里,指的是改变数据库的结构(schema)。这个通过db/migrate目录下面的 Ruby 脚本执行。

另一方面,db/seeds.rb 也向数据库插入数据。例如,在希望从开始状态准备初期管理者帐户的情况下使用。

在此之前这样的代码只在迁移脚本内描述,我想对此感觉不方便的不止我一个人。

db/seeds.rb 是像下面的例子一样的普通的 Rails 脚本:

Administrator.create(:name => ‘root’, :password => ”)专用的 Rake 任务 db:seeds 也具备。

数据类型
引用
# :string, :text, :integer, :float,:decimal, :datetime, :timestamp, :time, :date,
# :binary, :boolean

与db有关的rake任务

db:charset 检索当前环境下数据库的字符设置
db:collation 检索当前环境下数据库的校对
db:create 用configdatabase.yml中的定义创建当前 RAILS_ENV 项目环境下的数据库
db:create:all 用configdatabase.yml中的定义创建所有数据库
db:drop 删除当前 RAILS_ENV项目环境中的数据库
db:drop:all 删除所有在 configdatabase.yml中定义的数据库
db:reset 从dbschema.rb中为当前环境重建数据库(先删后建).
db:rollback 回滚(清华出版社一本SQLSERVER书的名词[很奇怪为什么不直接用滚回])数据库到前一个版本. 指定回滚到哪一步要用 STEP=n 参数
db:version 检索当前模式下的版本

备份数据库
rake db:backup:create 根据database.yml的信息备份数据库
rake db:backup:destroy 默认删除一天前的备份数据
rake db:backup:rebuild 默认恢复最新的备份数据

注意:这里设置的备份目录是db的backup目录,可以修改

添加索引
引用

add_index :acls, ["action_id","group_id"],:unique=>true
add_index :acls, :action_id

drop all tables 删除全部表
rake db:migrate VERSION=0

指定恢复/删除:
rake db:migrate:down/up VERSION = version_of_migrati

定义数字精确度
t.integer :total_price, :precision=>8,:scale=>2,:default=>0

========================================================================================================================================================
rake db:abort_if_pending_migrations # Raises an error if there are pending migrations
rake db:charset # Retrieves the charset for the current environment’s database
rake db:collation # Retrieves the collation for the current environment’s database
rake db:create # Create the database defined in config/database.yml for the current RAILS_ENV
rake db:create:all # Create all the local databases defined in config/database.yml
rake db:drop # Drops the database for the current RAILS_ENV
rake db:drop:all # Drops all the local databases defined in config/database.yml
rake db:fixtures:identify # Search for a fixture given a LABEL or ID.
rake db:fixtures:load # Load fixtures into the current environment’s database.
rake db:migrate # Migrate the database through scripts in db/migrate.
rake db:migrate:down # Runs the “down” for a given migration VERSION.
rake db:migrate:redo # Rollbacks the database one migration and re migrate up.
rake db:migrate:reset # Resets your database using your migrations for the current environment
rake db:migrate:up # Runs the “up” for a given migration VERSION.
rake db:reset # Drops and recreates the database from db/schema.rb for the current environment.
rake db:rollback # Rolls the schema back to the previous version.
rake db:schema:dump # Create a db/schema.rb file that can be portably used against any DB supported by AR
rake db:schema:load # Load a schema.rb file into the database
rake db:sessions:clear # Clear the sessions table
rake db:sessions:create # Creates a sessions migration for use with CGI::Session::ActiveRecordStore
rake db:structure:dump # Dump the database structure to a SQL file
rake db:test:clone # Recreate the test database from the current environment’s database schema
rake db:test:clone_structure # Recreate the test databases from the development structure
rake db:test:load # Recreate the test database from the current schema.rb
rake db:test:prepare # Check for pending migrations and load the test schema
rake db:test:purge # Empty the test database
rake db:version # Retrieves the current schema version number
rake doc:app # Build the app HTML Files
rake doc:clobber_app # Remove rdoc products
rake doc:clobber_plugins # Remove plugin documentation
rake doc:clobber_rails # Remove rdoc products
rake doc:guides # Generate Rails guides
rake doc:plugins # Generate documentation for all installed plugins
rake doc:rails # Build the rails HTML Files
rake doc:reapp # Force a rebuild of the RDOC files
rake doc:rerails # Force a rebuild of the RDOC files
rake gems # List the gems that this rails application depends on
rake gems:build # Build any native extensions for unpacked gems
rake gems:install # Installs all required gems for this application.
rake gems:refresh_specs # Regenerate gem specifications in correct format.
rake gems:unpack # Unpacks the specified gem into vendor/gems.
rake gems:unpack:dependencies # Unpacks the specified gems and its dependencies into vendor/gems
rake log:clear # Truncates all *.log files in log/ to zero bytes
rake notes # Enumerate all annotations
rake notes:custom # Enumerate a custom annotation, specify with ANNOTATION=WTFHAX
rake notes:fixme # Enumerate all FIXME annotations
rake notes:optimize # Enumerate all OPTIMIZE annotations
rake notes:todo # Enumerate all TODO annotations
rake rails:freeze:edge # Lock to latest Edge Rails, for a specific release use RELEASE=1.2.0
rake rails:freeze:gems # Lock this application to the current gems (by unpacking them into vendor/rails)
rake rails:unfreeze # Unlock this application from freeze of gems or edge and return to a fluid use of system gems
rake rails:update # Update both configs, scripts and public/javascripts from Rails
rake rails:update:configs # Update config/boot.rb from your current rails install
rake rails:update:javascripts # Update your javascripts from your current rails install
rake rails:update:scripts # Add new scripts to the application script/ directory
rake routes # Print out all defined routes in match order, with names.
rake secret # Generate a crytographically secure secret key.
rake stats # Report code statistics (KLOCs, etc) from the application
rake test # Run all unit, functional and integration tests
rake test:benchmark # Run tests for benchmarkdb:test:prepare / Benchmark the performance tests
rake test:functionals # Run tests for functionalsdb:test:prepare / Run the functional tests in test/functional
rake test:integration # Run tests for integrationdb:test:prepare / Run the integration tests in test/integration
rake test:plugins # Run tests for pluginsenvironment / Run the plugin tests in vendor/plugins/*/**/test (or specify with PLUGIN=name)
rake test:profile # Run tests for profiledb:test:prepare / Profile the performance tests
rake test:recent # Run tests for recentdb:test:prepare / Test recent changes
rake test:uncommitted # Run tests for uncommitteddb:test:prepare / Test changes since last checkin (only Subversion and Git)
rake test:units # Run tests for unitsdb:test:prepare / Run the unit tests in test/unit
rake time:zones:all # Displays names of all time zones recognized by the Rails TimeZone class, grouped by offset.
rake time:zones:local # Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time
rake time:zones:us # Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset.
rake tmp:cache:clear # Clears all files and directories in tmp/cache
rake tmp:clear # Clear session, cache, and socket files from tmp/
rake tmp:create # Creates tmp directories for sessions, cache, and sockets
rake tmp:pids:clear # Clears all files in tmp/pids
rake tmp:sessions:clear # Clears all files in tmp/sessions
rake tmp:sockets:clear # Clears all files in tmp/sockets

Posted in Uncategorized

rvm下安装gem编译失败 linecache19

报错:An error occured while installing linecache19 (0.5.12), and Bundler cannot continue.

以下引用stackoverflow中的解答

ruby -v

then manually use –force to bypass version check:

gem install ruby_core_source

gem install linecache19 –force

if you faced with another error starting with following lines:

checking for vm_core.h… no

*** extconf.rb failed ***

You have to explicitly set the source path to vm_core.h

In my case:

$ which ruby

/Users/Reza/.rvm/rubies/ruby-1.9.2-rc2/bin/ruby

$ echo $rvm_path

/Users/Reza/.rvm/

so to install linecache19 :

gem install ruby_core_source

gem install linecache19 –force — –with-ruby-include=$rvm_path/src/ruby-1.9.2-rc2/

ruby-debug19 has a similar issue:

gem install ruby-debug19 –force — –with-ruby-include=$rvm_path/src/ruby-1.9.2-rc2/

That’s all!

=====================================

关键地方:

gem install ruby_core_source

if cannot `rvm pkg install openssl`
  `sudo rvm pkg install openssl`
end

rvm remove 1.9.2-p290
rvm install 1.9.2-p290 –with-openssl-dir=$rvm_path/usr

ruby-debug and ruby 1.9

If you have trouble installing ruby-debug19 try installing with the following command:

$ rvm reinstall 1.9.2 --patch debug --force-autoconf
#可以先试试下面这个命令
$ gem install ruby-debug19 -- --with-ruby-include="${MY_RUBY_HOME/rubies/src}"
Posted in Uncategorized

Rails Rake指南

这是Rails Envy网站的一篇Rake指南,你可以在这里找到原文。

作为RoR开发者,你对Rake一定不会陌生,你可能用它来完成你的单元测试,也可能是迁移数据库,但是你真正理解这些Rake任务到底是怎么运作的吗?你有尝试过创建你自己的Rake任务吗?

在这篇文章中,我们将会介绍Rake的由来,以及如何在Rails中使用它,通过这篇文章的学习,你将掌握如何使用Rake创建你自己的任务。

为什么要有Rake

要理解Rake,我们首先得来了解一下Rake的历史悠远的祖先:Make。因此我们需要暂且回到解释型语言产生之前的久远年代,在那个时代,所有代码都需 要被编译,然后才能够被执行,所以当你从Internet下载到一个相当庞大的程序后,一般来说,除了源代码,程序包中还会包含一个类似 “install_me.sh”的Shell脚本,它会负责帮你完成源代码的编译,并生成最终的可执行文件。

这看起来很完美,不是吗?对用户来说可能如此,但对于开发者来说,这却是个相当粗笨的方法,因为即使你只是修改了一个文件中的一小段代码,”install_me.sh”也必须将所有的源代码都重新编译一遍,才能生成最终的可执行文件。

因此,针对这个问题,Bell实验室的Stuart Feldman创造了Make:

Make可以识别自上次编译之后那些文件发生了改变,从而在下次编译时只对这些发生改变的文件进行编译,而忽略那些没有变化的文件,从而大大降低了程序的编译时间。
Make同时支持依赖关系追踪,也就是你可以告诉编译器,文件A依赖于文件B,因此当文件B发生改变后,文件A也会被重新编译,并且如果编译文件A时,文件B还没有被编译,那么Make会告诉编译器应该先编译文件B。
Make 实际上是一个跟ls和dir差不多的可执行文件,只不过你需要提供一个Makefile文件给它作为输入,Makefile中对每个需要编译的文件及它们 的依赖关系进行定义,Makefile的语法类似于Shell脚本,但又有些不同,这里我们不需要关心Makefile的语法。

随着Make的语言中立性,任何语言的程序都可以使用它来作为构建(build)系统,事实上,在Rake产生之前,许多Ruby项目也是采用Make作为构建系统的。

你可能会奇怪:“Ruby程序并不需要被编译,为何还要使用Make呢?”,是的,Ruby的确不需要编译,事实上,Ruby程序员使用Make是出于以下两个原因:

创 建任务,对于大型程序来说,编译完成并不意味着可以了事,往往你需要编写一大堆的脚本来控制它的运行,或者查看它的运行状态等等,这种情况下,你就可以创 建一个Makefile来管理所有这些任务,然后你就可以使用诸如“make stupid”,“make clever”来分别运行糊涂和聪明任务了。
依 赖关系追踪,当你开始写一个库的时候,你可能会发现,越来越的的任务存在重复,比如”migrate“和”shema: dump“就都需要连接数据库,这时你就可以抽象出一个”connect_to_db”任务,并设置”migrate”和”shema: dump”任务都依赖于”connect_to_db”,这样当你单独运行”migrate”或者”shema:dump”任务时, “connect_to_db”任务都会被首先调用,如果你同时运行这两个任务,那么”connect_to_db”任务只会被执行一次。
Rake是怎么来的?

很多年以前,当 Jim Weirich还在为一个Java项目工作时,他最初的选择也是Makefile,但是很快他意识到:要是能够在Makefile中嵌入Ruby代码,那会是多么的方便呀,于是,Rake就这么产生了。

Rake支持任务创建,任务依赖关系追踪,以及文件编译时间识别。最后一个功能对Ruby程序员应该用不到,但如果你同时还是一个C程序员,那么你可以尝试使用Rake来替代Make。

Rake如何工作

让我们通过一个例子来说明吧,假设我今天很郁闷,想要借酒消愁,这个过程涉及以下几个任务:

买酒
买下酒菜
搞掂它们
假设使用Rake来管理这3个任务,那么我首先需要创建一个Rakefile文件:

task :purchaseAlcohol do
puts “来瓶五粮液”
end
task :mixDrink do
puts “上盘花生米”
end
task :getSmashed do
puts “老板, 啥时学的分身术, 很强嘛?”
end

然后我就可以从Rakefile文件所在的目录来完成这些任务了:

$ rake purchaseAlcohol
来瓶五粮液
$ rake mixDrink
上盘花生米
$ rake getSmashed
老板, 啥时学的分身术, 很强嘛?

很简单吧!但是有些问题,我可不想还没喝酒吃东西就看到老板的分身,这会被人当作精神不正常。

如何组织任务的依赖关系

很简单:

task :purchaseAlcohol do
puts “来瓶五粮液”
end
task :mixDrink => :purchaseAlcohol do
puts “上盘花生米”
end
task :getSmashed => :mixDrink do
puts “老板, 啥时学的分身术, 很强嘛?”
end

搞掂,现在再试试:

$ rake purchaseAlcohol
来瓶五粮液
$ rake mixDrink
来瓶五粮液
上盘花生米
$ rake getSmashed
来瓶五粮液
上盘花生米
老板, 啥时学的分身术, 很强嘛?

就像你看到的,现在我想要醉必须得先喝点酒吃点花生才行,不过一个人喝酒总归有些无聊,所以我想喊些哥们一起来喝,但是我又懒得跟他们解释为啥突然喊他们来喝酒(本人平时比较吝啬),我想到一个偷懒的办法,给他们看文档,但是究竟该怎么为我的Rake任务生成文档呢?

为Rake任务生成文档
再简单不过了:

desc “工作郁闷,想喝点酒”
task :purchaseAlcohol do
puts “来瓶五粮液”
end
desc “得有点下酒菜”
task :mixDrink => :purchaseAlcohol do
puts “来盘花生米”
end
desc “开喝,不醉不归”
task :getSmashed => :mixDrink do
puts “老板, 啥时学的分身术, 很强嘛?”
end

就像你看到的,我的每个任务都有了一个desc字段,现在我和我的朋友们就可以通过rake -T或者rake –task来查看每个任务的文档了:

$ rake –tasks
rake getSmashed # 开喝,不醉不归
rake mixDrink # 得有点下酒菜
rake purchaseAlcohol # 工作郁闷,想喝点酒

Rake命名空间

一旦养成了工作郁闷就喝酒的好习惯之后,你很快就会发现,自己成了个酒鬼,因此你不得不写一大堆Rake任务来集结你的狐朋狗友们,这时你就会发现命名空间的重要性:

namespace :alcoholic do
desc “工作郁闷,想喝点酒”
task :purchaseAlcohol do
puts “来瓶五粮液”
end
desc “得有点下酒菜”
task :mixDrink => :purchaseAlcohol do
puts “来盘花生米”
end
desc “开喝,不醉不归”
task :getSmashed => :mixDrink do
puts “老板, 啥时学的分身术, 很强嘛?”
end
end
namespace :girlfriend do
desc “那个,喝点红酒吧”
task :purchaseAlcohol do
puts “来瓶干红”
end
end

命名空间允许你将你的任务进行分类,你可以在一个Rakefile内创建多个命名空间,现在输入rake –tasks你会得到如下输出:

rake alcoholic:getSmashed # 开喝,不醉不归
rake alcoholic:mixDrink # 得有点下酒菜
rake alcoholic:purchaseAlcohol # 工作郁闷,想喝点酒
rake girlfriend:purchaseAlcohol # 那个,喝点红酒吧

有用的任务

上面都是扯淡,毕竟人生除了喝酒还有更重要的事情等着我们去做,下面,我们来干点正经事,假设我们需要完成这样一个任务,给定一组目录,如果不存在,就创建它们,我创建的Rake任务如下:

desc “Create blank directories if they don’t already exist”
task(:create_directories) do
# The folders I need to create
shared_folders = ["icons","images","groups"]
for folder in shared_folders
# Check to see if it exists
if File.exists?(folder)
puts “#{folder} exists”
else
puts “#{folder} doesn’t exist so we’re creating”
Dir.mkdir “#{folder}”
end
end
end

默认情况下,Rake具有所有File Utils包的功能,当然你也可以通过引用其他库来做任何你想做的事情,那么下一个问题就是:我应该如何在Rails中使用Rake呢?

在Rails中使用Rake

每个Rails应用本身都在带有许多预定义的Rake任务,你可以通过在你的Rails应用的根目录下执行rake –tasks来查看可用的rake任务,别以后了,现在就试试吧,我等你!

要创建新的Rake任务,你只需打开你的Rails应用的lib/tasks目录,并将你的Rakefile命名为”somethins.rake”即可,它会自动被主Rakefile引用,然后你就可以在主目录下调用你的rake任务了,让我们继续上面那个例子:

utils.rake

namespace :utils do
desc “Create blank directories if they don’t already exist”
task(:create_directories) do
# The folders I need to create
shared_folders = ["icons","images","groups"]
for folder in shared_folders
# Check to see if it exists
if File.exists?(“#{RAILS_ROOT}/public/#{folder}”)
puts “#{RAILS_ROOT}/public/#{folder} exists”
else
puts “#{RAILS_ROOT}/public/#{folder} doesn’t exist so we’re creating”
Dir.mkdir “#{RAILS_ROOT}/public/#{folder}”
end
end
end
end

再次执行rake –tasks,你会看到如下结果

……
rake tmp:pids:clear # Clears all files in tmp/pids
rake tmp:sessions:clear # Clears all files in tmp/sessions
rake tmp:sockets:clear # Clears all files in tmp/sockets
rake utils:create_directories # Create blank directories if they
don’t already exist

从Rake任务中可以访问rails model吗?

是的,当然可以,这是我使用Rake最主要的用途:运行一些需要手动执行的任务,或者是需要脱离Rails定期运行的任务,下面是一个简单的例子:

namespace :utils do
desc “Finds soon to expire subscriptions and emails users”
task(:send_expire_soon_emails => :environment) do
# Find users to email
for user in User.members_soon_to_expire
puts “Emailing #{user.name}”
UserNotifier.deliver_expire_soon_notification(user)
end
end
end

实在是很简单,你只需要在你的任务之前执行”=> :environment“就可以了。

如果需要在开发模式执行这个任务,直接敲”rake utils:send_expire_soon_emails”就可以了,如果是产品模式,敲”rake RAILS_ENV=production utils:send_expire_soon_emails”,现在如果我想让这个任务每天晚上运行一次,那么,我只需要在cronjob文件中加入下面这行就可以了:

0 0 * * * cd /var/www/apps/rails_app/ && /usr/local/bin/rake RAILS_ENV=production utils:send_expire_soon_emails

还有更多的例子吗?

namespace :sunspot do
  namespace :solr do
    desc 'Start the Solr instance'
    task :start => :environment do
      case RUBY_PLATFORM
      when /w(in)?32$/, /java$/
        abort("This command is not supported on #{RUBY_PLATFORM}. " +
              "Use rake sunspot:solr:run to run Solr in the foreground.")
      end

      if defined?(Sunspot::Rails::Server)
        Sunspot::Rails::Server.new.start
      else
        Sunspot::Solr::Server.new.start
      end

      puts "Successfully started Solr ..."
    end

    desc 'Run the Solr instance in the foreground'
    task :run => :environment do
      if defined?(Sunspot::Rails::Server)
        Sunspot::Rails::Server.new.run
      else
        Sunspot::Solr::Server.new.run
      end
    end

    desc 'Stop the Solr instance'
    task :stop => :environment do
      case RUBY_PLATFORM
      when /w(in)?32$/, /java$/
        abort("This command is not supported on #{RUBY_PLATFORM}. " +
              "Use rake sunspot:solr:run to run Solr in the foreground.")
      end

      if defined?(Sunspot::Rails::Server)
        Sunspot::Rails::Server.new.stop
      else
        Sunspot::Solr::Server.new.stop
      end

      puts "Successfully stopped Solr ..."
    end

    # for backwards compatibility
    task :reindex => :"sunspot:reindex"
  end
end

不用担心,只要你有时间,并且愿意看,例子大把:

These brand new rake tasks in Edge Rails create and reset your databases for you. Neato!
Craig Ambrose wrote a Rake task to do database backups, which you can use.
Adam Greene put together a set of Rake tasks that allow you to backup all your data to Amazon S3
Jay Fields made a good point when he talked about testing rake tasks
Err the blog talks about a new way of setting the RAILS_ENV and teaches how to use rake to boot you into a Mysql shell (be sure to read the comments if you browse this one).
Last, but not least, there’s the Rake Bookshelf Books and Martin Fowler’s Using the Rake Build Language tutorial . Both of these are pretty thorough, but also a little dated

Posted in Uncategorized

Tracking Rails 3.1 and asset pipeline problems with Apache

ubuneu install apache module
sudo apt-get install libapache2-mod-xsendfile

#enki_apache_passenger.conf


LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
LoadModule passenger_module /usr/share/ruby-rvm/gems/ruby-1.8.7-p357/gems/passenger-3.0.11/ext/apache2/mod_passenger.so
#sudo apt-get install libapache2-mod-xsendfile
LoadModule xsendfile_module /usr/lib/apache2/modules/mod_xsendfile.so
PassengerRoot /usr/share/ruby-rvm/gems/ruby-1.8.7-p357/gems/passenger-3.0.11
PassengerRuby /usr/share/ruby-rvm/rubies/ruby-1.8.7-p357/bin/ruby

# PassengerMaxPoolSize 10

#ServerName www.yourhost.com
# DocumentRoot /var/www/enki/public
XSendFile On

RewriteEngine On

# AllowOverride all
# Options -MultiViews
# THIS IS REALLY IMPORTANT
XSendFilePath /var/www/enki

LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
LoadModule expires_module /usr/lib/apache2/modules/mod_expires.so

# Some browsers still send conditional-GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
Header unset Last-Modified
Header unset ETag
# This required etag module to be enabled. Comment out
FileETag None
# RFC says only cache for 1 year
ExpiresActive On
ExpiresDefault "access plus 1 year"

If you have a module version < 0.10, add this to the virtual host config that needs to send files:

XSendFile On
XSendFileAllowAbove On

This allows any file path (not only those below your VHost root) to be sent through X-Sendfile, which is sort of bad practice. You can either live with it or compile a newer version of the module.

If you got a module version >= 0.10 you can whitelist the allowed paths instead:

XSendFile On
XSendFilePath /opt/www/awesome-project

Migrating my blog over to Rails 3.1 beta

# Enable the asset pipeline
config.assets.enabled = true

config/environments/development.rb (remove or comment out the following line)

#config.action_view.debug_rjs             = true

config/environments/production.rb (add these lines)

# Compress both stylesheets and JavaScripts
config.assets.js_compressor  = :uglifier
config.assets.css_compressor = :scss

mkdir app/assets
git mv public/images app/assets/images
git mv public/javascripts app/assets/javascripts
git mv public/stylesheets app/assets/stylesheets

Finally, in your production environment, don’t forget to statically generate your compiled assets with this Rake task:

RAILS_ENV=production bundle exec rake assets:precompile

It will create files such as these:


public/assets/application-2a8947193a591b79c885c52fbc6b01d3.css

Posted in Uncategorized

Ruby 异常处理 begin …end

在java中,异常的捕捉是在try … catch当中进行,而ruby则是在begin … end代码块中进行异常的捕捉,在该代码块中使用rescue关键字进行捕捉异常类型,注意哦,这个是关键字,而不是方法。
Ruby代码
begin
…… #可能出现异常的代码
rescue errorType1 #要捕捉的异常类型
…… #处理异常的代码
rescue errorType2 #要捕捉的异常类型
…… #处理异常的代码
end
以上代码就是一个大概的捕捉异常的例子,在begin和end代码块中通过rescue进行异常类型的捕捉然后进行适当的处理,可是如果抛出的异常类型并没有显示的捕捉如何处理呢?那就是在最后使用else,如下:
Ruby代码
begin
…… #可能出现异常的代码
rescue errorType1 #要捕捉的异常类型
…… #处理异常的代码
rescue errorType2 #要捕捉的异常类型
…… #处理异常的代码
else
…… #如果以上代码类型都没有捕捉到,则运行该段代码
end

这时又有一个问题,如果我想获取异常信息又该如何做呢?请看下面的代码:
Ruby代码
begin
raise ArgumentError, “Bad data”
rescue => err
puts err
end
通 过rescue => variable的方式,就可以将异常保存为一个variable了。又解决了一个问题,还有什么问题呢?啊,对了,在java的使用当中,比如使用 Connection进行数据库连接后,最后一定要进行资源的清理,都是在finally块当中进行的,可是在ruby中又如何进行这些资源的清理呢?看 看下面的代码:
Ruby代码
begin
raise ArgumentError, “Bad data”
rescue => err
puts err
ensure
… #执行清理工作
end

module GitHelper
  def list_user_repos(user="jhjguxin", options={})
    skip_fork = options[:skip_fork] || false
    sort_by = options[:sort_by] || "created_at"
    github = Github.new
    begin
      repos=github.repos.list_repos :user => user
    #rescue   errorType1             #要捕捉的异常类型
    #......                                #处理异常的代码
    rescue => err
      puts err
      return repos=[]
    ensure #这里的代码在总是被执行,可以做一些清理工作
      puts "process finished ..."
    end
    if skip_fork
      repos.each do |r|
        repos.delete(r) if r.fork==true
        #breakpoint
      end

    else
      repos
    end
  end
end
Posted in Uncategorized