mr 0.35.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +22 -0
  5. data/README.md +29 -0
  6. data/bench/all.rb +4 -0
  7. data/bench/factory.rb +68 -0
  8. data/bench/fake_record.rb +174 -0
  9. data/bench/model.rb +201 -0
  10. data/bench/read_model.rb +191 -0
  11. data/bench/results/factory.txt +21 -0
  12. data/bench/results/fake_record.txt +37 -0
  13. data/bench/results/model.txt +44 -0
  14. data/bench/results/read_model.txt +46 -0
  15. data/bench/setup.rb +132 -0
  16. data/lib/mr.rb +11 -0
  17. data/lib/mr/after_commit.rb +49 -0
  18. data/lib/mr/after_commit/fake_record.rb +39 -0
  19. data/lib/mr/after_commit/record.rb +48 -0
  20. data/lib/mr/after_commit/record_procs_methods.rb +82 -0
  21. data/lib/mr/factory.rb +82 -0
  22. data/lib/mr/factory/config.rb +240 -0
  23. data/lib/mr/factory/model_factory.rb +103 -0
  24. data/lib/mr/factory/model_stack.rb +28 -0
  25. data/lib/mr/factory/read_model_factory.rb +104 -0
  26. data/lib/mr/factory/record_factory.rb +130 -0
  27. data/lib/mr/factory/record_stack.rb +219 -0
  28. data/lib/mr/fake_query.rb +53 -0
  29. data/lib/mr/fake_record.rb +58 -0
  30. data/lib/mr/fake_record/associations.rb +257 -0
  31. data/lib/mr/fake_record/attributes.rb +168 -0
  32. data/lib/mr/fake_record/persistence.rb +116 -0
  33. data/lib/mr/json_field.rb +180 -0
  34. data/lib/mr/json_field/fake_record.rb +31 -0
  35. data/lib/mr/json_field/record.rb +38 -0
  36. data/lib/mr/model.rb +67 -0
  37. data/lib/mr/model/associations.rb +161 -0
  38. data/lib/mr/model/configuration.rb +67 -0
  39. data/lib/mr/model/fields.rb +177 -0
  40. data/lib/mr/model/persistence.rb +79 -0
  41. data/lib/mr/query.rb +126 -0
  42. data/lib/mr/read_model.rb +83 -0
  43. data/lib/mr/read_model/data.rb +38 -0
  44. data/lib/mr/read_model/fields.rb +218 -0
  45. data/lib/mr/read_model/query_expression.rb +188 -0
  46. data/lib/mr/read_model/querying.rb +214 -0
  47. data/lib/mr/read_model/set_querying.rb +82 -0
  48. data/lib/mr/read_model/subquery.rb +98 -0
  49. data/lib/mr/record.rb +35 -0
  50. data/lib/mr/test_helpers.rb +229 -0
  51. data/lib/mr/type_converter.rb +85 -0
  52. data/lib/mr/version.rb +3 -0
  53. data/log/.gitkeep +0 -0
  54. data/mr.gemspec +29 -0
  55. data/test/helper.rb +21 -0
  56. data/test/support/db.rb +10 -0
  57. data/test/support/factory.rb +13 -0
  58. data/test/support/factory/area.rb +6 -0
  59. data/test/support/factory/comment.rb +14 -0
  60. data/test/support/factory/image.rb +6 -0
  61. data/test/support/factory/user.rb +6 -0
  62. data/test/support/models/area.rb +58 -0
  63. data/test/support/models/comment.rb +60 -0
  64. data/test/support/models/image.rb +53 -0
  65. data/test/support/models/user.rb +96 -0
  66. data/test/support/read_model/querying.rb +150 -0
  67. data/test/support/read_models/comment_with_user_data.rb +27 -0
  68. data/test/support/read_models/set_data.rb +49 -0
  69. data/test/support/read_models/subquery_data.rb +41 -0
  70. data/test/support/read_models/user_with_area_data.rb +15 -0
  71. data/test/support/schema.rb +39 -0
  72. data/test/support/setup_test_db.rb +10 -0
  73. data/test/system/factory/model_factory_tests.rb +87 -0
  74. data/test/system/factory/model_stack_tests.rb +30 -0
  75. data/test/system/factory/record_factory_tests.rb +84 -0
  76. data/test/system/factory/record_stack_tests.rb +51 -0
  77. data/test/system/factory_tests.rb +32 -0
  78. data/test/system/read_model_tests.rb +199 -0
  79. data/test/system/with_model_tests.rb +275 -0
  80. data/test/unit/after_commit/fake_record_tests.rb +110 -0
  81. data/test/unit/after_commit/record_procs_methods_tests.rb +177 -0
  82. data/test/unit/after_commit/record_tests.rb +134 -0
  83. data/test/unit/after_commit_tests.rb +113 -0
  84. data/test/unit/factory/config_tests.rb +651 -0
  85. data/test/unit/factory/model_factory_tests.rb +473 -0
  86. data/test/unit/factory/model_stack_tests.rb +97 -0
  87. data/test/unit/factory/read_model_factory_tests.rb +195 -0
  88. data/test/unit/factory/record_factory_tests.rb +446 -0
  89. data/test/unit/factory/record_stack_tests.rb +549 -0
  90. data/test/unit/factory_tests.rb +213 -0
  91. data/test/unit/fake_query_tests.rb +137 -0
  92. data/test/unit/fake_record/associations_tests.rb +585 -0
  93. data/test/unit/fake_record/attributes_tests.rb +265 -0
  94. data/test/unit/fake_record/persistence_tests.rb +239 -0
  95. data/test/unit/fake_record_tests.rb +106 -0
  96. data/test/unit/json_field/fake_record_tests.rb +75 -0
  97. data/test/unit/json_field/record_tests.rb +80 -0
  98. data/test/unit/json_field_tests.rb +302 -0
  99. data/test/unit/model/associations_tests.rb +346 -0
  100. data/test/unit/model/configuration_tests.rb +92 -0
  101. data/test/unit/model/fields_tests.rb +278 -0
  102. data/test/unit/model/persistence_tests.rb +114 -0
  103. data/test/unit/model_tests.rb +137 -0
  104. data/test/unit/query_tests.rb +300 -0
  105. data/test/unit/read_model/data_tests.rb +56 -0
  106. data/test/unit/read_model/fields_tests.rb +416 -0
  107. data/test/unit/read_model/query_expression_tests.rb +381 -0
  108. data/test/unit/read_model/querying_tests.rb +613 -0
  109. data/test/unit/read_model/set_querying_tests.rb +149 -0
  110. data/test/unit/read_model/subquery_tests.rb +242 -0
  111. data/test/unit/read_model_tests.rb +187 -0
  112. data/test/unit/record_tests.rb +45 -0
  113. data/test/unit/test_helpers_tests.rb +431 -0
  114. data/test/unit/type_converter_tests.rb +207 -0
  115. metadata +285 -0
@@ -0,0 +1,381 @@
1
+ require 'assert'
2
+ require 'mr/read_model/query_expression'
3
+
4
+ require 'ardb/relation_spy'
5
+ require 'mr/fake_record'
6
+ require 'mr/read_model/set_querying'
7
+ require 'test/support/read_model/querying'
8
+
9
+ module MR::ReadModel
10
+
11
+ class UnitTests < Assert::Context
12
+ include Querying::TestHelpers
13
+ desc "MR::ReadModel"
14
+
15
+ end
16
+
17
+ class QueryExpressionTests < UnitTests
18
+ desc "QueryExpression"
19
+ subject{ QueryExpression }
20
+
21
+ should "return the correct type of expression using `new`" do
22
+ expression = subject.new(:select, 'column')
23
+ assert_instance_of StaticQueryExpression, expression
24
+ expression = subject.new(:select){ 'column' }
25
+ assert_instance_of DynamicQueryExpression, expression
26
+ end
27
+
28
+ should "raise an invalid error when not passed args or a block" do
29
+ assert_raises(InvalidQueryExpressionError){ subject.new(:select) }
30
+ end
31
+
32
+ end
33
+
34
+ class StaticQueryExpressionTests < UnitTests
35
+ desc "StaticQueryExpression"
36
+ setup do
37
+ @ar_relation = FakeTestRecord.scoped
38
+ @expression = StaticQueryExpression.new(:select, 'column')
39
+ end
40
+ subject{ @expression }
41
+
42
+ should have_readers :type, :args
43
+
44
+ should "apply itself to an ActiveRecord relation using `apply_to`" do
45
+ subject.apply_to(@ar_relation)
46
+ assert_expression_applied @ar_relation, subject.type, *subject.args
47
+ end
48
+
49
+ end
50
+
51
+ class DynamicQueryExpressionTests < UnitTests
52
+ desc "DynamicQueryExpression"
53
+ setup do
54
+ @ar_relation = FakeTestRecord.scoped
55
+ block = proc{ 'column' }
56
+ @expression = DynamicQueryExpression.new(:select, &block)
57
+ end
58
+ subject{ @expression }
59
+
60
+ should have_readers :type, :block
61
+
62
+ should "apply itself to an ActiveRecord relation using `apply_to`" do
63
+ subject.apply_to(@ar_relation, 'test')
64
+ assert_expression_applied @ar_relation, subject.type, 'column'
65
+ end
66
+
67
+ should "yield any params to it's block using `apply_to`" do
68
+ yielded = nil
69
+ block = proc{ |params| yielded = params }
70
+ expression = DynamicQueryExpression.new(:select, &block)
71
+ expression.apply_to(@ar_relation, 'test')
72
+ assert_equal 'test', yielded
73
+ end
74
+
75
+ should "eval it's block in the ActiveRecord relation's scope using `apply_to`" do
76
+ scope = nil
77
+ block = proc{ |params| scope = self }
78
+ expression = DynamicQueryExpression.new(:select, &block)
79
+ expression.apply_to(@ar_relation, 'test')
80
+ assert_equal @ar_relation, scope
81
+ end
82
+
83
+ end
84
+
85
+ class MergeQueryExpressionTests < UnitTests
86
+ desc "MergeQueryExpression"
87
+ setup do
88
+ @ar_relation = FakeTestRecord.scoped
89
+ @expression = MergeQueryExpression.new(:order, 'relation')
90
+ end
91
+ subject{ @expression }
92
+
93
+ should have_readers :type, :query_expression
94
+ should have_imeths :apply_to
95
+
96
+ should "build a query expression for a merge" do
97
+ query_expression = subject.query_expression
98
+ assert_instance_of StaticQueryExpression, query_expression
99
+ assert_equal :merge, query_expression.type
100
+ assert_equal [ 'relation' ], query_expression.args
101
+ end
102
+
103
+ should "apply it's query expression using `apply_to`" do
104
+ query_expression = subject.query_expression
105
+ subject.apply_to(@ar_relation)
106
+ assert_expression_applied @ar_relation, query_expression.type, 'relation'
107
+ end
108
+
109
+ end
110
+
111
+ class SubqueryExpressionTests < UnitTests
112
+ desc "SubqueryExpression"
113
+ setup do
114
+ @expression_class = SubqueryExpression
115
+ end
116
+ subject{ @expression_class }
117
+
118
+ should "know its types" do
119
+ exp = {
120
+ :joins => JoinSubquery
121
+ }
122
+ assert_equal exp, subject::TYPES
123
+ end
124
+
125
+ end
126
+
127
+ class SubqueryExpressionInitTests < SubqueryExpressionTests
128
+ desc "when init"
129
+ setup do
130
+ @type = @expression_class::TYPES.keys.sample
131
+ @args = Factory.integer(3).times.map{ Factory.string }
132
+ @block = proc{ Factory.string }
133
+
134
+ subquery_class = @expression_class::TYPES[@type]
135
+ Assert.stub(subquery_class, :new) do |*args, &block|
136
+ SubquerySpy.new(*args, &block)
137
+ end
138
+
139
+ @expression = @expression_class.new(@type, *@args, &@block)
140
+ end
141
+ subject{ @expression }
142
+
143
+ should have_readers :subquery_type, :subquery_args, :subquery_block
144
+ should have_imeths :type, :subquery
145
+
146
+ should "know its subquery attributes" do
147
+ assert_equal @type, subject.subquery_type
148
+ assert_equal @args, subject.subquery_args
149
+ assert_equal @block, subject.subquery_block
150
+ end
151
+
152
+ should "alias its subquery type, to be a compatible query expression" do
153
+ assert_equal subject.subquery_type, subject.type
154
+ end
155
+
156
+ should "know its subquery" do
157
+ subquery_spy = subject.subquery
158
+ assert_instance_of SubquerySpy, subquery_spy
159
+ assert_equal subject.subquery_args, subquery_spy.args
160
+ assert_equal subject.subquery_block, subquery_spy.block
161
+ assert_same subquery_spy, subject.subquery
162
+ end
163
+
164
+ should "apply the subquery's SQL to a relation using `apply_to`" do
165
+ ar_relation = FakeTestRecord.scoped
166
+ subject.apply_to(ar_relation)
167
+
168
+ subquery_spy = subject.subquery
169
+ assert_nil subquery_spy.sql_built_with
170
+ assert_expression_applied ar_relation, subject.type, subquery_spy.built_sql
171
+
172
+ params = { Factory.string => Factory.string }
173
+ ar_relation = FakeTestRecord.scoped
174
+ subject.apply_to(ar_relation, params)
175
+
176
+ subquery_spy = subject.subquery
177
+ assert_equal params, subquery_spy.sql_built_with
178
+ assert_expression_applied ar_relation, subject.type, subquery_spy.built_sql
179
+ end
180
+
181
+ should "raise an argument error if a block isn't passed" do
182
+ assert_raises(ArgumentError){ @expression_class.new(@type, *@args) }
183
+ end
184
+
185
+ end
186
+
187
+ class FromExpressionTests < UnitTests
188
+ desc "FromExpression"
189
+ setup do
190
+ @expression_class = FromExpression
191
+ @from_expression = @expression_class.new(FakeTestRecord)
192
+ end
193
+ subject{ @from_expression }
194
+
195
+ should have_readers :record_class
196
+ should have_imeths :ar_relation
197
+
198
+ should "know its record class" do
199
+ assert_equal FakeTestRecord, subject.record_class
200
+ end
201
+
202
+ should "use its record class to build an ar relation" do
203
+ assert_instance_of Ardb::RelationSpy, subject.ar_relation
204
+ params = { Factory.string => Factory.string }
205
+ assert_instance_of Ardb::RelationSpy, subject.ar_relation(params)
206
+ end
207
+
208
+ should "raise an argument error if passed a non MR record class" do
209
+ assert_raises(ArgumentError){ @expression_class.new(Class.new) }
210
+ end
211
+
212
+ end
213
+
214
+ class FromSubqueryExpressionTests < UnitTests
215
+ desc "FromSubqueryExpression"
216
+ setup do
217
+ @expression_class = FromSubqueryExpression
218
+ @subquery_block = proc do
219
+ read_model{ from FakeTestRecord }
220
+ end
221
+ @from_expression = @expression_class.new(&@subquery_block)
222
+ end
223
+ subject{ @from_expression }
224
+
225
+ should have_readers :subquery_block
226
+ should have_imeths :from_subquery, :record_class, :ar_relation
227
+
228
+ should "know its subquery block" do
229
+ assert_equal @subquery_block, subject.subquery_block
230
+ end
231
+
232
+ should "know its from subquery" do
233
+ subquery = subject.from_subquery
234
+ assert_instance_of FromSubquery, subquery
235
+ assert_equal FakeTestRecord, subquery.record_class
236
+ assert_same subquery, subject.from_subquery
237
+ end
238
+
239
+ should "know its record class" do
240
+ assert_equal subject.from_subquery.record_class, subject.record_class
241
+ end
242
+
243
+ should "use its from subquery to build an ar relation" do
244
+ build_sql_called_with = nil
245
+ from_subquery_sql = Factory.text
246
+ Assert.stub(subject.from_subquery, :build_sql) do |params|
247
+ build_sql_called_with = params
248
+ from_subquery_sql
249
+ end
250
+
251
+ ar_relation_spy = subject.ar_relation
252
+ assert_instance_of Ardb::RelationSpy, ar_relation_spy
253
+ assert_nil build_sql_called_with
254
+ assert_expression_applied ar_relation_spy, :from, from_subquery_sql
255
+
256
+ params = { Factory.string => Factory.string }
257
+ ar_relation_spy = subject.ar_relation(params)
258
+ assert_instance_of Ardb::RelationSpy, ar_relation_spy
259
+ assert_equal params, build_sql_called_with
260
+ assert_expression_applied ar_relation_spy, :from, from_subquery_sql
261
+ end
262
+
263
+ should "raise an argument error if not passed a block" do
264
+ assert_raises(ArgumentError){ @expression_class.new }
265
+ end
266
+
267
+ end
268
+
269
+ class NullFromExpressionTests < UnitTests
270
+ desc "NullFromExpression"
271
+ setup do
272
+ @from_expression = NullFromExpression.new
273
+ end
274
+ subject{ @from_expression }
275
+
276
+ should have_imeths :record_class, :ar_relation
277
+
278
+ should "raise a no from expression error" do
279
+ assert_raises(NoFromExpressionError){ subject.record_class }
280
+ assert_raises(NoFromExpressionError){ subject.ar_relation }
281
+ assert_raises(NoFromExpressionError) do
282
+ subject.ar_relation(Factory.string => Factory.string)
283
+ end
284
+ end
285
+
286
+ end
287
+
288
+ class SetExpressionTests < UnitTests
289
+ desc "SetExpression"
290
+ setup do
291
+ @expression_class = SetExpression
292
+ end
293
+ subject{ @expression_class }
294
+
295
+ should "know its operator SQL" do
296
+ exp = {
297
+ :union => 'UNION',
298
+ :union_all => 'UNION ALL',
299
+ :intersect => 'INTERSECT',
300
+ :intersect_all => 'INTERSECT ALL',
301
+ :except => 'EXCEPT',
302
+ :except_all => 'EXCEPT ALL'
303
+ }
304
+ assert_equal exp, subject::OPERATOR_SQL
305
+ end
306
+
307
+ end
308
+
309
+ class SetExpressionInitTests < SetExpressionTests
310
+ desc "when init"
311
+ setup do
312
+ @operator = @expression_class::OPERATOR_SQL.keys.sample
313
+ column = Factory.string
314
+ @block = proc do
315
+ select{ |p| p ? p['column'] : column }
316
+ from FakeTestRecord
317
+ end
318
+ @expression = @expression_class.new(@operator, &@block)
319
+ end
320
+ subject{ @expression }
321
+
322
+ should have_readers :operator_sql, :read_model_block
323
+ should have_imeths :read_model_class, :combine_sql
324
+
325
+ should "know its attributes" do
326
+ exp = @expression_class::OPERATOR_SQL[@operator]
327
+ assert_equal exp, subject.operator_sql
328
+ assert_equal @block, subject.read_model_block
329
+ end
330
+
331
+ should "know its read model class" do
332
+ read_model_class = subject.read_model_class
333
+ assert_includes MR::ReadModel, read_model_class
334
+ assert_includes MR::ReadModel::SetQuerying, read_model_class
335
+ assert_equal FakeTestRecord, read_model_class.record_class
336
+ assert_same read_model_class, subject.read_model_class
337
+ end
338
+
339
+ should "combine SQL with its operator using `combine_sql`" do
340
+ sql = Factory.text
341
+ exp = "#{sql} #{subject.operator_sql} " \
342
+ "#{subject.read_model_class.relation.build_for_all.to_sql.strip}"
343
+ assert_equal exp, subject.combine_sql(sql)
344
+
345
+ params = { 'column' => Factory.string }
346
+ exp = "#{sql} #{subject.operator_sql} " \
347
+ "#{subject.read_model_class.build_sql(params)}"
348
+ assert_equal exp, subject.combine_sql(sql, params)
349
+ end
350
+
351
+ should "raise an argument error if a block isn't passed" do
352
+ assert_raises(ArgumentError){ @expression_class.new(@type, *@args) }
353
+ end
354
+
355
+ end
356
+
357
+ class FakeTestRecord
358
+ include MR::FakeRecord
359
+
360
+ def self.scoped
361
+ Ardb::RelationSpy.new
362
+ end
363
+ end
364
+
365
+ class SubquerySpy
366
+ attr_reader :args, :block, :built_sql, :sql_built_with
367
+
368
+ def initialize(*args, &block)
369
+ @args = args
370
+ @block = block
371
+ @built_sql = Factory.text
372
+ @sql_built_with = nil
373
+ end
374
+
375
+ def build_sql(params = nil)
376
+ @sql_built_with = params
377
+ @built_sql
378
+ end
379
+ end
380
+
381
+ end
@@ -0,0 +1,613 @@
1
+ require 'assert'
2
+ require 'mr/read_model/querying'
3
+
4
+ require 'ardb/relation_spy'
5
+ require 'much-plugin'
6
+ require 'mr/fake_record'
7
+ require 'mr/fake_query'
8
+ require 'mr/query'
9
+ require 'test/support/read_model/querying'
10
+
11
+ module MR::ReadModel
12
+
13
+ class UnitTests < Assert::Context
14
+ include MR::ReadModel::Querying::TestHelpers
15
+ desc "MR::ReadModel"
16
+
17
+ end
18
+
19
+ class QueryingTests < UnitTests
20
+ desc "Querying"
21
+ setup do
22
+ @read_model_class = Class.new do
23
+ include MR::ReadModel::Querying
24
+ attr_reader :data
25
+ def initialize(data); @data = data; end
26
+ end
27
+ end
28
+ subject{ @read_model_class }
29
+
30
+ should have_imeths :relation, :record_class
31
+ should have_imeths :find, :query, :count, :build_sql, :find_attr
32
+ should have_imeths :select
33
+ should have_imeths :from, :joins
34
+ should have_imeths :where
35
+ should have_imeths :order
36
+ should have_imeths :group, :having
37
+ should have_imeths :limit, :offset
38
+ should have_imeths :merge
39
+ should have_imeths :inner_join_subquery
40
+ should have_imeths :left_outer_join_subquery, :left_join_subquery
41
+ should have_imeths :right_outer_join_subquery, :right_join_subquery
42
+ should have_imeths :full_outer_join_subquery, :full_join_subquery
43
+
44
+ should "use much-plugin" do
45
+ assert_includes MuchPlugin, MR::ReadModel::Querying
46
+ end
47
+
48
+ should "return a relation using `relation`" do
49
+ relation = subject.relation
50
+ assert_instance_of MR::ReadModel::Relation, relation
51
+ assert_same relation, subject.relation
52
+ end
53
+
54
+ should "know its record class" do
55
+ if Factory.boolean
56
+ subject.from FakeTestRecord
57
+ else
58
+ subject.from_subquery do
59
+ read_model{ from FakeTestRecord }
60
+ end
61
+ end
62
+ assert_equal FakeTestRecord, subject.record_class
63
+ end
64
+
65
+ should "know how to find a specific result" do
66
+ build_for_find_called_with = nil
67
+ relation_spy = Ardb::RelationSpy.new
68
+ Assert.stub(subject.relation, :build_for_find) do |*args|
69
+ build_for_find_called_with = args
70
+ relation_spy
71
+ end
72
+
73
+ record = FakeTestRecord.new
74
+ relation_spy.results << record
75
+
76
+ id = Factory.integer
77
+ result = subject.find(id)
78
+ assert_equal [id, {}], build_for_find_called_with
79
+ assert_instance_of subject, result
80
+ assert_same record, result.data
81
+
82
+ params = { Factory.string => Factory.string }
83
+ result = subject.find(id, params)
84
+ assert_equal [id, params], build_for_find_called_with
85
+ assert_instance_of subject, result
86
+ assert_same record, result.data
87
+
88
+ relation_spy.results.clear
89
+ assert_raises{ subject.find(id) }
90
+ assert_raises{ subject.find(id, params) }
91
+ end
92
+
93
+ should "know how to build its sql" do
94
+ build_sql_called_with = nil
95
+ built_sql = Factory.text
96
+ Assert.stub(subject.relation, :build_sql) do |params|
97
+ build_sql_called_with = params
98
+ built_sql
99
+ end
100
+
101
+ result = subject.build_sql
102
+ assert_nil build_sql_called_with
103
+ assert_equal built_sql, result
104
+
105
+ params = { Factory.string => Factory.string }
106
+ result = subject.build_sql(params)
107
+ assert_equal params, build_sql_called_with
108
+ assert_equal built_sql, result
109
+ end
110
+
111
+ should "allow setting the relation's find attr" do
112
+ column = Factory.string
113
+ subject.find_attr column
114
+ assert_equal column, subject.relation.find_attr
115
+ assert_equal column, subject.find_attr
116
+ end
117
+
118
+ should "set the relation's from using `from`" do
119
+ from_called_with = nil
120
+ Assert.stub(subject.relation, :from){ |*args| from_called_with = args }
121
+
122
+ subject.from FakeTestRecord
123
+ assert_equal [FakeTestRecord], from_called_with
124
+ end
125
+
126
+ should "set the relation's from subquery using `from_subquery`" do
127
+ from_subquery_called_with = nil
128
+ Assert.stub(subject.relation, :from_subquery) do |&block|
129
+ from_subquery_called_with = block
130
+ end
131
+
132
+ subquery_block = proc{ Factory.string }
133
+ subject.from_subquery(&subquery_block)
134
+ assert_equal subquery_block, from_subquery_called_with
135
+ end
136
+
137
+ should "raise an argument error when passing `from` a non MR::Record" do
138
+ assert_raises(ArgumentError){ subject.from(Class.new) }
139
+ end
140
+
141
+ should "raise an argument error when no block is passed to `from_subquery`" do
142
+ assert_raises(ArgumentError){ subject.from_subquery }
143
+ end
144
+
145
+ should "raise a no record class error when using the relation before it's configured" do
146
+ assert_raises(MR::ReadModel::NoFromExpressionError){ subject.query }
147
+ end
148
+
149
+ end
150
+
151
+ class WithFromRecordClassTests < QueryingTests
152
+ setup do
153
+ @ar_relation_spy = Ardb::RelationSpy.new
154
+ Assert.stub(FakeTestRecord, :scoped){ @ar_relation_spy }
155
+ @read_model_class.from FakeTestRecord
156
+ @relation = @read_model_class.relation
157
+ end
158
+
159
+ should "add a static select to the relation with `select`" do
160
+ select_sql = "some_table.some_column AS 'something'"
161
+ subject.select select_sql
162
+ assert_static_expression_added @relation, :select, select_sql
163
+ end
164
+
165
+ should "add a dynamic select to the relation with `select`" do
166
+ select_proc = proc{ |name| "some_table.some_column AS '#{name}'" }
167
+ subject.select(&select_proc)
168
+ assert_dynamic_expression_added @relation, :select, select_proc
169
+ end
170
+
171
+ should "return the expression using `select`" do
172
+ select_sql = "some_table.some_column AS 'something'"
173
+ expression = subject.select select_sql
174
+ assert_static_expression expression, :select, select_sql
175
+ end
176
+
177
+ should "add a static join to the relation with `joins`" do
178
+ join_args = [ :some_table, :other_table ]
179
+ subject.joins(*join_args)
180
+ assert_static_expression_added @relation, :joins, *join_args
181
+ end
182
+
183
+ should "add a dynamic join to the relation with `joins`" do
184
+ join_proc = proc{ |name| "CROSS JOIN #{name}" }
185
+ subject.joins(&join_proc)
186
+ assert_dynamic_expression_added @relation, :joins, join_proc
187
+ end
188
+
189
+ should "return the expression using `joins`" do
190
+ join_args = [ :some_table, :other_table ]
191
+ expression = subject.joins(*join_args)
192
+ assert_static_expression expression, :joins, join_args
193
+ end
194
+
195
+ should "add a static merge to the relation with `where`" do
196
+ merge_args = 'fake-relation'
197
+ subject.where(merge_args)
198
+ assert_static_merge_expression_added @relation, :where, merge_args
199
+ end
200
+
201
+ should "add a dynamic merge to the relation with `where`" do
202
+ merge_proc = proc{ 'fake-relation' }
203
+ subject.where(&merge_proc)
204
+ assert_dynamic_merge_expression_added @relation, :where, merge_proc
205
+ end
206
+
207
+ should "return the merge expression using `where`" do
208
+ merge_args = 'fake-relation'
209
+ expression = subject.where(merge_args)
210
+ assert_static_merge_expression expression, :where, merge_args
211
+ end
212
+
213
+ should "add a static merge to the relation with `order`" do
214
+ merge_args = 'fake-relation'
215
+ subject.order(merge_args)
216
+ assert_static_merge_expression_added @relation, :order, merge_args
217
+ end
218
+
219
+ should "add a dynamic merge to the relation with `order`" do
220
+ merge_proc = proc{ 'fake-relation' }
221
+ subject.order(&merge_proc)
222
+ assert_dynamic_merge_expression_added @relation, :order, merge_proc
223
+ end
224
+
225
+ should "return the merge expression using `order`" do
226
+ merge_args = 'fake-relation'
227
+ expression = subject.order(merge_args)
228
+ assert_static_merge_expression expression, :order, merge_args
229
+ end
230
+
231
+ should "add a static group to the relation with `group`" do
232
+ group_args = 'some_table.some_column'
233
+ subject.group(group_args)
234
+ assert_static_expression_added @relation, :group, group_args
235
+ end
236
+
237
+ should "add a dynamic group to the relation with `group`" do
238
+ group_proc = proc{ |column| column }
239
+ subject.group(&group_proc)
240
+ assert_dynamic_expression_added @relation, :group, group_proc
241
+ end
242
+
243
+ should "return the expression using `group`" do
244
+ group_args = 'some_table.some_column'
245
+ expression = subject.group(group_args)
246
+ assert_static_expression expression, :group, group_args
247
+ end
248
+
249
+ should "add a static having to the relation with `having`" do
250
+ having_args = 'COUNT(*) > 0'
251
+ subject.having(having_args)
252
+ assert_static_expression_added @relation, :having, having_args
253
+ end
254
+
255
+ should "add a dynamic having to the relation with `having`" do
256
+ having_proc = proc{ |column| "COUNT(#{column}) > 0" }
257
+ subject.having(&having_proc)
258
+ assert_dynamic_expression_added @relation, :having, having_proc
259
+ end
260
+
261
+ should "return the expression using `having`" do
262
+ having_args = 'COUNT(*) > 0'
263
+ expression = subject.having(having_args)
264
+ assert_static_expression expression, :having, having_args
265
+ end
266
+
267
+ should "add a static limit to the relation with `limit`" do
268
+ limit_args = 1
269
+ subject.limit(limit_args)
270
+ assert_static_expression_added @relation, :limit, limit_args
271
+ end
272
+
273
+ should "add a dynamic limit to the relation with `limit`" do
274
+ limit_proc = proc{ |count| count }
275
+ subject.limit(&limit_proc)
276
+ assert_dynamic_expression_added @relation, :limit, limit_proc
277
+ end
278
+
279
+ should "return the expression using `limit`" do
280
+ limit_args = 1
281
+ expression = subject.limit(limit_args)
282
+ assert_static_expression expression, :limit, limit_args
283
+ end
284
+
285
+ should "add a static offset to the relation with `offset`" do
286
+ offset_args = 1
287
+ subject.offset(offset_args)
288
+ assert_static_expression_added @relation, :offset, offset_args
289
+ end
290
+
291
+ should "add a dynamic offset to the relation with `offset`" do
292
+ offset_proc = proc{ |count| count }
293
+ subject.offset(&offset_proc)
294
+ assert_dynamic_expression_added @relation, :offset, offset_proc
295
+ end
296
+
297
+ should "return the expression using `offset`" do
298
+ offset_args = 1
299
+ expression = subject.offset(offset_args)
300
+ assert_static_expression expression, :offset, offset_args
301
+ end
302
+
303
+ should "add a static merge to the relation with `merge`" do
304
+ merge_args = 'fake-relation'
305
+ subject.merge(merge_args)
306
+ assert_static_merge_expression_added @relation, :merge, merge_args
307
+ end
308
+
309
+ should "add a dynamic merge to the relation with `merge`" do
310
+ merge_proc = proc{ 'fake-relation' }
311
+ subject.merge(&merge_proc)
312
+ assert_dynamic_merge_expression_added @relation, :merge, merge_proc
313
+ end
314
+
315
+ should "return the merge expression using `merge`" do
316
+ merge_args = 'fake-relation'
317
+ expression = subject.merge(merge_args)
318
+ assert_static_merge_expression expression, :merge, merge_args
319
+ end
320
+
321
+ should "add a join subquery to the relation with `inner_join_subquery`" do
322
+ subquery_proc = proc{ as('my_table') }
323
+ expression = subject.inner_join_subquery(&subquery_proc)
324
+ assert_inner_join_subquery_added @relation, subquery_proc
325
+ end
326
+
327
+ should "return the subquery expression using `inner_join_subquery`" do
328
+ subquery_proc = proc{ as('my_table') }
329
+ expression = subject.inner_join_subquery(&subquery_proc)
330
+ assert_join_subquery_expression expression, :inner, subquery_proc
331
+ end
332
+
333
+ should "add a join subquery to the relation with `left_outer_join_subquery`" do
334
+ subquery_proc = proc{ as('my_table') }
335
+ expression = subject.left_outer_join_subquery(&subquery_proc)
336
+ assert_left_outer_join_subquery_added @relation, subquery_proc
337
+ end
338
+
339
+ should "return the subquery expression using `left_outer_join_subquery`" do
340
+ subquery_proc = proc{ as('my_table') }
341
+ expression = subject.left_outer_join_subquery(&subquery_proc)
342
+ assert_join_subquery_expression expression, :left, subquery_proc
343
+ end
344
+
345
+ should "add a join subquery to the relation with `right_outer_join_subquery`" do
346
+ subquery_proc = proc{ as('my_table') }
347
+ expression = subject.right_outer_join_subquery(&subquery_proc)
348
+ assert_right_outer_join_subquery_added @relation, subquery_proc
349
+ end
350
+
351
+ should "return the subquery expression using `right_outer_join_subquery`" do
352
+ subquery_proc = proc{ as('my_table') }
353
+ expression = subject.right_outer_join_subquery(&subquery_proc)
354
+ assert_join_subquery_expression expression, :right, subquery_proc
355
+ end
356
+
357
+ should "add a join subquery to the relation with `full_outer_join_subquery`" do
358
+ subquery_proc = proc{ as('my_table') }
359
+ expression = subject.full_outer_join_subquery(&subquery_proc)
360
+ assert_full_outer_join_subquery_added @relation, subquery_proc
361
+ end
362
+
363
+ should "return the subquery expression using `full_outer_join_subquery`" do
364
+ subquery_proc = proc{ as('my_table') }
365
+ expression = subject.full_outer_join_subquery(&subquery_proc)
366
+ assert_join_subquery_expression expression, :full, subquery_proc
367
+ end
368
+
369
+ should "raise an argument error when any query method isn't provided args or a block" do
370
+ assert_raises(ArgumentError){ subject.select }
371
+ assert_raises(ArgumentError){ subject.joins }
372
+ assert_raises(ArgumentError){ subject.where }
373
+ assert_raises(ArgumentError){ subject.order }
374
+ assert_raises(ArgumentError){ subject.group }
375
+ assert_raises(ArgumentError){ subject.having }
376
+ assert_raises(ArgumentError){ subject.limit }
377
+ assert_raises(ArgumentError){ subject.offset }
378
+ assert_raises(ArgumentError){ subject.merge }
379
+ assert_raises(ArgumentError){ subject.inner_join_subquery }
380
+ assert_raises(ArgumentError){ subject.left_outer_join_subquery }
381
+ assert_raises(ArgumentError){ subject.right_outer_join_subquery }
382
+ assert_raises(ArgumentError){ subject.full_outer_join_subquery }
383
+ end
384
+
385
+ end
386
+
387
+ class QueryTests < WithFromRecordClassTests
388
+ desc "query"
389
+ setup do
390
+ @read_model_class.select(:name)
391
+ @query = @read_model_class.query
392
+ end
393
+ subject{ @query }
394
+
395
+ should "return an instance of an MR::Query for the class and relation" do
396
+ assert_instance_of MR::Query, subject
397
+ assert_equal @read_model_class, subject.model_class
398
+ assert_equal @ar_relation_spy, subject.relation
399
+ end
400
+
401
+ should "have applied the query expressions to the relation" do
402
+ assert_expression_applied @ar_relation_spy, :select, :name
403
+ end
404
+
405
+ end
406
+
407
+ class CountTests < WithFromRecordClassTests
408
+ desc "count"
409
+ setup do
410
+ results = Array.new(Factory.integer(10))
411
+ @query = MR::FakeQuery.new(results)
412
+
413
+ @args = if Factory.boolean
414
+ [{ Factory.string => Factory.string }]
415
+ else # should support when query is overidden
416
+ @read_model_class.class_eval do
417
+ def self.query(something, something_else)
418
+ super({
419
+ :something => something,
420
+ :something_else => something_else
421
+ })
422
+ end
423
+ end
424
+ [Factory.string, Factory.string]
425
+ end
426
+
427
+ @query_called_with = nil
428
+ Assert.stub(@read_model_class, :query) do |*args|
429
+ @query_called_with = args
430
+ @query
431
+ end
432
+ end
433
+
434
+ should "know how to return a count for its query" do
435
+ result = subject.count(*@args)
436
+ assert_equal @args, @query_called_with
437
+ assert_equal @query.count, result
438
+ end
439
+
440
+ end
441
+
442
+ class RelationTests < UnitTests
443
+ desc "Relation"
444
+ setup do
445
+ @relation_class = MR::ReadModel::Relation
446
+ @relation = @relation_class.new
447
+ end
448
+ subject{ @relation }
449
+
450
+ should have_readers :from_expression, :query_expressions
451
+ should have_writers :find_attr
452
+ should have_imeths :from, :from_subquery, :from_record_class, :find_attr
453
+ should have_imeths :build_for_find, :build_for_all, :build_sql
454
+
455
+ should "know its find find excluded types" do
456
+ exp = [:where, :limit, :offset]
457
+ assert_equal exp, @relation_class::FIND_EXCLUDED_TYPES
458
+ end
459
+
460
+ should "not have a from expression or any expressions by default" do
461
+ assert_instance_of NullFromExpression, subject.from_expression
462
+ assert_equal [], subject.query_expressions
463
+ end
464
+
465
+ should "set its from expression using `from`" do
466
+ subject.from FakeTestRecord
467
+
468
+ assert_instance_of FromExpression, subject.from_expression
469
+ assert_equal FakeTestRecord, subject.from_expression.record_class
470
+ end
471
+
472
+ should "set its from expression using `from_subquery`" do
473
+ subquery_block = proc{ Factory.string }
474
+ subject.from_subquery(&subquery_block)
475
+
476
+ assert_instance_of FromSubqueryExpression, subject.from_expression
477
+ assert_equal subquery_block, subject.from_expression.subquery_block
478
+ end
479
+
480
+ should "know how to build its sql" do
481
+ built_sql = Factory.text
482
+ relation_spy = Ardb::RelationSpy.new
483
+ Assert.stub(relation_spy, :to_sql){ " #{built_sql} " }
484
+
485
+ build_for_all_called_with = nil
486
+ Assert.stub(subject, :build_for_all) do |params|
487
+ build_for_all_called_with = params
488
+ relation_spy
489
+ end
490
+
491
+ result = subject.build_sql
492
+ assert_equal built_sql, result
493
+ assert_nil build_for_all_called_with
494
+
495
+ params = { Factory.string => Factory.string }
496
+ result = subject.build_sql(params)
497
+ assert_equal built_sql, result
498
+ assert_equal params, build_for_all_called_with
499
+ end
500
+
501
+ end
502
+
503
+ class RelationWithFromTests < RelationTests
504
+ desc "with a from expression"
505
+ setup do
506
+ if Factory.boolean
507
+ @relation.from FakeTestRecord
508
+ else
509
+ @relation.from_subquery do
510
+ read_model{ from FakeTestRecord }
511
+ end
512
+ @relation.find_attr = Factory.string
513
+ end
514
+ @from_ar_relation_built_with = nil
515
+ Assert.stub(@relation.from_expression, :ar_relation) do |params|
516
+ @from_ar_relation_built_with = params
517
+ Ardb::RelationSpy.new
518
+ end
519
+ @relation.query_expressions << QueryExpression.new(:select, Factory.string)
520
+ @relation.query_expressions << QueryExpression.new(:order, Factory.string)
521
+ @relation_class::FIND_EXCLUDED_TYPES.each do |type|
522
+ @relation.query_expressions << QueryExpression.new(type, Factory.string)
523
+ end
524
+ end
525
+
526
+ should "know its from record class" do
527
+ assert_equal subject.from_expression.record_class, subject.from_record_class
528
+ end
529
+
530
+ should "know its find attr" do
531
+ relation = @relation_class.new.tap{ |r| r.from FakeTestRecord }
532
+ assert_equal relation.from_expression.default_find_attr, relation.find_attr
533
+
534
+ column = Factory.string
535
+ relation.find_attr = column
536
+ assert_equal column, relation.find_attr
537
+
538
+ # we don't allow from subquery expressions to default their from attr so
539
+ # the user has to manually specify it
540
+ relation = @relation_class.new
541
+ relation.from_subquery do
542
+ read_model{ from FakeTestRecord }
543
+ end
544
+ assert_raises(NoFindAttrError){ relation.find_attr }
545
+
546
+ relation.find_attr = column
547
+ assert_equal column, relation.find_attr
548
+ end
549
+
550
+ should "build an activerecord relation for find using `build_for_find`" do
551
+ id = Factory.integer
552
+ ar_relation_spy = subject.build_for_find(id)
553
+ assert_nil @from_ar_relation_built_with
554
+ subject.query_expressions.each do |e|
555
+ if !@relation_class::FIND_EXCLUDED_TYPES.include?(e.type)
556
+ assert_expression_applied ar_relation_spy, e.type, *e.args
557
+ else
558
+ assert_not_expression_applied ar_relation_spy, e.type, *e.args
559
+ end
560
+ end
561
+ end
562
+
563
+ should "return a new relation everytime `build_for_find` is called" do
564
+ id = Factory.integer
565
+ ar_relation = subject.build_for_find(id)
566
+ assert_not_same ar_relation, subject.build_for_find(id)
567
+ end
568
+
569
+ should "apply expressions using the params passed to `build_for_find`" do
570
+ subject.query_expressions << QueryExpression.new(:select){ |p| p['column'] }
571
+
572
+ id = Factory.integer
573
+ params = { 'column' => Factory.string }
574
+ ar_relation = subject.build_for_find(id, params)
575
+ assert_equal params, @from_ar_relation_built_with
576
+ assert_expression_applied ar_relation, :select, params['column']
577
+ end
578
+
579
+ should "return a relation from the record class using `build_for_all`" do
580
+ ar_relation_spy = subject.build_for_all
581
+ assert_nil @from_ar_relation_built_with
582
+ subject.query_expressions.each do |e|
583
+ assert_expression_applied ar_relation_spy, e.type, *e.args
584
+ end
585
+ end
586
+
587
+ should "return a new relation everytime `build_for_all` is called" do
588
+ ar_relation = subject.build_for_all
589
+ assert_not_same ar_relation, subject.build_for_all
590
+ end
591
+
592
+ should "apply query expressions using the params passed to `build_for_all`" do
593
+ subject.query_expressions << QueryExpression.new(:select){ |p| p['column'] }
594
+ params = { 'column' => Factory.string }
595
+ ar_relation = subject.build_for_all(params)
596
+ assert_equal params, @from_ar_relation_built_with
597
+ assert_expression_applied ar_relation, :select, params['column']
598
+ end
599
+
600
+ end
601
+
602
+ class FakeTestRecord
603
+ include MR::FakeRecord
604
+
605
+ def self.table_name; @table_name ||= Factory.string; end
606
+ def self.primary_key; @primary_key ||= Factory.string; end
607
+
608
+ def self.scoped
609
+ Ardb::RelationSpy.new
610
+ end
611
+ end
612
+
613
+ end