unreliable 0.1.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 492ba9597605f55f1b9ee0d92a132a072a70257ef2b12bb54390f78dc6b67b7d
4
- data.tar.gz: 385fe7486e4b8bf7b24ffc2657eee2d956d88a77e765582974a3bf20eb41480c
3
+ metadata.gz: c392da89c1547259307a12e4674e96c9aaa07e0d7b2550d508fc98b7d490b8ea
4
+ data.tar.gz: de874288196a3383fce382e4e35ae425547d0a8afd3fb64e5b1989e49f703b83
5
5
  SHA512:
6
- metadata.gz: 7328df183703ef3aa8410eb86995558d11f5855c86c20142b046632d95110681f162db99dede9f7b75f7e1bf72d0e05e7bdad675412005d84d05d9af957acedb
7
- data.tar.gz: 453b0ae1dfc15bf3354a3caf7ea421caf61dd2b5d48ec8d1c04166749db470693e0da0ec4c863aeb81f03b970e2a94addc7795860396bbf9c025e2ad2c665f1a
6
+ metadata.gz: d9a215c602f513619493f094994098ae4e15555b8f9d9962b0ddf3a9c15fe7d6cb0a19b3d75b064c12985383bfc2fd8d247d7a1d4400485dafd69cd63aea94e4
7
+ data.tar.gz: 70ae7ac42726add25f320037c57c7d42a4d28d6af1e6380f4be8c5ccbecd24deb34bd095255579645a3fc345e3ee7913b85108b7c2bcf6cd05b8fb3e3f4c0a05
data/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- ## Unreliable 0.1.2 (April xx, 2022) ##
1
+ ## Unreliable 0.9.0 (November 20, 2022) ##
2
+
3
+ ### Changed
4
+
5
+ * Minor README, gemspec, and rubocop changes.
6
+ * More tests, including queries generated by ActiveRecord associations.
7
+ * Don't randomize order from ar\_internal\_metadata.
8
+ * When a single-table query or subquery has an existing ORDER that includes all columns of that table's primary key, this is one of the few cases where we know we don't need to randomize order, so don't bother.
9
+
10
+ ## Unreliable 0.1.3 (August 21, 2022) ##
11
+
12
+ * README and minor gemspec changes.
13
+
14
+ ## Unreliable 0.1.2 (April 27, 2022) ##
2
15
 
3
16
  * README and internal dependency changes.
4
17
 
data/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  ![CI workflow](https://github.com/jamiemccarthy/unreliable/actions/workflows/ci.yml/badge.svg)
4
4
  ![Gem version](https://img.shields.io/gem/v/unreliable)
5
5
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
6
- [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa)](code_of_conduct.md)
6
+ [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa)](CODE_OF_CONDUCT.md)
7
7
 
8
- **Unreliable makes your ActiveRecord scopes and test suite better, by forcing your tests to not rely on ambiguous ordering.**
8
+ **The `unreliable` gem forces your ActiveRecord tests not to rely on ambiguous ordering. This makes your app and its tests more robust.**
9
9
 
10
10
  ## Installation
11
11
 
@@ -19,106 +19,98 @@ group :test do
19
19
  end
20
20
  ```
21
21
 
22
- Then `bundle install` and run your test suite repeatedly, maybe 6-8 times, looking for new failures.
22
+ The next time your test suite runs, it may emit new errors and failures. If so, great!
23
23
 
24
- ## The problem
24
+ ## The problem with orders
25
25
 
26
- Here's an open secret: relational databases do not guarantee the order results are returned in, without a well-chosen ORDER BY clause.
26
+ Here's an [open secret](#references): **relational databases do not guarantee the order results are returned in, without a thorough `ORDER BY` clause.**
27
27
 
28
- Choosing a good ORDER is harder than it seems. And you can get it wrong silently. Your test suite won't help you if your database just happens to return the same order almost all the time. Which they usually do.
28
+ If all your ActiveRecord ordering is already unambiguous, congratulations! `unreliable` will have no effect.
29
29
 
30
- If your Rails code relies on that accidental ordering, that's a bug in your app. Or sometimes ambiguous order is fine for your app's purposes, but your tests rely on it. Either way, your tests are passing when they should be failing.
30
+ But sometimes we think we specified an unambiguous order, but didn't. Maybe we ordered on timestamps, which are usually unique but sometimes not. And the test suite will stay silent as long as our database just happens to return the same order.
31
31
 
32
- ## The test
32
+ If ambiguous ordering is fine for your app's purposes, but your tests rely on a specific order, that's a bug in your tests. Your tests are incorrectly failing -- rarely -- which can be confusing and annoying.
33
33
 
34
- In a Rails test environment, Unreliable patches ActiveRecord to always have a final ORDER BY clause that returns results in a random order.
34
+ Or, if your Rails code relies on that accidental ordering, that's a bug in your app. Your tests are passing when they should be failing.
35
35
 
36
- With Unreliable installed, every ActiveRecord relation invoked by the test suite will have any ambiguity replaced with randomness. Tests that rely on the ordering of two records will break half the time. Tests with three or more break most of the time.
36
+ In both cases, `unreliable` exposes the problem by making those tests fail most of the time.
37
37
 
38
- If you install Unreliable and your test suite starts failing, but only sometimes, that tells you to check your app's relations and scopes, and check your tests.
38
+ ## Fixing the new failures
39
39
 
40
- Even your relations with an order may have an ambiguous order. See "Ordering trivia" below.
40
+ When `unreliable` turns up a new test failure, you fix it in one of two ways. Either relax your test so it stops relying on order, or tighten up your app to specify order rigorously. (In my company's app, it was about 50/50.)
41
41
 
42
- ## The fixes
42
+ ### Relax a test
43
43
 
44
- When Unreliable turns up a new test failure, it's for one of two reasons. Either your test needs to stop relying on order, or your app needs to specify order better.
44
+ Take a look at what your test is checking. If you're testing a method or an endpoint that returns a list whose order doesn't matter, you may have written it to expect the order that was returned the first time you ran it. This often happens with fixtures. You might:
45
45
 
46
- ### Incorrect test
46
+ * Make your test accept all correct answers. For example, sort an array in the method's response before comparing.
47
47
 
48
- Take a look at what you're testing. If you're testing a method or an endpoint that returns a list whose order doesn't matter and isn't documented, you may have written it to expect the order that was returned the first time you ran it. This often happens with fixtures. You might:
48
+ * Help your test suite focus on what you're testing. If your fixtures' "latest" element could change because they don't specify a timestamp, that might be a distraction that's not relevant to how your app works, so you could assign timestamps to the fixtures.
49
49
 
50
- * Make your test accept all correct answers. For example, sort the method's response before comparing.
50
+ This makes your test suite more robust.
51
51
 
52
- * Or, help your test suite focus on what you're testing. If your fixtures' "latest" element is random because they don't specify a timestamp, that may be a distraction that's not relevant to how your app works, so you could just assign them timestamps.
52
+ If your test suite is checking generated `.to_sql` against known-good SQL text, `unreliable` isn't helpful. It's easiest to use `Unreliable::Config.disable { ... }` to turn it off for a block.
53
53
 
54
- Either way, you eliminate potential spurious failures in your test suite.
54
+ ### Tighten the app
55
55
 
56
- ### Incorrect app
56
+ If your app should be returning results in a particular order, and now with `unreliable` it sometimes does not, your test is correct and your app is wrong. Specify order rigorously in your app.
57
57
 
58
- If your app should be returning results in a particular order, and now with Unreliable it sometimes does not, your test is correct and your app is wrong. Specify order rigorously in your app.
59
-
60
- Maybe you've defined an ordering this way:
58
+ Maybe you're testing `Book.reverse_chron.first`, and you've defined that ordering this way:
61
59
 
62
60
  ```
63
- class Book
64
- scope :reverse_chron, -> { order(year_published: :desc) }
65
- end
61
+ class Book
62
+ scope :reverse_chron, -> { order(year_published: :desc) }
63
+ end
66
64
  ```
67
65
 
68
66
  When you meant to define it unambiguously:
69
67
 
70
68
  ```
71
- class Book
72
- scope :reverse_chron, -> { order(year_published: :desc, title: :desc) }
73
- end
69
+ scope :reverse_chron, -> { order(year_published: :desc, title: :desc) }
74
70
  ```
75
71
 
76
72
  Or, if `title` is not unique:
77
73
 
78
74
  ```
79
- class Book
80
- scope :reverse_chron, -> { order(year_published: :desc, title: :desc, id: :desc) }
81
- end
75
+ scope :reverse_chron, -> { order(year_published: :desc, title: :desc, id: :desc) }
82
76
  ```
83
77
 
84
- This example is obviously wrong because many books are published each year, but this error can occur at any time granularity.
78
+ The problem in this example is easy to see because many books are published each year. But this error can occur at any time granularity.
85
79
 
86
80
  ## Requirements
87
81
 
88
- Unreliable is tested to support Ruby 2.6 through 3.1, and Rails 5.0 through 7.0.
82
+ `unreliable` is tested to support Ruby 2.6 through 3.1, and Rails 5.0 through 7.0.
89
83
 
90
- As of April 2022, this is all released versions of both that are currently supported, plus several older releases.
84
+ As of November 2022, this is all released versions of both that are currently supported, plus several older releases.
91
85
 
92
- Unreliable depends only on ActiveRecord and Railties. If you have a non-Rails app that uses ActiveRecord, you can still use it.
86
+ `unreliable` depends only on ActiveRecord and Railties. If you have a non-Rails app that uses ActiveRecord, you can still use it.
93
87
 
94
88
  ## Implementation
95
89
 
96
- **Unreliable does exactly nothing outside of test environments. There is intentionally no way to enable Unreliable in production, and there never will be.**
90
+ `unreliable` does exactly nothing outside of test environments. There is intentionally no way to enable `unreliable` in production, and there never will be.
97
91
 
98
- Unreliable patches `ActiveRecord::QueryMethods#build_arel`, the point where an Arel is converted for use, to append an order to the existing order chain. (The patch is applied after ActiveRecord loads, using `ActiveSupport.on_load`, the standard interface since Rails 4.0.)
92
+ In a Rails test environment, `unreliable` patches ActiveRecord to append a final `ORDER BY` clause, when necessary, that returns results in a random order.
99
93
 
100
- This means that the ORDER BY applies to not just SELECTs but e.g. delete_all and update_all. It also applies within subqueries.
94
+ Because it's appended, the existing ordering is not affected unless it is ambiguous.
101
95
 
102
- The patch is only applied when `Rails.env.test?`, and that boolean is also checked on every invocation to make certain it has no effect in any other environment.
96
+ With `unreliable` installed, every ActiveRecord relation invoked by the test suite will have any ambiguity replaced with randomness. Tests that rely on the ordering of two records will break half the time. Tests with three or more break most of the time.
103
97
 
104
- `Unreliable::Config.disable do ... end` will turn it off for a block.
98
+ `unreliable` patches `ActiveRecord::QueryMethods#build_arel`, the point where an Arel is converted for use, to append an order to the existing order chain. (The patch is applied after ActiveRecord loads, using `ActiveSupport.on_load`, the standard interface since Rails 4.0.) It works with MySQL, Postgres, and SQLite.
105
99
 
106
- ## Ordering trivia
100
+ This means that the `ORDER BY` applies to not just `SELECT` but e.g. `delete_all` and `update_all`. It also applies within subqueries.
107
101
 
108
- The most common ambiguous ordering is an ORDER BY one column that is not unique, like a timestamp.
102
+ The patch is only applied when `Rails.env.test?`, and that boolean is also checked on every invocation, just to make certain it has no effect in any other environment.
109
103
 
110
- But there are other ways you can order a relation but still have your query be ambiguous:
104
+ ### No dual-purpose environment please
111
105
 
112
- * ORDER BY multiple columns, but with no subset which is unique
113
- * ORDER BY a column with values that differ only by [character case](https://dev.mysql.com/doc/refman/8.0/en/sorting-rows.html)
114
- * ORDER BY values that are identical within the [prefix length limit](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_sort_length) examined for sorting
115
-
116
- Unreliable correctly tests these because the random order is always appended.
106
+ Your test environment is just for running your test suite. If you've overloaded the test environment to do any actual work, you'd be frustrated when `unreliable` slows it down and changes its behavior, so don't install it (yet).
117
107
 
118
108
  ## Contributing
119
109
 
120
110
  Thoughts and suggestions are welcome. Please read the code of conduct, then create an issue or pull request on GitHub. If you just have questions, go ahead and open an issue, I'm pretty friendly.
121
111
 
112
+ ### Run the gem's tests
113
+
122
114
  To test locally, against the different versions of ActiveRecord, use Ruby 2.7, the only version currently compatible with all the ActiveRecord versions supported. Install the required gems with:
123
115
 
124
116
  ```
@@ -127,13 +119,13 @@ bundle install
127
119
  bundle exec appraisal install
128
120
  ```
129
121
 
130
- Run Unreliable's linter with:
122
+ Run `unreliable`'s linter with:
131
123
 
132
124
  ```
133
125
  bundle exec standardrb
134
126
  ```
135
127
 
136
- Then you can run Unreliable's tests with:
128
+ Then you can run `unreliable`'s tests with:
137
129
 
138
130
  ```
139
131
  bundle exec appraisal rake
@@ -143,29 +135,59 @@ Appraisal ensures the tests run against every compatible minor version of Active
143
135
 
144
136
  The GitHub CI workflow in `.github/` ensures those tests are also run against against every compatible minor version of Ruby. Your PR won't trigger my GitHub project's workflow, but you're welcome to run your own, or ask me to run mine manually.
145
137
 
146
- Testing against ActiveRecord is done with [Combustion](https://github.com/pat/combustion), which stands up a local single-table SQLite database and an ActiveRecord-based model for it. This gives more reliable coverage than mocking unit tests within ActiveRecord itself.
138
+ Testing against ActiveRecord is done with [Combustion](https://github.com/pat/combustion), which stands up a local SQLite database and ActiveRecord-based models for it. This gives more reliable coverage than mocking unit tests within ActiveRecord itself, though I do some of that too.
147
139
 
148
- If you'd like to see Unreliable in action on a small but real Rails app locally, you can do this:
140
+ ### Experiment
149
141
 
150
- 1. Start with a 1-line Gemfile: `gem "rails", "~> x.y"`
151
- 2. `bundle install`, then:
152
- 3. `bundle exec rails new . --skip-javascript --skip-webpack-install --skip-sprockets --skip-turbolinks --skip-jbuilder --skip-spring`
153
- 4. Add to Gemfile test block: `gem "unreliable", path: "../unreliable"`
154
- 5. `bundle install` again
155
- 6. `bundle exec rails generate model post title:string body:text`
156
- 7. `RAILS_ENV=test bundle exec rails db:migrate`
157
- 8. `RAILS_ENV=test bundle exec rails c`
158
- 9. You should see "Loading test environment" and the Rails console prompt. Then to test:
142
+ If you'd like to see `unreliable` in action on a small but real Rails app locally, you can do this:
143
+
144
+ 1. In a directory next to your `unreliable` working directory, create a `.ruby-version` of `2.7.6` and a 2-line `Gemfile`: `source "https://rubygems.org"`, `gem "rails", "~> 7.0"`
145
+ 2. `bundle install && bundle exec rails new . --force`
146
+ 3. `echo 'gem "unreliable", path: "../unreliable"' >> Gemfile`
147
+ 4. `bundle install && bundle exec rails generate model post title:string body:text`
148
+ 5. `RAILS_ENV=test bundle exec rails db:migrate`
149
+ 6. `RAILS_ENV=test bundle exec rails c`
150
+ 7. You should see SQLite's `ORDER BY RANDOM()` in ActiveRecord queries:
159
151
 
160
152
  ```
161
- 3.0.1 :001 > puts Post.where(title: "abc").to_sql
162
- (0.7ms) SELECT sqlite_version(*)
163
- SELECT "posts".* FROM "posts" WHERE "posts"."title" = 'abc' ORDER BY RAND()
153
+ irb(main):001:0> Post.where(title: "abc")
154
+ (2.1ms) SELECT sqlite_version(*)
155
+ Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."title" = ? ORDER BY RANDOM() [["title", "abc"]]
156
+ => []
157
+ irb(main):002:0> Post.limit(5).delete_all
158
+ Post Delete All (0.2ms) DELETE FROM "posts" WHERE "posts"."id" IN (SELECT "posts"."id" FROM "posts" ORDER BY RANDOM() LIMIT ?) [["LIMIT", 5]]
159
+ => 0
164
160
  ```
165
161
 
166
- ## Future development
162
+ ## Ordering trivia
163
+
164
+ The most common ambiguous ordering is an ORDER BY one column that is not unique, like a timestamp.
165
+
166
+ But there are other ways you can order a relation but still have your query be ambiguous:
167
+
168
+ * ORDER BY multiple columns, but with no subset which is unique
169
+ * ORDER BY a column with values that differ only by [character case](https://dev.mysql.com/doc/refman/8.0/en/sorting-rows.html)
170
+ * ORDER BY values that are identical within the [prefix length limit](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_sort_length) examined for sorting
171
+
172
+ `unreliable` ensures correct testing because it appends a random order to each of these cases.
173
+
174
+ ## References
175
+
176
+ SQL standard ([SQL-92](https://web.archive.org/web/20220730144627/https://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt)):
177
+
178
+ > When the ordering of a cursor is partially determined by an _order by clause_, then the relative positions of two rows are determined only by the _order by clause_; if the two rows have equal values for the purpose of evaluating the _order by clause_, then their relative positions are implementation-dependent.
179
+
180
+ MySQL ([5.6](https://dev.mysql.com/doc/refman/5.6/en/limit-optimization.html), [5.7](https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html), [8.0](https://dev.mysql.com/doc/refman/8.0/en/limit-optimization.html)):
181
+
182
+ > If multiple rows have identical values in the `ORDER BY` columns, the server is free to return those rows in any order, and may do so differently depending on the overall execution plan. In other words, the sort order of those rows is nondeterministic with respect to the nonordered columns.
183
+
184
+ Postgres ([12](https://www.postgresql.org/docs/12/sql-select.html#SQL-ORDERBY), [13](https://www.postgresql.org/docs/13/sql-select.html#SQL-ORDERBY), [14](https://www.postgresql.org/docs/14/sql-select.html#SQL-ORDERBY)):
185
+
186
+ > If two rows are equal according to the leftmost expression, they are compared according to the next expression and so on. If they are equal according to all specified expressions, they are returned in an implementation-dependent order.
187
+
188
+ SQLite ([3.39](https://www.sqlite.org/lang_select.html#the_order_by_clause)):
167
189
 
168
- When new minor versions of ActiveRecord or Ruby are released, I will update the Appraisals file and run `bundle exec appraisal update` as well as the install, and update the matrix in the ci.yml workflow. There will be a patch-level release for these changes, even if no Unreliable code changes are required.
190
+ > The order in which two rows for which all ORDER BY expressions evaluate to equal values are returned is undefined.
169
191
 
170
192
  ## See also
171
193
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Override ActiveRecord::QueryMethods.build_order to always append a final ORDER(RAND())
3
+ # Override ActiveRecord::QueryMethods.build_order to append a final ORDER(RAND()) when necessary
4
4
 
5
5
  require "active_record/connection_adapters/abstract_adapter"
6
6
 
@@ -10,6 +10,8 @@ module Unreliable
10
10
  super(arel)
11
11
 
12
12
  return unless Unreliable::Config.enabled?
13
+ return if from_only_internal_metadata?(arel)
14
+ return if from_one_table_with_ordered_pk?(arel)
13
15
 
14
16
  case Arel::Table.engine.connection.adapter_name
15
17
  when "Mysql2"
@@ -26,5 +28,37 @@ module Unreliable
26
28
 
27
29
  end
28
30
  end
31
+
32
+ def from_only_internal_metadata?(arel)
33
+ # No need to randomize queries on ar_internal_metadata
34
+ arel.froms.map(&:name) == [ActiveRecord::Base.internal_metadata_table_name]
35
+ end
36
+
37
+ def from_one_table_with_ordered_pk?(arel)
38
+ # This gem isn't (yet) capable of determining if ordering is reliable when two or
39
+ # more tables are being joined.
40
+ return false if arel.ast.cores.first.source.is_a?(Arel::Nodes::JoinSource) &&
41
+ arel.ast.cores.first.source.right.present?
42
+ return false if arel.froms.count > 1
43
+
44
+ # If the single table's primary key's column(s) are covered by the order columns,
45
+ # return true and don't randomize the order.
46
+ (primary_key_columns(arel) - order_columns(arel)).empty?
47
+ end
48
+
49
+ def primary_key_columns(arel)
50
+ # primary_key returns a String if it's one column, an Array if two or more
51
+ [ActiveRecord::Base.connection.primary_key(arel.froms.first.name)].flatten
52
+ end
53
+
54
+ def order_columns(arel)
55
+ from_table_name = arel.froms.first.name
56
+ arel.orders
57
+ .select { |order| order.is_a? Arel::Nodes::Ordering } # Don't try to parse textual orders
58
+ .map(&:expr)
59
+ .select { |expr| expr.relation.name == from_table_name }
60
+ .map(&:name)
61
+ .map(&:to_s) # In Rails < 5.2, the order column names are symbols; >= 5.2, strings
62
+ end
29
63
  end
30
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unreliable
4
- VERSION = "0.1.2"
4
+ VERSION = "0.9.0"
5
5
  end
data/spec/env_spec.rb CHANGED
@@ -3,14 +3,14 @@
3
3
  RSpec.describe Unreliable do
4
4
  it "does nothing in prod" do
5
5
  Rails.env = "production"
6
- expect(Thing.where(word: "foo").to_sql).to end_with(%q(WHERE "things"."word" = 'foo'))
6
+ expect(Cat.where(word: "foo").to_sql).to end_with(%q(WHERE "cats"."word" = 'foo'))
7
7
  ensure
8
8
  Rails.env = "test"
9
9
  end
10
10
 
11
11
  it "does nothing in dev" do
12
12
  Rails.env = "development"
13
- expect(Thing.where(word: "foo").to_sql).to end_with(%q(WHERE "things"."word" = 'foo'))
13
+ expect(Cat.where(word: "foo").to_sql).to end_with(%q(WHERE "cats"."word" = 'foo'))
14
14
  ensure
15
15
  Rails.env = "test"
16
16
  end
data/spec/examples.txt CHANGED
@@ -1,14 +1,39 @@
1
- example_id | status | run_time |
2
- ---------------------------------- | ------ | --------------- |
3
- ./spec/env_spec.rb[1:1] | passed | 0.00986 seconds |
4
- ./spec/env_spec.rb[1:2] | passed | 0.00041 seconds |
5
- ./spec/model_select_spec.rb[1:1] | passed | 0.00035 seconds |
6
- ./spec/model_select_spec.rb[1:2] | passed | 0.00044 seconds |
7
- ./spec/model_select_spec.rb[1:3] | passed | 0.0005 seconds |
8
- ./spec/model_select_spec.rb[1:4] | passed | 0.00061 seconds |
9
- ./spec/model_subquery_spec.rb[1:1] | passed | 0.00125 seconds |
10
- ./spec/model_update_spec.rb[1:1] | passed | 0.00012 seconds |
11
- ./spec/railtie_spec.rb[1:1] | passed | 0.00062 seconds |
12
- ./spec/railtie_spec.rb[1:2] | passed | 0.00018 seconds |
13
- ./spec/version_spec.rb[1:1] | passed | 0.00127 seconds |
14
- ./spec/version_spec.rb[1:2] | passed | 0.00016 seconds |
1
+ example_id | status | run_time |
2
+ ------------------------------------------ | ------ | --------------- |
3
+ ./spec/env_spec.rb[1:1] | passed | 0.00346 seconds |
4
+ ./spec/env_spec.rb[1:2] | passed | 0.00049 seconds |
5
+ ./spec/model_cache_versioning_spec.rb[1:1] | passed | 0.03983 seconds |
6
+ ./spec/model_indexes_books_spec.rb[1:1] | passed | 0.00152 seconds |
7
+ ./spec/model_indexes_books_spec.rb[1:2] | passed | 0.00132 seconds |
8
+ ./spec/model_indexes_books_spec.rb[1:3] | passed | 0.00108 seconds |
9
+ ./spec/model_indexes_cats_spec.rb[1:1] | passed | 0.00105 seconds |
10
+ ./spec/model_indexes_cats_spec.rb[1:2] | passed | 0.0015 seconds |
11
+ ./spec/model_indexes_dreams_spec.rb[1:1] | passed | 0.00313 seconds |
12
+ ./spec/model_indexes_dreams_spec.rb[1:2] | passed | 0.00129 seconds |
13
+ ./spec/model_indexes_shelves_spec.rb[1:1] | passed | 0.00178 seconds |
14
+ ./spec/model_indexes_shelves_spec.rb[1:2] | passed | 0.00174 seconds |
15
+ ./spec/model_indexes_shelves_spec.rb[1:3] | passed | 0.00076 seconds |
16
+ ./spec/model_indexes_shelves_spec.rb[1:4] | passed | 0.00087 seconds |
17
+ ./spec/model_indexes_shelves_spec.rb[1:5] | passed | 0.00079 seconds |
18
+ ./spec/model_indexes_shelves_spec.rb[1:6] | passed | 0.00101 seconds |
19
+ ./spec/model_joins_spec.rb[1:1] | passed | 0.01262 seconds |
20
+ ./spec/model_joins_spec.rb[1:2] | passed | 0.00359 seconds |
21
+ ./spec/model_joins_spec.rb[1:3] | passed | 0.01319 seconds |
22
+ ./spec/model_joins_spec.rb[1:4] | passed | 0.00217 seconds |
23
+ ./spec/model_select_spec.rb[1:1] | passed | 0.0007 seconds |
24
+ ./spec/model_select_spec.rb[1:2] | passed | 0.00093 seconds |
25
+ ./spec/model_select_spec.rb[1:3] | passed | 0.00091 seconds |
26
+ ./spec/model_select_spec.rb[1:4] | passed | 0.00127 seconds |
27
+ ./spec/model_subquery_spec.rb[1:1] | passed | 0.00155 seconds |
28
+ ./spec/model_update_arel_10_spec.rb[1:1] | passed | 0.00536 seconds |
29
+ ./spec/railtie_spec.rb[1:1] | passed | 0.00013 seconds |
30
+ ./spec/railtie_spec.rb[1:2] | passed | 0.00008 seconds |
31
+ ./spec/textual_order_spec.rb[1:1] | passed | 0.00108 seconds |
32
+ ./spec/textual_order_spec.rb[1:2] | passed | 0.00067 seconds |
33
+ ./spec/textual_order_spec.rb[1:3] | passed | 0.00078 seconds |
34
+ ./spec/textual_order_spec.rb[1:4] | passed | 0.00087 seconds |
35
+ ./spec/textual_order_spec.rb[1:5] | passed | 0.00069 seconds |
36
+ ./spec/textual_order_spec.rb[1:6] | passed | 0.00079 seconds |
37
+ ./spec/textual_order_spec.rb[1:7] | passed | 0.00088 seconds |
38
+ ./spec/version_spec.rb[1:1] | passed | 0.00151 seconds |
39
+ ./spec/version_spec.rb[1:2] | passed | 0.0002 seconds |
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collection_cache_versioning was added in Rails 6.0. It performs a
4
+ # SELECT COUNT(*), MAX(updated_at) which will have an unnecessary ORDER BY RANDOM()
5
+ # applied to it. That shouldn't hurt anything. This is a simple way to both
6
+ # cover that important code and ensure Unreliable works correctly with
7
+ # aggregate SELECTs.
8
+
9
+ RSpec.describe Cat do
10
+ it "in ActiveRecord >= 6, calculates cache versions",
11
+ skip: ((ActiveRecord::VERSION::MAJOR < 6) ? "test is for ActiveRecord >= 6 only" : false) do
12
+ old_setting = ActiveRecord::Base.collection_cache_versioning
13
+ ActiveRecord::Base.collection_cache_versioning = true
14
+ Cat.new(name: "spot").save!
15
+ Cat.new(name: "sadie").save!
16
+ expect(Cat.where("name LIKE 's%'").cache_version).to start_with("2-")
17
+ expect(Cat.where(name: "foo").cache_version).to eq("0")
18
+ ensure
19
+ Cat.delete_all
20
+ ActiveRecord::Base.collection_cache_versioning = old_setting
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "model_indexes_books" do
4
+ it "randomly selects from books with no order" do
5
+ expect(Book.all.to_sql).to end_with("ORDER BY RANDOM()")
6
+ end
7
+
8
+ it "randomly selects from books ordered by nonindexed column" do
9
+ expect(Book.all.order(:subject).to_sql).to end_with('ORDER BY "books"."subject" ASC, RANDOM()')
10
+ end
11
+
12
+ it "randomly selects from books ordered by unique column" do
13
+ expect(Book.all.order(:isbn).to_sql).to end_with('ORDER BY "books"."isbn" ASC, RANDOM()')
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "model_indexes_cats" do
4
+ it "randomly selects from cats" do
5
+ expect(Cat.all.to_sql).to end_with("ORDER BY RANDOM()")
6
+ end
7
+
8
+ it "nonrandomly selects from cats by implied primary key descending" do
9
+ expect(Cat.all.order(id: :desc).to_sql).to end_with('ORDER BY "cats"."id" DESC')
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "model_indexes_dreams" do
4
+ it "randomly selects from dreams ordered by nonindexed column" do
5
+ expect(Dream.all.order(:subject).to_sql).to end_with('ORDER BY "dreams"."subject" ASC, RANDOM()')
6
+ end
7
+
8
+ it "nonrandomly selects from dreams by explicit primary key" do
9
+ expect(Dream.all.order(:dream_id).to_sql).to end_with('ORDER BY "dreams"."dream_id" ASC')
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "model_indexes_shelves" do
4
+ it "randomly selects from shelves" do
5
+ expect(Shelf.all.to_sql).to end_with("ORDER BY RANDOM()")
6
+ end
7
+
8
+ it "randomly selects from some shelves" do
9
+ expect(Shelf.where(contents: "foo").to_sql).to end_with("ORDER BY RANDOM()")
10
+ end
11
+
12
+ it "randomly selects from shelves ordered by id" do
13
+ expect(Shelf.order(:shelf_id).to_sql).to end_with('ORDER BY "shelves"."shelf_id" ASC, RANDOM()')
14
+ end
15
+
16
+ it "randomly selects from shelves ordered by position" do
17
+ expect(Shelf.order(:shelf_position).to_sql).to end_with(
18
+ 'ORDER BY "shelves"."shelf_position" ASC, RANDOM()'
19
+ )
20
+ end
21
+
22
+ it "nonrandomly selects from shelves ordered by id and position" do
23
+ expect(Shelf.order(:shelf_id, :shelf_position).to_sql).to end_with(
24
+ 'ORDER BY "shelves"."shelf_id" ASC, "shelves"."shelf_position" ASC'
25
+ )
26
+ end
27
+
28
+ it "nonrandomly selects from some shelves ordered by id and position" do
29
+ expect(Shelf.where(contents: "bar").order(:shelf_id, :shelf_position).to_sql).to end_with(
30
+ 'ORDER BY "shelves"."shelf_id" ASC, "shelves"."shelf_position" ASC'
31
+ )
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # So far, unreliable does not try to know when it can omit the ORDER BY RANDOM()
4
+ # when joining tables. It's unnecessary in some of the below tests and a future
5
+ # version that's smart about joins might be able to omit it.
6
+
7
+ RSpec.describe "model_indexes_joins" do
8
+ it "randomly selects from owner has_many cats" do
9
+ expect(Owner.joins(:cats).all.to_sql).to end_with("ORDER BY RANDOM()")
10
+ end
11
+
12
+ it "randomly selects from owner has_many ordered cats" do
13
+ expect(Owner.joins(:cats).order("owners.id": :asc).all.to_sql).to end_with(", RANDOM()")
14
+ expect(Owner.joins(:cats).order(:"cats.id").all.to_sql).to end_with(", RANDOM()")
15
+ expect(Owner.joins(:cats).order(:"owners.id", "cats.id": :desc).all.to_sql).to end_with(", RANDOM()")
16
+ expect(Owner.joins(:cats).order(:"owners.id", :"cats.name").all.to_sql).to end_with(", RANDOM()")
17
+ end
18
+
19
+ it "randomly selects from dreamer has_one dream" do
20
+ expect(Dreamer.joins(:dream).all.to_sql).to end_with("ORDER BY RANDOM()")
21
+ end
22
+
23
+ it "randomly selects from dreamer has_one ordered dream" do
24
+ expect(Dreamer.joins(:dream).order("dreamers.id": :desc).all.to_sql).to end_with(", RANDOM()")
25
+ expect(Dreamer.joins(:dream).order(:"dreams.id").all.to_sql).to end_with(", RANDOM()")
26
+ end
27
+ end
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe Thing do
3
+ RSpec.describe Cat do
4
4
  it "randomly selects from all" do
5
- expect(Thing.all.to_sql).to end_with("ORDER BY RANDOM()")
5
+ expect(Cat.all.to_sql).to end_with("ORDER BY RANDOM()")
6
6
  end
7
7
 
8
8
  it "randomly selects from some" do
9
- expect(Thing.where(word: "foo").to_sql).to end_with("ORDER BY RANDOM()")
9
+ expect(Cat.where(name: "foo").to_sql).to end_with("ORDER BY RANDOM()")
10
10
  end
11
11
 
12
12
  it "adds randomness to existing order" do
13
- expect(Thing.order(:word).to_sql).to end_with('ORDER BY "things"."word" ASC, RANDOM()')
13
+ expect(Cat.order(:name).to_sql).to end_with('ORDER BY "cats"."name" ASC, RANDOM()')
14
14
  end
15
15
 
16
16
  it "respects a disable block" do
17
17
  Unreliable::Config.disable do
18
- expect(Thing.where(word: "foo").to_sql).to_not end_with("ORDER BY RANDOM()")
19
- expect(Thing.where(word: "foo").to_sql).to end_with(%q("things"."word" = 'foo'))
18
+ expect(Cat.where(name: "foo").to_sql).to_not end_with("ORDER BY RANDOM()")
19
+ expect(Cat.where(name: "foo").to_sql).to end_with(%q("cats"."name" = 'foo'))
20
20
  end
21
21
  end
22
22
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe Thing do
3
+ RSpec.describe Cat do
4
4
  it "randomly selects in main query and subquery" do
5
5
  # rubocop:disable Layout/SpaceInsideParens,Layout/DotPosition
6
- expect( Thing.where(word: Thing.where(word: "foo")).to_sql ).
7
- to end_with( %q[WHERE "things"."word" = 'foo' ORDER BY RANDOM()) ORDER BY RANDOM()] )
6
+ expect( Cat.where(name: Cat.where(name: "foo")).to_sql ).
7
+ to end_with( %q[WHERE "cats"."name" = 'foo' ORDER BY RANDOM()) ORDER BY RANDOM()] )
8
8
  # rubocop:enable Layout/SpaceInsideParens,Layout/DotPosition
9
9
  end
10
10
  end