filterable-by 0.5.3 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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