stacks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +85 -0
- data/LICENSE.txt +20 -0
- data/README.md +8 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/lib/stacks.rb +48 -0
- data/lib/stacks/backends/backend.rb +38 -0
- data/lib/stacks/backends/key_value_backend.rb +29 -0
- data/lib/stacks/backends/namespaced_backend.rb +47 -0
- data/lib/stacks/cache.rb +13 -0
- data/lib/stacks/column_dependent_cache.rb +98 -0
- data/lib/stacks/items/column_dependent_block.rb +29 -0
- data/lib/stacks/items/method_call.rb +27 -0
- data/lib/stacks/items/proc.rb +16 -0
- data/lib/stacks/items/timestamp.rb +11 -0
- data/lib/stacks/method_cache.rb +18 -0
- data/lib/stacks/model_extensions.rb +57 -0
- data/lib/stacks/report_cache.rb +78 -0
- data/spec/backends/backend_spec.rb +98 -0
- data/spec/backends/key_value_backend_spec.rb +83 -0
- data/spec/backends/namespaced_backend_spec.rb +108 -0
- data/spec/column_dependent_cache_spec.rb +148 -0
- data/spec/items/column_dependent_block_spec.rb +42 -0
- data/spec/items/method_call_spec.rb +43 -0
- data/spec/items/proc_spec.rb +17 -0
- data/spec/method_cache_spec.rb +45 -0
- data/spec/model_extensions_spec.rb +106 -0
- data/spec/report_cache_spec.rb +141 -0
- data/spec/spec_helper.rb +55 -0
- data/stacks.gemspec +96 -0
- metadata +196 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stacks::ColumnDependentCache do
|
4
|
+
|
5
|
+
describe ".validate_model" do
|
6
|
+
|
7
|
+
it "throws an exception if the model isn't an activerecord model" do
|
8
|
+
expect do
|
9
|
+
Stacks::ColumnDependentCache.validate_model(FakeModel)
|
10
|
+
end.to raise_error(Stacks::ColumnDependentCache::InvalidModel)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "doesn't throw an exception if the class is valid" do
|
14
|
+
expect do
|
15
|
+
Stacks::ColumnDependentCache.validate_model(TestModel)
|
16
|
+
end.to_not raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".get_backend" do
|
22
|
+
|
23
|
+
it "returns a backend namespaced to the model" do
|
24
|
+
backend = Stacks::ColumnDependentCache.get_backend(TestModel)
|
25
|
+
backend.namespace.should == TestModel.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".cached" do
|
31
|
+
|
32
|
+
before(:each) do
|
33
|
+
@model = TestModel
|
34
|
+
@columns = [:col1, :col2, :col3]
|
35
|
+
@identifier = "identifier"
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:call) do
|
39
|
+
Stacks::ColumnDependentCache.cached(@model,
|
40
|
+
@columns,
|
41
|
+
@identifier) { "wussup?" }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "validates the model" do
|
45
|
+
Stacks::ColumnDependentCache.should_receive(:validate_model).with(@model)
|
46
|
+
call
|
47
|
+
end
|
48
|
+
|
49
|
+
context "test actual cacheing" do
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
@model = TestClass
|
53
|
+
@model.stub_chain(:nbc_listener, :register_column)
|
54
|
+
Stacks::ColumnDependentCache.stub(:validate_model)
|
55
|
+
@backend = double
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
it "uses a backend appropriate to the model" do
|
60
|
+
@backend.stub(:get_or_set)
|
61
|
+
|
62
|
+
Stacks::ColumnDependentCache.should_receive(:get_backend).with(@model).and_return(@backend)
|
63
|
+
|
64
|
+
call
|
65
|
+
end
|
66
|
+
|
67
|
+
it "calls get_or_set on the backend" do
|
68
|
+
item = double
|
69
|
+
Stacks::Items::ColumnDependentBlock.stub(:new) { item }
|
70
|
+
Stacks::ColumnDependentCache.stub(:get_backend) { @backend }
|
71
|
+
|
72
|
+
@backend.should_receive(:get_or_set).with(item,
|
73
|
+
Stacks::ColumnDependentCache.default_ttl)
|
74
|
+
|
75
|
+
call
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
describe ".bust_cache" do
|
83
|
+
|
84
|
+
it "runs" do
|
85
|
+
backend = Stacks::Backends::NamespacedBackend.new
|
86
|
+
backend.namespace = "namespace"
|
87
|
+
Stacks::ColumnDependentCache.stub(:get_backend) { backend }
|
88
|
+
Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
|
89
|
+
end
|
90
|
+
|
91
|
+
context "stubbed backend" do
|
92
|
+
|
93
|
+
before(:each) do
|
94
|
+
@backend = double
|
95
|
+
@item = double
|
96
|
+
@item.stub(:clear_value)
|
97
|
+
@backend.stub(:keys) { ["identifier:key1", "identifier:key2"] }
|
98
|
+
Stacks::ColumnDependentCache.stub(:get_backend) { @backend }
|
99
|
+
end
|
100
|
+
|
101
|
+
it "doesn't delete a key when it's a fillable block key" do
|
102
|
+
Stacks::ColumnDependentCache.stub(:fillable_block_keys).and_return do
|
103
|
+
{ TestModel => { "identifier:key2" => @item } }
|
104
|
+
end
|
105
|
+
|
106
|
+
Stacks::ColumnDependentCache.should_receive(:make_fill_decision)
|
107
|
+
.with(TestModel, "identifier:key2")
|
108
|
+
@backend.should_receive(:del_key).exactly(0).times
|
109
|
+
Stacks::ColumnDependentCache.bust_cache(TestModel, [:key2])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should delete each the key with given attribute" do
|
113
|
+
@backend.should_receive(:del_key).exactly(1).times.with("identifier:key1")
|
114
|
+
Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should delete a key with multiple columns, with one that matches" do
|
118
|
+
@backend.stub(:keys) { ["identifier:key3:key7:key1", "identifier:key2"] }
|
119
|
+
@backend.should_receive(:del_key).exactly(1).times.with("identifier:key3:key7:key1")
|
120
|
+
Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
describe ".fill_block" do
|
128
|
+
|
129
|
+
it "should set the value when called with the correct key" do
|
130
|
+
Stacks::ColumnDependentCache.define_block(TestModel, [:test_column1], "test_identifier") do
|
131
|
+
"test_value"
|
132
|
+
end
|
133
|
+
|
134
|
+
Stacks::ColumnDependentCache.fillable_block_keys.keys.first.should == TestModel
|
135
|
+
Stacks::ColumnDependentCache.defined_blocks.keys.should == ["test_identifier"]
|
136
|
+
|
137
|
+
Stacks::ColumnDependentCache.fill_block(TestModel,
|
138
|
+
"test_identifier:test_column1")
|
139
|
+
|
140
|
+
Stacks::Backends::NamespacedBackend.any_instance.should_not_receive(:set)
|
141
|
+
|
142
|
+
Stacks::ColumnDependentCache.get_block_value("test_identifier").should ==
|
143
|
+
"test_value"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Stacks::Items::ColumnDependentBlock do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@model = TestModel
|
7
|
+
@columns = [:column1]
|
8
|
+
@identifier = "identifier"
|
9
|
+
@proc = lambda { "woo" }
|
10
|
+
|
11
|
+
@cdb = Stacks::Items::ColumnDependentBlock.new(@model,
|
12
|
+
@columns,
|
13
|
+
@identifier,
|
14
|
+
@proc)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#key" do
|
18
|
+
|
19
|
+
it "consists of the identifier and the columns" do
|
20
|
+
@cdb.key.should == "identifier:column1"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".key_to_columns" do
|
26
|
+
|
27
|
+
it "drops the identifier from the key and returns the columns"do
|
28
|
+
Stacks::Items::ColumnDependentBlock.key_to_columns("identifier:column1").should ==
|
29
|
+
["column1"]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#value" do
|
35
|
+
|
36
|
+
it "just calls the proc" do
|
37
|
+
@cdb.value.should == "woo"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Stacks::Items::MethodCall do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@instance = TestClass.new
|
7
|
+
@args = ["woo", "yoo"]
|
8
|
+
@method_call = Stacks::Items::MethodCall.new(@instance, :test_method, @args)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#key_str" do
|
12
|
+
|
13
|
+
it "contains the method name" do
|
14
|
+
@method_call.key_str.include?(:test_method.to_s).should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "contains the marshaled object" do
|
18
|
+
@method_call.key_str.include?(Marshal.dump(@instance)).should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "contains the marshaled arguments" do
|
22
|
+
@method_call.key_str.include?(Marshal.dump(@args)).should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#key" do
|
28
|
+
|
29
|
+
it "contains the SHA2 value of the key_str" do
|
30
|
+
@method_call.key.should == Digest::SHA2.hexdigest(@method_call.key_str)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#value" do
|
36
|
+
|
37
|
+
it "calls the method with the arguments" do
|
38
|
+
@method_call.value.should == "My arguments: woo and yoo"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Stacks::Items::Proc do
|
4
|
+
|
5
|
+
describe "#initialize" do
|
6
|
+
|
7
|
+
it "keeps track of a proc" do
|
8
|
+
test_proc = proc { "hello" }
|
9
|
+
item = Stacks::Items::Proc.new("test-identifier", test_proc)
|
10
|
+
|
11
|
+
item.key.should == "test-identifier"
|
12
|
+
item.value.should == "hello"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stacks::MethodCache do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@instance = TestClass.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".cached" do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
Stacks::MethodCache.cached(@instance,
|
13
|
+
:test_method2,
|
14
|
+
["woo", "yoo"],
|
15
|
+
666).should == "My arguments: woo and yoo"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return the method value" do
|
19
|
+
Stacks::MethodCache.cached(@instance,
|
20
|
+
:test_method2,
|
21
|
+
["woo", "yoo"],
|
22
|
+
666).should == "My arguments: woo and yoo"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return the same value even if the method result" do
|
26
|
+
TestClass.prefix = "HAHA"
|
27
|
+
@instance.test_method2("woo", "yoo").should == "HAHA: woo and yoo"
|
28
|
+
|
29
|
+
Stacks::MethodCache.cached(@instance,
|
30
|
+
:test_method2,
|
31
|
+
["woo", "yoo"],
|
32
|
+
666).should == "My arguments: woo and yoo"
|
33
|
+
TestClass.prefix = "My arguments"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "shouldn't conflict with other cached methods" do
|
37
|
+
Stacks::MethodCache.cached(@instance,
|
38
|
+
:test_method3,
|
39
|
+
[],
|
40
|
+
666).should == "this is a test"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Stacks::ModelExtensions do
|
4
|
+
|
5
|
+
describe ".bust_cache_for_column" do
|
6
|
+
|
7
|
+
it "documents that a cache is watching a column" do
|
8
|
+
Stacks.model_listening_caches.each do |cache|
|
9
|
+
cache.should_receive(:bust_cache).with(TestModel, [:test_column])
|
10
|
+
end
|
11
|
+
|
12
|
+
Stacks::ModelExtensions.bust_cache_for_column(TestModel, :test_column)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Stacks::ModelExtensions::Extension do
|
20
|
+
|
21
|
+
describe ".stacks_watched_columns" do
|
22
|
+
|
23
|
+
it "is attached to every model" do
|
24
|
+
ActiveRecord::Base.stacks_watched_columns.is_a?(Set).should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".stacks_watch_columns" do
|
30
|
+
|
31
|
+
after(:each) { TestModel.stacks_watched_columns.clear }
|
32
|
+
|
33
|
+
it "adds a column to the watch set" do
|
34
|
+
TestModel.stacks_watched_columns.should == Set.new
|
35
|
+
|
36
|
+
TestModel.stacks_watch_column(:test_column1)
|
37
|
+
TestModel.stacks_watch_column(:test_column2)
|
38
|
+
|
39
|
+
TestModel.stacks_watched_columns.should == Set.new([:test_column1, :test_column2])
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe ".bust_stackss" do
|
45
|
+
|
46
|
+
after(:each) { TestModel.stacks_watched_columns.clear }
|
47
|
+
|
48
|
+
it "busts caches for each watched attribute" do
|
49
|
+
TestModel.stacks_watch_column(:test_column1)
|
50
|
+
TestModel.stacks_watch_column(:test_column2)
|
51
|
+
|
52
|
+
TestModel.stacks_watched_columns.count.should == 2
|
53
|
+
|
54
|
+
Stacks::ModelExtensions.should_receive(:bust_cache_for_columns).with(TestModel,
|
55
|
+
Set.new([:test_column1,
|
56
|
+
:test_column2]))
|
57
|
+
|
58
|
+
TestModel.bust_stacks
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ".stacks_check_columns" do
|
64
|
+
|
65
|
+
it "is included in all models now" do
|
66
|
+
ActiveRecord::Base.instance_methods.include?(:stacks_check_columns).should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
context "modify column" do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
TestModel.stacks_watch_column(:test_column1)
|
73
|
+
end
|
74
|
+
|
75
|
+
after(:each) { TestModel.stacks_watched_columns.clear }
|
76
|
+
|
77
|
+
it "busts the column cache when the attribute is modified" do
|
78
|
+
Stacks.model_listening_caches.each do |cache|
|
79
|
+
cache.should_receive(:bust_cache).exactly(1).times.with(TestModel, [:test_column1])
|
80
|
+
end
|
81
|
+
|
82
|
+
y = TestModel.create
|
83
|
+
y.test_column1 = "hello!"
|
84
|
+
y.save!
|
85
|
+
end
|
86
|
+
|
87
|
+
it "busts the column cache when the attribute is modified at creation time" do
|
88
|
+
Stacks.model_listening_caches.each do |cache|
|
89
|
+
cache.should_receive(:bust_cache).exactly(1).times.with(TestModel, [:test_column1])
|
90
|
+
end
|
91
|
+
|
92
|
+
y = TestModel.create(:test_column1 => "hello!")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesn't trigger the callback for a non-watched column" do
|
96
|
+
Stacks.model_listening_caches.each do |cache|
|
97
|
+
cache.should_receive(:bust_cache).exactly(0).times
|
98
|
+
end
|
99
|
+
|
100
|
+
y = TestModel.create(:test_column2 => "hello!")
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Stacks::ReportCache do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@report = Stacks::ReportCache.new("test report", 100)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#backend" do
|
10
|
+
|
11
|
+
it "uses a namespaced backend customized to the report name" do
|
12
|
+
backend = @report.backend
|
13
|
+
backend.namespace.should == "test report"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".report" do
|
19
|
+
|
20
|
+
it "allows defining values through passing a block" do
|
21
|
+
report = Stacks::ReportCache.report("test report", 100) do
|
22
|
+
value("a test value") { "woooo" }
|
23
|
+
end
|
24
|
+
|
25
|
+
report.report_name.should == "test report"
|
26
|
+
report.ttl.should == 100
|
27
|
+
report.values["a test value"].key.should == "a test value"
|
28
|
+
report.values["a test value"].value.should == "woooo"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#value" do
|
34
|
+
|
35
|
+
it "defines a value for the report" do
|
36
|
+
@report.value("hello there") { "wussup" }
|
37
|
+
@report.values["hello there"].value.should == "wussup"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#get_value" do
|
43
|
+
|
44
|
+
it "returns the cached value stored for the key" do
|
45
|
+
@report.value("hello there") { "wussup" }
|
46
|
+
@report.get_value("hello there").should == "wussup"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't return a cached value if cache_condition evaluates to false" do
|
50
|
+
@report.value("hello there") { "wussup" }
|
51
|
+
@report.set_cache_condition { false }
|
52
|
+
item = @report.values["hello there"]
|
53
|
+
item.should_receive(:value)
|
54
|
+
@report.backend.should_receive(:get_or_set).exactly(0).times
|
55
|
+
|
56
|
+
@report.get_value("hello there")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns a cached value if cache_condition evaluates to true" do
|
60
|
+
@report.value("hello there") { "wussup" }
|
61
|
+
@report.set_cache_condition { true }
|
62
|
+
item = @report.values["hello there"]
|
63
|
+
@report.backend.should_receive(:get_or_set)
|
64
|
+
|
65
|
+
@report.get_value("hello there")
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#fill_cache" do
|
71
|
+
|
72
|
+
it "should fill the cache" do
|
73
|
+
@report.value("hello there") { "wussup" }
|
74
|
+
@report.value("yo") { "ratchet" }
|
75
|
+
|
76
|
+
@report.values["hello there"].class.should == Stacks::Items::Proc
|
77
|
+
@report.values["yo"].class.should == Stacks::Items::Proc
|
78
|
+
|
79
|
+
@report.backend.should_receive(:fill).with(@report.values["hello there"], 100)
|
80
|
+
@report.backend.should_receive(:fill).with(@report.values["yo"], 100)
|
81
|
+
|
82
|
+
timestamp = double
|
83
|
+
|
84
|
+
Stacks::Items::Timestamp.stub(:new).and_return(timestamp)
|
85
|
+
@report.backend.should_receive(:fill).with(timestamp, 100)
|
86
|
+
|
87
|
+
@report.fill_cache
|
88
|
+
end
|
89
|
+
|
90
|
+
it "shouldn't do anything if the cache condition isn't satisfied" do
|
91
|
+
@report.value("yo") { "ratchet" }
|
92
|
+
@report.values["yo"].class.should == Stacks::Items::Proc
|
93
|
+
@report.set_cache_condition { false }
|
94
|
+
@report.backend.should_receive(:fill).exactly(0).times
|
95
|
+
|
96
|
+
@report.fill_cache
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe ".reports" do
|
102
|
+
|
103
|
+
it "keeps track of all defined reports" do
|
104
|
+
report = Stacks::ReportCache.report("test report", 100) do
|
105
|
+
value("a test value") { "woooo" }
|
106
|
+
end
|
107
|
+
|
108
|
+
Stacks::ReportCache.reports["test report"].should == report
|
109
|
+
Stacks::ReportCache.get_report("test report").should == report
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#register_instance_variables" do
|
115
|
+
|
116
|
+
it "transfers create attributes for each value" do
|
117
|
+
instance = TestClass.new
|
118
|
+
report = Stacks::ReportCache.report("test report", 100) do
|
119
|
+
value("my_attr") { "woooo" }
|
120
|
+
end
|
121
|
+
|
122
|
+
report.register_instance_variables(instance)
|
123
|
+
instance.instance_eval { @my_attr }.should == "woooo"
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#timestamp" do
|
129
|
+
|
130
|
+
it "it returns a timestamp" do
|
131
|
+
report = Stacks::ReportCache.report("test report", 100) do
|
132
|
+
value("my_attr") { "woooo" }
|
133
|
+
end
|
134
|
+
|
135
|
+
report.timestamp.class.should == Time
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
end
|