hightop 0.2.1 → 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/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
|
-
[![Build Status](https://travis-ci.org/ankane/hightop.svg)](https://travis-ci.org/ankane/hightop)
|
16
|
+
[![Build Status](https://github.com/ankane/hightop/workflows/build/badge.svg?branch=master)](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: []
|