filterable-by 0.5.3 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7df6ffda90f61fe76374ea6f338c2857a262f7a824620719fb36df05ba22fa0e
4
- data.tar.gz: 31cec22f5a8c62759ca8b5155ea9060ff84a85f5dc702baf9237bc3aa3219baa
3
+ metadata.gz: a92cb2cde41910a93ed81348238f61031c3e5c8071ab2b118bfceb25307a8c14
4
+ data.tar.gz: 231b7712b1fe257b34ab63d8a3dc362304c45318984c3eb1a2c8cf004ef2ee44
5
5
  SHA512:
6
- metadata.gz: c3e36d0fb257cb048470825bc2669b050759fec897588a0bdad51e2f5b9a7f4702d68abf32fad5412f718ef233d9a6ca5a5c4a8c64c2a6768a1140a09cb57a88
7
- data.tar.gz: 6a488f6969ab64b3dad1091966a5da7aa072da42b973f714fdb85934a4fbfe555a9543d2d25bfad2a035b5d542033fb1411dbdb324faa921a70e41520653e3c3
6
+ metadata.gz: 8ca4f69341eaa58f296280b703398c5ce6ad1dcd6551758322c36a03db7f2b9af7e37defeb96c205a5a1520f40e154d4ebd2f5c6110591ccb1505f55f99ac870
7
+ data.tar.gz: 10691ed7dc8e33b7c6920e8a8e6e670b854bc51d6155baa0923d0cfe363ec2fb8824ed79f3cc635cc9b68f824e3c1951ea32cda8e320e0f40b5ad12fbfa37c2f
@@ -12,6 +12,9 @@ jobs:
12
12
  strategy:
13
13
  matrix:
14
14
  ruby-version: ["2.7", "3.0", "3.1"]
15
+ gemfiles: ["Gemfile", "Gemfile.rails6"]
16
+ env:
17
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
15
18
  steps:
16
19
  - uses: actions/checkout@v2
17
20
  - uses: ruby/setup-ruby@v1
data/Gemfile.lock CHANGED
@@ -1,30 +1,30 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- filterable-by (0.5.3)
4
+ filterable-by (0.6.2)
5
5
  activerecord
6
6
  activesupport
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- activemodel (7.0.2.2)
12
- activesupport (= 7.0.2.2)
13
- activerecord (7.0.2.2)
14
- activemodel (= 7.0.2.2)
15
- activesupport (= 7.0.2.2)
16
- activesupport (7.0.2.2)
11
+ activemodel (7.0.2.3)
12
+ activesupport (= 7.0.2.3)
13
+ activerecord (7.0.2.3)
14
+ activemodel (= 7.0.2.3)
15
+ activesupport (= 7.0.2.3)
16
+ activesupport (7.0.2.3)
17
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
18
  i18n (>= 1.6, < 2)
19
19
  minitest (>= 5.1)
20
20
  tzinfo (~> 2.0)
21
21
  ast (2.4.2)
22
- concurrent-ruby (1.1.9)
22
+ concurrent-ruby (1.1.10)
23
23
  diff-lcs (1.5.0)
24
24
  i18n (1.10.0)
25
25
  concurrent-ruby (~> 1.0)
26
26
  minitest (5.15.0)
27
- parallel (1.21.0)
27
+ parallel (1.22.1)
28
28
  parser (3.1.1.0)
29
29
  ast (~> 2.4.1)
30
30
  rainbow (3.1.1)
@@ -40,17 +40,17 @@ GEM
40
40
  rspec-expectations (3.11.0)
41
41
  diff-lcs (>= 1.2.0, < 2.0)
42
42
  rspec-support (~> 3.11.0)
43
- rspec-mocks (3.11.0)
43
+ rspec-mocks (3.11.1)
44
44
  diff-lcs (>= 1.2.0, < 2.0)
45
45
  rspec-support (~> 3.11.0)
46
46
  rspec-support (3.11.0)
47
- rubocop (1.25.1)
47
+ rubocop (1.26.1)
48
48
  parallel (~> 1.10)
49
49
  parser (>= 3.1.0.0)
50
50
  rainbow (>= 2.2.2, < 4.0)
51
51
  regexp_parser (>= 1.8, < 3.0)
52
52
  rexml
53
- rubocop-ast (>= 1.15.1, < 2.0)
53
+ rubocop-ast (>= 1.16.0, < 2.0)
54
54
  ruby-progressbar (~> 1.7)
55
55
  unicode-display_width (>= 1.4.0, < 3.0)
56
56
  rubocop-ast (1.16.0)
data/Gemfile.rails6 ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'activerecord', '~> 6.1'
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ filterable-by (0.6.1)
5
+ activerecord
6
+ activesupport
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (6.1.4.7)
12
+ activesupport (= 6.1.4.7)
13
+ activerecord (6.1.4.7)
14
+ activemodel (= 6.1.4.7)
15
+ activesupport (= 6.1.4.7)
16
+ activesupport (6.1.4.7)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
22
+ ast (2.4.2)
23
+ concurrent-ruby (1.1.9)
24
+ diff-lcs (1.5.0)
25
+ i18n (1.10.0)
26
+ concurrent-ruby (~> 1.0)
27
+ minitest (5.15.0)
28
+ parallel (1.21.0)
29
+ parser (3.1.1.0)
30
+ ast (~> 2.4.1)
31
+ rainbow (3.1.1)
32
+ rake (13.0.6)
33
+ regexp_parser (2.2.1)
34
+ rexml (3.2.5)
35
+ rspec (3.11.0)
36
+ rspec-core (~> 3.11.0)
37
+ rspec-expectations (~> 3.11.0)
38
+ rspec-mocks (~> 3.11.0)
39
+ rspec-core (3.11.0)
40
+ rspec-support (~> 3.11.0)
41
+ rspec-expectations (3.11.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.11.0)
44
+ rspec-mocks (3.11.0)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.11.0)
47
+ rspec-support (3.11.0)
48
+ rubocop (1.26.0)
49
+ parallel (~> 1.10)
50
+ parser (>= 3.1.0.0)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ regexp_parser (>= 1.8, < 3.0)
53
+ rexml
54
+ rubocop-ast (>= 1.16.0, < 2.0)
55
+ ruby-progressbar (~> 1.7)
56
+ unicode-display_width (>= 1.4.0, < 3.0)
57
+ rubocop-ast (1.16.0)
58
+ parser (>= 3.1.1.0)
59
+ rubocop-bsm (0.6.0)
60
+ rubocop (~> 1.0)
61
+ rubocop-performance
62
+ rubocop-rake
63
+ rubocop-rspec
64
+ rubocop-performance (1.13.3)
65
+ rubocop (>= 1.7.0, < 2.0)
66
+ rubocop-ast (>= 0.4.0)
67
+ rubocop-rake (0.6.0)
68
+ rubocop (~> 1.0)
69
+ rubocop-rspec (2.9.0)
70
+ rubocop (~> 1.19)
71
+ ruby-progressbar (1.11.0)
72
+ sqlite3 (1.4.2)
73
+ tzinfo (2.0.4)
74
+ concurrent-ruby (~> 1.0)
75
+ unicode-display_width (2.1.0)
76
+ zeitwerk (2.5.4)
77
+
78
+ PLATFORMS
79
+ x86_64-linux
80
+
81
+ DEPENDENCIES
82
+ activerecord (~> 6.1)
83
+ bundler
84
+ filterable-by!
85
+ rake
86
+ rspec
87
+ rubocop-bsm
88
+ sqlite3
89
+
90
+ BUNDLED WITH
91
+ 2.3.9
data/README.md CHANGED
@@ -17,15 +17,15 @@ class Comment < ActiveRecord::Base
17
17
  belongs_to :post
18
18
 
19
19
  filterable_by :post_id, :user_id
20
- filterable_by :post_author_id do |scope, value|
21
- scope.joins(:posts).where(:'posts.author_id' => value)
20
+ filterable_by :post_author_id do |value|
21
+ joins(:posts).where(:'posts.author_id' => value)
22
22
  end
23
- filterable_by :only do |scope, value, **opts|
23
+ filterable_by :only do |value, **opts|
24
24
  case value
25
25
  when 'mine'
26
- scope.where(user_id: opts[:user_id]) if opts[:user_id]
26
+ where(user_id: opts[:user_id]) if opts[:user_id]
27
27
  else
28
- scope
28
+ all
29
29
  end
30
30
  end
31
31
  end
@@ -39,6 +39,12 @@ Simple use cases:
39
39
  Comment.filter_by({ 'post_id' => '1' })
40
40
  # => WHERE post_id = 1
41
41
 
42
+ Comment.filter_by({ 'post_id' => ['1', '2'] })
43
+ # => WHERE post_id IN (1, 2)
44
+
45
+ Comment.filter_by({ 'post_id_not' => '3' })
46
+ # => WHERE post_id != 3
47
+
42
48
  Comment.filter_by({ 'user_id' => '2', 'ignored' => '3' })
43
49
  # => WHERE user_id = 2
44
50
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'filterable-by'
3
- s.version = '0.5.3'
3
+ s.version = '0.6.2'
4
4
  s.authors = ['Dimitrij Denissenko']
5
5
  s.email = ['dimitrij@blacksquaremedia.com']
6
6
  s.summary = 'Generate white-listed filter scopes from URL parameter values'
data/lib/filterable_by.rb CHANGED
@@ -13,6 +13,33 @@ module ActiveRecord
13
13
  end
14
14
  end
15
15
 
16
+ def self.merge(scope, unscoped, hash, name, **opts, &block)
17
+ key = name
18
+ positive = normalize(hash[key]) if hash.key?(key)
19
+ if positive.present?
20
+ sub = block.arity == 2 ? yield(unscoped, positive, **opts) : yield(positive, **opts)
21
+ return nil unless sub
22
+
23
+ scope = scope.merge(sub)
24
+ end
25
+
26
+ key = "#{name}_not"
27
+ negative = normalize(hash[key]) if hash.key?(key)
28
+ if negative.present?
29
+ sub = block.arity == 2 ? yield(unscoped, negative, **opts) : yield(negative, **opts)
30
+ return nil unless sub
31
+
32
+ if sub.respond_to?(:invert_where)
33
+ sub = sub.invert_where
34
+ else
35
+ sub.where_clause = sub.where_clause.invert
36
+ end
37
+ scope = scope.merge(sub)
38
+ end
39
+
40
+ scope
41
+ end
42
+
16
43
  module ClassMethods
17
44
  def self.extended(base) # :nodoc:
18
45
  base.class_attribute :_filterable_by_config, instance_accessor: false, instance_predicate: false
@@ -26,8 +53,12 @@ module ActiveRecord
26
53
  end
27
54
 
28
55
  def filterable_by(*names, &block)
56
+ if block && block.arity > 1
57
+ ActiveSupport::Deprecation.warn('using scope in filterable_by blocks is deprecated. Please use filterable_by(:x) {|val| where(field: val) } instead.')
58
+ end
59
+
29
60
  names.each do |name|
30
- _filterable_by_config[name.to_s] = block || ->(scope, value, **) { scope.where(name.to_sym => value) }
61
+ _filterable_by_config[name.to_s] = block
31
62
  end
32
63
  end
33
64
 
@@ -38,16 +69,14 @@ module ActiveRecord
38
69
  hash = opts
39
70
  opts = {}
40
71
  end
72
+
41
73
  scope = all
42
74
  return scope unless hash.respond_to?(:key?) && hash.respond_to?(:[])
43
75
 
44
76
  _filterable_by_config.each do |name, block|
45
- next unless hash.key?(name)
46
-
47
- value = FilterableBy.normalize(hash[name])
48
- next if value.blank?
49
-
50
- scope = block.call(scope, value, **opts)
77
+ block = ->(value, **) { where(name.to_sym => value) } if block.nil?
78
+ scope = FilterableBy.merge(scope, unscoped, hash, name, **opts, &block)
79
+ break unless scope
51
80
  end
52
81
 
53
82
  scope || none
@@ -8,8 +8,8 @@ describe ActiveRecord::FilterableBy do
8
8
  let(:bpost) { POSTS[:bobs] }
9
9
 
10
10
  it 'has config' do
11
- expect(Comment.send(:_filterable_by_config).count).to eq(3)
12
- expect(Rating.send(:_filterable_by_config).count).to eq(2)
11
+ expect(Comment.send(:_filterable_by_config).count).to eq(5)
12
+ expect(Rating.send(:_filterable_by_config).count).to eq(4)
13
13
  expect(Post.send(:_filterable_by_config).count).to eq(2)
14
14
  end
15
15
 
@@ -50,9 +50,23 @@ describe ActiveRecord::FilterableBy do
50
50
  expect(scope.pluck(:title)).to match_array(%w[AB BB])
51
51
  end
52
52
 
53
+ it 'generates negated scopes' do
54
+ expect(Comment.filter_by('author_id_not' => alice.id).pluck(:title)).to match_array(%w[BA BB])
55
+ expect(Comment.filter_by('author_id_not' => [alice.id, bob.id]).pluck(:title)).to match_array(%w[])
56
+ expect(Comment.filter_by('post_id_not' => apost.id).pluck(:title)).to match_array(%w[AB BB])
57
+ expect(Comment.filter_by('post_author_id_not' => alice.id).pluck(:title)).to match_array(%w[AB BB])
58
+ expect(Comment.filter_by('author_id' => bob.id, 'post_id_not' => bpost.id).pluck(:title)).to match_array(['BA'])
59
+ end
60
+
53
61
  it 'combines with other scopes' do
54
62
  scope = Comment.where(author_id: alice.id).filter_by('post_id' => apost.id)
55
63
  expect(scope.pluck(:title)).to match_array(['AA'])
64
+
65
+ expect(alice.posts.filter_by('post_id' => apost.id).count).to be(1)
66
+ expect(alice.posts.filter_by('author_id' => alice.id).count).to be(1)
67
+ expect(alice.posts.filter_by('author_id_not' => bob.id).count).to be(1)
68
+ expect(alice.posts.filter_by('author_id' => bob.id).count).to be_zero
69
+ expect(alice.posts.filter_by('author_id_not' => alice.id).count).to be_zero
56
70
  end
57
71
 
58
72
  it 'allows custom options' do
@@ -77,4 +91,14 @@ describe ActiveRecord::FilterableBy do
77
91
  expect(Post.filter_by('post_id' => bpost.id).count).to eq(2)
78
92
  expect(Rating.filter_by('post_author_id' => bob.id).count).to eq(1)
79
93
  end
94
+
95
+ it 'supports deprecated scoping' do
96
+ expect(Comment.filter_by('deprecated' => alice.id).pluck(:title)).to match_array(%w[AA AB])
97
+ expect(Comment.filter_by('deprecated_with_opts' => alice.id).pluck(:title)).to match_array(%w[AA AB])
98
+ expect(Comment.filter_by('deprecated_not' => alice.id).pluck(:title)).to match_array(%w[BA BB])
99
+ end
100
+
101
+ it 'supports abstract classes' do
102
+ expect(Post.filter_by('author_id' => bob.id).count).to eq(1)
103
+ end
80
104
  end
data/spec/spec_helper.rb CHANGED
@@ -22,18 +22,23 @@ ActiveRecord::Base.connection.instance_eval do
22
22
  end
23
23
 
24
24
  class Author < ActiveRecord::Base
25
+ has_many :posts
25
26
  end
26
27
 
27
- class Post < ActiveRecord::Base
28
+ class AbstractPost < ActiveRecord::Base
29
+ self.abstract_class = true
30
+ filterable_by :author_id
31
+ end
32
+
33
+ class Post < AbstractPost
28
34
  belongs_to :author
29
35
 
30
- filterable_by :author_id
31
- filterable_by :only do |scope, value, **opts|
36
+ filterable_by :only do |value, **opts|
32
37
  case value
33
38
  when 'me'
34
- scope.where(author_id: opts[:user_id]) if opts[:user_id]
39
+ where(author_id: opts[:user_id]) if opts[:user_id]
35
40
  else
36
- scope
41
+ all
37
42
  end
38
43
  end
39
44
  end
@@ -43,11 +48,20 @@ class Feedback < ActiveRecord::Base
43
48
  belongs_to :post
44
49
 
45
50
  filterable_by :post_id, :author_id
51
+
52
+ ActiveSupport::Deprecation.silence do
53
+ filterable_by :deprecated do |scope, value|
54
+ scope.where(author_id: value)
55
+ end
56
+ filterable_by :deprecated_with_opts do |scope, value, **_opts|
57
+ scope.where(author_id: value)
58
+ end
59
+ end
46
60
  end
47
61
 
48
62
  class Comment < Feedback
49
- filterable_by :post_author_id do |scope, value|
50
- scope.joins(:post).where(Post.arel_table[:author_id].eq(value))
63
+ filterable_by :post_author_id do |value|
64
+ joins(:post).where(Post.arel_table[:author_id].eq(value))
51
65
  end
52
66
  end
53
67
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filterable-by
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitrij Denissenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-08 00:00:00.000000000 Z
11
+ date: 2022-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -120,6 +120,8 @@ files:
120
120
  - ".rubocop.yml"
121
121
  - Gemfile
122
122
  - Gemfile.lock
123
+ - Gemfile.rails6
124
+ - Gemfile.rails6.lock
123
125
  - LICENSE
124
126
  - README.md
125
127
  - Rakefile