hightop 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -11
- data/LICENSE.txt +1 -1
- data/README.md +28 -14
- data/lib/hightop.rb +7 -1
- data/lib/hightop/enumerable.rb +8 -9
- data/lib/hightop/kicks.rb +15 -6
- data/lib/hightop/mongoid.rb +52 -0
- data/lib/hightop/utils.rb +25 -0
- data/lib/hightop/version.rb +1 -1
- metadata +13 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee918ba56e73e37d1c623e99f4bf2971f1f99a82a369582eaf78144951dab192
|
4
|
+
data.tar.gz: c626b6953aae5d3e43e00291b5fba24d8326d133f5aba945ab58f59f4defcb65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e82a5854442ce691bc664cf6bb32aa1db21ec5e527fbc4c2ff9ddca49c8b278b8edc454cb0178ddc147ab47db324b091a65840783c9912a3051c16185bf45be3
|
7
|
+
data.tar.gz: '0978f96d0c782e7e6cb9221c11ea01f1ec55ebc1e287fec595416b587f64d30dbe8dcda0859a18d81445ea5900b21c3acf1838cb9c29592bfa76a192f7bd76d2'
|
data/CHANGELOG.md
CHANGED
@@ -1,45 +1,66 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.3.0 (2021-08-12)
|
2
|
+
|
3
|
+
- Raise `ActiveRecord::UnknownAttributeReference` for non-attribute arguments
|
4
|
+
- Raise `ArgumentError` for too many arguments with enumerable
|
5
|
+
- Removed `uniq` option (use `distinct` instead)
|
6
|
+
- Dropped support for Active Record < 5.2 and Ruby < 2.6
|
7
|
+
|
8
|
+
## 0.2.4 (2020-09-07)
|
9
|
+
|
10
|
+
- Added warning for non-attribute argument
|
11
|
+
- Added deprecation warning for `uniq`
|
12
|
+
|
13
|
+
## 0.2.3 (2020-06-18)
|
14
|
+
|
15
|
+
- Dropped support for Active Record 4.2 and Ruby 2.3
|
16
|
+
- Fixed deprecation warning in Ruby 2.7
|
17
|
+
|
18
|
+
## 0.2.2 (2019-08-12)
|
19
|
+
|
20
|
+
- Added support for Mongoid
|
21
|
+
|
22
|
+
## 0.2.1 (2019-08-04)
|
2
23
|
|
3
24
|
- Added support for arrays and hashes
|
4
25
|
|
5
|
-
## 0.2.0
|
26
|
+
## 0.2.0 (2017-03-19)
|
6
27
|
|
7
28
|
- Use keyword arguments
|
8
29
|
|
9
|
-
## 0.1.4
|
30
|
+
## 0.1.4 (2016-02-04)
|
10
31
|
|
11
32
|
- Added `distinct` option to replace `uniq`
|
12
33
|
|
13
|
-
## 0.1.3
|
34
|
+
## 0.1.3 (2015-06-18)
|
14
35
|
|
15
36
|
- Fixed `min` option with `uniq`
|
16
37
|
|
17
|
-
## 0.1.2
|
38
|
+
## 0.1.2 (2014-11-05)
|
18
39
|
|
19
40
|
- Added `min` option
|
20
41
|
|
21
|
-
## 0.1.1
|
42
|
+
## 0.1.1 (2014-07-02)
|
22
43
|
|
23
44
|
- Added `uniq` option
|
24
45
|
- Fixed `Model.limit(n).top`
|
25
46
|
|
26
|
-
## 0.1.0
|
47
|
+
## 0.1.0 (2014-06-11)
|
27
48
|
|
28
49
|
- No changes, just bump
|
29
50
|
|
30
|
-
## 0.0.4
|
51
|
+
## 0.0.4 (2014-06-11)
|
31
52
|
|
32
53
|
- Added support for multiple groups
|
33
54
|
- Added `nil` option
|
34
55
|
|
35
|
-
## 0.0.3
|
56
|
+
## 0.0.3 (2014-06-11)
|
36
57
|
|
37
58
|
- Fixed escaping
|
38
59
|
|
39
|
-
## 0.0.2
|
60
|
+
## 0.0.2 (2014-05-29)
|
40
61
|
|
41
62
|
- Added `limit` parameter
|
42
63
|
|
43
|
-
## 0.0.1
|
64
|
+
## 0.0.1 (2014-05-11)
|
44
65
|
|
45
66
|
- First release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -4,23 +4,16 @@ A nice shortcut for group count queries
|
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
Visit.top(:browser)
|
7
|
+
# {
|
8
|
+
# "Chrome" => 63,
|
9
|
+
# "Safari" => 50,
|
10
|
+
# "Firefox" => 34
|
11
|
+
# }
|
7
12
|
```
|
8
13
|
|
9
|
-
|
14
|
+
Works with Active Record, Mongoid, arrays and hashes
|
10
15
|
|
11
|
-
|
12
|
-
Visit.group(:browser).where("browser IS NOT NULL").order("count_all DESC, browser").count
|
13
|
-
```
|
14
|
-
|
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)
|
16
|
+
[](https://github.com/ankane/hightop/actions)
|
24
17
|
|
25
18
|
## Installation
|
26
19
|
|
@@ -100,6 +93,18 @@ Min count
|
|
100
93
|
["up", "up", "down"].top(min: 2)
|
101
94
|
```
|
102
95
|
|
96
|
+
## Upgrading
|
97
|
+
|
98
|
+
### 0.3.0
|
99
|
+
|
100
|
+
Hightop 0.3.0 protects against unsafe input by default. For non-attribute arguments, use:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Visit.top(Arel.sql(known_safe_value))
|
104
|
+
```
|
105
|
+
|
106
|
+
Also, the `uniq` option has been removed. Use `distinct` instead.
|
107
|
+
|
103
108
|
## History
|
104
109
|
|
105
110
|
View the [changelog](https://github.com/ankane/hightop/blob/master/CHANGELOG.md)
|
@@ -112,3 +117,12 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
112
117
|
- Fix bugs and [submit pull requests](https://github.com/ankane/hightop/pulls)
|
113
118
|
- Write, clarify, or fix documentation
|
114
119
|
- Suggest or add new features
|
120
|
+
|
121
|
+
To get started with development:
|
122
|
+
|
123
|
+
```sh
|
124
|
+
git clone https://github.com/ankane/hightop.git
|
125
|
+
cd hightop
|
126
|
+
bundle install
|
127
|
+
bundle exec rake test
|
128
|
+
```
|
data/lib/hightop.rb
CHANGED
@@ -3,9 +3,15 @@ require "active_support"
|
|
3
3
|
|
4
4
|
# modules
|
5
5
|
require "hightop/enumerable"
|
6
|
-
require "hightop/kicks"
|
7
6
|
require "hightop/version"
|
8
7
|
|
9
8
|
ActiveSupport.on_load(:active_record) do
|
9
|
+
require "hightop/utils"
|
10
|
+
require "hightop/kicks"
|
10
11
|
extend Hightop::Kicks
|
11
12
|
end
|
13
|
+
|
14
|
+
ActiveSupport.on_load(:mongoid) do
|
15
|
+
require "hightop/mongoid"
|
16
|
+
Mongoid::Document::ClassMethods.include(Hightop::Mongoid)
|
17
|
+
end
|
data/lib/hightop/enumerable.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
module Enumerable
|
2
|
-
def top(*args, &block)
|
3
|
-
if block || !respond_to?(:scoping)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
limit = nil
|
8
|
-
end
|
9
|
-
options ||= {}
|
2
|
+
def top(*args, **options, &block)
|
3
|
+
if block || !(respond_to?(:scoping) || respond_to?(:with_scope))
|
4
|
+
raise ArgumentError, "wrong number of arguments (given 2, expected 0..1)" if args.size > 1
|
5
|
+
|
6
|
+
limit = args[0]
|
10
7
|
min = options[:min]
|
11
8
|
|
12
9
|
counts = Hash.new(0)
|
@@ -19,8 +16,10 @@ module Enumerable
|
|
19
16
|
arr = counts.sort_by { |_, v| -v }
|
20
17
|
arr = arr[0...limit] if limit
|
21
18
|
Hash[arr]
|
19
|
+
elsif respond_to?(:scoping)
|
20
|
+
scoping { @klass.send(:top, *args, **options, &block) }
|
22
21
|
else
|
23
|
-
|
22
|
+
with_scope(self) { klass.send(:top, *args, **options, &block) }
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
data/lib/hightop/kicks.rb
CHANGED
@@ -1,22 +1,31 @@
|
|
1
1
|
module Hightop
|
2
2
|
module Kicks
|
3
|
-
def top(column, limit = nil, distinct: nil,
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
def top(column, limit = nil, distinct: nil, min: nil, nil: nil)
|
4
|
+
columns = column.is_a?(Array) ? column : [column]
|
5
|
+
columns = columns.map { |c| Utils.validate_column(c) }
|
6
|
+
|
7
|
+
distinct = Utils.validate_column(distinct) if distinct
|
8
|
+
|
9
|
+
relation = group(*columns).order("1 DESC", *columns)
|
7
10
|
if limit
|
8
11
|
relation = relation.limit(limit)
|
9
12
|
end
|
10
13
|
|
11
14
|
# terribly named option
|
12
15
|
unless binding.local_variable_get(:nil)
|
13
|
-
|
16
|
+
columns.each do |c|
|
17
|
+
c = Utils.resolve_column(self, c)
|
14
18
|
relation = relation.where("#{c} IS NOT NULL")
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
22
|
if min
|
19
|
-
|
23
|
+
if distinct
|
24
|
+
d = Utils.resolve_column(self, distinct)
|
25
|
+
relation = relation.having("COUNT(DISTINCT #{d}) >= #{min.to_i}")
|
26
|
+
else
|
27
|
+
relation = relation.having("COUNT(*) >= #{min.to_i}")
|
28
|
+
end
|
20
29
|
end
|
21
30
|
|
22
31
|
if distinct
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Hightop
|
2
|
+
module Mongoid
|
3
|
+
# super helpful article
|
4
|
+
# https://maximomussini.com/posts/mongoid-aggregation-dsl/
|
5
|
+
def top(column, limit = nil, distinct: nil, min: nil, nil: nil)
|
6
|
+
columns = column.is_a?(Array) ? column : [column]
|
7
|
+
|
8
|
+
relation = all
|
9
|
+
|
10
|
+
# terribly named option
|
11
|
+
unless binding.local_variable_get(:nil)
|
12
|
+
columns.each do |c|
|
13
|
+
relation = relation.and(c.ne => nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ids = {}
|
18
|
+
columns.each_with_index do |c, i|
|
19
|
+
ids["c#{i}"] = "$#{c}"
|
20
|
+
end
|
21
|
+
|
22
|
+
if distinct
|
23
|
+
# group with distinct column first, then group without it
|
24
|
+
# https://stackoverflow.com/questions/24761266/select-group-by-count-and-distinct-count-in-same-mongodb-query/24770233#24770233
|
25
|
+
distinct_ids = ids.merge("c#{ids.size}" => "$#{distinct}")
|
26
|
+
relation = relation.group(_id: distinct_ids, count: {"$sum" => 1})
|
27
|
+
ids.each_key do |k|
|
28
|
+
ids[k] = "$_id.#{k}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
relation = relation.group(_id: ids, count: {"$sum" => 1})
|
33
|
+
|
34
|
+
if min
|
35
|
+
relation.pipeline.push("$match" => {"count" => {"$gte" => min}})
|
36
|
+
end
|
37
|
+
|
38
|
+
relation = relation.desc(:count)
|
39
|
+
if limit
|
40
|
+
relation = relation.limit(limit)
|
41
|
+
end
|
42
|
+
|
43
|
+
result = {}
|
44
|
+
collection.aggregate(relation.pipeline).each do |doc|
|
45
|
+
key = doc["_id"].values
|
46
|
+
key = key[0] if key.size == 1
|
47
|
+
result[key] = doc["count"]
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hightop
|
2
|
+
module Utils
|
3
|
+
class << self
|
4
|
+
# basic version of Active Record disallow_raw_sql!
|
5
|
+
# symbol = column (safe), Arel node = SQL (safe), other = untrusted
|
6
|
+
# matches table.column and column
|
7
|
+
def validate_column(column)
|
8
|
+
unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
|
9
|
+
column = column.to_s
|
10
|
+
unless /\A\w+(\.\w+)?\z/i.match(column)
|
11
|
+
raise ActiveRecord::UnknownAttributeReference, "Query method called with non-attribute argument(s): #{column.inspect}. Use Arel.sql() for known-safe values."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
column
|
15
|
+
end
|
16
|
+
|
17
|
+
# resolves eagerly
|
18
|
+
def resolve_column(relation, column)
|
19
|
+
node = relation.send(:relation).send(:arel_columns, [column]).first
|
20
|
+
node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
|
21
|
+
relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/hightop/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hightop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,72 +16,16 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.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: '
|
27
|
-
|
28
|
-
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
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: rake
|
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: minitest
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '5'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '5'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: sqlite3
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
description:
|
84
|
-
email: andrew@chartkick.com
|
26
|
+
version: '5.2'
|
27
|
+
description:
|
28
|
+
email: andrew@ankane.org
|
85
29
|
executables: []
|
86
30
|
extensions: []
|
87
31
|
extra_rdoc_files: []
|
@@ -92,12 +36,14 @@ files:
|
|
92
36
|
- lib/hightop.rb
|
93
37
|
- lib/hightop/enumerable.rb
|
94
38
|
- lib/hightop/kicks.rb
|
39
|
+
- lib/hightop/mongoid.rb
|
40
|
+
- lib/hightop/utils.rb
|
95
41
|
- lib/hightop/version.rb
|
96
42
|
homepage: https://github.com/ankane/hightop
|
97
43
|
licenses:
|
98
44
|
- MIT
|
99
45
|
metadata: {}
|
100
|
-
post_install_message:
|
46
|
+
post_install_message:
|
101
47
|
rdoc_options: []
|
102
48
|
require_paths:
|
103
49
|
- lib
|
@@ -105,15 +51,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
51
|
requirements:
|
106
52
|
- - ">="
|
107
53
|
- !ruby/object:Gem::Version
|
108
|
-
version: '2.
|
54
|
+
version: '2.6'
|
109
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
56
|
requirements:
|
111
57
|
- - ">="
|
112
58
|
- !ruby/object:Gem::Version
|
113
59
|
version: '0'
|
114
60
|
requirements: []
|
115
|
-
rubygems_version: 3.
|
116
|
-
signing_key:
|
61
|
+
rubygems_version: 3.2.22
|
62
|
+
signing_key:
|
117
63
|
specification_version: 4
|
118
64
|
summary: A nice shortcut for group count queries
|
119
65
|
test_files: []
|