estimate_count 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +24 -14
- data/README.md +12 -10
- data/docker-compose.yml +27 -0
- data/lib/estimate_count/version.rb +1 -1
- data/lib/estimate_count.rb +19 -1
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7aea186e3b48086345021244c6ec7fb24e5fba0d2eb464e0ef5858c6843539dd
|
4
|
+
data.tar.gz: 4e3988b31ecceec9a39141e2771745bc9bfda212521c3f92faebb7ae680d8898
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9f4074b66c6a2e2cb42b2816617f4af9ded4b18500c9dc4fe92e484f2f50a9d9692790539712d021db62725428b5009bf06d1cb5dfff8e6a737ca6f5985f952
|
7
|
+
data.tar.gz: dd8ac6e023b16cd382b167703352ff7b254dbc3af06d89a034694aa0ff33b5375b03879ab4c3e548b3f8f46ae94cd87bb6c371d46e6b9f8f539a52da1b8e0051
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,29 +1,34 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
estimate_count (0.
|
4
|
+
estimate_count (0.3.0)
|
5
5
|
activerecord
|
6
|
-
pg
|
7
6
|
|
8
7
|
GEM
|
9
8
|
remote: https://rubygems.org/
|
10
9
|
specs:
|
11
|
-
activemodel (7.0.3
|
12
|
-
activesupport (= 7.0.3
|
13
|
-
activerecord (7.0.3
|
14
|
-
activemodel (= 7.0.3
|
15
|
-
activesupport (= 7.0.3
|
16
|
-
activesupport (7.0.3
|
10
|
+
activemodel (7.0.4.3)
|
11
|
+
activesupport (= 7.0.4.3)
|
12
|
+
activerecord (7.0.4.3)
|
13
|
+
activemodel (= 7.0.4.3)
|
14
|
+
activesupport (= 7.0.4.3)
|
15
|
+
activesupport (7.0.4.3)
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
17
|
i18n (>= 1.6, < 2)
|
19
18
|
minitest (>= 5.1)
|
20
19
|
tzinfo (~> 2.0)
|
21
|
-
|
20
|
+
coderay (1.1.3)
|
21
|
+
concurrent-ruby (1.2.2)
|
22
22
|
diff-lcs (1.5.0)
|
23
|
-
i18n (1.
|
23
|
+
i18n (1.13.0)
|
24
24
|
concurrent-ruby (~> 1.0)
|
25
|
-
|
25
|
+
method_source (1.0.0)
|
26
|
+
minitest (5.18.0)
|
27
|
+
mysql2 (0.5.5)
|
26
28
|
pg (1.4.1)
|
29
|
+
pry (0.14.2)
|
30
|
+
coderay (~> 1.1)
|
31
|
+
method_source (~> 1.0)
|
27
32
|
rake (13.0.6)
|
28
33
|
rspec (3.11.0)
|
29
34
|
rspec-core (~> 3.11.0)
|
@@ -38,17 +43,22 @@ GEM
|
|
38
43
|
diff-lcs (>= 1.2.0, < 2.0)
|
39
44
|
rspec-support (~> 3.11.0)
|
40
45
|
rspec-support (3.11.0)
|
41
|
-
tzinfo (2.0.
|
46
|
+
tzinfo (2.0.6)
|
42
47
|
concurrent-ruby (~> 1.0)
|
43
48
|
|
44
49
|
PLATFORMS
|
50
|
+
arm64-darwin-21
|
45
51
|
arm64-darwin-22
|
52
|
+
ruby
|
53
|
+
x86_64-linux
|
46
54
|
|
47
55
|
DEPENDENCIES
|
48
|
-
activerecord
|
49
56
|
estimate_count!
|
57
|
+
mysql2
|
58
|
+
pg
|
59
|
+
pry
|
50
60
|
rake (~> 13.0)
|
51
61
|
rspec (~> 3.0)
|
52
62
|
|
53
63
|
BUNDLED WITH
|
54
|
-
2.3.
|
64
|
+
2.3.10
|
data/README.md
CHANGED
@@ -2,11 +2,17 @@
|
|
2
2
|
|
3
3
|
This gems help with a common pagination problem in which the calculation of total number of pages takes too long.
|
4
4
|
|
5
|
-
Currently only PostgreSQL
|
5
|
+
Currently only PostgreSQL and MySQL are supported.
|
6
6
|
|
7
7
|
## Problem
|
8
8
|
|
9
|
-
Let's say you have a table with 1 million records and you want to paginate it. You add filters and sorting.
|
9
|
+
Let's say you have a table with 1 million records and you want to paginate it. You add filters and sorting.
|
10
|
+
|
11
|
+
Suddenly your performance drops even though you're only displaying a few records per page.
|
12
|
+
|
13
|
+
The problematic part is `#count`, which causes the entire scope to be calculated and then counted. This is slow. However you can use table statistics to estimate the number of records in the table (same as `rows` in `EXPLAIN`). This is much faster.
|
14
|
+
|
15
|
+
Be aware though that this rely on table statistics being refreshed from time to time.
|
10
16
|
|
11
17
|
## Example
|
12
18
|
Given the following code:
|
@@ -31,17 +37,13 @@ In a view:
|
|
31
37
|
# app/views/users/index.html.erb
|
32
38
|
Total pages - <%= @users.total_pages %>
|
33
39
|
```
|
34
|
-
|
40
|
+
If you want to use estimate number of pages change the above line to:
|
35
41
|
|
36
42
|
```ruby
|
37
43
|
# app/views/users/index.html.erb
|
38
|
-
Total pages - About <%= (@users.estimate_count / @users.per_page).ceil
|
44
|
+
Total pages - About <%= (@users.estimate_count / @users.per_page).ceil %>
|
39
45
|
```
|
40
46
|
|
41
|
-
This extracts the estimation from PostgreSQL statistics and uses it to calculate the total number of pages. This is much faster than the `count` method.
|
42
|
-
|
43
|
-
You can use it with any pagination library (or without one).
|
44
|
-
|
45
47
|
## Installation
|
46
48
|
|
47
49
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -56,7 +58,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
56
58
|
|
57
59
|
This gem adds a method `#estimate_count` to the `ActiveRecord::Relation` class.
|
58
60
|
|
59
|
-
You can use it for any scope
|
61
|
+
You can use it for any scope:
|
60
62
|
|
61
63
|
```ruby
|
62
64
|
User.active.estimate_count
|
@@ -73,7 +75,7 @@ User.active.estimate_count(threshold: 1000)
|
|
73
75
|
|
74
76
|
## Development
|
75
77
|
|
76
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `
|
78
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
77
79
|
|
78
80
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
79
81
|
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
version: "3"
|
2
|
+
|
3
|
+
services:
|
4
|
+
mysql:
|
5
|
+
image: mysql:8.0
|
6
|
+
environment:
|
7
|
+
MYSQL_ROOT_PASSWORD: root
|
8
|
+
MYSQL_DATABASE: test
|
9
|
+
MYSQL_USER: test
|
10
|
+
MYSQL_PASSWORD: test
|
11
|
+
ports:
|
12
|
+
- 3306:3306
|
13
|
+
volumes:
|
14
|
+
- ./db/mysql:/var/lib/mysql
|
15
|
+
healthcheck:
|
16
|
+
test: [ "CMD", "mysql", "-u", "root", "-e", "USE test;" ]
|
17
|
+
retries: 1
|
18
|
+
postgresql:
|
19
|
+
image: postgres:15-alpine
|
20
|
+
environment:
|
21
|
+
POSTGRES_USER: test
|
22
|
+
POSTGRES_PASSWORD: test
|
23
|
+
POSTGRES_DB: test
|
24
|
+
ports:
|
25
|
+
- 5432:5432
|
26
|
+
volumes:
|
27
|
+
- ./db/postgresql:/var/lib/postgresql/data
|
data/lib/estimate_count.rb
CHANGED
@@ -1,18 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "estimate_count/version"
|
4
|
+
require "active_record"
|
4
5
|
|
5
6
|
module ActiveRecord
|
6
7
|
class Base
|
7
8
|
def self.estimate_count(threshold: 1000)
|
8
|
-
full_scope = current_scope
|
9
|
+
full_scope = current_scope&.limit(nil)&.offset(nil) || all
|
9
10
|
rows = estimate_rows(full_scope.to_sql)
|
10
11
|
rows = full_scope.count if threshold.is_a?(Integer) && rows < threshold
|
11
12
|
rows
|
12
13
|
end
|
13
14
|
|
14
15
|
private_class_method def self.estimate_rows(query)
|
16
|
+
case connection.adapter_name
|
17
|
+
when "PostgreSQL"
|
18
|
+
estimate_rows_postgresql(query)
|
19
|
+
when "MySQL", "Mysql2"
|
20
|
+
estimate_rows_mysql(query)
|
21
|
+
else
|
22
|
+
raise "Unsupported database"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private_class_method def self.estimate_rows_postgresql(query)
|
15
27
|
connection.execute("EXPLAIN #{query}").to_a.first["QUERY PLAN"].match(/rows=(\d+)/)[1].to_i
|
16
28
|
end
|
29
|
+
|
30
|
+
private_class_method def self.estimate_rows_mysql(query)
|
31
|
+
result = connection.execute("EXPLAIN FORMAT=TRADITIONAL #{query}")
|
32
|
+
index = result.fields.index("rows")
|
33
|
+
result.first[index].to_i
|
34
|
+
end
|
17
35
|
end
|
18
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: estimate_count
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hasiński
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -31,7 +31,35 @@ dependencies:
|
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
|
-
type: :
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mysql2
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
35
63
|
prerelease: false
|
36
64
|
version_requirements: !ruby/object:Gem::Requirement
|
37
65
|
requirements:
|
@@ -52,6 +80,7 @@ files:
|
|
52
80
|
- LICENSE.txt
|
53
81
|
- README.md
|
54
82
|
- Rakefile
|
83
|
+
- docker-compose.yml
|
55
84
|
- lib/estimate_count.rb
|
56
85
|
- lib/estimate_count/version.rb
|
57
86
|
- sig/estimate_count.rbs
|
@@ -77,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
106
|
- !ruby/object:Gem::Version
|
78
107
|
version: '0'
|
79
108
|
requirements: []
|
80
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.2.32
|
81
110
|
signing_key:
|
82
111
|
specification_version: 4
|
83
112
|
summary: Adds a method to get an estimate count for an ActiveRecord::Relation
|