sequel-association-filtering 0.0.4 → 0.0.5

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: db9148c1cf2c63a77fec6b64d6064a39cac6e811d0ec53feeb69b880e15a61e2
4
- data.tar.gz: 66a54816ac8284a3bb67e081d4637648a9c0c1979827ee2a32cf3865dde2483c
3
+ metadata.gz: eebfe24a7a4bc0831e9b5d983f7d49c46f1af66b531b137cbad11d65195c02dd
4
+ data.tar.gz: 812a28ac539eab752717576a44cd63f4e95d306c9ceeb3830f044227ec6c09db
5
5
  SHA512:
6
- metadata.gz: b06d337f0702b7381689eb9d7cf62114e73ce7e38ee5a7fcc8f7ed19e1416aa9623848f4179a3787b3db37e838666b4736ee448c30a4354275fd224e62dcc8e2
7
- data.tar.gz: f053fbae21edb9996e1a8a367c81feb5f23eae56d9e349dbdeecc6a2975f8b06a16e656b8cabc604fedaa86412bb7b78cad3c4a06050965eaea35e58ed623999
6
+ metadata.gz: d87c8e29c99c922be6411c7f74589b3685781502a38131f7f832465a334ab62cbee3125ba0fe4c844b98d496d1c0ef49cba6091c1f96c0268a0b46bc75f665d6
7
+ data.tar.gz: 00155ca49d9f3f01d8b4cf7385f81bb8e72e0acb8ec2db436ee3ae74468cb606cab343ac52e62120a65b4f5c129eaa3cb3a1ea6212fdd9f30520372cc3173087
@@ -19,27 +19,47 @@ module Sequel
19
19
  end
20
20
 
21
21
  module DatasetMethods
22
- def association_filter(association_name, invert: false)
22
+ COUNT_STAR = Sequel.virtual_row{count.function.*}
23
+
24
+ def association_filter(
25
+ association_name,
26
+ invert: false,
27
+ at_least: nil,
28
+ at_most: nil,
29
+ exactly: nil
30
+ )
31
+ case [at_least, at_most, exactly].compact.length
32
+ when 0
33
+ filtering_by_count = false
34
+ when 1
35
+ filtering_by_count = true
36
+ else
37
+ raise Error, "cannot pass more than one of :at_least, :at_most, and :exactly"
38
+ end
39
+
23
40
  reflection =
24
41
  model.association_reflections.fetch(association_name) do
25
42
  raise Error, "association #{association_name} not found on model #{model}"
26
43
  end
27
44
 
28
- if block_given?
29
- cond = yield(_association_filter_dataset(reflection)).exists
30
- invert ? exclude(cond) : where(cond)
31
- else
32
- cache_key =
33
- _association_filter_cache_key(
34
- reflection: reflection,
35
- extra: :"bare_#{invert}",
36
- )
45
+ ds = _association_filter_dataset(reflection, group_by_remote: filtering_by_count)
46
+ ds = yield(ds) if block_given?
37
47
 
38
- cached_dataset(cache_key) do
39
- cond = _association_filter_dataset(reflection).exists
40
- invert ? exclude(cond) : where(cond)
41
- end
48
+ if filtering_by_count
49
+ ds =
50
+ ds.having(
51
+ case
52
+ when at_least then COUNT_STAR >= at_least
53
+ when at_most then COUNT_STAR <= at_most
54
+ when exactly then COUNT_STAR =~ exactly
55
+ else raise Error, ""
56
+ end
57
+ )
42
58
  end
59
+
60
+ cond = ds.exists
61
+ cond = Sequel.~(cond) if invert
62
+ where(cond)
43
63
  end
44
64
 
45
65
  def association_exclude(association_name, &block)
@@ -48,8 +68,12 @@ module Sequel
48
68
 
49
69
  private
50
70
 
51
- def _association_filter_dataset(reflection)
52
- cache_key = _association_filter_cache_key(reflection: reflection)
71
+ def _association_filter_dataset(reflection, group_by_remote:)
72
+ cache_key =
73
+ _association_filter_cache_key(
74
+ reflection: reflection,
75
+ extra: (:group_by_remote if group_by_remote)
76
+ )
53
77
 
54
78
  ds = reflection.associated_dataset
55
79
 
@@ -71,12 +95,19 @@ module Sequel
71
95
  local_keys = Array(local_keys)
72
96
  remote_keys = Array(remote_keys)
73
97
 
74
- ds.where(
75
- remote_keys.
76
- zip(local_keys).
77
- map{|r,l| {r => l}}.
78
- inject{|a,b| Sequel.&(a, b)}
79
- ).select(1)
98
+ result =
99
+ ds.where(
100
+ remote_keys.
101
+ zip(local_keys).
102
+ map{|r,l| {r => l}}.
103
+ inject{|a,b| Sequel.&(a, b)}
104
+ ).select(1)
105
+
106
+ if group_by_remote
107
+ result.group_by(*remote_keys)
108
+ else
109
+ result
110
+ end
80
111
  end
81
112
  end
82
113
 
@@ -3,7 +3,7 @@
3
3
  module Sequel
4
4
  module Plugins
5
5
  module AssociationFiltering
6
- VERSION = '0.0.4'
6
+ VERSION = '0.0.5'
7
7
  end
8
8
  end
9
9
  end
@@ -29,5 +29,14 @@ class BasicSpec < AssociationFilteringSpecs
29
29
 
30
30
  assert_equal "association widgets not found on model BasicSpec::Artist", error.message
31
31
  end
32
+
33
+ it "with more than one of at_least, at_most, or exactly should throw an error" do
34
+ error =
35
+ assert_raises(Sequel::Plugins::AssociationFiltering::Error) do
36
+ Artist.association_filter(:widgets, at_most: 4, exactly: 5)
37
+ end
38
+
39
+ assert_equal "cannot pass more than one of :at_least, :at_most, and :exactly", error.message
40
+ end
32
41
  end
33
42
  end
@@ -58,6 +58,25 @@ class OneToManySpec < AssociationFilteringSpecs
58
58
  Album.where(artist_id: 5).delete
59
59
  assert_equal 9, ds.count
60
60
  end
61
+
62
+ it "should support at_least/exactly/at_most args" do
63
+ Album.create(artist_id: 1)
64
+
65
+ ds = Artist.association_filter(:albums, exactly: 11)
66
+ assert_equal 1, ds.count
67
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE ("albums"."artist_id" = "artists"."id") GROUP BY "albums"."artist_id" HAVING (count(*) = 11)))), ds.sql
68
+ assert_equal [1], ds.select_map(:id)
69
+
70
+ ds = Artist.association_filter(:albums, at_least: 2){|albums| albums.where{id > 90}}
71
+ assert_equal 1, ds.count
72
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."artist_id" = "artists"."id") AND ("id" > 90)) GROUP BY "albums"."artist_id" HAVING (count(*) >= 2)))), ds.sql
73
+ assert_equal [1], ds.select_map(:id)
74
+
75
+ ds = Artist.association_filter(:albums, at_most: 1){|albums| albums.where{id > 90}}
76
+ assert_equal 9, ds.count
77
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."artist_id" = "artists"."id") AND ("id" > 90)) GROUP BY "albums"."artist_id" HAVING (count(*) <= 1)))), ds.sql
78
+ assert_equal (2..10).to_a, ds.select_order_map(:id)
79
+ end
61
80
  end
62
81
  end
63
82
 
@@ -130,6 +149,25 @@ class OneToManySpec < AssociationFilteringSpecs
130
149
  Album.where(id_1: 5, id_2: 6).delete
131
150
  assert_equal 99, ds.count
132
151
  end
152
+
153
+ it "should support at_least/exactly/at_most args" do
154
+ Album.dataset.insert(id_1: 1, id_2: 1, id_3: 11)
155
+
156
+ ds = Artist.association_filter(:albums, exactly: 11)
157
+ assert_equal 1, ds.count
158
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2")) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) = 11)))), ds.sql
159
+ assert_equal [[1, 1]], ds.select_map([:id_1, :id_2])
160
+
161
+ ds = Artist.association_filter(:albums, at_least: 2){|albums| albums.where{serial_column > 900}}
162
+ assert_equal 1, ds.count
163
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2") AND ("serial_column" > 900)) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) >= 2)))), ds.sql
164
+ assert_equal [[1, 1]], ds.select_map([:id_1, :id_2])
165
+
166
+ ds = Artist.association_filter(:albums, at_most: 1){|albums| albums.where{serial_column > 900}}
167
+ assert_equal 99, ds.count
168
+ assert_equal %(SELECT * FROM "artists" WHERE (EXISTS (SELECT 1 FROM "albums" WHERE (("albums"."id_1" = "artists"."id_1") AND ("albums"."id_2" = "artists"."id_2") AND ("serial_column" > 900)) GROUP BY "albums"."id_1", "albums"."id_2" HAVING (count(*) <= 1)))), ds.sql
169
+ assert_equal (2..100).to_a, ds.select_order_map(:serial_column)
170
+ end
133
171
  end
134
172
 
135
173
  describe "association_exclude through a one_to_many association" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-association-filtering
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-11 00:00:00.000000000 Z
11
+ date: 2018-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler