mochigome 0.0.3 → 0.0.4
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.
- data/Gemfile +2 -0
- data/Gemfile.lock +4 -0
- data/Rakefile +2 -1
- data/lib/arel_rails2_hacks.rb +49 -0
- data/lib/data_node.rb +4 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mochigome.rb +1 -0
- data/lib/mochigome_ver.rb +1 -1
- data/lib/model_extensions.rb +202 -122
- data/lib/query.rb +295 -148
- data/test/app_root/app/models/category.rb +4 -2
- data/test/app_root/app/models/product.rb +14 -5
- data/test/app_root/app/models/sale.rb +3 -1
- data/test/app_root/config/initializers/arel.rb +2 -0
- data/test/app_root/db/migrate/20110817163830_create_tables.rb +0 -1
- data/test/console.sh +6 -0
- data/test/factories.rb +1 -2
- data/test/test_helper.rb +49 -49
- data/test/unit/data_node_test.rb +7 -0
- data/test/unit/model_extensions_test.rb +110 -93
- data/test/unit/query_test.rb +233 -64
- metadata +32 -14
data/test/unit/query_test.rb
CHANGED
@@ -3,12 +3,9 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
|
3
3
|
describe Mochigome::Query do
|
4
4
|
before do
|
5
5
|
@category1 = create(:category, :name => "Category 1")
|
6
|
-
@product_a = create(:product, :name => "Product A", :category => @category1)
|
6
|
+
@product_a = create(:product, :name => "Product A", :category => @category1, :price => 5)
|
7
7
|
@product_b = create(:product, :name => "Product B", :category => @category1)
|
8
8
|
|
9
|
-
# Belongs to a category, but fails Category's has_many(:products) conditions
|
10
|
-
@product_x = create(:product, :name => "Product X", :category => @category1, :categorized => false)
|
11
|
-
|
12
9
|
@category2 = create(:category, :name => "Category 2")
|
13
10
|
@product_c = create(:product, :name => "Product C", :category => @category2)
|
14
11
|
@product_d = create(:product, :name => "Product D", :category => @category2)
|
@@ -43,10 +40,20 @@ describe Mochigome::Query do
|
|
43
40
|
end
|
44
41
|
end
|
45
42
|
|
46
|
-
|
47
|
-
|
43
|
+
after do
|
44
|
+
Category.delete_all
|
45
|
+
Product.delete_all
|
46
|
+
Owner.delete_all
|
47
|
+
Store.delete_all
|
48
|
+
StoreProduct.delete_all
|
49
|
+
Sale.delete_all
|
50
|
+
end
|
51
|
+
|
52
|
+
# Convenience functions to check DataNode output validity
|
53
|
+
|
54
|
+
def assert_equal_children(a, node)
|
55
|
+
b = node.children
|
48
56
|
assert_equal a.size, b.size
|
49
|
-
# Not checking aggregate data because we don't know about a's context here
|
50
57
|
a.zip(b).each do |obj, fields|
|
51
58
|
obj.mochigome_focus.field_data.each do |k,v|
|
52
59
|
assert_equal v, fields[k]
|
@@ -54,60 +61,93 @@ describe Mochigome::Query do
|
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
|
-
|
64
|
+
def assert_no_children(obj)
|
65
|
+
assert_empty obj.children
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns an empty DataNode if given an empty array" do
|
58
69
|
q = Mochigome::Query.new([Category, Product])
|
59
70
|
data_node = q.run([])
|
60
71
|
assert_empty data_node
|
61
|
-
|
72
|
+
assert_no_children data_node
|
62
73
|
end
|
63
74
|
|
64
|
-
it "
|
75
|
+
it "returns all possible results if no conditions given" do
|
76
|
+
q = Mochigome::Query.new([Category, Product])
|
77
|
+
data_node = q.run()
|
78
|
+
assert_equal 2, data_node.children.size
|
79
|
+
assert_equal 4, (data_node/0).children.size + (data_node/1).children.size
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can build a one-layer DataNode given an object with an id to focus on" do
|
65
83
|
q = Mochigome::Query.new([Product])
|
66
84
|
data_node = q.run(@product_a)
|
67
|
-
|
68
|
-
|
85
|
+
assert_equal_children [@product_a], data_node
|
86
|
+
assert_no_children data_node/0
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can build a one-layer DataNode when given an arbitrary Arel condition" do
|
90
|
+
q = Mochigome::Query.new([Product])
|
91
|
+
tbl = Arel::Table.new(Product.table_name)
|
92
|
+
data_node = q.run(tbl[:name].eq(@product_a.name))
|
93
|
+
assert_equal_children [@product_a], data_node
|
94
|
+
assert_no_children data_node/0
|
95
|
+
end
|
96
|
+
|
97
|
+
it "orders by ID by default" do
|
98
|
+
q = Mochigome::Query.new([Product])
|
99
|
+
data_node = q.run([@product_b, @product_a, @product_c])
|
100
|
+
assert_equal_children [@product_a, @product_b, @product_c], data_node
|
101
|
+
end
|
102
|
+
|
103
|
+
it "orders by custom fields when the model focus settings specify so" do
|
104
|
+
q = Mochigome::Query.new([Category])
|
105
|
+
catZ = create(:category, :name => "Zebras") # Created first, has lower ID
|
106
|
+
catA = create(:category, :name => "Apples")
|
107
|
+
data_node = q.run([catZ, catA])
|
108
|
+
assert_equal catA.name, (data_node/0).name
|
109
|
+
assert_equal catZ.name, (data_node/1).name
|
69
110
|
end
|
70
111
|
|
71
112
|
it "uses the model focus's type name for the DataNode's type name" do
|
72
113
|
q = Mochigome::Query.new([Store])
|
73
114
|
data_node = q.run(@store_x)
|
74
|
-
assert_equal "Storefront", data_node
|
115
|
+
assert_equal "Storefront", (data_node/0).type_name.to_s
|
75
116
|
end
|
76
117
|
|
77
118
|
it "adds an internal_type attribute containing the model class's name" do
|
78
119
|
q = Mochigome::Query.new([Store])
|
79
120
|
data_node = q.run(@store_x)
|
80
|
-
assert_equal "Store", data_node
|
121
|
+
assert_equal "Store", (data_node/0)[:internal_type]
|
81
122
|
end
|
82
123
|
|
83
124
|
it "can build a two-layer tree from a record with a belongs_to association" do
|
84
125
|
q = Mochigome::Query.new([Category, Product])
|
85
126
|
data_node = q.run(@product_a)
|
86
|
-
|
87
|
-
|
88
|
-
|
127
|
+
assert_equal_children [@category1], data_node
|
128
|
+
assert_equal_children [@product_a], data_node/0
|
129
|
+
assert_no_children data_node/0/0
|
89
130
|
end
|
90
131
|
|
91
132
|
it "can build a two-layer tree from an array of records in the second layer" do
|
92
133
|
q = Mochigome::Query.new([Category, Product])
|
93
134
|
data_node = q.run([@product_a, @product_d, @product_b])
|
94
|
-
|
95
|
-
|
96
|
-
|
135
|
+
assert_equal_children [@category1, @category2], data_node
|
136
|
+
assert_equal_children [@product_a, @product_b], data_node/0
|
137
|
+
assert_equal_children [@product_d], data_node/1
|
97
138
|
end
|
98
139
|
|
99
140
|
it "can build a two-layer tree from a record with a has_many association" do
|
100
141
|
q = Mochigome::Query.new([Category, Product])
|
101
142
|
data_node = q.run(@category1)
|
102
|
-
|
103
|
-
|
104
|
-
|
143
|
+
assert_equal_children [@category1], data_node
|
144
|
+
assert_equal_children [@product_a, @product_b], data_node/0
|
145
|
+
assert_no_children data_node/0/0
|
105
146
|
end
|
106
147
|
|
107
|
-
it "cannot build a
|
108
|
-
q = Mochigome::Query.new([Category, BoringDatum])
|
148
|
+
it "cannot build a Query through disconnected layers" do
|
109
149
|
assert_raises Mochigome::QueryError do
|
110
|
-
|
150
|
+
q = Mochigome::Query.new([Category, BoringDatum])
|
111
151
|
end
|
112
152
|
end
|
113
153
|
|
@@ -119,70 +159,158 @@ describe Mochigome::Query do
|
|
119
159
|
[@product_a, @product_b, @product_c, @product_d, @product_e]
|
120
160
|
].each do |tgt|
|
121
161
|
data_node = q.run(tgt)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
assert_equal_objs [@product_c, @product_d],
|
128
|
-
data_node.children[1].children[1].children
|
162
|
+
assert_equal_children [@john, @jane], data_node
|
163
|
+
assert_equal_children [@store_x], data_node/0
|
164
|
+
assert_equal_children [@store_y, @store_z], data_node/1
|
165
|
+
assert_equal_children [@product_a, @product_c], data_node/0/0
|
166
|
+
assert_equal_children [@product_c, @product_d], data_node/1/1
|
129
167
|
end
|
130
168
|
end
|
131
169
|
|
132
|
-
it "collects aggregate data
|
133
|
-
q = Mochigome::Query.new(
|
170
|
+
it "collects aggregate data by grouping on all layers" do
|
171
|
+
q = Mochigome::Query.new(
|
172
|
+
[Owner, Store, Product],
|
173
|
+
:aggregate_sources => [[Product, Sale]]
|
174
|
+
)
|
175
|
+
|
134
176
|
data_node = q.run([@john, @jane])
|
135
177
|
# Store X, Product C
|
136
|
-
assert_equal "Product C", data_node
|
137
|
-
assert_equal 3, data_node
|
178
|
+
assert_equal "Product C", (data_node/0/0/1).name
|
179
|
+
assert_equal 3, (data_node/0/0/1)['Sales count']
|
138
180
|
# Store Z, Product C
|
139
|
-
assert_equal "Product C", data_node
|
140
|
-
assert_equal 2, data_node
|
141
|
-
end
|
181
|
+
assert_equal "Product C", (data_node/1/1/0).name
|
182
|
+
assert_equal 2, (data_node/1/1/0)['Sales count']
|
142
183
|
|
143
|
-
it "collects aggregate data in the context of all layers when traversing up" do
|
144
|
-
q = Mochigome::Query.new([Owner, Store, Product])
|
145
184
|
data_node = q.run(@product_c)
|
146
185
|
# Store X, Product C
|
147
|
-
assert_equal "Product C", data_node
|
148
|
-
assert_equal 3, data_node
|
186
|
+
assert_equal "Product C", (data_node/0/0/0).name
|
187
|
+
assert_equal 3, (data_node/0/0/0)['Sales count']
|
149
188
|
# Store Z, Product C
|
150
|
-
assert_equal "Product C", data_node
|
151
|
-
assert_equal 2, data_node
|
189
|
+
assert_equal "Product C", (data_node/1/0/0).name
|
190
|
+
assert_equal 2, (data_node/1/0/0)['Sales count']
|
191
|
+
end
|
192
|
+
|
193
|
+
it "collects aggregate data on layers above the focus" do
|
194
|
+
q = Mochigome::Query.new(
|
195
|
+
[Owner, Store, Product],
|
196
|
+
:aggregate_sources => [[Product, Sale]]
|
197
|
+
)
|
198
|
+
|
199
|
+
data_node = q.run([@john, @jane])
|
200
|
+
|
201
|
+
assert_equal "Jane's Store (North)", (data_node/1/0).name
|
202
|
+
assert_equal 11, (data_node/1/0)['Sales count']
|
203
|
+
|
204
|
+
assert_equal "Jane Doe", (data_node/1).name
|
205
|
+
assert_equal 16, (data_node/1)['Sales count']
|
206
|
+
|
207
|
+
assert_equal 24, data_node['Sales count']
|
152
208
|
end
|
153
209
|
|
154
|
-
it "
|
155
|
-
|
210
|
+
it "can do conditional counts" do
|
211
|
+
q = Mochigome::Query.new(
|
212
|
+
[Category],
|
213
|
+
:aggregate_sources => [[Category, Product]]
|
214
|
+
)
|
215
|
+
data_node = q.run([@category1, @category2])
|
216
|
+
assert_equal 1, (data_node/0)['Expensive products']
|
217
|
+
assert_equal 2, (data_node/1)['Expensive products']
|
156
218
|
end
|
157
219
|
|
220
|
+
it "can do sums" do
|
221
|
+
q = Mochigome::Query.new(
|
222
|
+
[Owner, Store],
|
223
|
+
:aggregate_sources => [[Store, Product]]
|
224
|
+
)
|
225
|
+
data_node = q.run([@john])
|
226
|
+
assert_equal (@product_a.price + @product_c.price),
|
227
|
+
data_node['Products sum price']
|
228
|
+
end
|
229
|
+
|
230
|
+
it "still does conditional counts correctly when joins below focus used" do
|
231
|
+
af = proc do |cls|
|
232
|
+
return {} unless cls == Product
|
233
|
+
return {
|
234
|
+
:join_paths => [[Product, StoreProduct, Store]]
|
235
|
+
}
|
236
|
+
end
|
237
|
+
q = Mochigome::Query.new(
|
238
|
+
[Category],
|
239
|
+
:aggregate_sources => [[Category, Product]],
|
240
|
+
:access_filter => af
|
241
|
+
)
|
242
|
+
data_node = q.run([@category1, @category2])
|
243
|
+
assert_equal 1, (data_node/0)['Expensive products']
|
244
|
+
assert_equal 2, (data_node/1)['Expensive products']
|
245
|
+
end
|
246
|
+
|
247
|
+
it "still does sums correctly when joins below focus are used" do
|
248
|
+
af = proc do |cls|
|
249
|
+
return {} unless cls == Store
|
250
|
+
return {
|
251
|
+
:join_paths => [[Store, StoreProduct, Sale]]
|
252
|
+
}
|
253
|
+
end
|
254
|
+
q = Mochigome::Query.new(
|
255
|
+
[Owner, Store],
|
256
|
+
:aggregate_sources => [[Store, Product]],
|
257
|
+
:access_filter => af
|
258
|
+
)
|
259
|
+
data_node = q.run([@john])
|
260
|
+
assert_equal (@product_a.price + @product_c.price),
|
261
|
+
data_node['Products sum price']
|
262
|
+
end
|
263
|
+
|
264
|
+
it "does not include hidden aggregation fields in output" do
|
265
|
+
q = Mochigome::Query.new(
|
266
|
+
[Owner, Store],
|
267
|
+
:aggregate_sources => [[Store, Product]]
|
268
|
+
)
|
269
|
+
data_node = q.run([@john])
|
270
|
+
refute data_node.has_key?('Secret count')
|
271
|
+
end
|
272
|
+
|
273
|
+
it "correctly runs aggregation fields implemented in ruby" do
|
274
|
+
q = Mochigome::Query.new(
|
275
|
+
[Owner, Store],
|
276
|
+
:aggregate_sources => [[Store, Product]]
|
277
|
+
)
|
278
|
+
data_node = q.run([@john])
|
279
|
+
assert_equal 4, data_node["Count squared"]
|
280
|
+
end
|
281
|
+
|
282
|
+
# TODO: Test case where data model is already in layer path
|
283
|
+
# TODO: Test case where the condition is deeper than the focus model
|
284
|
+
# TODO: Test use of non-trivial function for aggregation value
|
285
|
+
|
158
286
|
it "puts a comment on the root node describing the query" do
|
159
287
|
q = Mochigome::Query.new([Owner, Store, Product])
|
160
288
|
data_node = q.run([@store_x, @store_y, @store_z])
|
161
289
|
c = data_node.comment
|
162
290
|
assert_match c, /^Mochigome Version: #{Mochigome::VERSION}\n/
|
163
|
-
assert_match c, /\
|
291
|
+
assert_match c, /\nReport Generated: \w{3} \w{3} \d+ .+\n/
|
164
292
|
assert_match c, /\nLayers: Owner => Store => Product\n/
|
165
|
-
assert_match c, /\nAR
|
293
|
+
assert_match c, /\nAR Path: Owner => Store => StoreProduct => Product\n/
|
166
294
|
end
|
167
295
|
|
168
|
-
it "
|
296
|
+
it "names the root node 'report' by default" do
|
169
297
|
q = Mochigome::Query.new([Owner, Store, Product])
|
170
298
|
data_node = q.run([@store_x, @store_y, @store_z])
|
299
|
+
assert_equal "report", data_node.name
|
300
|
+
end
|
171
301
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
product_comment = data_node.children[0].children[0].children[0].comment
|
181
|
-
assert product_comment
|
182
|
-
assert_nil data_node.children[0].children[0].children[1].comment # No comment on 2nd product
|
302
|
+
it "can set the root node's name to a provided value" do
|
303
|
+
q = Mochigome::Query.new(
|
304
|
+
[Owner, Store, Product],
|
305
|
+
:root_name => "cheese"
|
306
|
+
)
|
307
|
+
data_node = q.run([@store_x, @store_y, @store_z])
|
308
|
+
assert_equal "cheese", data_node.name
|
309
|
+
end
|
183
310
|
|
184
|
-
|
185
|
-
|
311
|
+
it "will complain if initialized with an unknown option" do
|
312
|
+
assert_raises Mochigome::QueryError do
|
313
|
+
q = Mochigome::Query.new([Owner, Store, Product], :flim_flam => 123)
|
186
314
|
end
|
187
315
|
end
|
188
316
|
|
@@ -199,4 +327,45 @@ describe Mochigome::Query do
|
|
199
327
|
q.run(@category1)
|
200
328
|
end
|
201
329
|
end
|
330
|
+
|
331
|
+
it "can use a provided access filter function to limit query results" do
|
332
|
+
af = proc do |cls|
|
333
|
+
return {} unless cls == Product
|
334
|
+
return {
|
335
|
+
:condition => Arel::Table.new(Product.table_name)[:category_id].gt(0)
|
336
|
+
}
|
337
|
+
end
|
338
|
+
q = Mochigome::Query.new([Product], :access_filter => af)
|
339
|
+
dn = q.run(Product.all) # FIXME: Need a better way of doing "all objs" queries
|
340
|
+
assert_equal 4, dn.children.size
|
341
|
+
refute dn.children.any?{|c| c.name == "Product E"}
|
342
|
+
end
|
343
|
+
|
344
|
+
it "can do joins at the request of an access filter" do
|
345
|
+
af = proc do |cls|
|
346
|
+
return {} unless cls == Product
|
347
|
+
return {
|
348
|
+
:join_paths => [[Product, StoreProduct, Store]],
|
349
|
+
:condition => Arel::Table.new(Store.table_name)[:name].matches("Jo%")
|
350
|
+
}
|
351
|
+
end
|
352
|
+
q = Mochigome::Query.new([Product], :access_filter => af)
|
353
|
+
dn = q.run(Product.all) # FIXME: Need a better way of doing "all objs" queries
|
354
|
+
assert_equal 2, dn.children.size
|
355
|
+
refute dn.children.any?{|c| c.name == "Product E"}
|
356
|
+
end
|
357
|
+
|
358
|
+
it "access filter joins will not duplicate joins already in the query" do
|
359
|
+
af = proc do |cls|
|
360
|
+
return {} unless cls == Product
|
361
|
+
return {
|
362
|
+
:join_paths => [[Product, StoreProduct, Store]],
|
363
|
+
:condition => Arel::Table.new(Store.table_name)[:name].matches("Jo%")
|
364
|
+
}
|
365
|
+
end
|
366
|
+
q = Mochigome::Query.new([Product, Store], :access_filter => af)
|
367
|
+
assert_equal 1, q.instance_variable_get(:@ids_rel).to_sql.scan(/join .stores./i).size
|
368
|
+
end
|
369
|
+
|
370
|
+
# TODO: Test that access filter join paths are followed, rather than closest path
|
202
371
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mochigome
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Mike Simon
|
@@ -15,10 +15,25 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-03-02 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 1
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 1
|
30
|
+
version: "2.1"
|
31
|
+
requirement: *id001
|
32
|
+
prerelease: false
|
33
|
+
type: :runtime
|
34
|
+
name: arel
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
22
37
|
none: false
|
23
38
|
requirements:
|
24
39
|
- - ">="
|
@@ -27,12 +42,12 @@ dependencies:
|
|
27
42
|
segments:
|
28
43
|
- 0
|
29
44
|
version: "0"
|
30
|
-
|
31
|
-
name: ruport
|
45
|
+
requirement: *id002
|
32
46
|
prerelease: false
|
33
47
|
type: :runtime
|
48
|
+
name: ruport
|
34
49
|
- !ruby/object:Gem::Dependency
|
35
|
-
|
50
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
36
51
|
none: false
|
37
52
|
requirements:
|
38
53
|
- - ">="
|
@@ -41,12 +56,12 @@ dependencies:
|
|
41
56
|
segments:
|
42
57
|
- 0
|
43
58
|
version: "0"
|
44
|
-
|
45
|
-
name: nokogiri
|
59
|
+
requirement: *id003
|
46
60
|
prerelease: false
|
47
61
|
type: :runtime
|
62
|
+
name: nokogiri
|
48
63
|
- !ruby/object:Gem::Dependency
|
49
|
-
|
64
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ">="
|
@@ -55,11 +70,11 @@ dependencies:
|
|
55
70
|
segments:
|
56
71
|
- 0
|
57
72
|
version: "0"
|
58
|
-
|
59
|
-
name: rgl
|
73
|
+
requirement: *id004
|
60
74
|
prerelease: false
|
61
75
|
type: :runtime
|
62
|
-
|
76
|
+
name: rgl
|
77
|
+
description: Report generator that uses your ActiveRecord associations
|
63
78
|
email: david.mike.simon@gmail.com
|
64
79
|
executables: []
|
65
80
|
|
@@ -75,6 +90,7 @@ files:
|
|
75
90
|
- README.rdoc
|
76
91
|
- Rakefile
|
77
92
|
- TODO
|
93
|
+
- lib/arel_rails2_hacks.rb
|
78
94
|
- lib/data_node.rb
|
79
95
|
- lib/exceptions.rb
|
80
96
|
- lib/mochigome.rb
|
@@ -96,11 +112,13 @@ files:
|
|
96
112
|
- test/app_root/config/database.yml
|
97
113
|
- test/app_root/config/environment.rb
|
98
114
|
- test/app_root/config/environments/test.rb
|
115
|
+
- test/app_root/config/initializers/arel.rb
|
99
116
|
- test/app_root/config/offroad.yml
|
100
117
|
- test/app_root/config/preinitializer.rb
|
101
118
|
- test/app_root/config/routes.rb
|
102
119
|
- test/app_root/db/migrate/20110817163830_create_tables.rb
|
103
120
|
- test/app_root/vendor/plugins/mochigome/init.rb
|
121
|
+
- test/console.sh
|
104
122
|
- test/factories.rb
|
105
123
|
- test/test.watchr
|
106
124
|
- test/test_helper.rb
|