mr 0.35.2

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.
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