babik 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +16 -0
  3. data/README.md +718 -0
  4. data/Rakefile +18 -0
  5. data/lib/babik.rb +122 -0
  6. data/lib/babik/database.rb +16 -0
  7. data/lib/babik/queryset.rb +154 -0
  8. data/lib/babik/queryset/components/aggregation.rb +172 -0
  9. data/lib/babik/queryset/components/limit.rb +22 -0
  10. data/lib/babik/queryset/components/order.rb +161 -0
  11. data/lib/babik/queryset/components/projection.rb +118 -0
  12. data/lib/babik/queryset/components/select_related.rb +78 -0
  13. data/lib/babik/queryset/components/sql_renderer.rb +99 -0
  14. data/lib/babik/queryset/components/where.rb +43 -0
  15. data/lib/babik/queryset/lib/association/foreign_association_chain.rb +97 -0
  16. data/lib/babik/queryset/lib/association/select_related_association_chain.rb +32 -0
  17. data/lib/babik/queryset/lib/condition.rb +103 -0
  18. data/lib/babik/queryset/lib/field.rb +34 -0
  19. data/lib/babik/queryset/lib/join/association_joiner.rb +39 -0
  20. data/lib/babik/queryset/lib/join/join.rb +86 -0
  21. data/lib/babik/queryset/lib/selection/config.rb +19 -0
  22. data/lib/babik/queryset/lib/selection/foreign_selection.rb +39 -0
  23. data/lib/babik/queryset/lib/selection/local_selection.rb +40 -0
  24. data/lib/babik/queryset/lib/selection/operation/base.rb +126 -0
  25. data/lib/babik/queryset/lib/selection/operation/date.rb +178 -0
  26. data/lib/babik/queryset/lib/selection/operation/operations.rb +201 -0
  27. data/lib/babik/queryset/lib/selection/operation/regex.rb +58 -0
  28. data/lib/babik/queryset/lib/selection/path/foreign_path.rb +50 -0
  29. data/lib/babik/queryset/lib/selection/path/local_path.rb +44 -0
  30. data/lib/babik/queryset/lib/selection/path/path.rb +23 -0
  31. data/lib/babik/queryset/lib/selection/select_related_selection.rb +38 -0
  32. data/lib/babik/queryset/lib/selection/selection.rb +19 -0
  33. data/lib/babik/queryset/lib/update/assignment.rb +108 -0
  34. data/lib/babik/queryset/mixins/aggregatable.rb +17 -0
  35. data/lib/babik/queryset/mixins/bounded.rb +38 -0
  36. data/lib/babik/queryset/mixins/clonable.rb +52 -0
  37. data/lib/babik/queryset/mixins/countable.rb +44 -0
  38. data/lib/babik/queryset/mixins/deletable.rb +13 -0
  39. data/lib/babik/queryset/mixins/distinguishable.rb +27 -0
  40. data/lib/babik/queryset/mixins/filterable.rb +51 -0
  41. data/lib/babik/queryset/mixins/limitable.rb +88 -0
  42. data/lib/babik/queryset/mixins/lockable.rb +31 -0
  43. data/lib/babik/queryset/mixins/none.rb +16 -0
  44. data/lib/babik/queryset/mixins/projectable.rb +34 -0
  45. data/lib/babik/queryset/mixins/related_selector.rb +28 -0
  46. data/lib/babik/queryset/mixins/set_operations.rb +32 -0
  47. data/lib/babik/queryset/mixins/sortable.rb +49 -0
  48. data/lib/babik/queryset/mixins/sql_renderizable.rb +17 -0
  49. data/lib/babik/queryset/mixins/updatable.rb +14 -0
  50. data/lib/babik/queryset/templates/default/delete/main.sql.erb +14 -0
  51. data/lib/babik/queryset/templates/default/select/components/aggregation.sql.erb +5 -0
  52. data/lib/babik/queryset/templates/default/select/components/from.sql.erb +16 -0
  53. data/lib/babik/queryset/templates/default/select/components/from_set.sql.erb +3 -0
  54. data/lib/babik/queryset/templates/default/select/components/from_table.sql.erb +2 -0
  55. data/lib/babik/queryset/templates/default/select/components/limit.sql.erb +10 -0
  56. data/lib/babik/queryset/templates/default/select/components/order_by.sql.erb +9 -0
  57. data/lib/babik/queryset/templates/default/select/components/projection.sql.erb +7 -0
  58. data/lib/babik/queryset/templates/default/select/components/select_related.sql.erb +26 -0
  59. data/lib/babik/queryset/templates/default/select/components/where.sql.erb +39 -0
  60. data/lib/babik/queryset/templates/default/select/main.sql.erb +42 -0
  61. data/lib/babik/queryset/templates/default/update/main.sql.erb +15 -0
  62. data/lib/babik/queryset/templates/mssql/select/components/limit.sql.erb +8 -0
  63. data/lib/babik/queryset/templates/mssql/select/components/order_by.sql.erb +21 -0
  64. data/lib/babik/queryset/templates/mysql2/delete/main.sql.erb +15 -0
  65. data/lib/babik/queryset/templates/mysql2/update/main.sql.erb +18 -0
  66. data/lib/babik/queryset/templates/sqlite3/select/components/from_set.sql.erb +5 -0
  67. data/test/config/db/schema.rb +83 -0
  68. data/test/config/models/bad_post.rb +5 -0
  69. data/test/config/models/bad_tag.rb +5 -0
  70. data/test/config/models/category.rb +4 -0
  71. data/test/config/models/geozone.rb +6 -0
  72. data/test/config/models/group.rb +5 -0
  73. data/test/config/models/group_user.rb +5 -0
  74. data/test/config/models/post.rb +24 -0
  75. data/test/config/models/post_tag.rb +5 -0
  76. data/test/config/models/tag.rb +5 -0
  77. data/test/config/models/user.rb +6 -0
  78. data/test/delete/delete_test.rb +60 -0
  79. data/test/delete/foreign_conditions_delete_test.rb +57 -0
  80. data/test/delete/local_conditions_delete_test.rb +20 -0
  81. data/test/enable_coverage.rb +17 -0
  82. data/test/lib/selection/operation/log/test-queries.log +1 -0
  83. data/test/lib/selection/operation/test_date.rb +131 -0
  84. data/test/lib/selection/operation/test_regex.rb +55 -0
  85. data/test/other/clone_test.rb +129 -0
  86. data/test/other/escape_test.rb +21 -0
  87. data/test/other/inverse_of_required_test.rb +33 -0
  88. data/test/select/aggregate_test.rb +151 -0
  89. data/test/select/bounds_test.rb +46 -0
  90. data/test/select/count_test.rb +147 -0
  91. data/test/select/distinct_test.rb +38 -0
  92. data/test/select/exclude_test.rb +72 -0
  93. data/test/select/filter_from_object_test.rb +125 -0
  94. data/test/select/filter_test.rb +207 -0
  95. data/test/select/for_update_test.rb +19 -0
  96. data/test/select/foreign_selection_test.rb +60 -0
  97. data/test/select/get_test.rb +40 -0
  98. data/test/select/limit_test.rb +109 -0
  99. data/test/select/local_selection_test.rb +24 -0
  100. data/test/select/lookup_test.rb +208 -0
  101. data/test/select/none_test.rb +40 -0
  102. data/test/select/order_test.rb +165 -0
  103. data/test/select/project_test.rb +107 -0
  104. data/test/select/select_related_test.rb +124 -0
  105. data/test/select/subquery_test.rb +50 -0
  106. data/test/set_operations/basic_usage_test.rb +121 -0
  107. data/test/test_helper.rb +55 -0
  108. data/test/update/update_test.rb +93 -0
  109. metadata +278 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../test_helper'
5
+
6
+ # Project method test
7
+ class ProjectTest < Minitest::Test
8
+
9
+ def setup
10
+ if GeoZone.objects.filter(name: 'Castilla').exists?
11
+ return
12
+ end
13
+ @castille = GeoZone.create!(name: 'Castilla')
14
+ ['Juan II', 'Isabel I', 'Juana I'].each do |name|
15
+ User.create!(first_name: name, last_name: 'de Castilla', email: "#{name.downcase.delete(' ')}@example.com", zone: @castille)
16
+ end
17
+ @spain = GeoZone.create!(name: 'España')
18
+ ['Carlos I', 'Felipe II', 'Felipe III'].each do |name|
19
+ User.create!(first_name: name, last_name: 'de Austria', email: "#{name.downcase.delete(' ')}@example.com", zone: @spain)
20
+ end
21
+ end
22
+
23
+ def teardown
24
+ User.destroy_all
25
+ GeoZone.destroy_all
26
+ end
27
+
28
+ def test_project
29
+ users_projection = User.objects
30
+ .filter('zone::name': 'Castilla')
31
+ .order_by('first_name')
32
+ .project('first_name', 'email')
33
+
34
+ local_projection_expectation = [
35
+ { first_name: 'Isabel I', email: 'isabeli@example.com' },
36
+ { first_name: 'Juan II', email: 'juanii@example.com' },
37
+ { first_name: 'Juana I', email: 'juanai@example.com' }
38
+ ]
39
+
40
+ assert users_projection.projection?
41
+ users_projection.each_with_index do |user_projection, user_projection_index|
42
+ assert_equal local_projection_expectation[user_projection_index], user_projection.symbolize_keys
43
+ end
44
+ end
45
+
46
+ def test_no_projection
47
+ users_projection = User.objects
48
+ .filter('zone::name': 'Castilla')
49
+ .order_by('first_name')
50
+ refute users_projection.projection?
51
+ end
52
+
53
+ def test_foreign_field_project
54
+ users_projection = User.objects
55
+ .filter('zone::name': 'Castilla')
56
+ .order_by('first_name')
57
+ .project('first_name', 'email', %w[zone::name country])
58
+ foreign_projection_expectation = [
59
+ { first_name: 'Isabel I', email: 'isabeli@example.com', country: 'Castilla' },
60
+ { first_name: 'Juan II', email: 'juanii@example.com', country: 'Castilla' },
61
+ { first_name: 'Juana I', email: 'juanai@example.com', country: 'Castilla' }
62
+ ]
63
+
64
+ assert users_projection.projection?
65
+ users_projection.each_with_index do |user_projection, user_projection_index|
66
+ assert_equal foreign_projection_expectation[user_projection_index], user_projection.symbolize_keys
67
+ end
68
+ end
69
+
70
+ def test_explicit_transform
71
+ datetime_format = '%Y-%m-%d %H:%M:%S'
72
+ datetime_transformer = ->(datetime) { datetime.strftime(datetime_format) }
73
+ users = User.objects.filter('zone::name': 'Castilla').order_by('first_name')
74
+ users_projection = users.project(
75
+ ['first_name', ->(s) { s.upcase }], 'email',
76
+ ['created_at', 'creation_date', ->(d) { datetime_transformer.call(Time.parse(d.to_s + 'UTC')) }]
77
+ )
78
+ users_projection.each_with_index do |user_projection, user_index|
79
+ assert_equal users[user_index].first_name.upcase, user_projection[:first_name]
80
+ assert_equal users[user_index].email, user_projection[:email]
81
+ assert_equal users[user_index].created_at.strftime(datetime_format), user_projection[:creation_date]
82
+ assert_equal users[user_index].created_at.strftime(datetime_format).class, user_projection[:creation_date].class
83
+ end
84
+ end
85
+
86
+ def test_wrong_transform_params
87
+ exception = assert_raises RuntimeError do
88
+ datetime_format = '%Y-%m-%d %H:%M:%S'
89
+ datetime_transformer = ->(datetime) { datetime.strftime(datetime_format) }
90
+ users = User.objects.filter('zone::name': 'Castilla').order_by('first_name')
91
+ users.project(
92
+ ['first_name', 2], 'email',
93
+ ['created_at', 'creation_date', ->(d) { datetime_transformer.call(Time.parse(d.to_s + 'UTC')) }]
94
+ )
95
+ end
96
+ assert_equal('Babik::QuerySet::ProjectedField.new only accepts String/Symbol or Proc. Passed a Integer.', exception.message)
97
+ end
98
+
99
+ def test_wrong_params
100
+ exception = assert_raises RuntimeError do
101
+ users = User.objects.filter('zone::name': 'Castilla').order_by('first_name')
102
+ users.project('first_name', 'email', 2222)
103
+ end
104
+ assert_equal('No other parameter type is permitted in Babik::QuerySet::ProjectedField.new than Array, String and Symbol.', exception.message)
105
+ end
106
+
107
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../test_helper'
5
+
6
+ # Select related method test
7
+ class SelectRelatedTest < Minitest::Test
8
+
9
+ def setup
10
+ @baetica = GeoZone.create!(name: 'Baetica')
11
+ @corduba = GeoZone.create!(name: 'Corduba', parent_zone: @baetica)
12
+ @seneca_sr = User.create!(first_name: 'Marcus Annaeus', last_name: 'Seneca', zone: @corduba)
13
+ @seneca_jr = User.create!(first_name: 'Lucius Annaeus', last_name: 'Seneca', zone: @corduba)
14
+
15
+ @africa = GeoZone.create!(name: 'Africa')
16
+ @cartago = GeoZone.create!(name: 'Cartago', parent_zone: @africa)
17
+ @tertullianus = User.create!(first_name: 'Quintus Septimius', last_name: 'Florens Tertullianus', zone: @cartago)
18
+
19
+ short_stories = Category.create(name: 'Short stories')
20
+ legal = Category.create(name: 'Legal texts')
21
+ oratory = Category.create(name: 'Oratory')
22
+ Post.create!(title: 'Gesta Romanorum', author: @seneca_sr, category: short_stories)
23
+ Post.create!(title: 'Controversiae', author: @seneca_sr, category: legal)
24
+ Post.create!(title: 'Suasoriae', author: @seneca_sr, category: oratory)
25
+
26
+ stoic_texts = Category.create(name: 'Stoic texts')
27
+ Post.create!(title: 'De brevitate vitae', author: @seneca_jr, category: stoic_texts)
28
+ Post.create!(title: 'Epistulae Morales ad Lucilium', author: @seneca_jr, category: stoic_texts)
29
+ Post.create!(title: 'De tranquillitate animi', author: @seneca_jr, category: stoic_texts)
30
+ end
31
+
32
+ def teardown
33
+ GeoZone.destroy_all
34
+ User.destroy_all
35
+ Category.destroy_all
36
+ Post.destroy_all
37
+ end
38
+
39
+ def test_wrong_select_related
40
+ exception = assert_raises RuntimeError do
41
+ User
42
+ .objects
43
+ .filter(zone__in: [@corduba.id, @cartago.id])
44
+ .select_related(:xxxxx)[0]
45
+ end
46
+ assert_equal(
47
+ 'Bad selection path: xxxxx. xxxxx not found in model User when filtering User objects',
48
+ exception.message
49
+ )
50
+
51
+ end
52
+
53
+ def test_select_related
54
+ users = User.objects.order_by('first_name')
55
+ number_of_users = User.objects.count
56
+ number_of_returned_users = 0
57
+
58
+ User
59
+ .objects
60
+ .filter(zone__in: [@corduba.id, @cartago.id])
61
+ .select_related(:zone)
62
+ .order_by('first_name')
63
+ .each_with_index do |user_with_related, user_with_related_index|
64
+ # Loop through each user with his/her related objects
65
+ user, select_related = user_with_related
66
+ # User tests
67
+ expected_user = users[user_with_related_index]
68
+ assert_equal User, user.class
69
+ assert_equal expected_user.id, user.id
70
+ assert_equal expected_user, user
71
+ # Zone tests
72
+ expected_zone = expected_user.zone
73
+ assert_equal GeoZone, select_related[:zone].class
74
+ assert_equal user.zone_id, select_related[:zone].id
75
+ assert_equal expected_zone, select_related[:zone]
76
+ # Users count
77
+ number_of_returned_users += 1
78
+ end
79
+ assert_equal number_of_users, number_of_returned_users
80
+ end
81
+
82
+ def test_select_related_several_associations
83
+ expected_posts = Post.objects.filter(author: @seneca_sr).order_by(title: :DESC)
84
+ posts = Post.objects
85
+ .filter(author: @seneca_sr)
86
+ .order_by(title: :DESC)
87
+ .select_related([:author, :category])
88
+ _test_select_related_posts(expected_posts, posts)
89
+ end
90
+
91
+ def test_select_related_several_associations_from_instance
92
+ expected_posts = Post.objects.filter(author: @seneca_sr).order_by(title: :DESC)
93
+ posts = @seneca_sr.objects(:posts)
94
+ .order_by(title: :DESC)
95
+ .select_related([:author, :category])
96
+ _test_select_related_posts(expected_posts, posts)
97
+ end
98
+
99
+ def _test_select_related_posts(expected_posts, posts)
100
+ # Load the posts with 4 or more stars with their author and category
101
+ posts.each_with_index do |post_with_author_category, index|
102
+ # Load post and its two associated objects
103
+ post, foreign_objects = post_with_author_category
104
+ author = foreign_objects[:author]
105
+ category = foreign_objects[:category]
106
+ # Check post
107
+ expected_post = expected_posts[index]
108
+ assert_equal Post, post.class
109
+ assert_equal expected_post.id, post.id
110
+ assert_equal expected_post, post
111
+ # Check author (user)
112
+ assert_equal User, author.class
113
+ assert_equal post.author_id, author.id
114
+ assert_equal post.author, author
115
+ # Check category
116
+ assert_equal Category, category.class
117
+ assert_equal post.category_id, category.id
118
+ assert_equal post.category, category
119
+ end
120
+ end
121
+
122
+
123
+
124
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../test_helper'
5
+
6
+ # Subquery test
7
+ class SubqueryTest < Minitest::Test
8
+
9
+ def setup
10
+ @baetica = GeoZone.create!(name: 'Baetica')
11
+ @corduba = GeoZone.create!(name: 'Corduba', parent_zone: @baetica)
12
+ @seneca_sr = User.create!(first_name: 'Marcus Annaeus', last_name: 'Seneca', zone: @corduba)
13
+ short_stories = Category.create(name: 'Short stories')
14
+ legal = Category.create(name: 'Legal texts')
15
+ oratory = Category.create(name: 'Oratory')
16
+ Post.create!(title: 'Gesta Romanorum', author: @seneca_sr, category: short_stories)
17
+ Post.create!(title: 'Controversiae', author: @seneca_sr, category: legal)
18
+ Post.create!(title: 'Suasoriae', author: @seneca_sr, category: oratory)
19
+ end
20
+
21
+ def teardown
22
+ GeoZone.destroy_all
23
+ User.destroy_all
24
+ Category.destroy_all
25
+ Post.destroy_all
26
+ end
27
+
28
+ def test_subquery_with_equal
29
+ seneca_sr_posts = Post.objects.filter(id: @seneca_sr.objects(:posts).project(:id))
30
+ assert_equal @seneca_sr.objects(:posts).count, seneca_sr_posts.count
31
+ seneca_sr_posts_count = 0
32
+ seneca_sr_posts.each do |post|
33
+ assert_equal @seneca_sr.id, post.author_id
34
+ seneca_sr_posts_count += 1
35
+ end
36
+ assert_equal seneca_sr_posts_count, seneca_sr_posts.count
37
+ end
38
+
39
+ def test_subquery_with_in
40
+ seneca_sr_posts = Post.objects.filter(id__in: @seneca_sr.objects(:posts).project(:id))
41
+ assert_equal @seneca_sr.objects(:posts).count, seneca_sr_posts.count
42
+ seneca_sr_posts_count = 0
43
+ seneca_sr_posts.each do |post|
44
+ assert_equal @seneca_sr.id, post.author_id
45
+ seneca_sr_posts_count += 1
46
+ end
47
+ assert_equal seneca_sr_posts_count, seneca_sr_posts.count
48
+ end
49
+
50
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../test_helper'
5
+
6
+ # Class for basic set operation tests
7
+ class BasicUsageTest < Minitest::Test
8
+
9
+ def setup
10
+
11
+ patrician_families = %w[
12
+ Aebutia Aemilia Aquillia Atilia Claudia Cloelia Cornelia Curtia
13
+ Fabia Foslia Furia Gegania Genucia Herminia Horatia Julia Lartia
14
+ Lucretia Manlia Menenia Metilia Minucia Mucia Nautia Numicia Papiria
15
+ Pinaria Pollia Postumia Potitia Quinctia Quinctilia Romilia
16
+ Sempronia Sergia Servilia Sestia Siccia Sulpicia Tarpeia Tarquinia
17
+ Tarquitia Tullia Valeria Verginia Veturia Vitellia Volumnia
18
+ ]
19
+ roman_names = %w[
20
+ Aeliana Albia Antonia Aquilia Argentia Atticus Augusta Augustus Aurelia Aurelius Avita Caesar Camilla
21
+ Cassia Cassius Cato Cecilia Cicero Claudia Claudius Clemensia Cornelius Crispus Cyprian Decima Decimus
22
+ Drusilla Dulcia Fabia Faustina Felix Flavia Florentina Fortunata Gaia Galla Hilaria Horatia Julia Julius
23
+ Junia Justus Laelia Laurentia Livia Lucius Lucretia Magnus Marcella Marcus Marilla Marius Martia Maxima
24
+ Maximus Mila Nerilla Nero Octavia Octavius Philo Prima Priscilla Quintia Quintus Remus Romulus Rufina
25
+ Rufus Sabina Seneca Septima Septimus Sergia Tanaquil Tatiana Tauria Tertia Tiberius Tullia Urban Urbana
26
+ Valentina Varinia Vita
27
+ ]
28
+
29
+ random_generator = Random.new(1234)
30
+ patrician_families.each do |family_name|
31
+ roman_names.each do |roman_name|
32
+ User.create!(first_name: roman_name, last_name: family_name) if random_generator.rand(0..1) == 1
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ def teardown
39
+ User.destroy_all
40
+ end
41
+
42
+ def test_union
43
+ claudia = User.objects.filter(last_name: 'Claudia')
44
+ verturia = User.objects.filter(last_name: 'Veturia')
45
+ both_families = claudia.union(verturia).order_by!({ last_name: :DESC }, { first_name: :ASC })
46
+ both_families_without_union = User.where(last_name: ['Claudia', 'Veturia']).order(last_name: :DESC, first_name: :ASC)
47
+ _check_set_operation(both_families_without_union, both_families)
48
+ end
49
+
50
+ def test_chained_union
51
+ families = User.objects.filter(last_name: 'Aebutia')
52
+ .union(User.objects.filter(last_name: 'Fabia'))
53
+ .union(User.objects.filter(last_name: 'Atilia'))
54
+ .union(User.objects.filter(last_name: 'Claudia'))
55
+ .union(User.objects.filter(last_name: 'Cloelia'))
56
+ .order_by!({last_name: :DESC}, {first_name: :ASC})
57
+ families_without_union = User.where(last_name: ['Aebutia', 'Fabia', 'Atilia', 'Claudia', 'Cloelia'])
58
+ .order(last_name: :DESC, first_name: :ASC)
59
+ _check_set_operation(families_without_union, families)
60
+ end
61
+
62
+ def test_deep_union
63
+ claudia = User.objects.filter(last_name: 'Claudia')
64
+ verturia = User.objects.filter(last_name: 'Veturia')
65
+ aemilia = User.objects.filter(last_name: 'Aemilia')
66
+ three_families = claudia.union(verturia).union(aemilia).order_by!({ last_name: :DESC }, { first_name: :ASC })
67
+ three_families_without_union = User
68
+ .where(last_name: ['Claudia', 'Veturia', 'Aemilia'])
69
+ .order(last_name: :DESC, first_name: :ASC)
70
+ _check_set_operation(three_families_without_union, three_families)
71
+ end
72
+
73
+ def test_intersection
74
+ first_user = User.objects.first
75
+ qs_with_intersection = User.objects.filter(first_name: first_user.first_name)
76
+ .intersection(User.objects.filter(last_name: first_user.last_name))
77
+ .order_by!({ last_name: :DESC }, { first_name: :ASC })
78
+ qs_without_intersection = User.where(first_name: first_user.first_name, last_name: first_user.last_name)
79
+ .order(last_name: :DESC, first_name: :ASC)
80
+ _check_set_operation(qs_without_intersection, qs_with_intersection)
81
+ end
82
+
83
+ def test_deep_intersection
84
+ first_user = User.objects.first
85
+ qs_with_intersection = User.objects.filter(first_name: first_user.first_name)
86
+ .intersection(User.objects.filter(last_name: first_user.last_name))
87
+ .intersection(User.objects.filter(created_at__lt: Time.now))
88
+ .order_by!({ last_name: :DESC }, { first_name: :ASC })
89
+ qs_without_intersection = User.where(first_name: first_user.first_name, last_name: first_user.last_name)
90
+ .order(last_name: :DESC, first_name: :ASC)
91
+ _check_set_operation(qs_without_intersection, qs_with_intersection)
92
+ end
93
+
94
+ def test_difference
95
+ first_user = User.objects.first
96
+ qs_with_minus = User.objects
97
+ .filter(last_name: first_user.last_name)
98
+ .difference(User.objects.filter(first_name: first_user.first_name))
99
+ .order_by!({ last_name: :DESC }, { first_name: :ASC })
100
+ qs_without_minus = User.where(last_name: first_user.last_name)
101
+ .where.not(first_name: first_user.first_name)
102
+ .order(last_name: :DESC, first_name: :ASC)
103
+ _check_set_operation(qs_without_minus, qs_with_minus)
104
+ end
105
+
106
+ # Check a set-operation based queryset is correct
107
+ def _check_set_operation(expected_qs, actual_qs)
108
+ record_count = 0
109
+ expected_qs.each_with_index do |expected_qs_record, expected_qs_record_index|
110
+ actual_qs_record = actual_qs[expected_qs_record_index]
111
+ assert_equal expected_qs_record.id, actual_qs_record.id
112
+ assert_equal expected_qs_record.last_name, actual_qs_record.last_name
113
+ assert_equal expected_qs_record.first_name, actual_qs_record.first_name
114
+ assert_equal expected_qs_record.last_name, actual_qs_record.last_name
115
+ record_count += 1
116
+ end
117
+ assert_equal expected_qs.count, actual_qs.count
118
+ assert_equal expected_qs.count, record_count
119
+ end
120
+
121
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'minitest/unit'
5
+ require 'minitest/autorun'
6
+ require 'minitest/pride'
7
+
8
+ # Setup the log
9
+ require 'fileutils'
10
+ FileUtils.mkpath 'log' unless File.directory? 'log'
11
+ ActiveRecord::Base.logger = Logger.new('log/test-queries.log')
12
+
13
+ DEFAULT_DB_ADAPTER = 'sqlite3'
14
+ #DEFAULT_DB_ADAPTER = 'mysql2'
15
+ #DEFAULT_DB_ADAPTER = 'postgresql'
16
+
17
+ # Make a connection
18
+ adapter = ENV.fetch('DB', DEFAULT_DB_ADAPTER)
19
+ case adapter
20
+ when 'mysql2', 'postgresql'
21
+ config = {
22
+ host: '127.0.0.1',
23
+ database: 'babik_test',
24
+ encoding: 'utf8',
25
+ min_messages: 'WARNING',
26
+ adapter: adapter,
27
+ username: ENV['DB_USERNAME'] || 'postgres',
28
+ password: ENV['DB_PASSWORD'] || 'postgres'
29
+ }
30
+ ActiveRecord::Tasks::DatabaseTasks.create(config.stringify_keys)
31
+ ActiveRecord::Base.establish_connection(config)
32
+ when 'sqlite3'
33
+ ActiveRecord::Base.establish_connection adapter: adapter, database: ':memory:'
34
+ else
35
+ raise "Unknown DB adapter #{adapter}. Valid adapters are: mysql2, postgresql, sqlite3."
36
+ end
37
+
38
+ # Load babik library
39
+ require 'babik'
40
+
41
+ # Create tables
42
+ load "#{__dir__}/config/db/schema.rb"
43
+
44
+ # Load models
45
+ require 'config/models/geozone'
46
+ require 'config/models/user'
47
+ require 'config/models/tag'
48
+ require 'config/models/category'
49
+ require 'config/models/post'
50
+ require 'config/models/post_tag'
51
+ require 'config/models/bad_tag'
52
+ require 'config/models/bad_post'
53
+
54
+
55
+