hightop 0.2.0 → 0.2.1
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +4 -0
- data/LICENSE.txt +1 -1
- data/README.md +44 -10
- data/lib/hightop/enumerable.rb +26 -0
- data/lib/hightop/kicks.rb +29 -0
- data/lib/hightop/version.rb +1 -1
- data/lib/hightop.rb +8 -34
- metadata +18 -27
- data/.gitignore +0 -22
- data/.travis.yml +0 -11
- data/Gemfile +0 -4
- data/Rakefile +0 -8
- data/hightop.gemspec +0 -27
- data/test/hightop_test.rb +0 -117
- data/test/test_helper.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7bfda8167d2141d8bd0be9678f1f285fe8417d0deecac5a04cee44f50efcf876
|
4
|
+
data.tar.gz: 0414c92e5702679fc169dbecb03d754e79461968e97a39cd2cca15d94ec4daa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: baad6fa1e6f4f404fd2f33448a971412ea9bf50257f5dd69531e341aa0a3afb69c816ac3191a9988ed8f30b19cce2c1d0837fb077e78fe589a7975b0283ec6d7
|
7
|
+
data.tar.gz: a66db10388aff6057d63f9ae977a700c0065d222a52dd04df0e7ea4bbd87f5ee51a145ff875aea5b9042dbbbaff2a10543f612519b3c65ec525852728aef5f82
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
A nice shortcut for group count queries
|
4
4
|
|
5
|
-
[](https://travis-ci.org/ankane/hightop)
|
6
|
-
|
7
5
|
```ruby
|
8
6
|
Visit.top(:browser)
|
9
7
|
```
|
@@ -14,7 +12,25 @@ instead of
|
|
14
12
|
Visit.group(:browser).where("browser IS NOT NULL").order("count_all DESC, browser").count
|
15
13
|
```
|
16
14
|
|
17
|
-
Be sure to [sanitize user input](
|
15
|
+
Be sure to [sanitize user input](https://rails-sqli.org/) like you must with `group`
|
16
|
+
|
17
|
+
Also works with arrays and hashes
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
["up", "up", "down"].top(1)
|
21
|
+
```
|
22
|
+
|
23
|
+
[](https://travis-ci.org/ankane/hightop)
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Add this line to your application’s Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'hightop'
|
31
|
+
```
|
32
|
+
|
33
|
+
## Options
|
18
34
|
|
19
35
|
Limit the results
|
20
36
|
|
@@ -37,7 +53,7 @@ Visit.top([:city, :browser])
|
|
37
53
|
And expressions
|
38
54
|
|
39
55
|
```ruby
|
40
|
-
Visit.top("LOWER(referring_domain)")
|
56
|
+
Visit.top(Arel.sql("LOWER(referring_domain)"))
|
41
57
|
```
|
42
58
|
|
43
59
|
And distinct
|
@@ -52,18 +68,36 @@ And min count
|
|
52
68
|
Visit.top(:city, min: 10)
|
53
69
|
```
|
54
70
|
|
55
|
-
##
|
71
|
+
## Arrays and Hashes
|
56
72
|
|
57
|
-
|
73
|
+
Arrays
|
58
74
|
|
59
75
|
```ruby
|
60
|
-
|
76
|
+
["up", "up", "down"].top
|
61
77
|
```
|
62
78
|
|
63
|
-
|
79
|
+
Hashes
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
{a: "up", b: "up", c: "down"}.top { |k, v| v }
|
83
|
+
```
|
64
84
|
|
65
|
-
|
66
|
-
|
85
|
+
Limit the results
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
["up", "up", "down"].top(1)
|
89
|
+
```
|
90
|
+
|
91
|
+
Include nil values
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
[nil, nil, "down"].top(nil: true)
|
95
|
+
```
|
96
|
+
|
97
|
+
Min count
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
["up", "up", "down"].top(min: 2)
|
67
101
|
```
|
68
102
|
|
69
103
|
## History
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Enumerable
|
2
|
+
def top(*args, &block)
|
3
|
+
if block || !respond_to?(:scoping)
|
4
|
+
limit, options, _ = args
|
5
|
+
if limit.is_a?(Hash) && args.size == 1
|
6
|
+
options = limit
|
7
|
+
limit = nil
|
8
|
+
end
|
9
|
+
options ||= {}
|
10
|
+
min = options[:min]
|
11
|
+
|
12
|
+
counts = Hash.new(0)
|
13
|
+
map(&block).each do |v|
|
14
|
+
counts[v] += 1
|
15
|
+
end
|
16
|
+
counts.delete(nil) unless options[:nil]
|
17
|
+
counts.select! { |_, v| v >= min } if min
|
18
|
+
|
19
|
+
arr = counts.sort_by { |_, v| -v }
|
20
|
+
arr = arr[0...limit] if limit
|
21
|
+
Hash[arr]
|
22
|
+
else
|
23
|
+
scoping { @klass.send(:top, *args, &block) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Hightop
|
2
|
+
module Kicks
|
3
|
+
def top(column, limit = nil, distinct: nil, uniq: nil, min: nil, nil: nil)
|
4
|
+
distinct ||= uniq
|
5
|
+
order_str = column.is_a?(Array) ? column.map(&:to_s).join(", ") : column
|
6
|
+
relation = group(column).order(["count_#{distinct || 'all'} DESC", order_str])
|
7
|
+
if limit
|
8
|
+
relation = relation.limit(limit)
|
9
|
+
end
|
10
|
+
|
11
|
+
# terribly named option
|
12
|
+
unless binding.local_variable_get(:nil)
|
13
|
+
(column.is_a?(Array) ? column : [column]).each do |c|
|
14
|
+
relation = relation.where("#{c} IS NOT NULL")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if min
|
19
|
+
relation = relation.having("COUNT(#{distinct ? "DISTINCT #{distinct}" : '*'}) >= #{min.to_i}")
|
20
|
+
end
|
21
|
+
|
22
|
+
if distinct
|
23
|
+
relation.distinct.count(distinct)
|
24
|
+
else
|
25
|
+
relation.count
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/hightop/version.rb
CHANGED
data/lib/hightop.rb
CHANGED
@@ -1,37 +1,11 @@
|
|
1
|
-
|
2
|
-
require "
|
3
|
-
|
4
|
-
module Hightop
|
5
|
-
def top(column, limit = nil, distinct: nil, uniq: nil, min: nil, nil: nil)
|
6
|
-
distinct ||= uniq
|
7
|
-
order_str = column.is_a?(Array) ? column.map(&:to_s).join(", ") : column
|
8
|
-
relation = group(column).order("count_#{distinct || 'all'} DESC, #{order_str}")
|
9
|
-
if limit
|
10
|
-
relation = relation.limit(limit)
|
11
|
-
end
|
12
|
-
|
13
|
-
# terribly named option
|
14
|
-
unless binding.local_variable_get(:nil)
|
15
|
-
(column.is_a?(Array) ? column : [column]).each do |c|
|
16
|
-
relation = relation.where("#{c} IS NOT NULL")
|
17
|
-
end
|
18
|
-
end
|
1
|
+
# dependencies
|
2
|
+
require "active_support"
|
19
3
|
|
20
|
-
|
21
|
-
|
22
|
-
|
4
|
+
# modules
|
5
|
+
require "hightop/enumerable"
|
6
|
+
require "hightop/kicks"
|
7
|
+
require "hightop/version"
|
23
8
|
|
24
|
-
|
25
|
-
|
26
|
-
if ActiveRecord::VERSION::MAJOR > 3
|
27
|
-
relation.distinct.count(distinct)
|
28
|
-
else
|
29
|
-
relation.uniq.count(distinct)
|
30
|
-
end
|
31
|
-
else
|
32
|
-
relation.count
|
33
|
-
end
|
34
|
-
end
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
10
|
+
extend Hightop::Kicks
|
35
11
|
end
|
36
|
-
|
37
|
-
ActiveRecord::Base.send :extend, Hightop
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hightop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '4.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '5'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: sqlite3
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,25 +80,19 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description:
|
84
|
-
email:
|
85
|
-
- andrew@chartkick.com
|
83
|
+
description:
|
84
|
+
email: andrew@chartkick.com
|
86
85
|
executables: []
|
87
86
|
extensions: []
|
88
87
|
extra_rdoc_files: []
|
89
88
|
files:
|
90
|
-
- ".gitignore"
|
91
|
-
- ".travis.yml"
|
92
89
|
- CHANGELOG.md
|
93
|
-
- Gemfile
|
94
90
|
- LICENSE.txt
|
95
91
|
- README.md
|
96
|
-
- Rakefile
|
97
|
-
- hightop.gemspec
|
98
92
|
- lib/hightop.rb
|
93
|
+
- lib/hightop/enumerable.rb
|
94
|
+
- lib/hightop/kicks.rb
|
99
95
|
- lib/hightop/version.rb
|
100
|
-
- test/hightop_test.rb
|
101
|
-
- test/test_helper.rb
|
102
96
|
homepage: https://github.com/ankane/hightop
|
103
97
|
licenses:
|
104
98
|
- MIT
|
@@ -111,18 +105,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
105
|
requirements:
|
112
106
|
- - ">="
|
113
107
|
- !ruby/object:Gem::Version
|
114
|
-
version: '
|
108
|
+
version: '2.3'
|
115
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
110
|
requirements:
|
117
111
|
- - ">="
|
118
112
|
- !ruby/object:Gem::Version
|
119
113
|
version: '0'
|
120
114
|
requirements: []
|
121
|
-
|
122
|
-
rubygems_version: 2.6.8
|
115
|
+
rubygems_version: 3.0.4
|
123
116
|
signing_key:
|
124
117
|
specification_version: 4
|
125
118
|
summary: A nice shortcut for group count queries
|
126
|
-
test_files:
|
127
|
-
- test/hightop_test.rb
|
128
|
-
- test/test_helper.rb
|
119
|
+
test_files: []
|
data/.gitignore
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
*.gem
|
2
|
-
*.rbc
|
3
|
-
.bundle
|
4
|
-
.config
|
5
|
-
.yardoc
|
6
|
-
Gemfile.lock
|
7
|
-
InstalledFiles
|
8
|
-
_yardoc
|
9
|
-
coverage
|
10
|
-
doc/
|
11
|
-
lib/bundler/man
|
12
|
-
pkg
|
13
|
-
rdoc
|
14
|
-
spec/reports
|
15
|
-
test/tmp
|
16
|
-
test/version_tmp
|
17
|
-
tmp
|
18
|
-
*.bundle
|
19
|
-
*.so
|
20
|
-
*.o
|
21
|
-
*.a
|
22
|
-
mkmf.log
|
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/hightop.gemspec
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require "hightop/version"
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "hightop"
|
8
|
-
spec.version = Hightop::VERSION
|
9
|
-
spec.authors = ["Andrew Kane"]
|
10
|
-
spec.email = ["andrew@chartkick.com"]
|
11
|
-
spec.summary = "A nice shortcut for group count queries"
|
12
|
-
spec.description = "A nice shortcut for group count queries"
|
13
|
-
spec.homepage = "https://github.com/ankane/hightop"
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_dependency "activerecord"
|
22
|
-
|
23
|
-
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
-
spec.add_development_dependency "rake"
|
25
|
-
spec.add_development_dependency "minitest"
|
26
|
-
spec.add_development_dependency "sqlite3"
|
27
|
-
end
|
data/test/hightop_test.rb
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
require_relative "test_helper"
|
2
|
-
|
3
|
-
class TestHightop < Minitest::Test
|
4
|
-
def setup
|
5
|
-
Visit.delete_all
|
6
|
-
end
|
7
|
-
|
8
|
-
def test_top
|
9
|
-
create_city("San Francisco", 3)
|
10
|
-
create_city("Chicago", 2)
|
11
|
-
expected = {
|
12
|
-
"San Francisco" => 3,
|
13
|
-
"Chicago" => 2
|
14
|
-
}
|
15
|
-
assert_equal expected, Visit.top(:city)
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_limit
|
19
|
-
create_city("San Francisco", 3)
|
20
|
-
create_city("Chicago", 2)
|
21
|
-
create_city("Boston", 1)
|
22
|
-
expected = {
|
23
|
-
"San Francisco" => 3,
|
24
|
-
"Chicago" => 2
|
25
|
-
}
|
26
|
-
assert_equal expected, Visit.top(:city, 2)
|
27
|
-
assert_equal expected, Visit.limit(2).top(:city)
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_nil_values
|
31
|
-
create_city("San Francisco", 3)
|
32
|
-
create_city(nil, 2)
|
33
|
-
expected = {
|
34
|
-
"San Francisco" => 3
|
35
|
-
}
|
36
|
-
assert_equal expected, Visit.top(:city)
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_nil_option
|
40
|
-
create_city("San Francisco", 3)
|
41
|
-
create_city(nil, 2)
|
42
|
-
expected = {
|
43
|
-
"San Francisco" => 3,
|
44
|
-
nil => 2
|
45
|
-
}
|
46
|
-
assert_equal expected, Visit.top(:city, nil: true)
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_multiple_groups
|
50
|
-
create_city("San Francisco")
|
51
|
-
expected = {
|
52
|
-
["San Francisco", "San Francisco"] => 1
|
53
|
-
}
|
54
|
-
assert_equal expected, Visit.top([:city, :city])
|
55
|
-
end
|
56
|
-
|
57
|
-
def test_expressions
|
58
|
-
create_city("San Francisco")
|
59
|
-
expected = {
|
60
|
-
"san francisco" => 1
|
61
|
-
}
|
62
|
-
assert_equal expected, Visit.top("LOWER(city)")
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_distinct
|
66
|
-
create(city: "San Francisco", user_id: 1)
|
67
|
-
create(city: "San Francisco", user_id: 1)
|
68
|
-
expected = {
|
69
|
-
"San Francisco" => 1
|
70
|
-
}
|
71
|
-
assert_equal expected, Visit.top(:city, distinct: :user_id)
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_uniq
|
75
|
-
create(city: "San Francisco", user_id: 1)
|
76
|
-
create(city: "San Francisco", user_id: 1)
|
77
|
-
expected = {
|
78
|
-
"San Francisco" => 1
|
79
|
-
}
|
80
|
-
assert_equal expected, Visit.top(:city, uniq: :user_id)
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_min
|
84
|
-
create_city("San Francisco", 3)
|
85
|
-
create_city("Chicago", 2)
|
86
|
-
expected = {
|
87
|
-
"San Francisco" => 3
|
88
|
-
}
|
89
|
-
assert_equal expected, Visit.top(:city, min: 3)
|
90
|
-
end
|
91
|
-
|
92
|
-
def test_min_distinct
|
93
|
-
create(city: "San Francisco", user_id: 1)
|
94
|
-
create(city: "San Francisco", user_id: 1)
|
95
|
-
create(city: "San Francisco", user_id: 2)
|
96
|
-
create(city: "Chicago", user_id: 1)
|
97
|
-
create(city: "Chicago", user_id: 1)
|
98
|
-
expected = {
|
99
|
-
"San Francisco" => 2
|
100
|
-
}
|
101
|
-
assert_equal expected, Visit.top(:city, min: 2, distinct: :user_id)
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_bad_argument
|
105
|
-
assert_raises(ArgumentError) do
|
106
|
-
Visit.top(:city, boom: true)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def create_city(city, count = 1)
|
111
|
-
create({city: city}, count)
|
112
|
-
end
|
113
|
-
|
114
|
-
def create(attributes, count = 1)
|
115
|
-
count.times { Visit.create!(attributes) }
|
116
|
-
end
|
117
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require "bundler/setup"
|
2
|
-
Bundler.require(:default)
|
3
|
-
require "minitest/autorun"
|
4
|
-
require "minitest/pride"
|
5
|
-
require "logger"
|
6
|
-
|
7
|
-
Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
|
8
|
-
|
9
|
-
# for debugging
|
10
|
-
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
11
|
-
|
12
|
-
# migrations
|
13
|
-
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
14
|
-
|
15
|
-
ActiveRecord::Migration.create_table :visits do |t|
|
16
|
-
t.string :city
|
17
|
-
t.string :user_id
|
18
|
-
end
|
19
|
-
|
20
|
-
class Visit < ActiveRecord::Base
|
21
|
-
end
|