unreliable 0.1.3 → 0.9.1

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: '0081b3f189f7e459a112514a5c35d69bd903fbdc771b6eccd22bb53add828671'
4
- data.tar.gz: 0a5c663105b2f774cb64eb464340e36d8c63d393234e0cb855955487800d501f
3
+ metadata.gz: 9b11225a3c1dad62d2a283c3eb36fef7b69fda61815df13a860ce9c1970016eb
4
+ data.tar.gz: 1994dd9ea21ba01dc839c56cc08ca91742465dc9b850c0b6a5eebbc81c7558a2
5
5
  SHA512:
6
- metadata.gz: 798bef481aad072fa47730866506a6e040d5b99dea6b71f0af5ca86921380f42713b98182c813f7da2a9a152215e9a2a35a730923f10e3e35c389a5ffa78dd68
7
- data.tar.gz: 41d0546cf89f4bb0a2c66e98042fb6f27b4a57391ef7e0e25392a1b0a6eaa14064c6f9bd6e12915a05f8425656a62f794d48ab2ab45f08045b2ad3bfa4e79794
6
+ metadata.gz: 145d5bbe8790fe4ee33bf4f0ea874350edf9655c2ebed35bfb3f18acb123d03efbe86c826dd0d9d4ca7ff12938c368e5cc4ebdc7afba224e0a8c12669a92e816
7
+ data.tar.gz: 2a3ca2dfa53cbf67a0f8a6b7128df829874409a698586c15d10746624a71a18eddc77914768ec5da361541aa54706bd1899f473aa1f6093ec839f689428b36ed
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## Unreliable 0.9.1 (November 21, 2022) ##
2
+
3
+ ### Changed
4
+
5
+ * Improve efficiency by using ActiveRecord's SchemaCache.
6
+
7
+ ## Unreliable 0.9.0 (November 20, 2022) ##
8
+
9
+ ### Changed
10
+
11
+ * Minor README, gemspec, and rubocop changes.
12
+ * More tests, including queries generated by ActiveRecord associations.
13
+ * Don't randomize order from ar\_internal\_metadata.
14
+ * 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.
15
+
1
16
  ## Unreliable 0.1.3 (August 21, 2022) ##
2
17
 
3
18
  * README and minor gemspec changes.
data/README.md CHANGED
@@ -81,7 +81,7 @@ The problem in this example is easy to see because many books are published each
81
81
 
82
82
  `unreliable` is tested to support Ruby 2.6 through 3.1, and Rails 5.0 through 7.0.
83
83
 
84
- As of August 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.
85
85
 
86
86
  `unreliable` depends only on ActiveRecord and Railties. If you have a non-Rails app that uses ActiveRecord, you can still use it.
87
87
 
@@ -89,7 +89,7 @@ As of August 2022, this is all released versions of both that are currently supp
89
89
 
90
90
  `unreliable` does exactly nothing outside of test environments. There is intentionally no way to enable `unreliable` in production, and there never will be.
91
91
 
92
- In a Rails test environment, `unreliable` patches ActiveRecord to always append a final `ORDER BY` clause that returns results in a random order.
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.
93
93
 
94
94
  Because it's appended, the existing ordering is not affected unless it is ambiguous.
95
95
 
@@ -101,6 +101,10 @@ This means that the `ORDER BY` applies to not just `SELECT` but e.g. `delete_all
101
101
 
102
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.
103
103
 
104
+ ### No dual-purpose environment please
105
+
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).
107
+
104
108
  ## Contributing
105
109
 
106
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.
@@ -131,7 +135,7 @@ Appraisal ensures the tests run against every compatible minor version of Active
131
135
 
132
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.
133
137
 
134
- 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.
135
139
 
136
140
  ### Experiment
137
141
 
@@ -165,7 +169,7 @@ But there are other ways you can order a relation but still have your query be a
165
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)
166
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
167
171
 
168
- `unreliable` correctly tests these because the random order is always appended.
172
+ `unreliable` ensures correct testing because it appends a random order to each of these cases.
169
173
 
170
174
  ## References
171
175
 
@@ -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,40 @@ 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_keys returns a String if it's one column, an Array if two or more.
51
+ # Using the SchemaCache minimizes the number of times we have to, e.g. in MySQL,
52
+ # SELECT column_name FROM information_schema.statistics
53
+ # (or in Rails < 6, SELECT column_name FROM information_schema.key_column_usage)
54
+ [ActiveRecord::Base.connection.schema_cache.primary_keys(arel.froms.first.name)].flatten
55
+ end
56
+
57
+ def order_columns(arel)
58
+ from_table_name = arel.froms.first.name
59
+ arel.orders
60
+ .select { |order| order.is_a? Arel::Nodes::Ordering } # Don't try to parse textual orders
61
+ .map(&:expr)
62
+ .select { |expr| expr.relation.name == from_table_name }
63
+ .map(&:name)
64
+ .map(&:to_s) # In Rails < 5.2, the order column names are symbols; >= 5.2, strings
65
+ end
29
66
  end
30
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unreliable
4
- VERSION = "0.1.3"
4
+ VERSION = "0.9.1"
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.00261 seconds |
4
- ./spec/env_spec.rb[1:2] | passed | 0.00042 seconds |
5
- ./spec/model_select_spec.rb[1:1] | passed | 0.00033 seconds |
6
- ./spec/model_select_spec.rb[1:2] | passed | 0.0005 seconds |
7
- ./spec/model_select_spec.rb[1:3] | passed | 0.00042 seconds |
8
- ./spec/model_select_spec.rb[1:4] | passed | 0.00064 seconds |
9
- ./spec/model_subquery_spec.rb[1:1] | passed | 0.00135 seconds |
10
- ./spec/model_update_spec.rb[1:1] | passed | 0.00011 seconds |
11
- ./spec/railtie_spec.rb[1:1] | passed | 0.00064 seconds |
12
- ./spec/railtie_spec.rb[1:2] | passed | 0.00014 seconds |
13
- ./spec/version_spec.rb[1:1] | passed | 0.00116 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
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveRecord's update_all invokes compile_update for the Arel::SelectManager returned
4
+ # by build_arel. It returns an Arel::UpdateManager. This makes sure that internal call
5
+ # assembles the update query correctly.
6
+
7
+ module Unreliable
8
+ class SqlTestingData
9
+ class_attribute :update_manager_sql
10
+ end
11
+ end
12
+
13
+ RSpec.describe "update_manager" do
14
+ it "in ActiveRecord >= 7, updates by subquery with select in random order",
15
+ skip: ((ActiveRecord::VERSION::MAJOR < 7) ? "test is for ActiveRecord >= 7 only" : false) do
16
+ module Arel
17
+ class SelectManager
18
+ def testing_compile_update(*args)
19
+ um = old_compile_update(*args)
20
+ Unreliable::SqlTestingData.update_manager_sql = um.to_sql
21
+ um
22
+ end
23
+ alias_method :old_compile_update, :compile_update
24
+ alias_method :compile_update, :testing_compile_update
25
+ end
26
+ end
27
+ Cat.update_all(name: "bar")
28
+ expect(Unreliable::SqlTestingData.update_manager_sql).to end_with("ORDER BY RANDOM())")
29
+ Cat.where(name: "foo").update_all(name: "bar")
30
+ expect(Unreliable::SqlTestingData.update_manager_sql).to end_with("ORDER BY RANDOM())")
31
+ Cat.where(name: "bar").order(:id).update_all(name: "baz")
32
+ expect(Unreliable::SqlTestingData.update_manager_sql).to end_with("ORDER BY \"cats\".\"id\" ASC)")
33
+ ensure
34
+ module Arel
35
+ class SelectManager
36
+ alias_method :compile_update, :old_compile_update
37
+ end
38
+ end
39
+ end
40
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,13 @@ require "bundler"
4
4
 
5
5
  Bundler.require :default, :development
6
6
 
7
+ if ActiveRecord.gem_version >= Gem::Version.new("5.2") && ActiveRecord.gem_version < Gem::Version.new("6.0")
8
+ # This setting was introduced in Rails 5.2, made the default in Rails 6.0, and
9
+ # removed in Rails 6.1.
10
+ require "active_record/connection_adapters/sqlite3_adapter"
11
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
12
+ end
13
+
7
14
  Combustion.initialize! :active_record
8
15
 
9
16
  RSpec.configure do |config|
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "textual order" do
4
+ it "randomly selects from shelves ordered by textual id asc" do
5
+ expect(Shelf.order("shelf_id ASC").to_sql).to end_with("ORDER BY shelf_id ASC, RANDOM()")
6
+ end
7
+
8
+ it "randomly selects from shelves ordered by fully qualified textual id asc" do
9
+ expect(Shelf.order('"shelves"."shelf_id"').to_sql).to end_with('ORDER BY "shelves"."shelf_id", RANDOM()')
10
+ end
11
+
12
+ it "randomly selects from shelves ordered by textual position asc" do
13
+ expect(Shelf.order("shelf_position ASC").to_sql).to end_with("ORDER BY shelf_position ASC, RANDOM()")
14
+ end
15
+
16
+ it "randomly selects from shelves ordered by textual id desc" do
17
+ expect(Shelf.order("shelf_id DESC").to_sql).to end_with("ORDER BY shelf_id DESC, RANDOM()")
18
+ end
19
+
20
+ it "randomly selects from shelves ordered by textual position desc" do
21
+ expect(Shelf.order("shelf_position DESC").to_sql).to end_with("ORDER BY shelf_position DESC, RANDOM()")
22
+ end
23
+
24
+ it "randomly selects from shelves ordered by textual id and position asc" do
25
+ expect(Shelf.order("shelf_id ASC, shelf_position ASC").to_sql).to end_with(
26
+ "ORDER BY shelf_id ASC, shelf_position ASC, RANDOM()"
27
+ )
28
+ end
29
+
30
+ it "randomly selects from shelves ordered by textual id and position desc" do
31
+ expect(Shelf.order("shelf_id DESC, shelf_position DESC").to_sql).to end_with(
32
+ "ORDER BY shelf_id DESC, shelf_position DESC, RANDOM()"
33
+ )
34
+ end
35
+ end
data/spec/version_spec.rb CHANGED
@@ -8,6 +8,6 @@ RSpec.describe Unreliable, "version" do
8
8
  end
9
9
 
10
10
  it "is correct" do
11
- expect(Unreliable::VERSION).to start_with "0.1."
11
+ expect(Unreliable::VERSION).to start_with "0."
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unreliable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James McCarthy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-21 00:00:00.000000000 Z
11
+ date: 2022-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -140,14 +140,14 @@ dependencies:
140
140
  requirements:
141
141
  - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: '1.11'
143
+ version: '1.17'
144
144
  type: :development
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '1.11'
150
+ version: '1.17'
151
151
  description: |
152
152
  Unreliable helps uncover bugs in Rails apps that rely on ambiguous database ordering.
153
153
  Installing it makes both your app and your test suite more robust.
@@ -169,11 +169,18 @@ files:
169
169
  - lib/unreliable/version.rb
170
170
  - spec/env_spec.rb
171
171
  - spec/examples.txt
172
+ - spec/model_cache_versioning_spec.rb
173
+ - spec/model_indexes_books_spec.rb
174
+ - spec/model_indexes_cats_spec.rb
175
+ - spec/model_indexes_dreams_spec.rb
176
+ - spec/model_indexes_shelves_spec.rb
177
+ - spec/model_joins_spec.rb
172
178
  - spec/model_select_spec.rb
173
179
  - spec/model_subquery_spec.rb
174
- - spec/model_update_spec.rb
180
+ - spec/model_update_arel_10_spec.rb
175
181
  - spec/railtie_spec.rb
176
182
  - spec/spec_helper.rb
183
+ - spec/textual_order_spec.rb
177
184
  - spec/version_spec.rb
178
185
  homepage: https://github.com/jamiemccarthy/unreliable
179
186
  licenses:
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Thing do
4
- it "updates in random order tktk" do
5
- # TODO
6
- end
7
- end